SSR: match enhanced switch statements and switch expressions (IDEA-203910)

GitOrigin-RevId: 92b929008336c713597dbaf7d98346b6a89f8b5d
This commit is contained in:
Bas Leijdekkers
2019-12-10 18:38:18 +00:00
committed by intellij-monorepo-bot
parent dc6989c6c3
commit 016e77ab20
8 changed files with 188 additions and 72 deletions
@@ -335,7 +335,9 @@ class JavaPredefinedConfigurations {
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.any.boxing"), "'_expression:[ exprtype( int|boolean|long|char|short|byte ) && formal( Object|Integer|Boolean|Long|Character|Short|Byte )]", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.any.unboxing"), "'_expression:[ formal( int|boolean|long|char|short|byte ) && exprtype( Integer|Boolean|Long|Character|Short|Byte )]", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.try.without.resources"), "try ('_ResourceType '_resource{0,0} = '_init; '_expression{0,0}) {\n '_TryStatement*;\n} catch('_ExceptionType '_Exception{0,0}) {\n '_CatchStatement*;\n}", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.switch.with.branches"), "switch ('_expression) {\n case '_value{0,4}\\: '_statement*;\n}", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.switch.with.branches"), "[ script( \"import com.intellij.psi.*;\n" +
"import com.intellij.psi.util.*;\n" +
"PsiTreeUtil.getChildrenOfType(__context__.body, PsiSwitchLabelStatementBase.class).length < 5\" ) ]switch ('_expression) {\n}", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.labeled.break"), "break '_label;", INTERESTING_TYPE),
createSearchTemplateInfo(SSRBundle.message("predefined.configuration.methods.with.final.parameters"),
"'_ReturnType? '_Method('_BeforeType '_BeforeParameter*, final '_ParameterType '_Parameter, '_AfterType '_AfterParameter*);",
@@ -802,8 +802,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
parent instanceof PsiClass ? PsiMember.class : PsiJavaCodeReferenceElement.class, buf);
}
else if (info.isStatementContext() || info.isArgumentContext() || parent instanceof PsiPolyadicExpression) {
addSeparatorText
(previous, currentElement, buf);
addSeparatorText(previous, currentElement, buf);
}
else {
buf.append(" "); // doesn't happen
@@ -1050,12 +1049,6 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
if (grandParent instanceof PsiNameValuePair) return ((PsiNameValuePair)grandParent).getValue() == parent;
if (grandParent instanceof PsiForStatement) return true;
}
if (grandParent instanceof PsiExpressionList) {
final PsiElement label = grandParent.getParent();
if (label instanceof PsiSwitchLabelStatementBase) {
return ((PsiSwitchLabelStatementBase)label).getEnclosingSwitchStatement() != null;
}
}
if (grandParent instanceof PsiVariable) {
return ((PsiVariable)grandParent).getInitializer() == parent;
}
@@ -1100,7 +1093,6 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
final PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiPolyadicExpression) return true;
if (grandParent instanceof PsiExpressionList && grandParent.getParent() instanceof PsiSwitchLabelStatementBase) return true;
if (grandParent instanceof PsiExpressionStatement && isCompleteStatement((PsiExpressionStatement)grandParent)) {
final PsiElement greatGrandParent = grandParent.getParent();
if (greatGrandParent instanceof PsiForStatement) {
@@ -1125,7 +1117,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile {
if (grandParent instanceof PsiCatchSection && parent instanceof PsiParameter) return true;
if (grandParent instanceof PsiAnnotation && !(grandParent.getParent().getNextSibling() instanceof PsiErrorElement)) return true;
if (grandParent instanceof PsiParameterList || grandParent instanceof PsiArrayInitializerMemberValue ||
(grandParent instanceof PsiExpressionList && !(grandParent.getParent() instanceof PsiSwitchLabelStatementBase)) ||
grandParent instanceof PsiExpressionList ||
grandParent instanceof PsiTypeParameterList || grandParent instanceof PsiResourceList ||
grandParent instanceof PsiResourceExpression || grandParent instanceof PsiArrayInitializerExpression) {
return true;
@@ -105,7 +105,7 @@ public class JavaMatchingVisitor extends JavaElementVisitor {
final PsiAnnotation[] annotations = list.getAnnotations();
if (annotations.length > 0) {
Set<PsiAnnotation> annotationSet = ContainerUtil.set(annotations);
final Set<PsiAnnotation> annotationSet = ContainerUtil.set(annotations);
for (PsiAnnotation annotation : annotations) {
final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
@@ -1174,51 +1174,88 @@ public class JavaMatchingVisitor extends JavaElementVisitor {
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
final PsiSwitchStatement other = (PsiSwitchStatement)myMatchingVisitor.getElement();
if (myMatchingVisitor.setResult(myMatchingVisitor.match(statement.getExpression(), other.getExpression()))) {
final List<PsiSwitchLabelStatement> cases1 = PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatement.class);
if (cases1.isEmpty()) {
myMatchingVisitor.setResult(myMatchingVisitor.matchSons(statement.getBody(), other.getBody()));
}
else {
final List<PsiSwitchLabelStatement> cases2 = PsiTreeUtil.getChildrenOfTypeAsList(other.getBody(), PsiSwitchLabelStatement.class);
myMatchingVisitor.setResult(
myMatchingVisitor.matchSequentially(cases1.toArray(PsiElement.EMPTY_ARRAY), cases2.toArray(PsiElement.EMPTY_ARRAY)));
}
}
myMatchingVisitor.setResult(matchSwitchBlock(statement));
}
@Override
public void visitSwitchLabelStatement(PsiSwitchLabelStatement case1) {
final PsiSwitchLabelStatement other = (PsiSwitchLabelStatement)myMatchingVisitor.getElement();
final MatchContext context = myMatchingVisitor.getMatchContext();
final PsiExpression value1 = case1.getCaseValue();
final PsiExpression value2 = other.getCaseValue();
final boolean isTypedVar = context.getPattern().isTypedVar(value1);
context.pushResult();
try {
if (myMatchingVisitor.setResult(isTypedVar ||
case1.isDefaultCase() == other.isDefaultCase() && myMatchingVisitor.match(value1, value2))) {
final List<PsiStatement> statements = collectCaseStatements(case1);
if (!statements.isEmpty()) {
myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(statements.toArray(PsiElement.EMPTY_ARRAY),
collectCaseStatements(other).toArray(PsiElement.EMPTY_ARRAY)));
}
}
} finally {
myMatchingVisitor.scopeMatch(value1, isTypedVar, (value2 == null) ? other : value2);
}
public void visitSwitchExpression(PsiSwitchExpression expression) {
myMatchingVisitor.setResult(matchSwitchBlock(expression));
}
private static List<PsiStatement> collectCaseStatements(PsiSwitchLabelStatement switchLabelStatement) {
final List<PsiStatement> result = new SmartList<>();
PsiStatement sibling = PsiTreeUtil.getNextSiblingOfType(switchLabelStatement, PsiStatement.class);
while (sibling != null && !(sibling instanceof PsiSwitchLabelStatement)) {
result.add(sibling);
sibling = PsiTreeUtil.getNextSiblingOfType(sibling, PsiStatement.class);
private boolean matchSwitchBlock(PsiSwitchBlock switchBlock) {
final PsiSwitchBlock other = (PsiSwitchBlock)myMatchingVisitor.getElement();
if (!myMatchingVisitor.match(switchBlock.getExpression(), other.getExpression())) {
return false;
}
return result;
final PsiCodeBlock body = switchBlock.getBody();
final PsiSwitchLabelStatementBase[] cases1 = PsiTreeUtil.getChildrenOfType(body, PsiSwitchLabelStatementBase.class);
if (cases1 != null) {
final PsiSwitchLabelStatementBase[] cases2 = PsiTreeUtil.getChildrenOfType(other.getBody(), PsiSwitchLabelStatementBase.class);
return myMatchingVisitor.matchSequentially(cases1, cases2 != null ? cases2 : PsiElement.EMPTY_ARRAY);
}
final List<PsiElement> statements1 = PsiTreeUtil.getChildrenOfAnyType(body, PsiStatement.class, PsiComment.class);
if (!statements1.isEmpty()) {
final List<PsiElement> statements2 = PsiTreeUtil.getChildrenOfAnyType(other.getBody(), PsiStatement.class, PsiComment.class);
return myMatchingVisitor.matchSequentially(statements1.toArray(PsiElement.EMPTY_ARRAY), statements2.toArray(PsiElement.EMPTY_ARRAY));
}
return true;
}
@Override
public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) {
myMatchingVisitor.setResult(matchLabelStatement(statement, (PsiSwitchLabelStatementBase)myMatchingVisitor.getElement()));
}
@Override
public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) {
myMatchingVisitor.setResult(matchLabelStatement(statement, (PsiSwitchLabelStatementBase)myMatchingVisitor.getElement()));
}
private boolean matchLabelStatement(PsiSwitchLabelStatementBase statement1, PsiSwitchLabelStatementBase statement2) {
final PsiExpressionList values1 = statement1.getCaseValues();
final PsiExpressionList values2 = statement2.getCaseValues();
if (statement1.isDefaultCase() && !statement2.isDefaultCase()) {
return false;
}
if (values1 == null) {
return true;
}
final PsiExpression[] expressions = (values2 == null) ? PsiExpression.EMPTY_ARRAY : values2.getExpressions();
if (!myMatchingVisitor.matchInAnyOrder(values1.getExpressions(), expressions)) {
return false;
}
final PsiElement[] body = getBody(statement1);
return body.length == 0 || myMatchingVisitor.matchSequentially(body, getBody(statement2));
}
private static PsiElement[] getBody(PsiSwitchLabelStatementBase switchLabelStatement) {
final List<PsiElement> result = new SmartList<>();
if (switchLabelStatement instanceof PsiSwitchLabeledRuleStatement) {
final PsiStatement body = ((PsiSwitchLabeledRuleStatement)switchLabelStatement).getBody();
if (body instanceof PsiBlockStatement) {
result.addAll(PsiTreeUtil.getChildrenOfAnyType(((PsiBlockStatement)body).getCodeBlock(), PsiStatement.class, PsiComment.class));
}
else {
result.add(body);
}
}
else {
PsiElement sibling = PsiTreeUtil.getNextSiblingOfType(switchLabelStatement, PsiStatement.class);
while (sibling != null && !(sibling instanceof PsiSwitchLabelStatement)) {
if (sibling instanceof PsiStatement || sibling instanceof PsiComment) {
result.add(sibling);
}
sibling = sibling.getNextSibling();
}
}
return result.toArray(PsiElement.EMPTY_ARRAY);
}
@Override
public void visitYieldStatement(PsiYieldStatement statement) {
final PsiYieldStatement other = (PsiYieldStatement)myMatchingVisitor.getElement();
myMatchingVisitor.setResult(myMatchingVisitor.match(statement.getExpression(), other.getExpression()));
}
@Override
@@ -5,7 +5,6 @@ import com.intellij.dupLocator.iterators.NodeIterator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.JavaDummyHolder;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
@@ -290,14 +289,6 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
// typed var for expression (but not top level)
final MatchingHandler handler = myCompilingVisitor.getContext().getPattern().getHandler(reference);
GlobalCompilingVisitor.setFilter(handler, ExpressionFilter.getInstance());
if (handler instanceof SubstitutionHandler) {
final PsiSwitchLabelStatementBase label = PsiImplUtil.getSwitchLabel(reference);
if (label != null) {
final SubstitutionHandler handler1 = (SubstitutionHandler)handler;
pattern.setHandler(label, new SubstitutionHandler("__case_" + label.getTextOffset(), false,
handler1.getMinOccurs(), handler1.getMaxOccurs(), true));
}
}
typedVarProcessed = true;
}
@@ -339,9 +330,27 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
}
@Override
public void visitBlockStatement(PsiBlockStatement psiBlockStatement) {
super.visitBlockStatement(psiBlockStatement);
myCompilingVisitor.setFilterSimple(psiBlockStatement, BlockFilter.getInstance());
public void visitBlockStatement(PsiBlockStatement statement) {
super.visitBlockStatement(statement);
myCompilingVisitor.setFilterSimple(statement, BlockFilter.getInstance());
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);
myCompilingVisitor.setFilterSimple(statement, e -> e instanceof PsiSwitchBlock);
}
@Override
public void visitSwitchLabelStatement(PsiSwitchLabelStatement statement) {
super.visitSwitchLabelStatement(statement);
myCompilingVisitor.setFilterSimple(statement, e -> e instanceof PsiSwitchLabelStatementBase);
}
@Override
public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) {
super.visitSwitchLabeledRuleStatement(statement);
myCompilingVisitor.setFilterSimple(statement, e -> e instanceof PsiSwitchLabelStatementBase);
}
@Override
@@ -325,11 +325,13 @@ public class Matcher {
final CollectingMatchResultSink sink = new CollectingMatchResultSink();
try {
final PsiElement[] elements =
MatcherImplUtil.createSourceTreeFromText(source, fileContext ? PatternTreeContext.File : PatternTreeContext.Block,
sourceFileType, project, physicalSourceFile);
if (options.getScope() == null) {
final PsiElement[] elements =
MatcherImplUtil.createSourceTreeFromText(source, fileContext ? PatternTreeContext.File : PatternTreeContext.Block,
sourceFileType, project, physicalSourceFile);
options.setScope(new LocalSearchScope(elements));
options.setScope(new LocalSearchScope(elements));
}
testFindMatches(sink, options);
} finally {
options.setScope(null);
@@ -118,7 +118,7 @@ predefined.configuration.comments=comments
predefined.configuration.fields_variables.with.given.name.pattern.updated=fields/variables with given name pattern updated
predefined.configuration.trys=try's
predefined.configuration.try.without.resources=try statements without resources and catch blocks
predefined.configuration.switch.with.branches=switch statements with few branches
predefined.configuration.switch.with.branches=switch statements \\& expressions with few branches
predefined.configuration.labeled.break=labeled break statements
predefined.configuration.block.dcls=block dcls
predefined.configuration.methods.of.the.class=constructors \\& methods
@@ -241,6 +241,25 @@ public class JavaPredefinedConfigurationsTest extends PredefinedConfigurationsTe
" int z(int i) {" +
" return i;" +
" }");
doTest(configurationMap.remove(SSRBundle.message("predefined.configuration.switches")),
"class X {{" +
" int i = switch (1) {" +
" default -> {}" +
" }" +
" switch (2) {" +
" case 1,2:" +
" break;" +
" default:" +
" }" +
"}}",
"switch (1) {" +
" default -> {}" +
" }",
"switch (2) {" +
" case 1,2:" +
" break;" +
" default:" +
" }");
//assertTrue((templates.length - configurationMap.size()) + " of " + templates.length +
// " existing templates tested. Untested templates: " + configurationMap.keySet(), configurationMap.isEmpty());
}
@@ -3,7 +3,11 @@ package com.intellij.structuralsearch;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.testFramework.PlatformTestUtil;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
@@ -17,6 +21,13 @@ import java.util.List;
*/
@SuppressWarnings("ALL")
public class StructuralSearchTest extends StructuralSearchTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
setLanguageLevel(LanguageLevel.JDK_14);
}
@Override
protected int findMatchesCount(@Language("JAVA") String in, String pattern, LanguageFileType fileType) {
return super.findMatchesCount(in, pattern, fileType);
@@ -2608,7 +2619,7 @@ public class StructuralSearchTest extends StructuralSearchTestCase {
assertEquals("MINIMUM ZERO not applicable for b", checkApplicableConstraints(options, getProject()));
options.fillSearchCriteria("case '_a* :");
assertEquals("MINIMUM ZERO not applicable for a", checkApplicableConstraints(options, getProject()));
assertEquals(null, checkApplicableConstraints(options, getProject()));
options.fillSearchCriteria("int '_a:* ;");
assertEquals("TEXT HIERARCHY not applicable for a", checkApplicableConstraints(options, getProject()));
@@ -3268,8 +3279,8 @@ public class StructuralSearchTest extends StructuralSearchTestCase {
" '_st*;" +
"}"));
assertEquals("should find switch with 2 cases", 2, findMatchesCount(in, "switch ('_a) {" +
" case '_c{2,2} :" +
" '_st*;" +
" case '_c1 :" +
" case '_c2? :" +
"}"));
assertEquals("should find swith with one case and default", 2, findMatchesCount(in, "switch ('_a) {" +
" case '_c :" +
@@ -3278,7 +3289,51 @@ public class StructuralSearchTest extends StructuralSearchTestCase {
" '_st2*;" +
" }"));
assertEquals("should find defaults", 3, findMatchesCount(in, "default:"));
assertEquals("should find cases", 8, findMatchesCount(in, "case '_a :"));
assertEquals("should find cases", 5, findMatchesCount(in, "case '_a :"));
assertEquals("should find cases & defaults", 8, findMatchesCount(in, "case '_a? :"));
assertEquals("should match switch containing 2 statements", 3, findMatchesCount(in, "switch ('_x) {" +
" '_st{2,2};" +
"}"));
}
public void testFindSwitchExpressions() {
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(getProject());
final String in = "void dummy() {" +
" int j = switch (i) {\n" +
" case 10 -> {\n" +
" System.out.println(10);\n" +
" }\n" +
" default -> {}\n" +
" }\n" +
" int k = switch (i) {\n" +
" case 10 -> {\n" +
" yield 1;\n" +
" }\n" +
" default -> 0;\n" +
" };" +
" int l = switch (i) {" +
" case 1,2,3: " +
" break;" +
" case 5:" +
" break;" +
" }\n" +
"}";
// hack to generate code of the right language level
// will no longer be necessary when long LanguageLevel.HIGHEST == JDK_14
// (probably after JDK14 release in March 2020)
final PsiMethod method = factory.createMethodFromText(in, null, LanguageLevel.JDK_14);
System.out.println(PsiUtil.getLanguageLevel(method));
System.out.println(DebugUtil.psiToString(method, false));
options.setScope(new LocalSearchScope(method));
assertEquals("find expressions & statements", 2, findMatchesCount(null, "switch (i) {" +
" case 10 -> {" +
" '_st;" +
" }" +
" default -> $X$;" +
"}"));
//options.setScope(new LocalSearchScope(method));
//assertEquals("find yield statement", 1, findMatchesCount(null, "yield '_x;"));
}
public void testRepeatedVars() {