Skip to content

Commit

Permalink
Merge pull request #2147 from Haehnchen/feature/twig-tag-parser-cache
Browse files Browse the repository at this point in the history
introduce caching for Twig "token parser" and include controlflow collecting
  • Loading branch information
Haehnchen committed May 3, 2023
2 parents 29ad9c2 + 0f1fa55 commit aaa3f17
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,9 @@ protected void addCompletions(@NotNull CompletionParameters parameters, Processi
return;
}

TwigUtil.visitTokenParsers(position.getProject(), pair ->
resultSet.addElement(LookupElementBuilder.create(pair.getFirst()).withIcon(Symfony2Icons.SYMFONY))
);
for (String namedTokenParserTag : TwigUtil.getNamedTokenParserTags(position.getProject())) {
resultSet.addElement(LookupElementBuilder.create(namedTokenParserTag).withIcon(Symfony2Icons.SYMFONY));
}

// add special tag ending, provide a static list. there no suitable safe way to extract them
// search able via: "return $token->test(array('end"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import com.jetbrains.twig.TwigTokenTypes;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.templating.util.TwigUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

/**
* {% ta<caret>g %}
Expand All @@ -29,8 +27,10 @@ public PsiElementVisitor buildVisitor(final @NotNull ProblemsHolder holder, bool
}

return new PsiElementVisitor() {
Map<String, String> namedDeprecatedTokenParserTags = null;

@Override
public void visitElement(PsiElement element) {
public void visitElement(@NotNull PsiElement element) {
// {% tag %}
if (element.getNode().getElementType() == TwigTokenTypes.TAG_NAME) {
visitTagTokenName(element);
Expand All @@ -41,56 +41,30 @@ public void visitElement(PsiElement element) {

private void visitTagTokenName(PsiElement element) {
String tagName = element.getText();
if (StringUtils.isNotBlank(tagName)) {
// prevent adding multiple "deprecated" message, as we have alias classes
final boolean[] hasDeprecated = {false};

TwigUtil.visitTokenParsers(holder.getProject(), triple -> {
if (hasDeprecated[0]) {
return;
}

String currentTagName = triple.getFirst();
if(tagName.equalsIgnoreCase(currentTagName) || (tagName.toLowerCase().startsWith("end") && currentTagName.equalsIgnoreCase(tagName.substring(3)))) {
PhpClass phpClass = triple.getThird().getContainingClass();
if (phpClass != null && phpClass.isDeprecated()) {
String classDeprecatedMessage = getClassDeprecatedMessage(phpClass);

String message = classDeprecatedMessage != null
? classDeprecatedMessage
: "Deprecated Tag usage";
if (StringUtils.isBlank(tagName)) {
return;
}

hasDeprecated[0] = true;
// {% endspaceless % }
if (tagName.length() > 3 && tagName.startsWith("end")) {
tagName = tagName.substring(3);
}

// "Deprecated" highlight is not visible, so we are going here for weak warning
// WEAK_WARNING would be match; but not really visible
holder.registerProblem(element.getParent(), message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}
}
});
if (namedDeprecatedTokenParserTags == null) {
namedDeprecatedTokenParserTags = TwigUtil.getNamedDeprecatedTokenParserTags(element.getProject());
}
}

@Nullable
private String getClassDeprecatedMessage(@NotNull PhpClass phpClass) {
if (phpClass.isDeprecated()) {
PhpDocComment docComment = phpClass.getDocComment();
if (docComment != null) {
for (PhpDocTag deprecatedTag : docComment.getTagElementsByName("@deprecated")) {
// deprecatedTag.getValue provides a number !?
String tagValue = deprecatedTag.getText();
if (StringUtils.isNotBlank(tagValue)) {
String trim = tagValue.replace("@deprecated", "").trim();
if (namedDeprecatedTokenParserTags.containsKey(tagName)) {
// "Deprecated" highlight is not visible, so we are going here for weak warning
// WEAK_WARNING would be match; but not really visible

if (StringUtils.isNotBlank(tagValue)) {
return StringUtils.abbreviate("Deprecated: " + trim, 100);
}
}
}
}
holder.registerProblem(
element.getParent(),
namedDeprecatedTokenParserTags.getOrDefault(tagName, "Deprecated Tag usage"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
);
}

return null;
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public enum NamespaceType {
private static final Key<CachedValue<List<String>>> SYMFONY_TEMPLATE_EMBED_LIST = new Key<>("SYMFONY_TEMPLATE_EMBED_LIST");
private static final Key<CachedValue<List<String>>> SYMFONY_TEMPLATE_EXTENDS_LIST = new Key<>("SYMFONY_TEMPLATE_EXTENDS_LIST");

private static final Key<CachedValue<Set<String>>> SYMFONY_NAMED_TOKEN_TAGS = new Key<>("SYMFONY_NAMED_TOKEN_TAGS");
private static final Key<CachedValue<Map<String, String>>> SYMFONY_DEPRECATED_NAMED_TOKEN_TAGS = new Key<>("SYMFONY_DEPRECATED_NAMED_TOKEN_TAGS");

public static String[] CSS_FILES_EXTENSIONS = new String[] { "css", "less", "sass", "scss" };

public static String[] JS_FILES_EXTENSIONS = new String[] { "js", "dart", "coffee" };
Expand Down Expand Up @@ -2590,20 +2593,45 @@ public static void visitTokenParsers(@NotNull Project project, @NotNull Consumer
continue;
}

// get string return value
PhpReturn childrenOfType = PsiTreeUtil.findChildOfType(getTag, PhpReturn.class);
if(childrenOfType != null) {
PhpPsiElement returnValue = childrenOfType.getFirstPsiChild();
if(returnValue instanceof StringLiteralExpression) {
String contents = ((StringLiteralExpression) returnValue).getContents();
if(StringUtils.isNotBlank(contents)) {
for (PsiElement returnValue : PhpElementsUtil.collectPhpReturnArgumentsInsideControlFlow(getTag)) {
if (returnValue instanceof StringLiteralExpression stringLiteralExpression) {
String contents = stringLiteralExpression.getContents();
if (StringUtils.isNotBlank(contents)) {
consumer.consume(new Triple<>(contents, returnValue, getTag));
}
}
}
}
}

public static Set<String> getNamedTokenParserTags(@NotNull Project project) {
return CachedValuesManager.getManager(project).getCachedValue(project, SYMFONY_NAMED_TOKEN_TAGS, () -> {
Set<String> items = new HashSet<>();
TwigUtil.visitTokenParsers(project, pair ->items.add(pair.getFirst()));
return CachedValueProvider.Result.create(Collections.unmodifiableSet(items), PsiModificationTracker.MODIFICATION_COUNT);
}, false);
}
@NotNull

public static Map<String, String> getNamedDeprecatedTokenParserTags(@NotNull Project project) {
return CachedValuesManager.getManager(project).getCachedValue(project, SYMFONY_DEPRECATED_NAMED_TOKEN_TAGS, () -> {
Map<String, String> deprecations = new HashMap<>();

TwigUtil.visitTokenParsers(project, triple -> {
String currentTagName = triple.getFirst();

PhpClass phpClass = triple.getThird().getContainingClass();

if (phpClass != null && phpClass.isDeprecated()) {
PhpElementsUtil.getClassDeprecatedMessage(phpClass);
deprecations.put(currentTagName, PhpElementsUtil.getClassDeprecatedMessage(phpClass));
}
});

return CachedValueProvider.Result.create(Collections.unmodifiableMap(deprecations), PsiModificationTracker.MODIFICATION_COUNT);
}, false);
}

/**
* Visit all template variables which are completion in Twig file rendering call as array
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.jetbrains.php.completion.PhpLookupElement;
import com.jetbrains.php.lang.PhpLangUtil;
import com.jetbrains.php.lang.PhpLanguage;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.parser.PhpElementTypes;
import com.jetbrains.php.lang.patterns.PhpPatterns;
Expand Down Expand Up @@ -545,6 +547,29 @@ public static PsiElementPattern.Capture<PsiElement> getAttributeNamedArgumentArr
);
}


@Nullable
public static String getClassDeprecatedMessage(@NotNull PhpClass phpClass) {
if (phpClass.isDeprecated()) {
PhpDocComment docComment = phpClass.getDocComment();
if (docComment != null) {
for (PhpDocTag deprecatedTag : docComment.getTagElementsByName("@deprecated")) {
// deprecatedTag.getValue provides a number !?
String tagValue = deprecatedTag.getText();
if (StringUtils.isNotBlank(tagValue)) {
String trim = tagValue.replace("@deprecated", "").trim();

if (StringUtils.isNotBlank(tagValue)) {
return StringUtils.abbreviate("Deprecated: " + trim, 100);
}
}
}
}
}

return null;
}

/**
* Check if given Attribute
*/
Expand Down

0 comments on commit aaa3f17

Please sign in to comment.