Skip to content

Commit cabd739

Browse files
committed
initial xml service "missing argument" inspection quickfix #470
1 parent 2ddf801 commit cabd739

8 files changed

+535
-2
lines changed

META-INF/plugin.xml

+5
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@
332332
enabledByDefault="true" level="WARNING"
333333
implementationClass="fr.adrienbrault.idea.symfony2plugin.config.yaml.inspection.EventMethodCallInspection"/>
334334

335+
<localInspection groupPath="Symfony2" shortName="XmlServiceArgumentInspection" displayName="Add Arguments"
336+
groupName="Service"
337+
enabledByDefault="true" level="WARNING"
338+
implementationClass="fr.adrienbrault.idea.symfony2plugin.config.xml.inspection.XmlServiceArgumentInspection"/>
339+
335340
<toolWindow id="Symfony2" anchor="left" secondary="false" icon="SymfonyIcons.SymfonyToolWindow"
336341
factoryClass="fr.adrienbrault.idea.symfony2plugin.toolwindow.Symfony2SearchToolWindowFactory"
337342
conditionClass="fr.adrienbrault.idea.symfony2plugin.toolwindow.Symfony2SearchToolWindowFactory"

src/fr/adrienbrault/idea/symfony2plugin/action/ServiceActionUtil.java

+123
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fr.adrienbrault.idea.symfony2plugin.action;
22

33

4+
import com.intellij.codeInspection.ProblemsHolder;
45
import com.intellij.ide.IdeView;
56
import com.intellij.ide.highlighter.XmlFileType;
67
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -15,13 +16,27 @@
1516
import com.intellij.psi.PsiFile;
1617
import com.intellij.psi.PsiFileFactory;
1718
import com.intellij.psi.codeStyle.CodeStyleManager;
19+
import com.intellij.psi.util.PsiTreeUtil;
20+
import com.intellij.psi.xml.XmlAttribute;
21+
import com.intellij.psi.xml.XmlTag;
22+
import com.jetbrains.php.lang.psi.elements.Method;
23+
import com.jetbrains.php.lang.psi.elements.Parameter;
24+
import com.jetbrains.php.lang.psi.elements.PhpClass;
1825
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
26+
import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil;
27+
import fr.adrienbrault.idea.symfony2plugin.action.ui.SymfonyCreateService;
28+
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService;
29+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1930
import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil;
31+
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
2032
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle;
2133
import org.apache.commons.lang.StringUtils;
34+
import org.jetbrains.annotations.NotNull;
35+
import org.jetbrains.annotations.Nullable;
2236
import org.jetbrains.yaml.YAMLFileType;
2337

2438
import java.io.IOException;
39+
import java.util.*;
2540

