Skip to content

Commit c89803b

Browse files
committed
#2015 [Translation] Add inspection, autocompletion and navigation for named arguments
1 parent 1521749 commit c89803b

File tree

7 files changed

+162
-64
lines changed

7 files changed

+162
-64
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/PhpTranslationDomainInspection.java

+47-10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import com.intellij.codeInspection.LocalInspectionTool;
44
import com.intellij.codeInspection.ProblemHighlightType;
55
import com.intellij.codeInspection.ProblemsHolder;
6+
import com.intellij.lang.ASTNode;
67
import com.intellij.psi.PsiElement;
78
import com.intellij.psi.PsiElementVisitor;
9+
import com.intellij.psi.formatter.FormatterUtil;
10+
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
811
import com.jetbrains.php.lang.psi.elements.*;
912
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
1013
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
@@ -40,23 +43,41 @@ public void visitElement(PsiElement element) {
4043
}
4144

4245
private void invoke(@NotNull ProblemsHolder holder, @NotNull PsiElement psiElement) {
43-
if (!(psiElement instanceof StringLiteralExpression) || !(psiElement.getContext() instanceof ParameterList)) {
46+
if (!(psiElement instanceof StringLiteralExpression)) {
4447
return;
4548
}
4649

47-
ParameterList parameterList = (ParameterList) psiElement.getContext();
50+
PsiElement parameterList = psiElement.getContext();
51+
if (!(parameterList instanceof ParameterList)) {
52+
return;
53+
}
4854

49-
int domainParameter = -1;
50-
PsiElement methodReference = parameterList.getContext();
51-
if (methodReference instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReference, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) {
52-
domainParameter = 2;
53-
if("transChoice".equals(((MethodReference) methodReference).getName())) {
54-
domainParameter = 3;
55+
PsiElement methodReferenceOrNewExpression = parameterList.getContext();
56+
if (!(methodReferenceOrNewExpression instanceof MethodReference) && !(methodReferenceOrNewExpression instanceof NewExpression)) {
57+
return;
58+
}
59+
60+
ASTNode previousNonWhitespaceSibling = FormatterUtil.getPreviousNonWhitespaceSibling(psiElement.getNode());
61+
62+
if (previousNonWhitespaceSibling != null && previousNonWhitespaceSibling.getElementType() == PhpTokenTypes.opCOLON) {
63+
ASTNode previousNonWhitespaceSibling1 = FormatterUtil.getPreviousNonWhitespaceSibling(previousNonWhitespaceSibling);
64+
if (previousNonWhitespaceSibling1 != null && previousNonWhitespaceSibling1.getElementType() == PhpTokenTypes.IDENTIFIER) {
65+
String text = previousNonWhitespaceSibling1.getText();
66+
boolean isSupportedAttributeInsideContext = "domain".equals(text) && (
67+
(methodReferenceOrNewExpression instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_SIGNATURES))
68+
|| (methodReferenceOrNewExpression instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE))
69+
);
70+
71+
if (isSupportedAttributeInsideContext) {
72+
annotateTranslationDomain((StringLiteralExpression) psiElement, holder);
73+
}
5574
}
56-
} else if(methodReference instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReference, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)) {
57-
domainParameter = 2;
75+
76+
return;
5877
}
5978

79+
int domainParameter = getDomainParameter(methodReferenceOrNewExpression);
80+
6081
if (domainParameter >= 0) {
6182
ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(psiElement);
6283
if(currentIndex != null && currentIndex.getIndex() == domainParameter) {
@@ -65,6 +86,22 @@ private void invoke(@NotNull ProblemsHolder holder, @NotNull PsiElement psiEleme
6586
}
6687
}
6788

89+
public static int getDomainParameter(@NotNull PsiElement methodReferenceOrNewExpression) {
90+
if (methodReferenceOrNewExpression instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) {
91+
int domainParameter = 2;
92+
93+
if("transChoice".equals(((MethodReference) methodReferenceOrNewExpression).getName())) {
94+
domainParameter = 3;
95+
}
96+
97+
return domainParameter;
98+
} else if(methodReferenceOrNewExpression instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)) {
99+
return 2;
100+
}
101+
102+
return -1;
103+
}
104+
68105
private void annotateTranslationDomain(StringLiteralExpression psiElement, @NotNull ProblemsHolder holder) {
69106
String contents = psiElement.getContents();
70107
if(StringUtils.isBlank(contents) || TranslationUtil.hasDomain(psiElement.getProject(), contents)) {

src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/PhpTranslationKeyInspection.java

+17-22
Original file line numberDiff line numberDiff line change
@@ -42,45 +42,40 @@ public void visitElement(@NotNull PsiElement element) {
4242
}
4343

4444
private void invoke(@NotNull ProblemsHolder holder, @NotNull PsiElement psiElement) {
45-
if (!(psiElement instanceof StringLiteralExpression) || !(psiElement.getContext() instanceof ParameterList)) {
45+
if (!(psiElement instanceof StringLiteralExpression)) {
4646
return;
4747
}
4848

49-
ParameterList parameterList = (ParameterList) psiElement.getContext();
50-
51-
int domainParameter = -1;
52-
PsiElement methodReference = parameterList.getContext();
53-
if (methodReference instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReference, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) {
54-
domainParameter = 2;
55-
if("transChoice".equals(((MethodReference) methodReference).getName())) {
56-
domainParameter = 3;
57-
}
58-
} else if(methodReference instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReference, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)) {
59-
domainParameter = 2;
49+
PsiElement parameterList = psiElement.getContext();
50+
if (!(parameterList instanceof ParameterList)) {
51+
return;
6052
}
6153

62-
if (domainParameter < 0) {
54+
PsiElement methodReferenceOrNewExpression = parameterList.getContext();
55+
if (!(methodReferenceOrNewExpression instanceof MethodReference) && !(methodReferenceOrNewExpression instanceof NewExpression)) {
6356
return;
6457
}
6558

66-
ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(psiElement);
67-
if(currentIndex == null || currentIndex.getIndex() != 0) {
59+
if (!PsiElementUtils.isCurrentParameter(psiElement, "id", 0)) {
6860
return;
6961
}
7062

71-
PsiElement domainElement = PsiElementUtils.getMethodParameterPsiElementAt(parameterList, domainParameter);
63+
if (!(
64+
(methodReferenceOrNewExpression instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) ||
65+
(methodReferenceOrNewExpression instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)))
66+
) {
67+
return;
68+
}
7269

70+
PsiElement domainElement = ((ParameterList) parameterList).getParameter("domain", PhpTranslationDomainInspection.getDomainParameter(parameterList.getContext()));
7371
if(domainElement == null) {
7472
// no domain found; fallback to default domain
7573
annotateTranslationKey((StringLiteralExpression) psiElement, "messages", holder);
7674
} else {
7775
// resolve string in parameter
78-
PsiElement[] parameters = parameterList.getParameters();
79-
if(parameters.length >= domainParameter) {
80-
String domain = PhpElementsUtil.getStringValue(parameters[domainParameter]);
81-
if(domain != null) {
82-
annotateTranslationKey((StringLiteralExpression) psiElement, domain, holder);
83-
}
76+
String domain = PhpElementsUtil.getStringValue(domainElement);
77+
if(domain != null) {
78+
annotateTranslationKey((StringLiteralExpression) psiElement, domain, holder);
8479
}
8580
}
8681
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/translation/TranslationReferenceContributor.java

+15-26
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
1010
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
1111
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
12-
import fr.adrienbrault.idea.symfony2plugin.util.ParameterBag;
1312
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1413
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
1514
import org.jetbrains.annotations.NotNull;
@@ -33,48 +32,38 @@ public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistr
3332
}
3433

3534
ParameterList parameterList = (ParameterList) psiElement.getContext();
36-
PsiElement methodReference = parameterList.getContext();
35+
PsiElement methodReferenceOrNewExpression = parameterList.getContext();
3736

38-
int domainParameter = -1;
39-
if (methodReference instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReference, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) {
40-
domainParameter = 2;
41-
if("transChoice".equals(((MethodReference) methodReference).getName())) {
42-
domainParameter = 3;
43-
}
44-
} else if(methodReference instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReference, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)) {
45-
domainParameter = 2;
46-
}
47-
48-
if (domainParameter < 0) {
37+
if (!(
38+
(methodReferenceOrNewExpression instanceof MethodReference && PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_SIGNATURES)) ||
39+
(methodReferenceOrNewExpression instanceof NewExpression && PhpElementsUtil.isNewExpressionPhpClassWithInstance((NewExpression) methodReferenceOrNewExpression, TranslationUtil.PHP_TRANSLATION_TRANSLATABLE_MESSAGE)))
40+
) {
4941
return new PsiReference[0];
5042
}
5143

52-
ParameterBag currentIndex = PsiElementUtils.getCurrentParameterIndex(psiElement);
53-
if(currentIndex == null) {
54-
return new PsiReference[0];
55-
}
44+
int domainParameter = PhpTranslationDomainInspection.getDomainParameter(methodReferenceOrNewExpression);
5645

57-
if(currentIndex.getIndex() == domainParameter) {
46+
if (PsiElementUtils.isCurrentParameter(psiElement, "domain", domainParameter)) {
5847
return new PsiReference[]{ new TranslationDomainReference((StringLiteralExpression) psiElement) };
5948
}
6049

61-
if(currentIndex.getIndex() == 0) {
62-
String domain = PsiElementUtils.getMethodParameterAt(parameterList, domainParameter);
50+
if (PsiElementUtils.isCurrentParameter(psiElement, "id", 0)) {
51+
PsiElement domainPsi = parameterList.getParameter("domain", domainParameter);
6352

64-
if(domain == null) {
65-
domain = "messages";
53+
String domain = "messages";
54+
if (domainPsi != null) {
55+
String stringValue = PhpElementsUtil.getStringValue(domainPsi);
56+
if (stringValue != null) {
57+
domain = stringValue;
58+
}
6659
}
6760

6861
return new PsiReference[]{ new TranslationReference((StringLiteralExpression) psiElement, domain) };
6962
}
7063

7164
return new PsiReference[0];
7265
}
73-
7466
}
75-
7667
);
77-
7868
}
79-
8069
}

src/main/java/fr/adrienbrault/idea/symfony2plugin/util/PsiElementUtils.java

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

3+
import com.intellij.lang.ASTNode;
34
import com.intellij.openapi.project.Project;
45
import com.intellij.openapi.vfs.VirtualFile;
56
import com.intellij.patterns.ElementPattern;
@@ -8,12 +9,14 @@
89
import com.intellij.psi.PsiFile;
910
import com.intellij.psi.PsiManager;
1011
import com.intellij.psi.PsiWhiteSpace;
12+
import com.intellij.psi.formatter.FormatterUtil;
1113
import com.intellij.psi.tree.IElementType;
1214
import com.intellij.psi.util.PsiTreeUtil;
1315
import com.intellij.util.Processor;
1416
import com.jetbrains.php.lang.PhpLanguage;
1517
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
1618
import com.jetbrains.php.lang.psi.elements.*;
19+
import fr.adrienbrault.idea.symfony2plugin.translation.dict.TranslationUtil;
1720
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
1821
import org.jetbrains.annotations.NotNull;
1922
import org.jetbrains.annotations.Nullable;
@@ -117,17 +120,30 @@ public static ParameterBag getCurrentParameterIndex(PsiElement[] parameters, Psi
117120

118121
@Nullable
119122
public static ParameterBag getCurrentParameterIndex(PsiElement psiElement) {
120-
121-
if (!(psiElement.getContext() instanceof ParameterList)) {
123+
PsiElement parameterList = psiElement.getContext();
124+
if (!(parameterList instanceof ParameterList)) {
122125
return null;
123126
}
124127

125-
ParameterList parameterList = (ParameterList) psiElement.getContext();
126-
if (!(parameterList.getContext() instanceof ParameterListOwner)) {
127-
return null;
128+
return getCurrentParameterIndex(((ParameterList) parameterList).getParameters(), psiElement);
129+
}
130+
131+
public static boolean isCurrentParameter(@NotNull PsiElement psiElement, @NotNull String namedParameter, int index) {
132+
PsiElement parameterList = psiElement.getContext();
133+
if (!(parameterList instanceof ParameterList)) {
134+
return false;
135+
}
136+
137+
ASTNode previousNonWhitespaceSibling = FormatterUtil.getPreviousNonWhitespaceSibling(psiElement.getNode());
138+
if (previousNonWhitespaceSibling != null && previousNonWhitespaceSibling.getElementType() == PhpTokenTypes.opCOLON) {
139+
ASTNode previousNonWhitespaceSibling1 = FormatterUtil.getPreviousNonWhitespaceSibling(previousNonWhitespaceSibling);
140+
if (previousNonWhitespaceSibling1 != null && previousNonWhitespaceSibling1.getElementType() == PhpTokenTypes.IDENTIFIER && namedParameter.equals(previousNonWhitespaceSibling1.getText())) {
141+
return true;
142+
}
128143
}
129144

130-
return getCurrentParameterIndex(parameterList.getParameters(), psiElement);
145+
ParameterBag currentParameterIndex = getCurrentParameterIndex(((ParameterList) parameterList).getParameters(), psiElement);
146+
return currentParameterIndex != null && currentParameterIndex.getIndex() == index;
131147
}
132148

133149
public static int getParameterIndexValue(@Nullable PsiElement parameterListChild) {

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/translation/PhpTranslationDomainInspectionTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ public void testThatPhpTranslationDomainInspectionsAreProvided() {
4848
"$x->trans('foo<caret>bar')",
4949
PhpTranslationDomainInspection.MESSAGE
5050
);
51+
52+
assertLocalInspectionContains("test.php", "<?php\n" +
53+
"/** @var $x Symfony\\Component\\Translation\\TranslatorInterface */" +
54+
"$x->trans('id', domain: 'dom<caret>ain')",
55+
PhpTranslationDomainInspection.MESSAGE
56+
);
5157
}
5258

5359
public void testThatPhpTranslationDomainInspectionsForTranslatableMessageAreProvided() {
@@ -65,5 +71,10 @@ public void testThatPhpTranslationDomainInspectionsForTranslatableMessageAreProv
6571
"new \\Symfony\\Component\\Translation\\TranslatableMessage('foobar', [], 'sym<caret>fony');",
6672
PhpTranslationDomainInspection.MESSAGE
6773
);
74+
75+
assertLocalInspectionNotContains("test.php", "<?php\n" +
76+
"new \\Symfony\\Component\\Translation\\TranslatableMessage(domain: 'sym<caret>fony');",
77+
PhpTranslationDomainInspection.MESSAGE
78+
);
6879
}
6980
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/translation/PhpTranslationKeyInspectionTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,24 @@ public void testThatPhpTransInspectionsAreNotProvidedForKnownTranslations() {
5353
PhpTranslationKeyInspection.MESSAGE
5454
);
5555

56+
assertLocalInspectionContains("test.php", "<?php\n" +
57+
"/** @var $x Symfony\\Component\\Translation\\TranslatorInterface */" +
58+
"$x->trans(id: 'symfon<caret>y.great')",
59+
PhpTranslationKeyInspection.MESSAGE
60+
);
61+
5662
assertLocalInspectionNotContains("test.php", "<?php\n" +
5763
"/** @var $x Symfony\\Component\\Translation\\TranslatorInterface */" +
5864
"$x->trans('symfon<caret>y.great', [], 'symfony')",
5965
PhpTranslationKeyInspection.MESSAGE
6066
);
6167

68+
assertLocalInspectionNotContains("test.php", "<?php\n" +
69+
"/** @var $x Symfony\\Component\\Translation\\TranslatorInterface */" +
70+
"$x->trans('symfon<caret>y.great', domain: 'symfony')",
71+
PhpTranslationKeyInspection.MESSAGE
72+
);
73+
6274
assertLocalInspectionNotContains("test.php", "<?php\n" +
6375
"/** @var $x Symfony\\Component\\Translation\\TranslatorInterface */" +
6476
"$x->transChoice('symfon<caret>y.great', 1, [], 'symfony')",
@@ -104,6 +116,11 @@ public void testThatPhpTranslationKeyInspectionsForTranslatableMessageAreProvide
104116
PhpTranslationKeyInspection.MESSAGE
105117
);
106118

119+
assertLocalInspectionNotContains("test.php", "<?php\n" +
120+
"new \\Symfony\\Component\\Translation\\TranslatableMessage('symfon<caret>y.great', domain: 'symfony');",
121+
PhpTranslationKeyInspection.MESSAGE
122+
);
123+
107124
assertLocalInspectionNotContains("test.php", "<?php\n" +
108125
"new \\Symfony\\Component\\Translation\\TranslatableMessage('symfon<caret>y.great', [], 'symfony');",
109126
PhpTranslationKeyInspection.MESSAGE

0 commit comments

Comments
 (0)