diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java index aef72380cd0a..46fea30326cd 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch.impl.matcher.handlers; import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; @@ -39,8 +25,10 @@ public class DeclarationStatementHandler extends MatchingHandler { if (patternNode instanceof PsiComment) { return myCommentHandler.match(patternNode, matchedNode, context); } + if (!super.match(patternNode, matchedNode,context)) { + return false; + } - if (!super.match(patternNode,matchedNode,context)) return false; final PsiDeclarationStatement dcl = (PsiDeclarationStatement)patternNode; if (matchedNode instanceof PsiDeclarationStatement) { return context.getMatcher().matchSequentially(new SsrFilteringNodeIterator(patternNode.getFirstChild()), @@ -49,7 +37,7 @@ public class DeclarationStatementHandler extends MatchingHandler { final PsiElement[] declared = dcl.getDeclaredElements(); // declaration statement could wrap class or dcl - if (declared.length > 0 && !(matchedNode.getParent() instanceof PsiDeclarationStatement) /* skip twice matching for child*/) { + if (declared.length > 0 && (!context.shouldRecursivelyMatch() || !(matchedNode.getParent() instanceof PsiDeclarationStatement)) /* skip twice matching for child*/) { if (!(matchedNode instanceof PsiField)) { return context.getMatcher().matchSequentially( new ArrayBackedNodeIterator(declared), diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java index da240a4f62c6..ec9c5c3f2d16 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch; import org.jdom.Element; @@ -34,9 +20,8 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { private int minCount = 1; private int maxCount = 1; private boolean greedy = true; - private boolean reference; private boolean invertReference; - private String nameOfReferenceVar = ""; + private String referenceConstraint = ""; private boolean partOfSearchResults; private String nameOfExprType = ""; private boolean invertExprType; @@ -52,7 +37,7 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { private boolean invertWithinConstraint; private final boolean artificial; - @NonNls private static final String NAME_OF_REFEENCE_VAR = "nameOfReferenceVar"; + @NonNls private static final String REFERENCE_CONDITION = "reference"; @NonNls private static final String NAME_OF_EXPRTYPE = "nameOfExprType"; @NonNls private static final String NAME_OF_FORMALTYPE = "nameOfFormalType"; @NonNls private static final String REGEXP = "regexp"; @@ -68,6 +53,7 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { @NonNls private static final String NEGATE_FORMALTYPE_CONDITION = "negateFormalType"; @NonNls private static final String NEGATE_CONTAINS_CONDITION = "negateContains"; @NonNls private static final String NEGATE_WITHIN_CONDITION = "negateWithin"; + @NonNls private static final String NEGATE_REFERENCE_CONDITION = "negateReference"; @NonNls private static final String WITHIN_CONDITION = "within"; @NonNls private static final String CONTAINS_CONDITION = "contains"; @NonNls private static final String TARGET = "target"; @@ -134,14 +120,6 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { this.partOfSearchResults = partOfSearchResults; } - public boolean isReference() { - return reference; - } - - public void setReference(boolean reference) { - this.reference = reference; - } - public boolean isInvertReference() { return invertReference; } @@ -150,12 +128,12 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { this.invertReference = invertReference; } - public String getNameOfReferenceVar() { - return nameOfReferenceVar; + public String getReferenceConstraint() { + return referenceConstraint; } - public void setNameOfReferenceVar(String nameOfReferenceVar) { - this.nameOfReferenceVar = nameOfReferenceVar; + public void setReferenceConstraint(String nameOfReferenceVar) { + this.referenceConstraint = nameOfReferenceVar; } public boolean isStrictlyWithinHierarchy() { @@ -239,13 +217,12 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { if (maxCount != matchVariableConstraint.maxCount) return false; if (minCount != matchVariableConstraint.minCount) return false; if (partOfSearchResults != matchVariableConstraint.partOfSearchResults) return false; - if (reference != matchVariableConstraint.reference) return false; if (strictlyWithinHierarchy != matchVariableConstraint.strictlyWithinHierarchy) return false; if (wholeWordsOnly != matchVariableConstraint.wholeWordsOnly) return false; if (withinHierarchy != matchVariableConstraint.withinHierarchy) return false; if (!nameOfExprType.equals(matchVariableConstraint.nameOfExprType)) return false; if (!nameOfFormalArgType.equals(matchVariableConstraint.nameOfFormalArgType)) return false; - if (!nameOfReferenceVar.equals(matchVariableConstraint.nameOfReferenceVar)) return false; + if (!referenceConstraint.equals(matchVariableConstraint.referenceConstraint)) return false; if (!regExp.equals(matchVariableConstraint.regExp)) return false; if (!withinConstraint.equals(matchVariableConstraint.withinConstraint)) return false; if (!containsConstraint.equals(matchVariableConstraint.containsConstraint)) return false; @@ -256,8 +233,7 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { } public int hashCode() { - int result; - result = super.hashCode(); + int result = super.hashCode(); result = 29 * result + regExp.hashCode(); result = 29 * result + (invertRegExp ? 1 : 0); result = 29 * result + (withinHierarchy ? 1 : 0); @@ -266,9 +242,8 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { result = 29 * result + minCount; result = 29 * result + maxCount; result = 29 * result + (greedy ? 1 : 0); - result = 29 * result + (reference ? 1 : 0); result = 29 * result + (invertReference ? 1 : 0); - result = 29 * result + nameOfReferenceVar.hashCode(); + result = 29 * result + referenceConstraint.hashCode(); result = 29 * result + (partOfSearchResults ? 1 : 0); result = 29 * result + nameOfExprType.hashCode(); result = 29 * result + (invertExprType ? 1 : 0); @@ -284,99 +259,39 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { return result; } + @Override public void readExternal(Element element) { super.readExternal(element); - Attribute attribute; - attribute = element.getAttribute(REGEXP); + Attribute attribute = element.getAttribute(REGEXP); if (attribute != null) { regExp = attribute.getValue(); } + withinHierarchy = readBoolean(element, WITHIN_HIERARCHY); + invertRegExp = readBoolean(element, NEGATE_NAME_CONDITION); + wholeWordsOnly = readBoolean(element, WHOLE_WORDS_ONLY); attribute = element.getAttribute(NAME_OF_EXPRTYPE); if (attribute != null) { nameOfExprType = attribute.getValue(); } + exprTypeWithinHierarchy = readBoolean(element, EXPRTYPE_WITHIN_HIERARCHY); + invertExprType = readBoolean(element, NEGATE_EXPRTYPE_CONDITION); + attribute = element.getAttribute(NAME_OF_FORMALTYPE); if (attribute != null) { nameOfFormalArgType = attribute.getValue(); } - - attribute = element.getAttribute(NAME_OF_REFEENCE_VAR); - if (attribute != null) { - nameOfReferenceVar = attribute.getValue(); - } - - attribute = element.getAttribute(WITHIN_HIERARCHY); - if (attribute != null) { - try { - withinHierarchy = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(EXPRTYPE_WITHIN_HIERARCHY); - if (attribute != null) { - try { - exprTypeWithinHierarchy = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(FORMALTYPE_WITHIN_HIERARCHY); - if (attribute != null) { - try { - formalArgTypeWithinHierarchy = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(NEGATE_NAME_CONDITION); - if (attribute != null) { - try { - invertRegExp = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(NEGATE_EXPRTYPE_CONDITION); - if (attribute != null) { - try { - invertExprType = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(NEGATE_FORMALTYPE_CONDITION); - if (attribute != null) { - try { - invertFormalType = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(TARGET); - if (attribute != null) { - try { - partOfSearchResults = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } + formalArgTypeWithinHierarchy = readBoolean(element, FORMALTYPE_WITHIN_HIERARCHY); + invertFormalType = readBoolean(element, NEGATE_FORMALTYPE_CONDITION); attribute = element.getAttribute(MIN_OCCURS); if (attribute != null) { try { minCount = attribute.getIntValue(); } - catch (DataConversionException ex) { + catch (DataConversionException ignored) { } } @@ -385,63 +300,61 @@ public class MatchVariableConstraint extends NamedScriptableDefinition { try { maxCount = attribute.getIntValue(); } - catch (DataConversionException ex) { + catch (DataConversionException ignored) { } } - attribute = element.getAttribute(WHOLE_WORDS_ONLY); - if (attribute != null) { - try { - wholeWordsOnly = attribute.getBooleanValue(); - } - catch (DataConversionException ex) { - } - } - - attribute = element.getAttribute(NEGATE_WITHIN_CONDITION); - if (attribute != null) { - try { - invertWithinConstraint = attribute.getBooleanValue(); - } catch (DataConversionException ex) {} - } - - attribute = element.getAttribute(NEGATE_CONTAINS_CONDITION); - if (attribute != null) { - try { - invertContainsConstraint = attribute.getBooleanValue(); - } catch (DataConversionException ex) {} - } + attribute = element.getAttribute(REFERENCE_CONDITION); + if (attribute != null) referenceConstraint = attribute.getValue(); + invertReference = readBoolean(element, NEGATE_REFERENCE_CONDITION); attribute = element.getAttribute(CONTAINS_CONDITION); - if(attribute != null) containsConstraint = attribute.getValue(); + if (attribute != null) containsConstraint = attribute.getValue(); + invertContainsConstraint = readBoolean(element, NEGATE_CONTAINS_CONDITION); attribute = element.getAttribute(WITHIN_CONDITION); - if(attribute != null) withinConstraint = attribute.getValue(); + if (attribute != null) withinConstraint = attribute.getValue(); + invertWithinConstraint = readBoolean(element, NEGATE_WITHIN_CONDITION); + + partOfSearchResults = readBoolean(element, TARGET); } + private static boolean readBoolean(Element element, String attributeName) { + final Attribute attribute = element.getAttribute(attributeName); + if (attribute != null) { + try { + return attribute.getBooleanValue(); + } + catch (DataConversionException ignored) {} + } + return false; + } + + @Override public void writeExternal(Element element) { super.writeExternal(element); - if (regExp.length() > 0) element.setAttribute(REGEXP,regExp); - if (nameOfExprType.length() > 0) element.setAttribute(NAME_OF_EXPRTYPE,nameOfExprType); - if (nameOfReferenceVar.length() > 0) element.setAttribute(NAME_OF_REFEENCE_VAR,nameOfReferenceVar); - if (nameOfFormalArgType.length() > 0) element.setAttribute(NAME_OF_FORMALTYPE,nameOfFormalArgType); + if (!regExp.isEmpty()) element.setAttribute(REGEXP, regExp); + if (!nameOfExprType.isEmpty()) element.setAttribute(NAME_OF_EXPRTYPE, nameOfExprType); + if (!referenceConstraint.isEmpty()) element.setAttribute(REFERENCE_CONDITION, referenceConstraint); + if (!nameOfFormalArgType.isEmpty()) element.setAttribute(NAME_OF_FORMALTYPE, nameOfFormalArgType); - if (withinHierarchy) element.setAttribute(WITHIN_HIERARCHY,TRUE); - if (exprTypeWithinHierarchy) element.setAttribute(EXPRTYPE_WITHIN_HIERARCHY,TRUE); - if (formalArgTypeWithinHierarchy) element.setAttribute(FORMALTYPE_WITHIN_HIERARCHY,TRUE); + if (withinHierarchy) element.setAttribute(WITHIN_HIERARCHY, TRUE); + if (exprTypeWithinHierarchy) element.setAttribute(EXPRTYPE_WITHIN_HIERARCHY, TRUE); + if (formalArgTypeWithinHierarchy) element.setAttribute(FORMALTYPE_WITHIN_HIERARCHY, TRUE); - if (minCount!=1) element.setAttribute(MIN_OCCURS,String.valueOf(minCount)); - if (maxCount!=1) element.setAttribute(MAX_OCCURS,String.valueOf(maxCount)); - if (partOfSearchResults) element.setAttribute(TARGET,TRUE); + if (minCount != 1) element.setAttribute(MIN_OCCURS,String.valueOf(minCount)); + if (maxCount != 1) element.setAttribute(MAX_OCCURS,String.valueOf(maxCount)); + if (partOfSearchResults) element.setAttribute(TARGET, TRUE); - if (invertRegExp) element.setAttribute(NEGATE_NAME_CONDITION,TRUE); - if (invertExprType) element.setAttribute(NEGATE_EXPRTYPE_CONDITION,TRUE); - if (invertFormalType) element.setAttribute(NEGATE_FORMALTYPE_CONDITION,TRUE); + if (invertRegExp) element.setAttribute(NEGATE_NAME_CONDITION, TRUE); + if (invertExprType) element.setAttribute(NEGATE_EXPRTYPE_CONDITION, TRUE); + if (invertFormalType) element.setAttribute(NEGATE_FORMALTYPE_CONDITION, TRUE); + if (invertReference) element.setAttribute(NEGATE_REFERENCE_CONDITION, TRUE); - if (wholeWordsOnly) element.setAttribute(WHOLE_WORDS_ONLY,TRUE); - if (invertContainsConstraint) element.setAttribute(NEGATE_CONTAINS_CONDITION,TRUE); - if (invertWithinConstraint) element.setAttribute(NEGATE_WITHIN_CONDITION,TRUE); + if (wholeWordsOnly) element.setAttribute(WHOLE_WORDS_ONLY, TRUE); + if (invertContainsConstraint) element.setAttribute(NEGATE_CONTAINS_CONDITION, TRUE); + if (invertWithinConstraint) element.setAttribute(NEGATE_WITHIN_CONDITION, TRUE); element.setAttribute(WITHIN_CONDITION, withinConstraint); element.setAttribute(CONTAINS_CONDITION, containsConstraint); } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java index a90623b7c209..197ca135f807 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch; import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; @@ -28,6 +14,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ContentIterator; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; @@ -42,6 +29,7 @@ import com.intellij.structuralsearch.impl.matcher.iterators.SingleNodeIterator; import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.ConfigurationManager; import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink; import com.intellij.structuralsearch.plugin.util.DuplicateFilteringResultSink; import com.intellij.util.IncorrectOperationException; @@ -51,16 +39,17 @@ import com.intellij.util.indexing.FileBasedIndex; import org.jetbrains.annotations.NotNull; import java.lang.ref.SoftReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; /** * This class makes program structure tree matching: */ public class Matcher { private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl"); + + @SuppressWarnings("SSBasedInspection") + private static final ThreadLocal> ourRecursionGuard = ThreadLocal.withInitial(() -> new HashSet<>()); + // project being worked on private final Project project; private final DumbService myDumbService; @@ -93,6 +82,36 @@ public class Matcher { myDumbService = DumbService.getInstance(project); } + public static Matcher buildMatcher(Project project, FileType fileType, String constraint) { + if (StringUtil.isQuotedString(constraint)) { + // keep old configurations working, also useful for testing + final MatchOptions myMatchOptions = new MatchOptions(); + myMatchOptions.setLooseMatching(true); + myMatchOptions.setFileType(fileType); + myMatchOptions.fillSearchCriteria(StringUtil.unquoteString(constraint)); + return new Matcher(project, myMatchOptions); + } + else { + final Set set = ourRecursionGuard.get(); + if (!set.add(constraint)) { + throw new MalformedPatternException("Pattern recursively references itself"); + } + try { + final Configuration configuration = ConfigurationManager.getInstance(project).findConfigurationByName(constraint); + if (configuration == null) { + throw new MalformedPatternException("Configuration '" + constraint + "' not found"); + } + return new Matcher(project, configuration.getMatchOptions()); + } finally { + set.remove(constraint); + if (set.isEmpty()) { + // we're finished with this thread local + ourRecursionGuard.remove(); + } + } + } + } + static class LastMatchData { CompiledPattern lastPattern; MatchOptions lastOptions; @@ -140,7 +159,8 @@ public class Matcher { } } - public void processMatchesInElement(MatchContext context, Configuration configuration, + public void processMatchesInElement(MatchContext context, + Configuration configuration, NodeIterator matchedNodes, PairProcessor processor) { try { @@ -153,6 +173,18 @@ public class Matcher { } } + public boolean matchNode(PsiElement element) { + final CollectingMatchResultSink sink = new CollectingMatchResultSink(); + final MatchOptions options = matchContext.getOptions(); + final CompiledPattern compiledPattern = prepareMatching(sink, options); + if (compiledPattern == null) { + return false; + } + matchContext.setShouldRecursivelyMatch(false); + visitor.matchContext(new SingleNodeIterator(element)); + return !sink.getMatches().isEmpty(); + } + public void clearContext() { matchContext.clear(); } 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 fcef00feab79..efb4b2376f16 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 @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch.impl.matcher.compiler; import com.intellij.codeInsight.template.Template; @@ -358,7 +344,7 @@ public class PatternCompiler { final StringBuilder buf = new StringBuilder(); - Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern()); + Template template = TemplateManager.getInstance(project).createTemplate("", "", options.getSearchPattern()); int segmentsCount = template.getSegmentsCount(); String text = template.getTemplateText(); @@ -373,7 +359,7 @@ public class PatternCompiler { throw new MalformedPatternException(); } - buf.append(text.substring(prevOffset,offset)); + buf.append(text.substring(prevOffset, offset)); buf.append(prefix); buf.append(name); @@ -381,7 +367,7 @@ public class PatternCompiler { if (constraint==null) { // we do not edited the constraints constraint = new MatchVariableConstraint(); - constraint.setName( name ); + constraint.setName(name); options.addVariableConstraint(constraint); } @@ -415,16 +401,16 @@ public class PatternCompiler { if (constraint.isInvertRegExp()) { predicate = new NotPredicate(predicate); } - addPredicate(handler,predicate); + addPredicate(handler, predicate); } - if (constraint.isReference()) { - predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() ); + if (!StringUtil.isEmptyOrSpaces(constraint.getReferenceConstraint())) { + predicate = new ReferencePredicate(constraint.getReferenceConstraint(), options.getFileType(), project); if (constraint.isInvertReference()) { predicate = new NotPredicate(predicate); } - addPredicate(handler,predicate); + addPredicate(handler, predicate); } addExtensionPredicates(options, constraint, handler); @@ -435,7 +421,7 @@ public class PatternCompiler { if (constraint.isInvertContainsConstraint()) { predicate = new NotPredicate(predicate); } - addPredicate(handler,predicate); + addPredicate(handler, predicate); } if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) { @@ -461,7 +447,7 @@ public class PatternCompiler { if (constraint.isInvertWithinConstraint()) { predicate = new NotPredicate(predicate); } - addPredicate(handler,predicate); + addPredicate(handler, predicate); } addExtensionPredicates(options, constraint, handler); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java index d6edbee39c32..af836febe932 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java @@ -352,11 +352,9 @@ public class StringToConstraintsTransformer { boolean invert) { argument = argument.trim(); if (option.equalsIgnoreCase(REF)) { - constraint.setReference(true); + if (argument.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.pattern.argument.expected", option)); + constraint.setReferenceConstraint(argument); constraint.setInvertReference(invert); - if (argument.isEmpty() || argument.charAt(0) != '\'') - throw new MalformedPatternException(SSRBundle.message("error.reference.variable.name.expected", option)); - constraint.setNameOfReferenceVar(argument.substring(1)); } else if (option.equalsIgnoreCase(REGEX) || option.equalsIgnoreCase(REGEXW)) { if (argument.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.regular.expression.argument.expected", option)); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java index 5db95c77d167..1e171efcfff6 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java @@ -1,47 +1,29 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.s package com.intellij.structuralsearch.impl.matcher.predicates; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; -import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.psi.PsiReference; +import com.intellij.structuralsearch.Matcher; import com.intellij.structuralsearch.StructuralSearchUtil; import com.intellij.structuralsearch.impl.matcher.MatchContext; -import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; -import com.intellij.structuralsearch.impl.matcher.MatchUtils; -import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; public final class ReferencePredicate extends MatchPredicate { - private final String myName; + private final Matcher matcher; - public ReferencePredicate(String name) { - myName = name; + public ReferencePredicate(String constraint, FileType fileType, Project project) { + matcher = Matcher.buildMatcher(project, fileType, constraint); } @Override - public boolean match(PsiElement match, int start, int end, MatchContext context) { - match = StructuralSearchUtil.getParentIfIdentifier(match); - - final PsiElement target = MatchUtils.getReferencedElement(match); - if (target != null) { - final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(target); - assert profile != null; - final String image = profile.getText(target, 0, -1); - context.getResult().addSon(new MatchResultImpl(myName, image, new SmartPsiPointer(target), true)); + public boolean match(PsiElement matchedNode, int start, int end, MatchContext context) { + matchedNode = StructuralSearchUtil.getParentIfIdentifier(matchedNode); + if (!(matchedNode instanceof PsiReference)) { + return false; } - return true; + final PsiElement target = ((PsiReference)matchedNode).resolve(); + return target != null && matcher.matchNode(target); } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java index ec2c16d25eba..3eff3923ad42 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java @@ -3,58 +3,23 @@ package com.intellij.structuralsearch.impl.matcher.predicates; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.structuralsearch.MalformedPatternException; -import com.intellij.structuralsearch.MatchOptions; import com.intellij.structuralsearch.MatchResult; import com.intellij.structuralsearch.Matcher; import com.intellij.structuralsearch.impl.matcher.MatchContext; -import com.intellij.structuralsearch.plugin.ui.Configuration; -import com.intellij.structuralsearch.plugin.ui.ConfigurationManager; -import java.util.HashSet; import java.util.List; -import java.util.Set; /** * @author Maxim.Mossienko */ public class WithinPredicate extends MatchPredicate { - @SuppressWarnings("SSBasedInspection") - private static final ThreadLocal> ourRecursionGuard = ThreadLocal.withInitial(() -> new HashSet<>()); private final Matcher matcher; public WithinPredicate(String within, FileType fileType, Project project) { - if (StringUtil.isQuotedString(within)) { - // keep old configurations working - final MatchOptions myMatchOptions = new MatchOptions(); - myMatchOptions.setLooseMatching(true); - myMatchOptions.setFileType(fileType); - myMatchOptions.fillSearchCriteria(StringUtil.unquoteString(within)); - matcher = new Matcher(project, myMatchOptions); - } - else { - final Set set = ourRecursionGuard.get(); - if (!set.add(within)) { - throw new MalformedPatternException("Pattern recursively contained within itself"); - } - try { - final Configuration configuration = ConfigurationManager.getInstance(project).findConfigurationByName(within); - if (configuration == null) { - throw new MalformedPatternException("Configuration '" + within + "' not found"); - } - matcher = new Matcher(project, configuration.getMatchOptions()); - } finally { - set.remove(within); - if (set.isEmpty()) { - // we're finished with this thread local - ourRecursionGuard.remove(); - } - } - } + matcher = Matcher.buildMatcher(project, fileType, within); } @Override diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java index b12c166f2679..450ecdced42a 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java @@ -1,14 +1,15 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch.plugin.util; import com.intellij.structuralsearch.DefaultMatchResultSink; import com.intellij.structuralsearch.MatchResult; +import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.List; public class CollectingMatchResultSink extends DefaultMatchResultSink { - private final List matches = new ArrayList<>(); + private final List matches = new SmartList<>(); @Override public void newMatch(MatchResult result) { diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java index 82629dab22dc..47105d29e8e8 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.structuralsearch; import com.intellij.openapi.fileTypes.StdFileTypes; @@ -1289,19 +1275,31 @@ public class StructuralSearchTest extends StructuralSearchTestCase { } public void testHierarchy() { - final String s105 = "class B {} class A extends B { }"; - assertEquals("extends match", 1, findMatchesCount(s105, "class '_ extends '_:[ref('T)] {}")); + final String s105 = "class B {} class A extends B { } class C {} class D extends C {}"; + assertEquals("extends match", 1, findMatchesCount(s105, "class '_ extends '_:[ref( \"class B {}\" )] {}")); final String s107 = "interface IA {} interface IB extends IA { } interface IC extends IB {} interface ID extends IC {}" + "class A implements IA {} class B extends A { } class C extends B implements IC {} class D extends C {}"; assertEquals("extends navigation match", 2, findMatchesCount(s107, "class '_ extends 'Type:+A {}")); assertEquals("implements navigation match", 3, findMatchesCount(s107, "class '_ implements 'Type:+IA {}")); - final String s109 = "interface I {} interface I2 extends I {} class A implements I2 {} class B extends A { } class C extends B {} class D { void e() { C c; B b; A a;} }"; + final String s109 = "interface I {}" + + "interface I2 extends I {}" + + "class A implements I2 {}" + + "class B extends A {}" + + "class C extends B {}" + + "class D {" + + " void e() {" + + " D d;" + + " C c;" + + " B b;" + + " A a;" + + " }" + + "}"; assertEquals("extends navigation match in definition", 3, findMatchesCount(s109, "'_:*A '_;")); assertEquals("implements navigation match in definition 2", 3, findMatchesCount(s109, "'_:*I '_;")); - assertEquals("implements navigation match in definition 2 with nested conditions", 1, - findMatchesCount(s109, "'_:*[regex( I ) && ref('T)] '_;")); + assertEquals("implements navigation match in definition 2 with nested conditions", 3, + findMatchesCount(s109, "'_:[ref( \"class '_A:*[regex( I )] {}\" )] '_;")); try { findMatchesCount(s109, "'_:*[regex( I ) && ref2('T)] '_;"); fail("implements navigation match in definition 2 with nested conditions - incorrect cond"); @@ -1408,19 +1406,33 @@ public class StructuralSearchTest extends StructuralSearchTestCase { assertEquals("body of method by block search", 2, findMatchesCount(s79, " { '_T 'T3 = '_T2?; '_*; } ")); assertEquals("first matches, next not", 2, findMatchesCount(s95, " class '_ {private int 'T+:field.* ;}")); - final String s97 = "class A { int c; void b() { C d; } } class C { C() { A a; a.b(); a.c=1; } }"; - assertEquals("method predicate match", 1, findMatchesCount(s97, "'_.'_:[ref('T)] ()")); - assertEquals("field predicate match", 1, findMatchesCount(s97, "'_.'_:[ref('T)]")); - assertEquals("dcl predicate match", 1, findMatchesCount(s97, "'_:[ref('T)].'_ ();")); + final String s97 = "class A {" + + " int c;" + + " int d;" + + " void b(){}" + + " void x(){" + + " C d;" + + " }" + + "}" + + "class C {" + + " C() {" + + " A a;" + + " A z;" + + " z.b();" + + " a.x();" + + " z.c=1;" + + " a.d=2;" + + " }" + + "}"; + assertEquals("method predicate match", 1, findMatchesCount(s97, "'_.'_:[ref( \"void b(){}\" )] ()")); + assertEquals("field predicate match", 1, findMatchesCount(s97, "'_.'_:[ref( \"int c;\" )]")); + assertEquals("dcl predicate match", 1, findMatchesCount(s97, "'_:[ref( \"A a;\" )].'_ ();")); final String s99 = "class X {{ char s = '\\u1111'; char s1 = '\\n'; }}"; assertEquals("char constants in pattern", 1, findMatchesCount(s99, " char 'var = '\\u1111'; ")); assertEquals("char constants in pattern 2", 1, findMatchesCount(s99, " char 'var = '\\n'; ")); - assertEquals("class predicate match (from definition)", 3, findMatchesCount(s97, "'_:[ref('T)] '_;")); - - final String s101 = "class A { void b() { String d; String e; String[] f; f.length=1; f.length=1; } }"; - assertEquals("distinct match", 1, findMatchesCount(s101, "'_:[ref('T)] '_;")); + assertEquals("class predicate match (from definition)", 3, findMatchesCount(s97, "'_:[ref( \"class '_A{}\" )] '_;")); String s107 = "class A {\n" + " /* */\n" + @@ -2667,4 +2679,21 @@ public class StructuralSearchTest extends StructuralSearchTestCase { assertEquals(1, findMatchesCount(source6, "'x:[exprtype( List> )]")); assertEquals(2, findMatchesCount(source6, "'x:[exprtype( List )]")); } + + public void testSearchReferences() { + String source = "class X {" + + " @Deprecated" + + " void a() {}" + + " void b() {}" + + " void c() {" + + " a();" + + " b();" + + " b();" + + " }" + + "}"; + assertEquals("find calls to deprecated methods", 1, + findMatchesCount(source, "'_instance?.'_call:[ref( \"@Deprecated void '_x();\" )] ()")); + assertEquals("find calls to non-deprecated methods", 2, + findMatchesCount(source, "'_instance?.'_call:[ref( \"@'_Anno{0,0} void '_x();\" )] ()")); + } }