Skip to content

Commit 6cdd169

Browse files
committed
Add inspection and quick fix for fuzzy service class names
Since PhpIndex searches by fqn are not case sensitive, the missing class name inspection might not trigger. Contains a quick fix that can correct the FQN. Fixes: Haehnchen#1200
1 parent 4eda083 commit 6cdd169

File tree

3 files changed

+85
-18
lines changed

3 files changed

+85
-18
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlPsiElementFactory;
8+
import org.jetbrains.annotations.Nls;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.yaml.psi.YAMLKeyValue;
11+
12+
public class CorrectClassNameCasingYamlLocalQuickFix implements LocalQuickFix {
13+
14+
private final String replacementFQN;
15+
16+
public CorrectClassNameCasingYamlLocalQuickFix(String replacementFQN) {
17+
18+
this.replacementFQN = replacementFQN;
19+
}
20+
21+
@Nls(capitalization = Nls.Capitalization.Sentence)
22+
@NotNull
23+
@Override
24+
public String getFamilyName() {
25+
return "YAML";
26+
}
27+
28+
@Nls(capitalization = Nls.Capitalization.Sentence)
29+
@NotNull
30+
@Override
31+
public String getName() {
32+
return "Use " + replacementFQN;
33+
}
34+
35+
@Override
36+
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
37+
PsiElement psiElement1 = descriptor.getPsiElement();
38+
YAMLKeyValue replacement = YamlPsiElementFactory.createFromText(
39+
project,
40+
YAMLKeyValue.class,
41+
"class: " + replacementFQN
42+
);
43+
44+
if (replacement != null && replacement.getValue() != null) {
45+
psiElement1.replace(replacement.getValue());
46+
}
47+
}
48+
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/inspection/YamlClassInspection.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import com.intellij.psi.PsiElement;
77
import com.intellij.psi.PsiElementVisitor;
88
import com.jetbrains.php.PhpIndex;
9+
import com.jetbrains.php.lang.psi.elements.PhpClass;
910
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
11+
import fr.adrienbrault.idea.symfony2plugin.action.quickfix.CorrectClassNameCasingYamlLocalQuickFix;
1012
import fr.adrienbrault.idea.symfony2plugin.config.yaml.YamlElementPatternHelper;
1113
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
1214
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
@@ -20,6 +22,10 @@
2022
* @author Daniel Espendiller <daniel@espendiller.net>
2123
*/
2224
public class YamlClassInspection extends LocalInspectionTool {
25+
26+
public static final String MESSAGE_WRONG_CASING = "Wrong class casing";
27+
public static final String MESSAGE_MISSING_CLASS = "Missing class";
28+
2329
@NotNull
2430
public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, boolean isOnTheFly) {
2531
if (!Symfony2ProjectComponent.isEnabled(holder.getProject())) {
@@ -29,7 +35,7 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool
2935
return new PsiElementVisitor() {
3036
@Override
3137
public void visitElement(PsiElement psiElement) {
32-
if(!((YamlElementPatternHelper.getSingleLineScalarKey("class", "factory_class").accepts(psiElement) || YamlElementPatternHelper.getParameterClassPattern().accepts(psiElement)) && YamlElementPatternHelper.getInsideServiceKeyPattern().accepts(psiElement))) {
38+
if (!((YamlElementPatternHelper.getSingleLineScalarKey("class", "factory_class").accepts(psiElement) || YamlElementPatternHelper.getParameterClassPattern().accepts(psiElement)) && YamlElementPatternHelper.getInsideServiceKeyPattern().accepts(psiElement))) {
3339
super.visitElement(psiElement);
3440
return;
3541
}
@@ -44,15 +50,18 @@ public void visitElement(PsiElement psiElement) {
4450
private void invoke(@NotNull final PsiElement psiElement, @NotNull ProblemsHolder holder) {
4551
String className = PsiElementUtils.getText(psiElement);
4652

47-
if(YamlHelper.isValidParameterName(className)) {
53+
if (YamlHelper.isValidParameterName(className)) {
4854
String resolvedParameter = ContainerCollectionResolver.resolveParameter(psiElement.getProject(), className);
49-
if(resolvedParameter != null && PhpIndex.getInstance(psiElement.getProject()).getAnyByFQN(resolvedParameter).size() > 0) {
50-
return ;
55+
if (resolvedParameter != null && PhpIndex.getInstance(psiElement.getProject()).getAnyByFQN(resolvedParameter).size() > 0) {
56+
return;
5157
}
5258
}
5359

54-
if(PhpElementsUtil.getClassInterface(psiElement.getProject(), className) == null) {
55-
holder.registerProblem(psiElement, "Missing class", ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
60+
PhpClass foundClass = PhpElementsUtil.getClassInterface(psiElement.getProject(), className);
61+
if (foundClass == null) {
62+
holder.registerProblem(psiElement, MESSAGE_MISSING_CLASS, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
63+
} else if (!foundClass.getPresentableFQN().equals(className)) {
64+
holder.registerProblem(psiElement, MESSAGE_WRONG_CASING, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new CorrectClassNameCasingYamlLocalQuickFix(foundClass.getPresentableFQN()));
5665
}
5766
}
5867
}
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package fr.adrienbrault.idea.symfony2plugin.tests.dic.inspection;
22

3+
import fr.adrienbrault.idea.symfony2plugin.dic.inspection.YamlClassInspection;
34
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
45

5-
import java.io.File;
6-
76
/**
87
* @author Daniel Espendiller <daniel@espendiller.net>
98
* @see fr.adrienbrault.idea.symfony2plugin.dic.inspection.YamlClassInspection
@@ -20,15 +19,26 @@ public String getTestDataPath() {
2019
}
2120

2221
public void testInspectionForClass() {
23-
assertLocalInspectionContains("services.yml", "services:\n class: Args\\Fo<caret>oBar", "Missing Class");
24-
assertLocalInspectionContains("services.yml", "services:\n class: 'Args\\Fo<caret>oBar'", "Missing Class");
25-
assertLocalInspectionContains("services.yml", "services:\n class: \"Args\\Fo<caret>oBar\"", "Missing Class");
26-
assertLocalInspectionContains("services.yml", "services:\n factory_class: Args\\Fo<caret>oBar", "Missing Class");
27-
assertLocalInspectionNotContains("services.yml", "services:\n factory_class: Args\\Fo<caret>o", "Missing Class");
28-
29-
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>oBar", "Missing Class");
30-
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: 'Args\\Fo<caret>oBar'", "Missing Class");
31-
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: \"Args\\Fo<caret>oBar\"", "Missing Class");
32-
assertLocalInspectionNotContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>o", "Missing Class");
22+
assertLocalInspectionContains("services.yml", "services:\n class: Args\\Fo<caret>oBar", YamlClassInspection.MESSAGE_MISSING_CLASS);
23+
assertLocalInspectionContains("services.yml", "services:\n class: 'Args\\Fo<caret>oBar'", YamlClassInspection.MESSAGE_MISSING_CLASS);
24+
assertLocalInspectionContains("services.yml", "services:\n class: \"Args\\Fo<caret>oBar\"", YamlClassInspection.MESSAGE_MISSING_CLASS);
25+
assertLocalInspectionContains("services.yml", "services:\n factory_class: Args\\Fo<caret>oBar", YamlClassInspection.MESSAGE_MISSING_CLASS);
26+
assertLocalInspectionNotContains("services.yml", "services:\n factory_class: Args\\Fo<caret>o", YamlClassInspection.MESSAGE_MISSING_CLASS);
27+
28+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>oBar", YamlClassInspection.MESSAGE_MISSING_CLASS);
29+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: 'Args\\Fo<caret>oBar'", YamlClassInspection.MESSAGE_MISSING_CLASS);
30+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: \"Args\\Fo<caret>oBar\"", YamlClassInspection.MESSAGE_MISSING_CLASS);
31+
assertLocalInspectionNotContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>o", YamlClassInspection.MESSAGE_MISSING_CLASS);
32+
33+
assertLocalInspectionContains("services.yml", "services:\n class: Args\\Fo<caret>O", YamlClassInspection.MESSAGE_WRONG_CASING);
34+
assertLocalInspectionContains("services.yml", "services:\n class: 'Args\\Fo<caret>O'", YamlClassInspection.MESSAGE_WRONG_CASING);
35+
assertLocalInspectionContains("services.yml", "services:\n class: \"Args\\Fo<caret>O\"", YamlClassInspection.MESSAGE_WRONG_CASING);
36+
assertLocalInspectionContains("services.yml", "services:\n factory_class: Args\\Fo<caret>O", YamlClassInspection.MESSAGE_WRONG_CASING);
37+
assertLocalInspectionNotContains("services.yml", "services:\n factory_class: Args\\Fo<caret>o", YamlClassInspection.MESSAGE_WRONG_CASING);
38+
39+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>O", YamlClassInspection.MESSAGE_WRONG_CASING);
40+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: 'Args\\Fo<caret>O'", YamlClassInspection.MESSAGE_WRONG_CASING);
41+
assertLocalInspectionContains("services.yml", "parameters:\n foo.class: \"Args\\Fo<caret>O\"", YamlClassInspection.MESSAGE_WRONG_CASING);
42+
assertLocalInspectionNotContains("services.yml", "parameters:\n foo.class: Args\\Fo<caret>o", YamlClassInspection.MESSAGE_WRONG_CASING);
3343
}
3444
}

0 commit comments

Comments
 (0)