From 3eb243cfe5e283da7d788805cdb8e77f19fafa46 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 6 Jul 2018 16:36:50 +0200 Subject: [PATCH] SSR: implement explicit support for repeated variables (IDEA-195166) --- .../impl/matcher/MatchContext.java | 13 +++++++++- .../impl/matcher/MatchResultImpl.java | 13 ++++++++++ .../matcher/compiler/PatternCompiler.java | 10 +++++--- .../matcher/handlers/SubstitutionHandler.java | 17 ++++++++++--- .../StructuralSearchTest.java | 24 +++++++++++++++++++ 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java index 664523e50192..3e4a1134307c 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java @@ -56,7 +56,18 @@ public class MatchContext { } public MatchResultImpl getPreviousResult() { - return previousResults.isEmpty() ? null : previousResults.peek(); + if (previousResults.isEmpty()) { + return null; + } + else { + int index = previousResults.size() - 1; + MatchResultImpl result = previousResults.get(index); // may contain nulls + while (result == null && index > 0) { + index--; + result = previousResults.get(index); + } + return result; + } } public MatchResultImpl getResult() { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java index 9e2ed941fce3..0a07d2de3bc1 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java @@ -136,6 +136,19 @@ public final class MatchResultImpl extends MatchResult { return null; } + public static MatchResultImpl findChildDeep(MatchResult match , String name) { + for (MatchResult child : match.getChildren()) { + if (name.equals(child.getName())) { + return (MatchResultImpl)child; + } + final MatchResultImpl deep = findChildDeep(child, name); + if (deep != null) { + return deep; + } + } + return null; + } + public MatchResult removeChild(String typedVar) { // @todo this could be performance bottleneck, replace with hash lookup! for (int i = 0, size = myChildren.size(); i < size; i++) { diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java index 1517dd89fb7a..90cad298eb95 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java @@ -424,7 +424,6 @@ public class PatternCompiler { final int segmentsCount = template.getSegmentsCount(); final String text = template.getTemplateText(); int prevOffset = 0; - final Set seen = ContainerUtil.newTroveSet(); final Set variableNames = ContainerUtil.newTroveSet(); for(int i = 0; i < segmentsCount; i++) { @@ -439,8 +438,12 @@ public class PatternCompiler { final String compiledName = prefix + name; buf.append(text, prevOffset, offset).append(compiledName); - variableNames.add(name); - if (seen.add(compiledName)) { + final boolean repeated = !variableNames.add(name); + final SubstitutionHandler existing = (SubstitutionHandler)result.getHandler(compiledName); + if (existing != null) { + existing.setRepeatedVar(repeated); + } + else { // the same variable can occur multiple times in a single template // no need to process it more than once @@ -460,6 +463,7 @@ public class PatternCompiler { constraint.getMaxCount(), constraint.isGreedy() ); + handler.setRepeatedVar(repeated); if (constraint.isWithinHierarchy()) { handler.setSubtype(true); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java index 07ae354d5048..557e44334e25 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java @@ -39,6 +39,7 @@ public class SubstitutionHandler extends MatchingHandler { private int matchedOccurs; private int totalMatchedOccurs = -1; private MatchResultImpl myNestedResult; + private boolean myRepeatedVar = false; private static final NodeFilter VARS_DELIM_FILTER = new NodeFilter() { @Override @@ -95,6 +96,10 @@ public class SubstitutionHandler extends MatchingHandler { return findRegExpPredicate(getPredicate()); } + public void setRepeatedVar(boolean repeatedVar) { + myRepeatedVar = repeatedVar; + } + private static RegExpPredicate findRegExpPredicate(MatchPredicate start) { if (start==null) return null; if (start instanceof RegExpPredicate) return (RegExpPredicate)start; @@ -111,7 +116,10 @@ public class SubstitutionHandler extends MatchingHandler { return null; } - private static boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResult result, final MatchContext matchContext) { + private boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResult result, final MatchContext matchContext) { + if (!myRepeatedVar) { + return true; + } if (match != null) { if (start == 0 && end == -1 && result.getStart() == 0 && result.getEnd() == -1) { return matchContext.getMatcher().match(match, result.getMatch()); @@ -134,8 +142,11 @@ public class SubstitutionHandler extends MatchingHandler { MatchResult result = context.hasResult() ? context.getResult().findChild(name) : null; - if (result == null && context.getPreviousResult() != null) { - result = context.getPreviousResult().findChild(name); + if (result == null) { + final MatchResultImpl previous = context.getPreviousResult(); + if (previous != null) { + result = MatchResultImpl.findChildDeep(previous, name); + } } if (result != null) { diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java index 10fb6a394648..25b08752ce8c 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java @@ -3092,4 +3092,28 @@ public class StructuralSearchTest extends StructuralSearchTestCase { assertEquals("should find defaults", 3, findMatchesCount(in, "default:")); assertEquals("should find cases", 8, findMatchesCount(in, "case '_a :")); } + + public void testRepeatedVars() { + String in = "class SomePageObject {\n" + + " @FindBy(name = \"first-name\")\n" + + " private WebElement name;\n" + + "\n" + + " @FindBy(name = \"first-name\")\n" + + " private WebElement firstName;\n" + + "}" + + "class SomePageObject2 {\n" + + " @FindBy(name = \"last-name\")\n" + + " private WebElement name;\n" + + "\n" + + " @FindBy(name = \"first-name\")\n" + + " private WebElement firstName;\n" + + "}"; + + assertEquals("find repeated annotation value", 1, findMatchesCount(in, "class '_Class {\n" + + " @FindBy(name='_value)\n" + + " '_FieldType '_field = '_init?;\n" + + " @FindBy(name='_value)\n" + + " '_FieldType2 'field2 = '_init2?;\n" + + "}")); + } }