Skip to content

Commit c267602

Browse files
authored
Merge pull request #577 from magento/web-api-checks
Web API XML service tag inspection
2 parents ab9d583 + ed26b31 commit c267602

File tree

20 files changed

+401
-17
lines changed

20 files changed

+401
-17
lines changed

resources/META-INF/plugin.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@
204204
enabledByDefault="true" level="ERROR"
205205
implementationClass="com.magento.idea.magento2plugin.inspections.xml.AclResourceXmlInspection"/>
206206

207+
<localInspection language="XML" groupPath="XML"
208+
shortName="WebApiServiceInspection"
209+
displayName="Inspection for the Web API XML service declaration"
210+
groupName="Magento 2"
211+
enabledByDefault="true" level="WARNING"
212+
implementationClass="com.magento.idea.magento2plugin.inspections.xml.WebApiServiceInspection"/>
213+
207214
<internalFileTemplate name="Magento Composer JSON"/>
208215
<internalFileTemplate name="Magento Registration PHP"/>
209216
<internalFileTemplate name="Magento Module XML"/>

resources/magento2/inspection.properties

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,9 @@ inspection.observer.disabledObserverDoesNotExist=This observer does not exist to
2020
inspection.cache.disabledCache=Cacheable false attribute on the default layout will disable cache site-wide
2121
inspection.moduleDeclaration.warning.wrongModuleName=Provided module name "{0}" does not match expected "{1}"
2222
inspection.moduleDeclaration.fix=Fix module name
23-
inspection.aclResource.error.missingAttribute=Attribute "{0}" is required
24-
inspection.aclResource.error.idAttributeCanNotBeEmpty=Attribute value "{0}" can not be empty
23+
inspection.error.missingAttribute=Attribute "{0}" is required
24+
inspection.error.idAttributeCanNotBeEmpty=Attribute value "{0}" can not be empty
25+
inspection.warning.class.does.not.exist=The class "{0}" does not exist
26+
inspection.warning.method.does.not.exist=The method "{0}" does not exist in the service class
27+
inspection.warning.method.should.have.public.access=The method "{0}" should have public access
28+
inspection.warning.method.should.have.public.access.fix=Change the method access
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
/**
1+
/*
22
* Copyright © Magento, Inc. All rights reserved.
33
* See COPYING.txt for license details.
44
*/
5+
56
package com.magento.idea.magento2plugin;
67

78
import com.intellij.openapi.util.IconLoader;
8-
import javax.swing.*;
9+
import javax.swing.Icon;
910

11+
@SuppressWarnings({"PMD.ClassNamingConventions"})
1012
public class MagentoIcons {
11-
public static final Icon WEB_API = IconLoader.getIcon("/icons/webapi.png");
12-
public static final Icon MODULE = IconLoader.getIcon("/icons/module.png");
13+
public static final Icon WEB_API = IconLoader.getIcon("/icons/webapi.png", MagentoIcons.class);
14+
public static final Icon MODULE = IconLoader.getIcon("/icons/module.png", MagentoIcons.class);
1315
}

