Skip to content

Commit 2bdc85c

Browse files
author
Vitaliy
authored
Merge pull request #253 from magento/module-xml-registration-php-inspections
Implemented inspections for module name declaration in registration.php and module.xml
2 parents d03cab6 + 5cb91f1 commit 2bdc85c

File tree

19 files changed

+626
-0
lines changed

19 files changed

+626
-0
lines changed

resources/META-INF/plugin.xml

+15
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@
128128
level="ERROR"
129129
implementationClass="com.magento.idea.magento2plugin.inspections.php.PluginInspection"/>
130130

131+
<localInspection language="PHP" groupPath="PHP"
132+
shortName="ModuleDeclarationInRegistrationPhpInspection"
133+
displayName="Inspection for the Module declaration in the `registration.php` file"
134+
groupName="Magento 2"
135+
enabledByDefault="true"
136+
level="ERROR"
137+
implementationClass="com.magento.idea.magento2plugin.inspections.php.ModuleDeclarationInRegistrationPhpInspection"/>
138+
131139
<localInspection language="XML" groupPath="XML"
132140
shortName="ObserverDeclarationInspection"
133141
displayName="Duplicated Observer Usage in events XML"
@@ -151,6 +159,13 @@
151159
enabledByDefault="true" level="WARNING"
152160
implementationClass="com.magento.idea.magento2plugin.inspections.xml.CacheableFalseInDefaultLayoutInspection"/>
153161

162+
<localInspection language="XML" groupPath="XML"
163+
shortName="ModuleDeclarationInModuleXmlInspection"
164+
displayName="Inspection for the Module declaration in the `etc/module.xml` file"
165+
groupName="Magento 2"
166+
enabledByDefault="true" level="ERROR"
167+
implementationClass="com.magento.idea.magento2plugin.inspections.xml.ModuleDeclarationInModuleXmlInspection"/>
168+
154169
<libraryRoot id=".phpstorm.meta.php" path=".phpstorm.meta.php/" runtime="false"/>
155170

156171
<internalFileTemplate name="Magento Module Composer"/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<body>
3+
<p>
4+
Name of module declared in `app/code` should reflect the folder structure.
5+
</p>
6+
</body>
7+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<body>
3+
<p>
4+
Name of module declared in `app/code` should reflect the folder structure.
5+
</p>
6+
</body>
7+
</html>

