Skip to content

Commit 20fd411

Browse files
committed
Refactored ParameterLanguageInjector
1 parent 9e62a68 commit 20fd411

File tree

7 files changed

+817
-730
lines changed

7 files changed

+817
-730
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package fr.adrienbrault.idea.symfony2plugin.lang;
2+
3+
import com.intellij.lang.Language;
4+
import com.intellij.patterns.ElementPattern;
5+
import com.intellij.patterns.StandardPatterns;
6+
import com.intellij.psi.PsiElement;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public final class LanguageInjection {
14+
@NotNull
15+
private final String languageId;
16+
@Nullable
17+
private final String prefix;
18+
@Nullable
19+
private final String suffix;
20+
@NotNull
21+
private final ElementPattern<? extends PsiElement> pattern;
22+
23+
protected LanguageInjection(@NotNull String languageId, @Nullable String prefix, @Nullable String suffix, @NotNull ElementPattern<? extends PsiElement> pattern) {
24+
this.languageId = languageId;
25+
this.prefix = prefix;
26+
this.suffix = suffix;
27+
this.pattern = pattern;
28+
}
29+
30+
@Nullable
31+
public Language getLanguage() {
32+
return Language.findLanguageByID(languageId);
33+
}
34+
35+
@Nullable
36+
public String getPrefix() {
37+
return prefix;
38+
}
39+
40+
@Nullable
41+
public String getSuffix() {
42+
return suffix;
43+
}
44+
45+
@NotNull
46+
public ElementPattern<? extends PsiElement> getPattern() {
47+
return pattern;
48+
}
49+
50+
public static class Builder {
51+
@NotNull
52+
private final String languageId;
53+
@NotNull
54+
private final List<ElementPattern<? extends PsiElement>> patterns;
55+
@Nullable
56+
private String prefix;
57+
@Nullable
58+
private String suffix;
59+
60+
public Builder(@NotNull String languageId) {
61+
this.languageId = languageId;
62+
this.patterns = new ArrayList<>();
63+
}
64+
65+
public Builder withPrefix(@Nullable String prefix) {
66+
this.prefix = prefix;
67+
68+
return this;
69+
}
70+
71+
public Builder withSuffix(@Nullable String suffix) {
72+
this.suffix = suffix;
73+
74+
return this;
75+
}
76+
77+
public final Builder matchingPattern(@NotNull ElementPattern<? extends PsiElement> pattern) {
78+
patterns.add(pattern);
79+
return this;
80+
}
81+
82+
public Builder matchingAttributeArgument(@NotNull String classFQN, @NotNull String argumentName, int argumentIndex) {
83+
return matchingPattern(LanguageInjectionPatterns.getAttributeArgumentPattern(classFQN, argumentName, argumentIndex));
84+
}
85+
86+
public Builder matchingAnnotationProperty(@NotNull String classFQN, @NotNull String propertyName, boolean isDefaultProperty) {
87+
return matchingPattern(LanguageInjectionPatterns.getAnnotationPropertyPattern(classFQN, propertyName, isDefaultProperty));
88+
}
89+
90+
public Builder matchingConstructorCallArgument(@NotNull String classFQN, @NotNull String argumentName, int argumentIndex) {
91+
return matchingPattern(LanguageInjectionPatterns.getConstructorCallArgumentPattern(classFQN, argumentName, argumentIndex));
92+
}
93+
94+
public Builder matchingFunctionCallArgument(@NotNull String functionFQN, @NotNull String argumentName, int argumentIndex) {
95+
return matchingPattern(LanguageInjectionPatterns.getFunctionCallArgumentPattern(functionFQN, argumentName, argumentIndex));
96+
}
97+
98+
public Builder matchingMethodCallArgument(@NotNull String classFQN, @NotNull String methodName, @NotNull String argumentName, int argumentIndex) {
99+
return matchingPattern(LanguageInjectionPatterns.getMethodCallArgumentPattern(classFQN, methodName, argumentName, argumentIndex));
100+
}
101+
102+
public LanguageInjection build() {
103+
return new LanguageInjection(languageId, prefix, suffix, StandardPatterns.or(patterns.toArray(new ElementPattern[0])));
104+
}
105+
}
106+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package fr.adrienbrault.idea.symfony2plugin.lang;
2+
3+
import com.intellij.patterns.ElementPattern;
4+
import com.intellij.patterns.PatternCondition;
5+
import com.intellij.patterns.PlatformPatterns;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiWhiteSpace;
8+
import com.intellij.util.ProcessingContext;
9+
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
10+
import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes;
11+
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
12+
import com.jetbrains.php.lang.psi.elements.*;
13+
import com.jetbrains.php.lang.psi.elements.impl.ParameterListImpl;
14+
import de.espend.idea.php.annotation.util.AnnotationUtil;
15+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
16+
import org.jetbrains.annotations.NotNull;
17+
import org.jetbrains.annotations.Nullable;
18+
19+
public final class LanguageInjectionPatterns {
20+
21+
public static ElementPattern<? extends PsiElement> getAnnotationPropertyPattern(
22+
@NotNull String classFQN,
23+
@NotNull String propertyName,
24+
boolean isDefaultProperty) {
25+
return PlatformPatterns
26+
.psiElement(StringLiteralExpression.class)
27+
.with(new IsAnnotationProperty(classFQN, propertyName, isDefaultProperty));
28+
}
29+
30+
public static ElementPattern<? extends PsiElement> getAttributeArgumentPattern(
31+
@NotNull String classFQN,
32+
@NotNull String argumentName,
33+
int argumentIndex
34+
) {
35+
return PlatformPatterns.psiElement()
36+
.with(new IsArgument(argumentName, argumentIndex))
37+
.withParent(PlatformPatterns
38+
.psiElement(ParameterList.class)
39+
.withParent(
40+
PlatformPatterns.psiElement(PhpAttribute.class)
41+
.with(new IsAttribute(classFQN))
42+
)
43+
);
44+
}
45+
46+
public static ElementPattern<? extends PsiElement> getMethodCallArgumentPattern(
47+
@NotNull String classFQN,
48+
@NotNull String methodName,
49+
@NotNull String argumentName,
50+
int argumentIndex
51+
) {
52+
return PlatformPatterns.psiElement()
53+
.with(new IsArgument(argumentName, argumentIndex))
54+
.withParent(PlatformPatterns
55+
.psiElement(ParameterList.class)
56+
.withParent(
57+
PlatformPatterns.psiElement(MethodReference.class)
58+
.with(new IsMethodReference(classFQN, methodName))
59+
)
60+
);
61+
}
62+
63+
public static ElementPattern<? extends PsiElement> getConstructorCallArgumentPattern(
64+
@NotNull String classFQN,
65+
@NotNull String argumentName,
66+
int argumentIndex
67+
) {
68+
return PlatformPatterns.psiElement()
69+
.with(new IsArgument(argumentName, argumentIndex))
70+
.withParent(PlatformPatterns
71+
.psiElement(ParameterList.class)
72+
.withParent(
73+
PlatformPatterns.psiElement(NewExpression.class)
74+
.with(new IsConstructorReference(classFQN))
75+
)
76+
);
77+
}
78+
79+
public static ElementPattern<? extends PsiElement> getFunctionCallArgumentPattern(
80+
@NotNull String functionFQN,
81+
@NotNull String argumentName,
82+
int argumentIndex
83+
) {
84+
return PlatformPatterns.psiElement()
85+
.with(new IsArgument(argumentName, argumentIndex))
86+
.withParent(PlatformPatterns
87+
.psiElement(ParameterList.class)
88+
.withParent(
89+
PlatformPatterns.psiElement(FunctionReference.class)
90+
.with(new IsFunctionReference(functionFQN))
91+
)
92+
);
93+
}
94+
95+
private static class IsAnnotationProperty extends PatternCondition<StringLiteralExpression> {
96+
@NotNull
97+
private final String classFQN;
98+
@NotNull
99+
private final String propertyName;
100+
private final boolean isDefaultProperty;
101+
102+
public IsAnnotationProperty(@NotNull String classFQN, @NotNull String propertyName, boolean isDefaultProperty) {
103+
super(String.format("IsAnnotationProperty(%s, %s)", classFQN, propertyName));
104+
this.classFQN = classFQN;
105+
this.propertyName = propertyName;
106+
this.isDefaultProperty = isDefaultProperty;
107+
}
108+
109+
@Override
110+
public boolean accepts(@NotNull StringLiteralExpression element, ProcessingContext context) {
111+
if (element.getParent() == null || !(element.getParent().getParent() instanceof PhpDocTag)) {
112+
return false;
113+
}
114+
115+
var phpDocTag = (PhpDocTag) element.getParent().getParent();
116+
117+
var annotationClass = AnnotationUtil.getAnnotationReference(phpDocTag);
118+
if (annotationClass != null && annotationClass.getFQN().equals(classFQN)) {
119+
return element.equals(getPropertyValuePsiElement(phpDocTag));
120+
}
121+
122+
return false;
123+
}
124+
125+
@Nullable
126+
private PsiElement getPropertyValuePsiElement(@NotNull PhpDocTag phpDocTag) {
127+
PsiElement property = AnnotationUtil.getPropertyValueAsPsiElement(phpDocTag, propertyName);
128+
129+
if (property == null && isDefaultProperty) {
130+
var phpDocAttrList = phpDocTag.getFirstPsiChild();
131+
if (phpDocAttrList != null && phpDocAttrList.getNode().getElementType() == PhpDocElementTypes.phpDocAttributeList) {
132+
PhpPsiElement firstPhpPsiElement = phpDocAttrList.getFirstPsiChild();
133+
if (firstPhpPsiElement instanceof StringLiteralExpression && !hasIdentifier(firstPhpPsiElement)) {
134+
property = firstPhpPsiElement;
135+
}
136+
}
137+
}
138+
139+
return property;
140+
}
141+
142+
private boolean hasIdentifier(@NotNull PsiElement property) {
143+
return PlatformPatterns.psiElement()
144+
.afterLeafSkipping(
145+
PlatformPatterns.or(
146+
PlatformPatterns.psiElement(PsiWhiteSpace.class),
147+
PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_TEXT).withText("=")
148+
),
149+
PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_IDENTIFIER)
150+
)
151+
.accepts(property);
152+
}
153+
}
154+
155+
private static class IsAttribute extends PatternCondition<PhpAttribute> {
156+
@NotNull
157+
private final String classFQN;
158+
159+
public IsAttribute(@NotNull String classFQN) {
160+
super(String.format("IsAttribute(%s)", classFQN));
161+
this.classFQN = classFQN;
162+
}
163+
164+
@Override
165+
public boolean accepts(@NotNull PhpAttribute phpAttribute, ProcessingContext context) {
166+
return classFQN.equals(phpAttribute.getFQN());
167+
}
168+
}
169+
170+
private static class IsArgument extends PatternCondition<PsiElement> {
171+
@NotNull
172+
private final String name;
173+
private final int index;
174+
175+
public IsArgument(@NotNull String name, int index) {
176+
super(String.format("isArgument(%s, %d)", name, index));
177+
this.name = name;
178+
this.index = index;
179+
}
180+
181+
@Override
182+
public boolean accepts(@NotNull PsiElement parameter, ProcessingContext context) {
183+
if (parameter.getParent() instanceof ParameterListImpl) {
184+
var parameterList = (ParameterListImpl) parameter.getParent();
185+
if (parameterList.getParameter(name) == parameter) {
186+
return true;
187+
}
188+
189+
return parameterList.getParameter(index) == parameter && ParameterListImpl.getNameIdentifier(parameter) == null;
190+
}
191+
192+
return false;
193+
}
194+
}
195+
196+
private static class IsFunctionReference extends PatternCondition<FunctionReference> {
197+
@NotNull
198+
private final String name;
199+
200+
public IsFunctionReference(@NotNull String name) {
201+
super(String.format("IsFunctionReference(%s)", name));
202+
this.name = name;
203+
}
204+
205+
@Override
206+
public boolean accepts(@NotNull FunctionReference element, ProcessingContext context) {
207+
return name.equals(element.getFQN());
208+
}
209+
}
210+
211+
private static class IsMethodReference extends PatternCondition<MethodReference> {
212+
@NotNull
213+
private final String classFQN;
214+
@NotNull
215+
private final String methodName;
216+
217+
public IsMethodReference(@NotNull String classFQN, @NotNull String methodName) {
218+
super(String.format("IsMethodReference(%s::%s)", classFQN, methodName));
219+
this.classFQN = classFQN;
220+
this.methodName = methodName;
221+
}
222+
223+
@Override
224+
public boolean accepts(@NotNull MethodReference element, ProcessingContext context) {
225+
return PhpElementsUtil.isMethodReferenceInstanceOf(element, classFQN, methodName);
226+
}
227+
}
228+
229+
private static class IsConstructorReference extends PatternCondition<NewExpression> {
230+
@NotNull
231+
private final String classFQN;
232+
233+
public IsConstructorReference(@NotNull String classFQN) {
234+
super(String.format("IsConstructorReference(%s)", classFQN));
235+
this.classFQN = classFQN;
236+
}
237+
238+
@Override
239+
public boolean accepts(@NotNull NewExpression newExpression, ProcessingContext context) {
240+
return PhpElementsUtil.isNewExpressionPhpClassWithInstance(newExpression, classFQN);
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)