src/com/magento/idea/magento2plugin/inspections/php/PluginInspection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
2222
import com.magento.idea.magento2plugin.inspections.php.util.PhpClassImplementsInterfaceUtil;
2323
import com.magento.idea.magento2plugin.inspections.php.util.PhpClassImplementsNoninterceptableInterfaceUtil;
24+
import com.magento.idea.magento2plugin.magento.files.AbstractPhpFile;
2425
import com.magento.idea.magento2plugin.magento.files.Plugin;
2526
import com.magento.idea.magento2plugin.magento.packages.MagentoPhpClass;
2627
import com.magento.idea.magento2plugin.magento.packages.Package;
@@ -166,7 +167,7 @@ private void checkTargetMethod(
166167
ProblemHighlightType.ERROR
167168
);
168169
}
169-
if (!targetMethod.getAccess().toString().equals(Plugin.PUBLIC_ACCESS)) {
170+
if (!targetMethod.getAccess().toString().equals(AbstractPhpFile.PUBLIC_ACCESS)) {
170171
problemsHolder.registerProblem(
171172
pluginMethod.getNameIdentifier(),
172173
inspectionBundle.message("inspection.plugin.error.nonPublicMethod"),

src/com/magento/idea/magento2plugin/inspections/xml/AclResourceXmlInspection.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void visitXmlTag(final XmlTag xmlTag) {
5555
problemsHolder.registerProblem(
5656
identifier,
5757
inspectionBundle.message(
58-
"inspection.aclResource.error.idAttributeCanNotBeEmpty",
58+
"inspection.error.idAttributeCanNotBeEmpty",
5959
"id"
6060
),
6161
ProblemHighlightType.WARNING
@@ -80,7 +80,7 @@ public void visitXmlTag(final XmlTag xmlTag) {
8080
problemsHolder.registerProblem(
8181
identifier,
8282
inspectionBundle.message(
83-
"inspection.aclResource.error.missingAttribute",
83+
"inspection.error.missingAttribute",
8484
"title"
8585
),
8686
ProblemHighlightType.WARNING
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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.PsiElementVisitor;
12+
import com.intellij.psi.PsiFile;
13+
import com.intellij.psi.XmlElementVisitor;
14+
import com.intellij.psi.xml.XmlAttribute;
15+
import com.intellij.psi.xml.XmlTag;
16+
import com.jetbrains.php.PhpIndex;
17+
import com.jetbrains.php.lang.psi.elements.Method;
18+
import com.jetbrains.php.lang.psi.elements.PhpClass;
19+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
20+
import com.magento.idea.magento2plugin.inspections.xml.fix.MethodNotPublicAccessQuickFix;
21+
import com.magento.idea.magento2plugin.magento.files.ModuleWebApiXmlFile;
22+
import java.util.Collection;
23+
import org.jetbrains.annotations.NotNull;
24+
import org.jetbrains.annotations.Nullable;
25+
26+
@SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.NPathComplexity"})
27+
public class WebApiServiceInspection extends XmlSuppressableInspectionTool {
28+
29+
@NotNull
30+
@Override
31+
public PsiElementVisitor buildVisitor(
32+
final @NotNull ProblemsHolder problemsHolder,
33+
final boolean isOnTheFly
34+
) {
35+
return new XmlElementVisitor() {
36+
private final InspectionBundle inspectionBundle = new InspectionBundle();
37+
38+
@Override
39+
public void visitXmlTag(final XmlTag xmlTag) {
40+
final PsiFile file = xmlTag.getContainingFile();
41+
final String filename = file.getName();
42+
if (!filename.equals(ModuleWebApiXmlFile.FILE_NAME)) {
43+
return;
44+
}
45+
46+
if (!xmlTag.getName().equals(ModuleWebApiXmlFile.SERVICE_TAG_NAME)) {
47+
return;
48+
}
49+
50+
//Check whether the class attribute is not empty
51+
final XmlAttribute classAttribute = xmlTag.getAttribute(
52+
ModuleWebApiXmlFile.CLASS_ATTR
53+
);
54+
if (classAttribute == null) {
55+
return;
56+
}
57+
final String classFqn = classAttribute.getValue();
58+
if (classFqn == null || classFqn.isEmpty()) {
59+
problemsHolder.registerProblem(
60+
classAttribute,
61+
inspectionBundle.message(
62+
"inspection.error.idAttributeCanNotBeEmpty",
63+
ModuleWebApiXmlFile.CLASS_ATTR
64+
),
65+
ProblemHighlightType.WARNING
66+
);
67+
68+
return;
69+
}
70+
71+
//Check whether the class exists
72+
final PhpIndex phpIndex = PhpIndex.getInstance(
73+
problemsHolder.getProject()
74+
);
75+
@NotNull final Collection<PhpClass> classes = phpIndex.getClassesByFQN(classFqn);
76+
@NotNull final Collection<PhpClass> interfaces = phpIndex
77+
.getInterfacesByFQN(classFqn);
78+
if (classes.isEmpty() && interfaces.isEmpty()) {
79+
problemsHolder.registerProblem(
80+
classAttribute,
81+
inspectionBundle.message(
82+
"inspection.warning.class.does.not.exist",
83+
classFqn
84+
),
85+
ProblemHighlightType.WARNING
86+
);
87+
return;
88+
}
89+
90+
//Check whether the method attribute is not empty
91+
final XmlAttribute methodAttribute = xmlTag.getAttribute(
92+
ModuleWebApiXmlFile.METHOD_ATTR
93+
);
94+
if (methodAttribute == null) {
95+
return;
96+
}
97+
final String methodName = methodAttribute.getValue();
98+
if (methodName == null || methodName.isEmpty()) {
99+
problemsHolder.registerProblem(
100+
classAttribute,
101+
inspectionBundle.message(
102+
"inspection.error.idAttributeCanNotBeEmpty",
103+
ModuleWebApiXmlFile.METHOD_ATTR
104+
),
105+
ProblemHighlightType.WARNING
106+
);
107+
108+
return;
109+
}
110+
111+
//Check whether method exists
112+
Method targetMethod = findTargetMethod(classes, methodName);
113+
if (targetMethod == null) {
114+
targetMethod = findTargetMethod(interfaces, methodName);
115+
}
116+
if (targetMethod == null && methodAttribute.getValueElement() != null) {
117+
problemsHolder.registerProblem(
118+
methodAttribute.getValueElement(),
119+
inspectionBundle.message(
120+
"inspection.warning.method.does.not.exist",
121+
methodName
122+
),
123+
ProblemHighlightType.WARNING
124+
);
125+
126+
return;
127+
}
128+
129+
//API method should have public access
130+
if (targetMethod.getAccess() != null && !targetMethod.getAccess().isPublic()) {
131+
problemsHolder.registerProblem(
132+
methodAttribute,
133+
inspectionBundle.message(
134+
"inspection.warning.method.should.have.public.access",
135+
methodName
136+
),
137+
ProblemHighlightType.WARNING,
138+
new MethodNotPublicAccessQuickFix(targetMethod)
139+
);
140+
}
141+
}
142+
143+
@Nullable
144+
private Method findTargetMethod(
145+
final Collection<PhpClass> classes,
146+
final String methodName
147+
) {
148+
for (final PhpClass phpClass : classes) {
149+
for (final Method method : phpClass.getMethods()) {
150+
if (method.getName().equals(methodName)) {
151+
return method;
152+
}
153+
}
154+
}
155+
return null;
156+
}
157+
};
158+
}
159+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.project.Project;
11+
import com.jetbrains.php.lang.inspections.quickfix.PhpChangeMethodModifiersQuickFix;
12+
import com.jetbrains.php.lang.psi.elements.Method;
13+
import com.jetbrains.php.lang.psi.elements.PhpModifier;
14+
import com.magento.idea.magento2plugin.bundles.InspectionBundle;
15+
import org.jetbrains.annotations.NotNull;
16+
17+
public class MethodNotPublicAccessQuickFix implements LocalQuickFix {
18+
private final InspectionBundle inspectionBundle = new InspectionBundle();
19+
private final Method method;
20+
21+
public MethodNotPublicAccessQuickFix(final Method method) {
22+
this.method = method;
23+
}
24+
25+
@NotNull
26+
@Override
27+
public String getFamilyName() {
28+
return inspectionBundle.message("inspection.warning.method.should.have.public.access.fix");
29+
}
30+
31+
@Override
32+
public void applyFix(
33+
final @NotNull Project project,
34+
final @NotNull ProblemDescriptor descriptor
35+
) {
36+
PhpChangeMethodModifiersQuickFix.changeMethodModifier(
37+
this.method,
38+
PhpModifier.PUBLIC_IMPLEMENTED_DYNAMIC
39+
);
40+
}
41+
}

src/com/magento/idea/magento2plugin/magento/files/AbstractPhpFile.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
public abstract class AbstractPhpFile implements ModuleFileInterface {
1414

1515
public static final String FILE_EXTENSION = "php";
16+
17+
public static final String PUBLIC_ACCESS = "public";
18+
1619
private final String moduleName;
1720
private final String className;
1821
private NamespaceBuilder namespaceBuilder;

src/com/magento/idea/magento2plugin/magento/files/ModuleWebApiXmlFile.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ public final class ModuleWebApiXmlFile implements ModuleFileInterface {
1313
public static final String FILE_NAME = "webapi.xml";
1414
public static final String TEMPLATE = "Magento Web Api XML";
1515
public static final String DECLARATION_TEMPLATE = "Magento Web Api Declaration";
16+
public static final String SERVICE_TAG_NAME = "service";
17+
public static final String CLASS_ATTR = "class";
18+
public static final String METHOD_ATTR = "method";
1619

1720
@Override
1821
public String getFileName() {

src/com/magento/idea/magento2plugin/magento/files/Plugin.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ public enum PluginType {
2424
around//NOPMD
2525
}
2626

27-
//allowed methods access type
28-
public static final String PUBLIC_ACCESS = "public";
29-
3027
private String fileName;
3128

3229
/**

src/com/magento/idea/magento2plugin/util/magento/plugin/IsPluginAllowedForMethodUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package com.magento.idea.magento2plugin.util.magento.plugin;
77

88
import com.jetbrains.php.lang.psi.elements.Method;
9-
import com.magento.idea.magento2plugin.magento.files.Plugin;
9+
import com.magento.idea.magento2plugin.magento.files.AbstractPhpFile;
1010
import com.magento.idea.magento2plugin.magento.packages.MagentoPhpClass;
1111

1212
public final class IsPluginAllowedForMethodUtil {
@@ -30,6 +30,6 @@ public static boolean check(final Method targetMethod) {
3030
if (targetMethod.isStatic()) {
3131
return false;
3232
}
33-
return targetMethod.getAccess().toString().equals(Plugin.PUBLIC_ACCESS);
33+
return targetMethod.getAccess().toString().equals(AbstractPhpFile.PUBLIC_ACCESS);
3434
}
3535
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Magento\Catalog\Api\ProductRepositoryInterface" method="some" />
5+
</route>
6+
</routes>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Magento\Catalog\Api\ProductRepositoryInterface" method="save" />
5+
</route>
6+
</routes>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Not\Existent\Class" method="some" />
5+
</route>
6+
</routes>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Magento\Catalog\Api\ProductRepositoryInterface" method="notExistent" />
5+
</route>
6+
</routes>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Foo\Bar\Service\SimpleService" method="myProtected" />
5+
</route>
6+
</routes>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
3+
<route method="GET" url="/V1/my-awesome-route">
4+
<service class="Foo\Bar\Service\SimpleService" method="execute" />
5+
</route>
6+
</routes>

testData/project/magento2/app/code/Foo/Bar/Service/SimpleService.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ public function execute(int $param1, string $param2)
77
{
88

99
}
10+
11+
protected function myProtected(int $param1, string $param2)
12+
{
13+
14+
}
1015
}

0 commit comments

Comments
 (0)