resources/magento2/inspection.properties

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ inspection.plugin.error.typeIncompatibility=Possible type incompatibility. Consi
1414
inspection.observer.duplicateInSameFile=The observer name already used in this file. For more details see Inspection Description.
1515
inspection.observer.duplicateInOtherPlaces=The observer name "{0}" for event "{1}" is already used in the module "{2}" ({3} scope). For more details see Inspection Description.
1616
inspection.cache.disabledCache=Cacheable false attribute on the default layout will disable cache site-wide
17+
inspection.moduleDeclaration.warning.wrongModuleName=Provided module name "{0}" does not match expected "{1}"
18+
inspection.moduleDeclaration.fix=Fix module name
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.inspections.php;
7+
8+
import com.intellij.codeInspection.ProblemHighlightType;
9+
import com.intellij.codeInspection.ProblemsHolder;
10+
import com.intellij.psi.PsiElementVisitor;
11+
import com.intellij.psi.PsiFile;
12+
import com.jetbrains.php.lang.inspections.PhpInspection;
13+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
14+
import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor;
15+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
16+
import com.magento.idea.magento2plugin.inspections.php.fix.PhpModuleNameQuickFix;
17+
import com.magento.idea.magento2plugin.inspections.util.GetEditableModuleNameByRootFileUtil;
18+
import com.magento.idea.magento2plugin.magento.files.RegistrationPhp;
19+
import com.magento.idea.magento2plugin.util.magento.IsFileInEditableModuleUtil;
20+
import org.jetbrains.annotations.NotNull;
21+
22+
public class ModuleDeclarationInRegistrationPhpInspection extends PhpInspection {
23+
24+
@NotNull
25+
@Override
26+
public PsiElementVisitor buildVisitor(
27+
final @NotNull ProblemsHolder problemsHolder,
28+
final boolean isOnTheFly
29+
) {
30+
return new PhpElementVisitor() {
31+
32+
@Override
33+
public void visitPhpStringLiteralExpression(final StringLiteralExpression expression) {
34+
final PsiFile file = expression.getContainingFile();
35+
final String filename = file.getName();
36+
if (!filename.equals(RegistrationPhp.FILE_NAME)) {
37+
return;
38+
}
39+
if (!IsFileInEditableModuleUtil.execute(file)) {
40+
return;
41+
}
42+
final String expectedName = GetEditableModuleNameByRootFileUtil.execute(file);
43+
final String actualName = expression.getContents();
44+
if (actualName.equals(expectedName)) {
45+
return;
46+
}
47+
48+
final InspectionBundle inspectionBundle = new InspectionBundle();
49+
problemsHolder.registerProblem(
50+
expression,
51+
inspectionBundle.message(
52+
"inspection.moduleDeclaration.warning.wrongModuleName",
53+
actualName,
54+
expectedName
55+
),
56+
ProblemHighlightType.ERROR,
57+
new PhpModuleNameQuickFix(expectedName)
58+
);
59+
}
60+
};
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.inspections.php.fix;
7+
8+
import com.intellij.codeInspection.LocalQuickFix;
9+
import com.intellij.codeInspection.ProblemDescriptor;
10+
import com.intellij.openapi.command.WriteCommandAction;
11+
import com.intellij.openapi.project.Project;
12+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
13+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
14+
import org.jetbrains.annotations.NotNull;
15+
16+
public class PhpModuleNameQuickFix implements LocalQuickFix {
17+
18+
private final String expectedModuleName;
19+
20+
public PhpModuleNameQuickFix(final String expectedModuleName) {
21+
this.expectedModuleName = expectedModuleName;
22+
}
23+
24+
@NotNull
25+
@Override
26+
public String getFamilyName() {
27+
final InspectionBundle inspectionBundle = new InspectionBundle();
28+
29+
return inspectionBundle.message(
30+
"inspection.moduleDeclaration.fix"
31+
);
32+
}
33+
34+
@Override
35+
public void applyFix(
36+
@NotNull final Project project,
37+
@NotNull final ProblemDescriptor descriptor
38+
) {
39+
final StringLiteralExpression expression =
40+
(StringLiteralExpression) descriptor.getPsiElement();
41+
applyFix(expression);
42+
}
43+
44+
private void applyFix(final StringLiteralExpression expression) {
45+
WriteCommandAction.writeCommandAction(
46+
expression.getManager().getProject(),
47+
expression.getContainingFile()
48+
).run(() ->
49+
expression.updateText(expectedModuleName)
50+
);
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.inspections.util;
7+
8+
import com.intellij.psi.PsiDirectory;
9+
import com.intellij.psi.PsiFileSystemItem;
10+
import com.magento.idea.magento2plugin.magento.packages.Package;
11+
12+
public final class GetEditableModuleNameByRootFileUtil {
13+
14+
private GetEditableModuleNameByRootFileUtil() {}
15+
16+
/**
17+
* Method detects Magento Framework Root.
18+
*
19+
* @param moduleRootFile PsiFileSystemItem
20+
* @return boolean
21+
*/
22+
public static String execute(final PsiFileSystemItem moduleRootFile) {
23+
final PsiFileSystemItem moduleDirectory = moduleRootFile.getParent();
24+
if (moduleDirectory == null) {
25+
return null;
26+
}
27+
final PsiDirectory packageDirectory = (PsiDirectory) moduleDirectory.getParent();
28+
if (packageDirectory == null) {
29+
return null;
30+
}
31+
32+
return packageDirectory.getName()
33+
+ Package.vendorModuleNameSeparator
34+
+ moduleDirectory.getName();
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.inspections.xml;
7+
8+
import com.intellij.codeInspection.ProblemHighlightType;
9+
import com.intellij.codeInspection.ProblemsHolder;
10+
import com.intellij.codeInspection.XmlSuppressableInspectionTool;
11+
import com.intellij.psi.PsiDirectory;
12+
import com.intellij.psi.PsiElementVisitor;
13+
import com.intellij.psi.PsiFile;
14+
import com.intellij.psi.XmlElementVisitor;
15+
import com.intellij.psi.util.PsiTreeUtil;
16+
import com.intellij.psi.xml.XmlAttribute;
17+
import com.intellij.psi.xml.XmlAttributeValue;
18+
import com.intellij.psi.xml.XmlDocument;
19+
import com.intellij.psi.xml.XmlFile;
20+
import com.intellij.psi.xml.XmlTag;
21+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
22+
import com.magento.idea.magento2plugin.inspections.util.GetEditableModuleNameByRootFileUtil;
23+
import com.magento.idea.magento2plugin.inspections.xml.fix.XmlModuleNameQuickFix;
24+
import com.magento.idea.magento2plugin.magento.files.ModuleXml;
25+
import com.magento.idea.magento2plugin.util.magento.IsFileInEditableModuleUtil;
26+
import org.jetbrains.annotations.NotNull;
27+
28+
public class ModuleDeclarationInModuleXmlInspection extends XmlSuppressableInspectionTool {
29+
30+
@NotNull
31+
@Override
32+
public PsiElementVisitor buildVisitor(
33+
final @NotNull ProblemsHolder problemsHolder,
34+
final boolean isOnTheFly
35+
) {
36+
return new XmlElementVisitor() {
37+
@Override
38+
public void visitXmlAttributeValue(final XmlAttributeValue value) {
39+
final PsiFile file = value.getContainingFile();
40+
final String filename = file.getName();
41+
if (!filename.equals(ModuleXml.FILE_NAME)) {
42+
return;
43+
}
44+
if (!IsFileInEditableModuleUtil.execute(file)) {
45+
return;
46+
}
47+
48+
if (isSubTag(value, (XmlFile) file)) {
49+
return;
50+
}
51+
52+
final PsiDirectory etcDirectory = file.getParent();
53+
if (etcDirectory == null) {
54+
return;
55+
}
56+
57+
final String expectedName
58+
= GetEditableModuleNameByRootFileUtil.execute(etcDirectory);
59+
final String actualName = value.getValue();
60+
if (actualName.equals(expectedName)) {
61+
return;
62+
}
63+
64+
final InspectionBundle inspectionBundle = new InspectionBundle();
65+
problemsHolder.registerProblem(
66+
value,
67+
inspectionBundle.message(
68+
"inspection.moduleDeclaration.warning.wrongModuleName",
69+
actualName,
70+
expectedName
71+
),
72+
ProblemHighlightType.ERROR,
73+
new XmlModuleNameQuickFix(expectedName)
74+
);
75+
}
76+
};
77+
}
78+
79+
protected boolean isSubTag(final XmlAttributeValue value, final XmlFile file) {
80+
final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(value, XmlAttribute.class);
81+
if (xmlAttribute == null) {
82+
return true;
83+
}
84+
85+
final XmlTag xmlTag = PsiTreeUtil.getParentOfType(xmlAttribute, XmlTag.class);
86+
if (xmlTag == null) {
87+
return true;
88+
}
89+
90+
final XmlDocument xmlDocument = file.getDocument();
91+
if (xmlDocument == null) {
92+
return true;
93+
}
94+
95+
final XmlTag xmlRootTag = xmlDocument.getRootTag();
96+
if (xmlRootTag == null) {
97+
return true;
98+
}
99+
100+
final XmlTag rootTag = PsiTreeUtil.getParentOfType(xmlTag, XmlTag.class);
101+
return rootTag == null || !(rootTag.getName().equals(xmlRootTag.getName()));
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.inspections.xml.fix;
7+
8+
import com.intellij.codeInspection.LocalQuickFix;
9+
import com.intellij.codeInspection.ProblemDescriptor;
10+
import com.intellij.openapi.command.WriteCommandAction;
11+
import com.intellij.openapi.project.Project;
12+
import com.intellij.psi.xml.XmlAttribute;
13+
import com.intellij.psi.xml.XmlAttributeValue;
14+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
15+
import org.jetbrains.annotations.NotNull;
16+
17+
public class XmlModuleNameQuickFix implements LocalQuickFix {
18+
19+
private final String expectedModuleName;
20+
21+
public XmlModuleNameQuickFix(final String expectedModuleName) {
22+
this.expectedModuleName = expectedModuleName;
23+
}
24+
25+
@NotNull
26+
@Override
27+
public String getFamilyName() {
28+
final InspectionBundle inspectionBundle = new InspectionBundle();
29+
30+
return inspectionBundle.message(
31+
"inspection.moduleDeclaration.fix"
32+
);
33+
}
34+
35+
@Override
36+
public void applyFix(
37+
@NotNull final Project project,
38+
@NotNull final ProblemDescriptor descriptor
39+
) {
40+
final XmlAttributeValue value = (XmlAttributeValue) descriptor.getPsiElement();
41+
applyFix(value);
42+
}
43+
44+
private void applyFix(final XmlAttributeValue value) {
45+
WriteCommandAction.writeCommandAction(
46+
value.getManager().getProject(),
47+
value.getContainingFile()
48+
).run(() ->
49+
doFix(value)
50+
);
51+
}
52+
53+
protected void doFix(
54+
final XmlAttributeValue value
55+
) {
56+
final XmlAttribute xmlAttribute = (XmlAttribute) value.getParent();
57+
xmlAttribute.setValue(expectedModuleName);
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright © Magento, Inc. All rights reserved.
3+
* See COPYING.txt for license details.
4+
*/
5+
6+
package com.magento.idea.magento2plugin.util.magento;
7+
8+
import com.intellij.psi.PsiFile;
9+
import com.magento.idea.magento2plugin.magento.packages.File;
10+
import com.magento.idea.magento2plugin.magento.packages.Package;
11+
import com.magento.idea.magento2plugin.project.Settings;
12+
13+
public final class IsFileInEditableModuleUtil {
14+
15+
private IsFileInEditableModuleUtil() {}
16+
17+
/**
18+
* Module is considered editable if it is declared within `MAGENTO_ROOT/app/code` directory.
19+
*
20+
* @param file PsiFile
21+
* @return boolean
22+
*/
23+
public static boolean execute(final PsiFile file) {
24+
final String magentoPath = Settings.getMagentoPath(file.getProject());
25+
final String editablePath = magentoPath + File.separator + Package.packagesRoot;
26+
final String filePath = file.getVirtualFile().getPath();
27+
28+
return filePath.startsWith(editablePath);
29+
}
30+
}

0 commit comments

Comments
 (0)