2641
public class ServiceActionUtil {
2742

@@ -98,4 +113,112 @@ public void run() {
98113

99114
}
100115

116+
@NotNull
117+
public static Set<String> getPossibleServices(Project project, String type, Map<String, ContainerService> serviceClasses) {
118+
119+
Set<String> possibleServices = new LinkedHashSet<String>();
120+
List<ContainerService> matchedContainer = new ArrayList<ContainerService>();
121+
122+
PhpClass typeClass = PhpElementsUtil.getClassInterface(project, type);
123+
if(typeClass != null) {
124+
for(Map.Entry<String, ContainerService> entry: serviceClasses.entrySet()) {
125+
if(entry.getValue().getClassName() != null) {
126+
PhpClass serviceClass = PhpElementsUtil.getClassInterface(project, entry.getValue().getClassName());
127+
if(serviceClass != null) {
128+
if(new Symfony2InterfacesUtil().isInstanceOf(serviceClass, typeClass)) {
129+
matchedContainer.add(entry.getValue());
130+
}
131+
}
132+
}
133+
134+
}
135+
}
136+
137+
if(matchedContainer.size() > 0) {
138+
139+
// weak service have lower priority
140+
Collections.sort(matchedContainer, new SymfonyCreateService.ContainerServicePriorityWeakComparator());
141+
142+
// lower priority of services like "doctrine.orm.default_entity_manager"
143+
Collections.sort(matchedContainer, new SymfonyCreateService.ContainerServicePriorityNameComparator());
144+
145+
for(ContainerService containerService: matchedContainer) {
146+
possibleServices.add(containerService.getName());
147+
}
148+
149+
}
150+
151+
return possibleServices;
152+
}
153+
154+
@NotNull
155+
public static Collection<XmlTag> getXmlContainerServiceDefinition(PsiFile psiFile) {
156+
157+
Collection<XmlTag> xmlTags = new ArrayList<XmlTag>();
158+
159+
for(XmlTag xmlTag: PsiTreeUtil.getChildrenOfTypeAsList(psiFile.getFirstChild(), XmlTag.class)) {
160+
if(xmlTag.getName().equals("container")) {
161+
for(XmlTag servicesTag: xmlTag.getSubTags()) {
162+
if(servicesTag.getName().equals("services")) {
163+
for(XmlTag parameterTag: servicesTag.getSubTags()) {
164+
if(parameterTag.getName().equals("service")) {
165+
xmlTags.add(parameterTag);
166+
}
167+
}
168+
}
169+
}
170+
}
171+
}
172+
173+
return xmlTags;
174+
}
175+
176+
@Nullable
177+
public static List<String> getXmlMissingArgumentTypes(XmlTag xmlTag) {
178+
179+
XmlAttribute classAttribute = xmlTag.getAttribute("class");
180+
if(classAttribute == null) {
181+
return null;
182+
}
183+
184+
String value = classAttribute.getValue();
185+
if(StringUtils.isBlank(value)) {
186+
return null;
187+
}
188+
189+
// @TODO: cache defs
190+
PhpClass resolvedClassDefinition = ServiceUtil.getResolvedClassDefinition(xmlTag.getProject(), value);
191+
if(resolvedClassDefinition == null) {
192+
return null;
193+
}
194+
195+
Method constructor = resolvedClassDefinition.getConstructor();
196+
if(constructor == null) {
197+
return null;
198+
}
199+
200+
int serviceArguments = 0;
201+
202+
for (XmlTag tag : xmlTag.getSubTags()) {
203+
if("argument".equals(tag.getName())) {
204+
serviceArguments++;
205+
}
206+
}
207+
208+
Parameter[] parameters = constructor.getParameters();
209+
if(parameters.length <= serviceArguments) {
210+
return null;
211+
}
212+
213+
final List<String> args = new ArrayList<String>();
214+
215+
for (int i = serviceArguments; i < parameters.length; i++) {
216+
Parameter parameter = parameters[i];
217+
String s = parameter.getDeclaredType().toString();
218+
args.add(s);
219+
}
220+
221+
return args;
222+
}
223+
101224
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package fr.adrienbrault.idea.symfony2plugin.action.quickfix;
2+
3+
import com.intellij.codeInspection.LocalQuickFix;
4+
import com.intellij.codeInspection.ProblemDescriptor;
5+
import com.intellij.openapi.project.Project;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.XmlElementFactory;
8+
import com.intellij.psi.xml.XmlTag;
9+
import fr.adrienbrault.idea.symfony2plugin.action.ServiceActionUtil;
10+
import fr.adrienbrault.idea.symfony2plugin.action.ui.ServiceArgumentSelectionDialog;
11+
import fr.adrienbrault.idea.symfony2plugin.dic.ContainerService;
12+
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
13+
import org.jetbrains.annotations.NotNull;
14+
15+
import java.util.LinkedHashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Set;
19+
20+
/**
21+
* @author Daniel Espendiller <daniel@espendiller.net>
22+
*/
23+
public class AddServiceXmlArgumentLocalQuickFix implements LocalQuickFix {
24+
25+
private final List<String> args;
26+
27+
public AddServiceXmlArgumentLocalQuickFix(List<String> args) {
28+
this.args = args;
29+
}
30+
31+
@NotNull
32+
@Override
33+
public String getName() {
34+
return "Missing Argument";
35+
}
36+
37+
@NotNull
38+
@Override
39+
public String getFamilyName() {
40+
return "Missing Argument";
41+
}
42+
43+
@Override
44+
public void applyFix(final @NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) {
45+
46+
final PsiElement parent = problemDescriptor.getPsiElement().getParent();
47+
if(!(parent instanceof XmlTag)) {
48+
return;
49+
}
50+
51+
Map<String, Set<String>> resolved = new LinkedHashMap<String, Set<String>>();
52+
Map<String, ContainerService> services = ContainerCollectionResolver.getServices(project);
53+
54+
for (String arg : args) {
55+
resolved.put(arg, ServiceActionUtil.getPossibleServices(project, arg, services));
56+
}
57+
58+
ServiceArgumentSelectionDialog.createDialog(project, resolved, new ServiceArgumentSelectionDialog.Callback() {
59+
@Override
60+
public void onOk(List<String> items) {
61+
for (String item : items) {
62+
XmlTag tag = XmlElementFactory.getInstance(project).createTagFromText(String.format("<argument type=\"service\" id=\"%s\"/>", item), parent.getLanguage());
63+
((XmlTag) parent).addSubTag(tag, false);
64+
}
65+
}
66+
});
67+
68+
}
69+
70+
}

0 commit comments

Comments
 (0)