Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
51481c8
Impl. Expression Language Parser
adamwojs Jul 3, 2021
88983da
Impl. Syntax Highlighter for Expression Language
adamwojs Jul 3, 2021
e8a2df7
Impl. Brace Matcher for Expression Language
adamwojs Jul 3, 2021
17407b9
Impl. Quote Handler for Expression Language
adamwojs Jul 3, 2021
aac5182
Impl. Expression Language Injections for XML
adamwojs Jul 3, 2021
7195971
Impl. Expression Language Injections for YAML
adamwojs Jul 3, 2021
717ac6e
Impl. Expression Language Injections for PHP
adamwojs Jul 3, 2021
d8d37b5
Registered extensions in plugin.xml
adamwojs Jul 3, 2021
e405591
Impl. Expression Language Parser (tests)
adamwojs Jul 3, 2021
378ade2
Impl. Expression Language Injections for XML (tests)
adamwojs Jul 3, 2021
16d3204
Impl. Expression Language Injections for YAML (tests)
adamwojs Jul 3, 2021
4488c35
Impl. Expression Language Injections for PHP (tests)
adamwojs Jul 3, 2021
0f1d4a5
Deprecated ParameterLanguageInjector in favour of StringLiteralLangua…
adamwojs Jul 3, 2021
91b70a9
fixup! Impl. Expression Language Injections for PHP
adamwojs Jul 8, 2021
61906bb
fixup! Impl. Expression Language Injections for PHP (tests)
adamwojs Jul 8, 2021
ba45a7c
Added null check in YamlHelper.{isRoutingFile,isConfigFile,isServices…
adamwojs Sep 18, 2021
daca2db
Extracted literals to own PSI nodes
adamwojs Sep 18, 2021
e5b4001
Rebased and fixed conflicts
adamwojs May 22, 2022
1d37466
Removed unused EOL macro
adamwojs May 22, 2022
a8df36c
fixup! Rebased and fixed conflicts
adamwojs May 22, 2022
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Impl. Expression Language Injections for PHP
  • Loading branch information
adamwojs committed May 21, 2022
commit 717ac6ec5b9bc0685d2453e64dbe8a17f2b532c0
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package fr.adrienbrault.idea.symfony2plugin.lang;

import com.intellij.lang.Language;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

public final class LanguageInjection {
@NotNull
private final String languageId;
@Nullable
private final String prefix;
@Nullable
private final String suffix;
@NotNull
private final ElementPattern<? extends PsiElement> pattern;

protected LanguageInjection(@NotNull String languageId, @Nullable String prefix, @Nullable String suffix, @NotNull ElementPattern<? extends PsiElement> pattern) {
this.languageId = languageId;
this.prefix = prefix;
this.suffix = suffix;
this.pattern = pattern;
}

@Nullable
public Language getLanguage() {
return Language.findLanguageByID(languageId);
}

@Nullable
public String getPrefix() {
return prefix;
}

@Nullable
public String getSuffix() {
return suffix;
}

@NotNull
public ElementPattern<? extends PsiElement> getPattern() {
return pattern;
}

public static class Builder {
@NotNull
private final String languageId;
@NotNull
private final List<ElementPattern<? extends PsiElement>> patterns;
@Nullable
private String prefix;
@Nullable
private String suffix;

public Builder(@NotNull String languageId) {
this.languageId = languageId;
this.patterns = new ArrayList<>();
}

public Builder withPrefix(@Nullable String prefix) {
this.prefix = prefix;

return this;
}

public Builder withSuffix(@Nullable String suffix) {
this.suffix = suffix;

return this;
}

public final Builder matchingPattern(@NotNull ElementPattern<? extends PsiElement> pattern) {
patterns.add(pattern);
return this;
}

public Builder matchingAttributeArgument(@NotNull String classFQN, @NotNull String argumentName, int argumentIndex) {
return matchingPattern(LanguageInjectionPatterns.getAttributeArgumentPattern(classFQN, argumentName, argumentIndex));
}

public Builder matchingAnnotationProperty(@NotNull String classFQN, @NotNull String propertyName, boolean isDefaultProperty) {
return matchingPattern(LanguageInjectionPatterns.getAnnotationPropertyPattern(classFQN, propertyName, isDefaultProperty));
}

public Builder matchingConstructorCallArgument(@NotNull String classFQN, @NotNull String argumentName, int argumentIndex) {
return matchingPattern(LanguageInjectionPatterns.getConstructorCallArgumentPattern(classFQN, argumentName, argumentIndex));
}

public Builder matchingFunctionCallArgument(@NotNull String functionFQN, @NotNull String argumentName, int argumentIndex) {
return matchingPattern(LanguageInjectionPatterns.getFunctionCallArgumentPattern(functionFQN, argumentName, argumentIndex));
}

public Builder matchingMethodCallArgument(@NotNull String classFQN, @NotNull String methodName, @NotNull String argumentName, int argumentIndex) {
return matchingPattern(LanguageInjectionPatterns.getMethodCallArgumentPattern(classFQN, methodName, argumentName, argumentIndex));
}

public LanguageInjection build() {
return new LanguageInjection(languageId, prefix, suffix, StandardPatterns.or(patterns.toArray(new ElementPattern[0])));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package fr.adrienbrault.idea.symfony2plugin.lang;

import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.util.ProcessingContext;
import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes;
import com.jetbrains.php.lang.documentation.phpdoc.parser.PhpDocElementTypes;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.elements.impl.ParameterListImpl;
import de.espend.idea.php.annotation.util.AnnotationUtil;
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class LanguageInjectionPatterns {

public static ElementPattern<? extends PsiElement> getAnnotationPropertyPattern(
@NotNull String classFQN,
@NotNull String propertyName,
boolean isDefaultProperty) {
return PlatformPatterns
.psiElement(StringLiteralExpression.class)
.with(new IsAnnotationProperty(classFQN, propertyName, isDefaultProperty));
}

public static ElementPattern<? extends PsiElement> getAttributeArgumentPattern(
@NotNull String classFQN,
@NotNull String argumentName,
int argumentIndex
) {
return PlatformPatterns.psiElement()
.with(new IsArgument(argumentName, argumentIndex))
.withParent(PlatformPatterns
.psiElement(ParameterList.class)
.withParent(
PlatformPatterns.psiElement(PhpAttribute.class)
.with(new IsAttribute(classFQN))
)
);
}

public static ElementPattern<? extends PsiElement> getMethodCallArgumentPattern(
@NotNull String classFQN,
@NotNull String methodName,
@NotNull String argumentName,
int argumentIndex
) {
return PlatformPatterns.psiElement()
.with(new IsArgument(argumentName, argumentIndex))
.withParent(PlatformPatterns
.psiElement(ParameterList.class)
.withParent(
PlatformPatterns.psiElement(MethodReference.class)
.with(new IsMethodReference(classFQN, methodName))
)
);
}

public static ElementPattern<? extends PsiElement> getConstructorCallArgumentPattern(
@NotNull String classFQN,
@NotNull String argumentName,
int argumentIndex
) {
return PlatformPatterns.psiElement()
.with(new IsArgument(argumentName, argumentIndex))
.withParent(PlatformPatterns
.psiElement(ParameterList.class)
.withParent(
PlatformPatterns.psiElement(NewExpression.class)
.with(new IsConstructorReference(classFQN))
)
);
}

public static ElementPattern<? extends PsiElement> getFunctionCallArgumentPattern(
@NotNull String functionFQN,
@NotNull String argumentName,
int argumentIndex
) {
return PlatformPatterns.psiElement()
.with(new IsArgument(argumentName, argumentIndex))
.withParent(PlatformPatterns
.psiElement(ParameterList.class)
.withParent(
PlatformPatterns.psiElement(FunctionReference.class)
.with(new IsFunctionReference(functionFQN))
)
);
}

private static class IsAnnotationProperty extends PatternCondition<StringLiteralExpression> {
@NotNull
private final String classFQN;
@NotNull
private final String propertyName;
private final boolean isDefaultProperty;

public IsAnnotationProperty(@NotNull String classFQN, @NotNull String propertyName, boolean isDefaultProperty) {
super(String.format("IsAnnotationProperty(%s, %s)", classFQN, propertyName));
this.classFQN = classFQN;
this.propertyName = propertyName;
this.isDefaultProperty = isDefaultProperty;
}

@Override
public boolean accepts(@NotNull StringLiteralExpression element, ProcessingContext context) {
if (element.getParent() == null || !(element.getParent().getParent() instanceof PhpDocTag)) {
return false;
}

var phpDocTag = (PhpDocTag) element.getParent().getParent();

var annotationClass = AnnotationUtil.getAnnotationReference(phpDocTag);
if (annotationClass != null && annotationClass.getFQN().equals(classFQN)) {
return element.equals(getPropertyValuePsiElement(phpDocTag));
}

return false;
}

@Nullable
private PsiElement getPropertyValuePsiElement(@NotNull PhpDocTag phpDocTag) {
PsiElement property = AnnotationUtil.getPropertyValueAsPsiElement(phpDocTag, propertyName);

if (property == null && isDefaultProperty) {
var phpDocAttrList = phpDocTag.getFirstPsiChild();
if (phpDocAttrList != null && phpDocAttrList.getNode().getElementType() == PhpDocElementTypes.phpDocAttributeList) {
PhpPsiElement firstPhpPsiElement = phpDocAttrList.getFirstPsiChild();
if (firstPhpPsiElement instanceof StringLiteralExpression && !hasIdentifier(firstPhpPsiElement)) {
property = firstPhpPsiElement;
}
}
}

return property;
}

private boolean hasIdentifier(@NotNull PsiElement property) {
return PlatformPatterns.psiElement()
.afterLeafSkipping(
PlatformPatterns.or(
PlatformPatterns.psiElement(PsiWhiteSpace.class),
PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_TEXT).withText("=")
),
PlatformPatterns.psiElement(PhpDocTokenTypes.DOC_IDENTIFIER)
)
.accepts(property);
}
}

private static class IsAttribute extends PatternCondition<PhpAttribute> {
@NotNull
private final String classFQN;

public IsAttribute(@NotNull String classFQN) {
super(String.format("IsAttribute(%s)", classFQN));
this.classFQN = classFQN;
}

@Override
public boolean accepts(@NotNull PhpAttribute phpAttribute, ProcessingContext context) {
return classFQN.equals(phpAttribute.getFQN());
}
}

private static class IsArgument extends PatternCondition<PsiElement> {
@NotNull
private final String name;
private final int index;

public IsArgument(@NotNull String name, int index) {
super(String.format("isArgument(%s, %d)", name, index));
this.name = name;
this.index = index;
}

@Override
public boolean accepts(@NotNull PsiElement parameter, ProcessingContext context) {
if (parameter.getParent() instanceof ParameterListImpl) {
var parameterList = (ParameterListImpl) parameter.getParent();
if (parameterList.getParameter(name) == parameter) {
return true;
}

return parameterList.getParameter(index) == parameter && ParameterListImpl.getNameIdentifier(parameter) == null;
}

return false;
}
}

private static class IsFunctionReference extends PatternCondition<FunctionReference> {
@NotNull
private final String name;

public IsFunctionReference(@NotNull String name) {
super(String.format("IsFunctionReference(%s)", name));
this.name = name;
}

@Override
public boolean accepts(@NotNull FunctionReference element, ProcessingContext context) {
return name.equals(element.getFQN());
}
}

private static class IsMethodReference extends PatternCondition<MethodReference> {
@NotNull
private final String classFQN;
@NotNull
private final String methodName;

public IsMethodReference(@NotNull String classFQN, @NotNull String methodName) {
super(String.format("IsMethodReference(%s::%s)", classFQN, methodName));
this.classFQN = classFQN;
this.methodName = methodName;
}

@Override
public boolean accepts(@NotNull MethodReference element, ProcessingContext context) {
return PhpElementsUtil.isMethodReferenceInstanceOf(element, classFQN, methodName);
}
}

private static class IsConstructorReference extends PatternCondition<NewExpression> {
@NotNull
private final String classFQN;

public IsConstructorReference(@NotNull String classFQN) {
super(String.format("IsConstructorReference(%s)", classFQN));
this.classFQN = classFQN;
}

@Override
public boolean accepts(@NotNull NewExpression newExpression, ProcessingContext context) {
return PhpElementsUtil.isNewExpressionPhpClassWithInstance(newExpression, classFQN);
}
}
}
Loading