SSR: support regex type filter and allow to select/switch in UI (IDEA-229632)

GitOrigin-RevId: 4dfb5e223b5a1e667f83496787efc84726604a9c
This commit is contained in:
Bas Leijdekkers
2019-12-21 16:50:03 +01:00
committed by intellij-monorepo-bot
parent f6f5830850
commit 4e2f58edc3
10 changed files with 113 additions and 93 deletions

View File

@@ -272,22 +272,24 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
if (!StringUtil.isEmptyOrSpaces(constraint.getNameOfExprType())) {
final MatchPredicate predicate = new ExprTypePredicate(
constraint.getNameOfExprType(),
constraint.isRegexExprType() ? constraint.getNameOfExprType() : constraint.getExpressionTypes(),
name,
constraint.isExprTypeWithinHierarchy(),
options.isCaseSensitiveMatch(),
constraint.isPartOfSearchResults()
constraint.isPartOfSearchResults(),
constraint.isRegexExprType()
);
result.add(constraint.isInvertExprType() ? new NotPredicate(predicate) : predicate);
}
if (!StringUtil.isEmptyOrSpaces(constraint.getNameOfFormalArgType())) {
final MatchPredicate predicate = new FormalArgTypePredicate(
constraint.getNameOfFormalArgType(),
constraint.isRegexFormalType() ? constraint.getNameOfFormalArgType() : constraint.getExpectedTypes(),
name,
constraint.isFormalArgTypeWithinHierarchy(),
options.isCaseSensitiveMatch(),
constraint.isPartOfSearchResults()
constraint.isPartOfSearchResults(),
constraint.isRegexFormalType()
);
result.add(constraint.isInvertFormalType() ? new NotPredicate(predicate) : predicate);
}
@@ -1009,6 +1011,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
else if (grandParent instanceof PsiStatement) return false;
}
case UIUtil.TYPE:
case UIUtil.TYPE_REGEX:
if (variableNode instanceof PsiExpressionStatement) {
final PsiElement child = variableNode.getLastChild();
if (child instanceof PsiErrorElement) {

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2019 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.predicates;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.structuralsearch.impl.matcher.MatchContext;
@@ -17,22 +16,22 @@ import java.util.Set;
* @author Maxim.Mossienko
*/
public class ExprTypePredicate extends MatchPredicate {
private final RegExpPredicate delegate;
private final boolean withinHierarchy;
private final RegExpPredicate myDelegate;
private final boolean myWithinHierarchy;
private final boolean needsTypeParameters;
private final boolean needsFullyQualified;
private final boolean needsArrayType;
private final boolean myCaseSensitive;
private final List<String> myTypes;
public ExprTypePredicate(String type, String baseName, boolean _withinHierarchy, boolean caseSensitiveMatch, boolean target) {
delegate = Registry.is("ssr.use.regexp.to.specify.type") ? new RegExpPredicate(type, caseSensitiveMatch, baseName, false, target) : null;
withinHierarchy = _withinHierarchy;
public ExprTypePredicate(String type, String baseName, boolean withinHierarchy, boolean caseSensitiveMatch, boolean target, boolean regex) {
myDelegate = regex ? new RegExpPredicate(type, caseSensitiveMatch, baseName, false, target) : null;
myWithinHierarchy = withinHierarchy;
needsTypeParameters = type.indexOf('<') >= 0;
needsFullyQualified = type.indexOf('.') >= 0;
needsArrayType = type.indexOf('[') >= 0;
myCaseSensitive = caseSensitiveMatch;
myTypes = StringUtil.split(type, "|");
myTypes = regex ? null : StringUtil.split(type, "|");
}
@Override
@@ -69,11 +68,11 @@ public class ExprTypePredicate extends MatchPredicate {
private boolean doMatchWithTheType(final PsiType type, MatchContext context, PsiElement matchedNode, Set<? super PsiType> visited) {
final List<String> permutations = getTextPermutations(type);
for (String permutation : permutations) {
if (delegate == null ? doMatch(permutation) : delegate.doMatch(permutation, context, matchedNode)) {
if (myDelegate == null ? doMatch(permutation) : myDelegate.doMatch(permutation, context, matchedNode)) {
return true;
}
}
if (withinHierarchy) {
if (myWithinHierarchy) {
if (visited == null) {
visited = new THashSet<>();
visited.add(type);

View File

@@ -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-2019 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.predicates;
import com.intellij.psi.PsiExpression;
@@ -22,8 +8,13 @@ import com.siyeh.ig.psiutils.ExpectedTypeUtils;
public class FormalArgTypePredicate extends ExprTypePredicate {
public FormalArgTypePredicate(String type, String baseName, boolean withinHierarchy, boolean caseSensitiveMatch, boolean target) {
super(type, baseName, withinHierarchy, caseSensitiveMatch, target);
public FormalArgTypePredicate(String type,
String baseName,
boolean withinHierarchy,
boolean caseSensitiveMatch,
boolean target,
boolean regex) {
super(type, baseName, withinHierarchy, caseSensitiveMatch, target, regex);
}
@Override

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2019 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.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import org.jdom.Attribute;
import org.jdom.DataConversionException;
@@ -49,9 +48,7 @@ public class MatchVariableConstraint extends NamedScriptableDefinition {
@NonNls private static final String REFERENCE_CONDITION = "reference";
@NonNls private static final String NAME_OF_EXPRTYPE = "nameOfExprType";
@NonNls private static final String EXPRESSION_TYPES = "expressionTypes";
@NonNls private static final String NAME_OF_FORMALTYPE = "nameOfFormalType";
@NonNls private static final String EXPECTED_TYPES = "exceptedTypes";
@NonNls private static final String REGEXP = "regexp";
@NonNls private static final String EXPRTYPE_WITHIN_HIERARCHY = "exprTypeWithinHierarchy";
@NonNls private static final String FORMALTYPE_WITHIN_HIERARCHY = "formalTypeWithinHierarchy";
@@ -248,18 +245,26 @@ public class MatchVariableConstraint extends NamedScriptableDefinition {
@NotNull
public String getNameOfExprType() {
return Registry.is("ssr.use.regexp.to.specify.type") ? nameOfExprType : expressionTypes;
return nameOfExprType;
}
@NotNull
public String getExpressionTypes() {
return expressionTypes;
}
public void setNameOfExprType(@NotNull String nameOfExprType) {
if (Registry.is("ssr.use.regexp.to.specify.type")) {
this.nameOfExprType = nameOfExprType;
this.expressionTypes = convertRegExpTypeToTypeString(nameOfExprType);
}
else {
this.nameOfExprType = convertTypeStringToRegExp(nameOfExprType);
this.expressionTypes = nameOfExprType;
}
this.nameOfExprType = nameOfExprType;
this.expressionTypes = convertRegExpTypeToTypeString(nameOfExprType);
}
public void setExpressionTypes(@NotNull String expressionTypes) {
this.expressionTypes = expressionTypes;
this.nameOfExprType = convertTypeStringToRegExp(expressionTypes);
}
public boolean isRegexExprType() {
return StringUtil.isEmpty(expressionTypes) && !StringUtil.isEmpty(nameOfExprType);
}
public boolean isInvertExprType() {
@@ -288,18 +293,26 @@ public class MatchVariableConstraint extends NamedScriptableDefinition {
@NotNull
public String getNameOfFormalArgType() {
return Registry.is("ssr.use.regexp.to.specify.type") ? nameOfFormalArgType : expectedTypes;
return nameOfFormalArgType;
}
@NotNull
public String getExpectedTypes() {
return expectedTypes;
}
public void setNameOfFormalArgType(@NotNull String nameOfFormalArgType) {
if (Registry.is("ssr.use.regexp.to.specify.type")) {
this.nameOfFormalArgType = nameOfFormalArgType;
this.expectedTypes = convertRegExpTypeToTypeString(nameOfFormalArgType);
}
else {
this.nameOfFormalArgType = convertTypeStringToRegExp(nameOfFormalArgType);
this.expectedTypes = nameOfFormalArgType;
}
this.nameOfFormalArgType = nameOfFormalArgType;
this.expectedTypes = convertRegExpTypeToTypeString(nameOfFormalArgType);
}
public void setExpectedTypes(@NotNull String expectedTypes) {
this.expectedTypes = expectedTypes;
this.nameOfFormalArgType = convertTypeStringToRegExp(expectedTypes);
}
public boolean isRegexFormalType() {
return StringUtil.isEmpty(expectedTypes) && !StringUtil.isEmpty(nameOfFormalArgType);
}
public boolean isInvertFormalType() {
@@ -390,21 +403,14 @@ public class MatchVariableConstraint extends NamedScriptableDefinition {
invertRegExp = getBooleanValue(element, NEGATE_NAME_CONDITION, false);
wholeWordsOnly = getBooleanValue(element, WHOLE_WORDS_ONLY, false);
expressionTypes = StringUtil.notNullize(element.getAttributeValue(EXPRESSION_TYPES));
nameOfExprType = StringUtil.notNullize(element.getAttributeValue(NAME_OF_EXPRTYPE));
if (expressionTypes.isEmpty() && !nameOfExprType.isEmpty()) {
expressionTypes = convertRegExpTypeToTypeString(nameOfExprType);
}
expressionTypes = convertRegExpTypeToTypeString(nameOfExprType);
exprTypeWithinHierarchy = getBooleanValue(element, EXPRTYPE_WITHIN_HIERARCHY, false);
invertExprType = getBooleanValue(element, NEGATE_EXPRTYPE_CONDITION, false);
expectedTypes = StringUtil.notNullize(element.getAttributeValue(EXPECTED_TYPES));
nameOfFormalArgType = StringUtil.notNullize(element.getAttributeValue(NAME_OF_FORMALTYPE));
if (expectedTypes.isEmpty() && !nameOfFormalArgType.isEmpty()) {
expectedTypes = convertRegExpTypeToTypeString(nameOfFormalArgType);
}
expectedTypes = convertRegExpTypeToTypeString(nameOfFormalArgType);
formalArgTypeWithinHierarchy = getBooleanValue(element, FORMALTYPE_WITHIN_HIERARCHY, false);
invertFormalType = getBooleanValue(element, NEGATE_FORMALTYPE_CONDITION, false);
@@ -453,14 +459,8 @@ public class MatchVariableConstraint extends NamedScriptableDefinition {
if (!regExp.isEmpty()) element.setAttribute(REGEXP, regExp);
if (!nameOfExprType.isEmpty()) element.setAttribute(NAME_OF_EXPRTYPE, nameOfExprType);
if (!Registry.is("ssr.use.regexp.to.specify.type") && !expressionTypes.isEmpty() && !expressionTypes.equals(nameOfExprType)) {
element.setAttribute(EXPRESSION_TYPES, expressionTypes);
}
if (!referenceConstraint.isEmpty()) element.setAttribute(REFERENCE_CONDITION, referenceConstraint);
if (!nameOfFormalArgType.isEmpty()) element.setAttribute(NAME_OF_FORMALTYPE, nameOfFormalArgType);
if (!Registry.is("ssr.use.regexp.to.specify.type") && !expectedTypes.isEmpty() && !expectedTypes.equals(nameOfFormalArgType)) {
element.setAttribute(EXPECTED_TYPES, expectedTypes);
}
if (withinHierarchy) element.setAttribute(WITHIN_HIERARCHY, TRUE);
if (exprTypeWithinHierarchy) element.setAttribute(EXPRTYPE_WITHIN_HIERARCHY, TRUE);

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2019 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.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.structuralsearch.MalformedPatternException;
import com.intellij.structuralsearch.MatchOptions;
@@ -393,9 +392,8 @@ public class StringToConstraintsTransformer {
argument = argument.substring(1);
constraint.setExprTypeWithinHierarchy(true);
}
if (Registry.is("ssr.use.regexp.to.specify.type")) checkRegex(argument);
else argument = unescape(argument);
constraint.setNameOfExprType(argument);
argument = unescape(argument);
constraint.setExpressionTypes(argument);
constraint.setInvertExprType(invert);
}
else if (option.equalsIgnoreCase(FORMAL)) {
@@ -403,9 +401,8 @@ public class StringToConstraintsTransformer {
argument = argument.substring(1);
constraint.setFormalArgTypeWithinHierarchy(true);
}
if (Registry.is("ssr.use.regexp.to.specify.type")) checkRegex(argument);
else argument = unescape(argument);
constraint.setNameOfFormalArgType(argument);
argument = unescape(argument);
constraint.setExpressionTypes(argument);
constraint.setInvertFormalType(invert);
}
else if (option.equalsIgnoreCase(SCRIPT)) {
@@ -437,10 +434,10 @@ public class StringToConstraintsTransformer {
}
private static String unescape(String s) {
StringBuilder result = new StringBuilder();
final StringBuilder result = new StringBuilder();
boolean escaped = false;
for (int i = 0, length = s.length(); i < length; i++) {
int c = s.codePointAt(i);
final int c = s.codePointAt(i);
if (c == '\\' && !escaped) {
escaped = true;
}

View File

@@ -183,15 +183,14 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
if (!constraint.getNameOfExprType().isEmpty()) {
append(buf, SSRBundle.message("exprtype.tooltip.message",
constraint.isInvertExprType() ? 1 : 0,
constraint.getNameOfExprType(),
constraint.isRegexExprType() ? constraint.getNameOfExprType() : constraint.getExpressionTypes(),
constraint.isExprTypeWithinHierarchy() ? 1 : 0));
}
constraint.getNameOfFormalArgType();
if (!constraint.getNameOfFormalArgType().isEmpty()) {
append(buf, SSRBundle.message("expected.type.tooltip.message",
constraint.isInvertFormalType() ? 1 : 0,
constraint.getNameOfFormalArgType(),
constraint.isRegexFormalType() ? constraint.getNameOfFormalArgType() : constraint.getExpectedTypes(),
constraint.isFormalArgTypeWithinHierarchy() ? 1 : 0));
}

View File

@@ -66,6 +66,7 @@ public class UIUtil {
@NonNls public static final String TEXT_HIERARCHY = "TEXT HIERARCHY";
@NonNls public static final String REFERENCE = "REFERENCE";
@NonNls public static final String TYPE = "TYPE";
@NonNls public static final String TYPE_REGEX = "TYPE REGEX";
@NonNls public static final String EXPECTED_TYPE = "EXPECTED TYPE";
@NonNls public static final String MINIMUM_ZERO = "MINIMUM ZERO";
@NonNls public static final String MAXIMUM_UNLIMITED = "MAXIMUM UNLIMITED";

View File

@@ -1,15 +1,18 @@
// Copyright 2000-2019 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.ui.filters;
import com.intellij.openapi.fileTypes.PlainTextFileType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.structuralsearch.MatchVariableConstraint;
import com.intellij.structuralsearch.NamedScriptableDefinition;
import com.intellij.structuralsearch.StructuralSearchProfile;
import com.intellij.structuralsearch.plugin.ui.UIUtil;
import com.intellij.ui.ContextHelpLabel;
import com.intellij.ui.EditorTextField;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import org.intellij.lang.regexp.RegExpFileType;
import javax.swing.*;
import java.util.List;
@@ -19,6 +22,8 @@ import java.util.List;
*/
public class TypeFilter extends FilterAction {
boolean showRegex;
public TypeFilter(FilterTable filterTable) {
super("Type", filterTable);
}
@@ -47,8 +52,12 @@ public class TypeFilter extends FilterAction {
@Override
public boolean isApplicable(List<? extends PsiElement> nodes, boolean completePattern, boolean target) {
return myTable.getVariable() instanceof MatchVariableConstraint &&
myTable.getProfile().isApplicableConstraint(UIUtil.TYPE, nodes, completePattern, target);
if (!(myTable.getVariable() instanceof MatchVariableConstraint)) {
return false;
}
final StructuralSearchProfile profile = myTable.getProfile();
showRegex = profile.isApplicableConstraint(UIUtil.TYPE_REGEX, nodes, completePattern, target);
return profile.isApplicableConstraint(UIUtil.TYPE, nodes, completePattern, target);
}
@Override
@@ -56,7 +65,7 @@ public class TypeFilter extends FilterAction {
final MatchVariableConstraint constraint = (MatchVariableConstraint)myTable.getVariable();
myLabel.append("type=");
if (constraint.isInvertExprType()) myLabel.append("!");
myLabel.append(constraint.getNameOfExprType());
myLabel.append(constraint.isRegexExprType() ? constraint.getNameOfExprType() : constraint.getExpressionTypes());
if (constraint.isExprTypeWithinHierarchy()) myLabel.append(", within hierarchy", SimpleTextAttributes.GRAYED_ATTRIBUTES);
}
@@ -67,6 +76,12 @@ public class TypeFilter extends FilterAction {
private final EditorTextField myTextField = UIUtil.createTextComponent("", myTable.getProject());
private final JLabel myTypeLabel = new JLabel("type=");
private final JCheckBox myHierarchyCheckBox = new JCheckBox("Within type hierarchy", false);
private final JCheckBox myRegexCheckBox = new JCheckBox("Regex", false);
{
myRegexCheckBox.addActionListener(e -> myTextField.setFileType(myRegexCheckBox.isSelected()
? RegExpFileType.INSTANCE
: PlainTextFileType.INSTANCE));
}
private final ContextHelpLabel myHelpLabel = ContextHelpLabel.create(
"<p>The type of the matched expression is checked against the provided \"|\"-separated patterns. " +
"<p>Use \"!\" to invert the pattern.");
@@ -86,7 +101,12 @@ public class TypeFilter extends FilterAction {
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 1, 1)
.addComponent(myHelpLabel)
)
.addComponent(myHierarchyCheckBox)
.addGroup(
layout.createSequentialGroup()
.addComponent(myHierarchyCheckBox)
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(myRegexCheckBox)
)
);
layout.setVerticalGroup(
layout.createSequentialGroup()
@@ -96,27 +116,39 @@ public class TypeFilter extends FilterAction {
.addComponent(myTextField)
.addComponent(myHelpLabel)
)
.addComponent(myHierarchyCheckBox)
.addGroup(
layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(myHierarchyCheckBox)
.addComponent(myRegexCheckBox)
)
);
}
@Override
protected void loadValues() {
myTextField.setText((myConstraint.isInvertExprType() ? "!" : "") + myConstraint.getNameOfExprType());
final boolean regex = myConstraint.isRegexExprType();
myTextField.setFileType(showRegex && regex ? RegExpFileType.INSTANCE : PlainTextFileType.INSTANCE);
myTextField.setText((myConstraint.isInvertExprType() ? "!" : "") +
(regex ? myConstraint.getNameOfExprType() : myConstraint.getExpressionTypes())) ;
myHierarchyCheckBox.setSelected(myConstraint.isExprTypeWithinHierarchy());
myRegexCheckBox.setSelected(showRegex && regex);
myRegexCheckBox.setVisible(showRegex);
}
@Override
public void saveValues() {
final String text = myTextField.getText();
if (text.startsWith("!")) {
myConstraint.setNameOfExprType(text.substring(1));
myConstraint.setInvertExprType(true);
String text = myTextField.getText();
final boolean inverted = text.startsWith("!");
if (inverted) {
text = text.substring(1);
}
if (showRegex && myRegexCheckBox.isSelected()) {
myConstraint.setNameOfExprType(text);
}
else {
myConstraint.setNameOfExprType(text);
myConstraint.setInvertExprType(false);
myConstraint.setExpressionTypes(text);
}
myConstraint.setInvertExprType(inverted);
myConstraint.setExprTypeWithinHierarchy(myHierarchyCheckBox.isSelected());
}

View File

@@ -308,7 +308,7 @@ public class StringToConstraintsTransformerTest {
public void testBrackets() {
test("'_x:[exprtype( java\\.lang\\.String\\[\\]\\[\\] )]");
final MatchVariableConstraint constraint = myOptions.getVariableConstraint("x");
assertEquals("java.lang.String[][]", constraint.getNameOfExprType());
assertEquals("java\\.lang\\.String\\[\\]\\[\\]", constraint.getNameOfExprType());
}
private void expectException(@NotNull String criteria, @NotNull String exceptionMessage) {

View File

@@ -1586,8 +1586,6 @@ file.system.trace.loading=/proc/cpuinfo
file.system.trace.loading.description=Semicolon-separated paths that should not be loaded into IDE's file system. Their loading stack traces will be logged
file.system.trace.loading.restartRequired=true
ssr.use.regexp.to.specify.type=false
ssr.use.regexp.to.specify.type.description=Revert back to using regexp to specify expression types
ssr.template.from.selection.builder=false
ssr.template.from.selection.builder.description=Allows to build template with live-template-like placeholders when invoking SSR from editor with selection
ssr.save.templates.to.ide.instead.of.project.workspace=true