[ssr] Fix Java SSR after the javadoc lexer changes

GitOrigin-RevId: 87fa31142e1cf0acd9d8c2f3c738fe481cbda2fe
This commit is contained in:
Louis Vignier
2025-06-13 15:12:12 +00:00
committed by intellij-monorepo-bot
parent 3b41193c33
commit a6981a4688
6 changed files with 64 additions and 231 deletions
@@ -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;
@@ -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
@@ -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;
}
}
@@ -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;
}
}
@@ -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<PsiElement> 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;
}
}
@@ -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 */