Skip to content

Commit c33b870

Browse files
committed
inspection for named arguments in yaml
1 parent bbfc0c0 commit c33b870

File tree

7 files changed

+179
-0
lines changed

7 files changed

+179
-0
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/yaml/YamlElementPatternHelper.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,23 @@ public static ElementPattern<PsiElement> getSingleLineScalarKey(String... keyNam
126126
);
127127
}
128128

129+
public static ElementPattern<PsiElement> getNamedArgumentPattern() {
130+
return PlatformPatterns
131+
.psiElement(YAMLTokenTypes.SCALAR_KEY).withText(PlatformPatterns.string().startsWith("$"))
132+
.withParent(
133+
PlatformPatterns.psiElement(YAMLKeyValue.class)
134+
.withParent(PlatformPatterns.psiElement(YAMLMapping.class)
135+
.withParent(PlatformPatterns.psiElement(YAMLKeyValue.class).with(new PatternCondition<>("YAMLKeyValue: with key 'arguments'") {
136+
@Override
137+
public boolean accepts(@NotNull YAMLKeyValue yamlKeyValue, ProcessingContext context) {
138+
return "arguments".equals(yamlKeyValue.getKeyText());
139+
}
140+
}))
141+
)
142+
)
143+
.withLanguage(YAMLLanguage.INSTANCE);
144+
}
145+
129146
public static ElementPattern<PsiElement> getSingleLineText() {
130147
return
131148
PlatformPatterns

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,44 @@ public static Parameter getYamlNamedArgument(@NotNull PsiElement psiElement, @No
545545
return null;
546546
}
547547

548+
/**
549+
* arguments: ['$foobar': '@foo']
550+
*/
551+
public static boolean hasMissingYamlNamedArgumentForInspection(@NotNull PsiElement psiElement, @NotNull ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector) {
552+
PsiElement context = psiElement.getContext();
553+
if(context instanceof YAMLKeyValue) {
554+
// arguments: ['$foobar': '@foo']
555+
556+
String parameterName = ((YAMLKeyValue) context).getKeyText();
557+
if(parameterName.startsWith("$") && parameterName.length() > 1) {
558+
PsiElement yamlMapping = context.getParent();
559+
if(yamlMapping instanceof YAMLMapping) {
560+
PsiElement yamlKeyValue = yamlMapping.getParent();
561+
if(yamlKeyValue instanceof YAMLKeyValue) {
562+
String keyText = ((YAMLKeyValue) yamlKeyValue).getKeyText();
563+
if(keyText.equals("arguments")) {
564+
YAMLMapping parentMapping = ((YAMLKeyValue) yamlKeyValue).getParentMapping();
565+
if(parentMapping != null) {
566+
String serviceId = getServiceClassFromServiceMapping(parentMapping);
567+
if(StringUtils.isNotBlank(serviceId)) {
568+
PhpClass serviceClass = ServiceUtil.getResolvedClassDefinition(psiElement.getProject(), serviceId, lazyServiceCollector);
569+
// class not found don't need a hint
570+
if (serviceClass == null) {
571+
return false;
572+
}
573+
574+
return PhpElementsUtil.getConstructorParameterArgumentByName(serviceClass, StringUtils.stripStart(parameterName, "$")) == null;
575+
}
576+
}
577+
}
578+
}
579+
}
580+
}
581+
}
582+
583+
return false;
584+
}
585+
548586
/**
549587
* services:
550588
* _defaults:
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package fr.adrienbrault.idea.symfony2plugin.dic.inspection;
2+
3+
import com.intellij.codeInspection.LocalInspectionTool;
4+
import com.intellij.codeInspection.ProblemHighlightType;
5+
import com.intellij.codeInspection.ProblemsHolder;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiElementVisitor;
8+
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
9+
import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper;
10+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
11+
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
12+
import org.jetbrains.annotations.NotNull;
13+
import org.jetbrains.yaml.psi.YAMLKeyValue;
14+
import org.jetbrains.yaml.psi.YAMLMapping;
15+
16+
/**
17+
* @author Daniel Espendiller <daniel@espendiller.net>
18+
*/
19+
public class ServiceNamedArgumentExistsInspection extends LocalInspectionTool {
20+
public static final String INSPECTION_MESSAGE = "Symfony: named argument does not exists";
21+
22+
@NotNull
23+
public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, boolean isOnTheFly) {
24+
if (!Symfony2ProjectComponent.isEnabled(holder.getProject())) {
25+
return super.buildVisitor(holder, isOnTheFly);
26+
}
27+
28+
return new PsiElementVisitor() {
29+
@Override
30+
public void visitElement(@NotNull PsiElement element) {
31+
if (YamlElementPatternHelper.getNamedArgumentPattern().accepts(element)) {
32+
if (isSupportedDefinition(element) && ServiceContainerUtil.hasMissingYamlNamedArgumentForInspection(element, new ContainerCollectionResolver.LazyServiceCollector(element.getProject()))) {
33+
holder.registerProblem(element, INSPECTION_MESSAGE, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
34+
}
35+
}
36+
37+
super.visitElement(element);
38+
}
39+
};
40+
}
41+
42+
private boolean isSupportedDefinition(@NotNull PsiElement element) {
43+
PsiElement context = element.getContext();
44+
45+
if (context instanceof YAMLKeyValue) {
46+
// arguments: ['$foobar': '@foo']
47+
PsiElement yamlMapping = context.getParent();
48+
if (yamlMapping instanceof YAMLMapping) {
49+
PsiElement yamlKeyValue = yamlMapping.getParent();
50+
if (yamlKeyValue instanceof YAMLKeyValue) {
51+
YAMLMapping parentMapping = ((YAMLKeyValue) yamlKeyValue).getParentMapping();
52+
if (parentMapping != null) {
53+
return parentMapping.getKeyValueByKey("factory") == null;
54+
}
55+
}
56+
}
57+
}
58+
59+
return true;
60+
}
61+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@
501501
level="WARNING"
502502
implementationClass="fr.adrienbrault.idea.symfony2plugin.routing.RouteControllerDeprecatedInspection"/>
503503

504+
<localInspection groupPath="Symfony" shortName="ServiceNamedArgumentExistsInspection" displayName="Symfony: Argument does not exists"
505+
groupName="Service"
506+
enabledByDefault="true"
507+
level="WARNING"
508+
implementationClass="fr.adrienbrault.idea.symfony2plugin.dic.inspection.ServiceNamedArgumentExistsInspection"/>
509+
504510
<intentionAction>
505511
<className>fr.adrienbrault.idea.symfony2plugin.intentions.php.PhpServiceIntention</className>
506512
<category>PHP</category>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
<!-- tooltip end -->
4+
</body>
5+
</html>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.dic.inspection;
2+
3+
import fr.adrienbrault.idea.symfony2plugin.dic.inspection.ServiceNamedArgumentExistsInspection;
4+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
5+
6+
/**
7+
* @author Daniel Espendiller <daniel@espendiller.net>
8+
*/
9+
public class ServiceNamedArgumentExistsInspectionTest extends SymfonyLightCodeInsightFixtureTestCase {
10+
public void setUp() throws Exception {
11+
super.setUp();
12+
myFixture.copyFileToProject("classes.php");
13+
myFixture.copyFileToProject("services.xml");
14+
}
15+
16+
public String getTestDataPath() {
17+
return "src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/inspection/fixtures";
18+
}
19+
20+
public void testMissingArgumentForYaml() {
21+
assertLocalInspectionContains("foo.yml",
22+
"Foobar\\NamedArgument:\n" +
23+
" arguments:\n" +
24+
" $foo<caret>bar1: ~",
25+
ServiceNamedArgumentExistsInspection.INSPECTION_MESSAGE
26+
);
27+
28+
assertLocalInspectionNotContains("foo.yml",
29+
"Foobar\\UnknownClassNamedArgument:\n" +
30+
" arguments:\n" +
31+
" $foo<caret>bar: ~",
32+
ServiceNamedArgumentExistsInspection.INSPECTION_MESSAGE
33+
);
34+
}
35+
36+
public void testMissingArgumentForFactoryServiceIsNotTriggeredYaml() {
37+
assertLocalInspectionNotContains("foo.yml",
38+
"Foobar\\NamedArgument:\n" +
39+
" factory: ~\n" +
40+
" arguments:\n" +
41+
" $foo<caret>bar1: ~",
42+
ServiceNamedArgumentExistsInspection.INSPECTION_MESSAGE
43+
);
44+
}
45+
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/inspection/fixtures/classes.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ class Car
1414
{
1515
const FOOBAR = null;
1616
}
17+
18+
class NamedArgument
19+
{
20+
public function __construct($foobar)
21+
{
22+
}
23+
}
1724
}

0 commit comments

Comments
 (0)