diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java index 3678f298ffef..dcfce8de50fa 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java @@ -1,4 +1,4 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.structuralsearch.impl.matcher; import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; @@ -9,6 +9,8 @@ import com.intellij.psi.*; import com.intellij.psi.impl.light.LightElement; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.javadoc.PsiDocTagValue; +import com.intellij.psi.javadoc.PsiDocToken; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -18,7 +20,6 @@ import com.intellij.structuralsearch.StructuralSearchUtil; import com.intellij.structuralsearch.impl.matcher.handlers.LiteralWithSubstitutionHandler; import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; -import com.intellij.structuralsearch.impl.matcher.iterators.DocValuesIterator; import com.intellij.structuralsearch.impl.matcher.iterators.HierarchyNodeIterator; import com.intellij.structuralsearch.impl.matcher.predicates.MatchPredicate; import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate; @@ -192,34 +193,37 @@ public class JavaMatchingVisitor extends JavaElementVisitor { if (!isTypedVar && !myMatchingVisitor.setResult(tag.getName().equals(other.getName()))) return; - PsiElement psiDocTagValue = tag.getValueElement(); - boolean isTypedValue = false; + PsiElement psiDocTagValue = Arrays.stream(tag.getDataElements()).findFirst().orElse(null); if (psiDocTagValue != null) { - final PsiElement[] children = psiDocTagValue.getChildren(); - if (children.length == 1) { - psiDocTagValue = children[0]; - } - isTypedValue = pattern.isTypedVar(psiDocTagValue); - - if (isTypedValue) { - if (other.getValueElement() != null) { - if (!myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(psiDocTagValue, other.getValueElement()))) return; - } - else { - if (!myMatchingVisitor.setResult(myMatchingVisitor.allowsAbsenceOfMatch(psiDocTagValue))) return; + if (psiDocTagValue instanceof PsiDocTagValue) { + final PsiElement[] children = psiDocTagValue.getChildren(); + if (children.length == 1) { + psiDocTagValue = children[0]; } } - } - if (!isTypedValue && !myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(new DocValuesIterator(tag.getFirstChild()), - new DocValuesIterator(other.getFirstChild())))) { - return; + if (pattern.isTypedVar(psiDocTagValue) && other.getDataElements().length == 0) { + myMatchingVisitor.setResult(myMatchingVisitor.allowsAbsenceOfMatch(psiDocTagValue)); + return; + } } if (isTypedVar) { myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(tag.getNameElement(), other.getNameElement())); } + + myMatchingVisitor.setResult(myMatchingVisitor.matchOptionally(tag.getDataElements(), other.getDataElements())); + } + + @Override + public void visitDocTagValue(@NotNull PsiDocTagValue value) { + myMatchingVisitor.setResult(substituteOrMatchText(value, myMatchingVisitor.getMatchContext())); + } + + @Override + public void visitDocToken(@NotNull PsiDocToken token) { + myMatchingVisitor.setResult(substituteOrMatchText(token, myMatchingVisitor.getMatchContext())); } @Override @@ -680,6 +684,16 @@ public class JavaMatchingVisitor extends JavaElementVisitor { : element; } + private boolean substituteOrMatchText(@NotNull PsiElement element, @NotNull MatchContext context) { + final var handler = context.getPattern().getHandler(element); + final PsiElement other = myMatchingVisitor.getElement(); + if (handler instanceof SubstitutionHandler substitutionHandler) { + return substitutionHandler.handle(other, context); + } else { + return myMatchingVisitor.matchText(element, other); + } + } + private static int getArrayDimensions(PsiElement element) { if (element == null) { return 0; diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/compiler/JavaCompilingVisitor.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/compiler/JavaCompilingVisitor.java index 8f0e5a31552d..8d9b2ef09117 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/compiler/JavaCompilingVisitor.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/compiler/JavaCompilingVisitor.java @@ -1,7 +1,6 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.structuralsearch.impl.matcher.compiler; -import com.intellij.dupLocator.iterators.NodeIterator; import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -10,6 +9,7 @@ import com.intellij.openapi.util.text.Strings; import com.intellij.psi.*; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.javadoc.PsiDocTagValue; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.psi.search.PsiShortNamesCache; @@ -24,7 +24,6 @@ import com.intellij.structuralsearch.impl.matcher.JavaCompiledPattern; import com.intellij.structuralsearch.impl.matcher.JavaMatchUtil; import com.intellij.structuralsearch.impl.matcher.filters.*; import com.intellij.structuralsearch.impl.matcher.handlers.*; -import com.intellij.structuralsearch.impl.matcher.iterators.DocValuesIterator; import com.intellij.structuralsearch.impl.matcher.predicates.ExprTypePredicate; import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; import com.intellij.util.SmartList; @@ -197,12 +196,12 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor { @Override public void visitDocTag(@NotNull PsiDocTag psiDocTag) { super.visitDocTag(psiDocTag); + } - final NodeIterator nodes = new DocValuesIterator(psiDocTag.getFirstChild()); - while (nodes.hasNext()) { - myCompilingVisitor.setHandler(nodes.current(), new DocDataHandler()); - nodes.advance(); - } + @Override + public void visitDocTagValue(@NotNull PsiDocTagValue value) { + super.visitDocTagValue(value); + myCompilingVisitor.setFilterSimple(value, JavaDocTagDataFilter.getInstance()); } @Override diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/JavaDocTagDataFilter.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/JavaDocTagDataFilter.java new file mode 100644 index 000000000000..57254048ac87 --- /dev/null +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/JavaDocTagDataFilter.java @@ -0,0 +1,23 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.javadoc.PsiDocTagValue; +import com.intellij.psi.javadoc.PsiDocToken; + +public final class JavaDocTagDataFilter implements NodeFilter { + + private static final NodeFilter INSTANCE = new JavaDocTagDataFilter(); + + private JavaDocTagDataFilter() {} + + @Override + public boolean accepts(PsiElement element) { + return element instanceof PsiDocTagValue || element instanceof PsiDocToken; + } + + public static NodeFilter getInstance() { + return INSTANCE; + } +} diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DocDataHandler.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DocDataHandler.java deleted file mode 100644 index aef75102a0ec..000000000000 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DocDataHandler.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.structuralsearch.impl.matcher.handlers; - -import com.intellij.psi.JavaDocTokenType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.javadoc.PsiDocTagValue; -import com.intellij.psi.javadoc.PsiDocToken; -import com.intellij.structuralsearch.impl.matcher.MatchContext; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Handler for doc nodes - */ -public class DocDataHandler extends MatchingHandler { - private static final @NonNls String P_STR = "^\\s*((?:\\w|_|-|\\$)+)\\s*(?:=\\s*\"(.*)\"\\s*)?$"; - private static final Pattern p = Pattern.compile( - P_STR, - Pattern.CASE_INSENSITIVE - ); - - @Override - public boolean match(PsiElement node, PsiElement match, @NotNull MatchContext context) { - String text1 = node.getText(); - - text1 = getTextFromNode(node, text1); - - Matcher m1 = p.matcher(text1); - - String text2 = match.getText(); - text2 = getTextFromNode(match, text2); - - Matcher m2 = p.matcher(text2); - - if (m1.matches() && m2.matches()) { - String name = m1.group(1); - String name2 = m2.group(1); - boolean isTypedName = context.getPattern().isTypedVar(name); - - if (name.equals(name2) || isTypedName) { - String value = m1.group(2); - String value2 = m2.group(2); - - if (value!=null) { - if (value2 == null || !value2.matches(value)) return false; - } - if (isTypedName) { - SubstitutionHandler handler = (SubstitutionHandler) context.getPattern().getHandler(name); - return handler.handle(match,context); - } - return true; - } - } - return text1.equals(text2); - } - - // since doctag value may be inside doc comment we specially build text including skipped nodes - private static String getTextFromNode(final PsiElement node, String text1) { - PsiElement nextSibling = node.getNextSibling(); - if (nextSibling instanceof PsiDocTagValue) { - text1 += nextSibling.getText(); - - nextSibling = nextSibling.getNextSibling(); - if (PsiDocToken.isDocToken(nextSibling, JavaDocTokenType.DOC_COMMENT_DATA)) { - text1 += nextSibling.getText(); - } - } - return text1; - } -} diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/iterators/DocValuesIterator.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/iterators/DocValuesIterator.java deleted file mode 100644 index 9dc68303ed09..000000000000 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/iterators/DocValuesIterator.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.structuralsearch.impl.matcher.iterators; - -import com.intellij.dupLocator.iterators.NodeIterator; -import com.intellij.psi.JavaDocTokenType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.javadoc.PsiDocTagValue; -import com.intellij.psi.javadoc.PsiDocToken; - -import java.util.ArrayList; - -/** - * Iterates over java doc values tag - */ -public class DocValuesIterator extends NodeIterator { - private int index; - private final ArrayList tokens = new ArrayList<>(2); - - public DocValuesIterator(PsiElement start) { - for (PsiElement e = start; e != null; e = e.getNextSibling()) { - if (e instanceof PsiDocTagValue) tokens.add(e); - else if (PsiDocToken.isDocToken(e, JavaDocTokenType.DOC_COMMENT_DATA)) { - tokens.add(e); - e = advanceToNext(e); - } - } - } - - // since doctag value may be inside doc comment we specially skip that nodes from list - static PsiElement advanceToNext(PsiElement e) { - PsiElement nextSibling = e.getNextSibling(); - if (nextSibling instanceof PsiDocTagValue) e = nextSibling; - - nextSibling = e.getNextSibling(); - if (PsiDocToken.isDocToken(nextSibling, JavaDocTokenType.DOC_COMMENT_DATA)) { - e = nextSibling; - } - return e; - } - - @Override - public boolean hasNext() { - return index >=0 && index < tokens.size(); - } - - @Override - public PsiElement current() { - return hasNext() ? tokens.get(index) : null; - } - - @Override - public void advance() { - if (index < tokens.size()) ++ index; - } - - @Override - public void rewind() { - if (index >= 0) --index; - } - - @Override - public void reset() { - index = 0; - } -} diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java index 80c4895da331..c0e388bb23b8 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.structuralsearch; import com.intellij.ide.highlighter.JavaFileType; @@ -1286,71 +1286,6 @@ public class StructuralSearchTest extends StructuralSearchTestCase { assertEquals("optional tag value match", 6, findMatchesCount(s57, "/** @'T '_T2? */")); assertEquals("no infinite loop on javadoc matching", 1, findMatchesCount(s57, "/** 'Text */ class '_ { }")); - final String s83 = """ - /** - * @hibernate.class - * table="CATS" - */ - public class Cat { - private Long id; // identifier - private Date birthdate; - /** - * @hibernate.id - * generator-class="native" - * column="CAT_ID" - */ - public Long getId() { - return id; - } - private void setId(Long id) { - this.id=id; - } - - /** - * @hibernate.property - * column="BIRTH_DATE" - */ - public Date getBirthdate() { - return birthdate; - } - void setBirthdate(Date date) { - birthdate = date; - } - /** - * @hibernate.property - * column="SEX" - * not-null="true" - * update="false" - */ - public char getSex() { - return sex; - } - void setSex(char sex) { - this.sex=sex; - } - }"""; - assertEquals("XDoclet metadata", 2, findMatchesCount(s83, """ - /** - * @hibernate.property - * 'Property - */ - """)); - assertEquals("XDoclet metadata 2", 1, findMatchesCount(s83, """ - /** - * @hibernate.property - * update="fa.se" - */ - """)); - - final String s75 = """ - /** @class aClass - @author the author */ class A {} - /** */ class B {} - /** @class aClass */ class C {}"""; - assertEquals("multiple tags match +", 2, findMatchesCount(s75, " /** @'_tag+ '_value+ */")); - assertEquals("multiple tags match *", 3, findMatchesCount(s75, " /** @'_tag* '_value* */")); - assertEquals("multiple tags match ?", 3, findMatchesCount(s75, " /** @'_tag? '_value? */ class 't {}")); - final String source = """ class outer { /** bla */