regex: new "Redundant nested character class" inspection (IDEA-236620)

GitOrigin-RevId: 49924c8b4fd920da3dc357d1d1655a9783d1a139
This commit is contained in:
Bas Leijdekkers
2020-04-03 18:44:34 +00:00
committed by intellij-monorepo-bot
parent cbd19c935f
commit d5f1358d26
5 changed files with 133 additions and 0 deletions
@@ -71,6 +71,9 @@
<localInspection groupName="RegExp" language="RegExp" shortName="RegExpDuplicateCharacterInClass" enabledByDefault="true" level="WARNING"
bundle="messages.RegExpBundle" key="inspection.name.duplicate.character.in.class"
implementationClass="org.intellij.lang.regexp.inspection.DuplicateCharacterInClassInspection"/>
<localInspection groupName="RegExp" language="RegExp" shortName="RegExpRedundantNestedCharacterClass" enabledByDefault="true" level="WARNING"
bundle="messages.RegExpBundle" key="inspection.name.redundant.nested.character.class"
implementationClass="org.intellij.lang.regexp.inspection.RedundantNestedCharacterClassInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<html>
<body>
Reports unnecessary nested character classes.
For example <code>[a-c[x-z]]</code>, which is equivalent too <code>[a-cx-z]</code>.
<!-- tooltip end -->
<p><small>New in 2020.2</small>
</body>
</html>
@@ -68,6 +68,7 @@ inspection.name.empty.branch.in.alternation=Empty branch in alternation
inspection.name.escaped.meta.character=Escaped meta character
inspection.name.octal.escape=Octal escape
inspection.name.redundant.character.escape=Redundant character escape
inspection.name.redundant.nested.character.class=Redundant nested character class
inspection.name.single.character.alternation=Single character alternation
inspection.quick.fix.remove.duplicate.0.from.character.class=Remove duplicate ''{0}'' from character class
inspection.quick.fix.remove.duplicate.branch=Remove duplicate branch
@@ -75,6 +76,7 @@ inspection.quick.fix.remove.duplicate.element.from.character.class=Remove duplic
inspection.quick.fix.remove.empty.branch=Remove empty branch
inspection.quick.fix.remove.redundant.escape=Remove redundant escape
inspection.quick.fix.replace.alternation.with.character.class=Replace alternation with character class
inspection.quick.fix.replace.redundant.character.class.with.contents=Replace redundant character class with contents
inspection.quick.fix.replace.with.character.inside.class=Replace with character inside class
inspection.quick.fix.replace.with.hexadecimal.escape=Replace with hexadecimal escape
inspection.quick.fix.replace.with.space.and.repeated.quantifier=Replace with space and repeated quantifier
@@ -87,6 +89,7 @@ inspection.warning.numeric.back.reference=Numeric back reference
inspection.warning.octal.escape.code.ref.code.in.regexp=Octal escape <code>#ref</code> in RegExp
inspection.warning.potential.exponential.backtracking=Potential exponential backtracking
inspection.warning.redundant.character.escape.0.in.regexp=Redundant character escape <code>{0}</code> in RegExp
inspection.warning.redundant.nested.character.class=Redundant nested character class
inspection.warning.single.character.alternation.in.regexp=Single character alternation in RegExp
intention.balloon.bad.pattern=Bad pattern
intention.balloon.matches=Matches!
@@ -0,0 +1,82 @@
// Copyright 2000-2020 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 org.intellij.lang.regexp.inspection;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import org.intellij.lang.regexp.RegExpBundle;
import org.intellij.lang.regexp.psi.RegExpClass;
import org.intellij.lang.regexp.psi.RegExpClassElement;
import org.intellij.lang.regexp.psi.RegExpElementVisitor;
import org.intellij.lang.regexp.psi.RegExpIntersection;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class RedundantNestedCharacterClassInspection extends LocalInspectionTool {
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new RedundantNestedCharacterClassVisitor(holder);
}
private static class RedundantNestedCharacterClassVisitor extends RegExpElementVisitor {
private final ProblemsHolder myHolder;
RedundantNestedCharacterClassVisitor(ProblemsHolder holder) {
myHolder = holder;
}
@Override
public void visitRegExpClass(RegExpClass regExpClass) {
super.visitRegExpClass(regExpClass);
final PsiElement parent = regExpClass.getParent();
if (parent instanceof RegExpClass) {
final RegExpClass parentClass = (RegExpClass)parent;
if (parentClass.isNegated() == regExpClass.isNegated()) {
myHolder.registerProblem(regExpClass.getFirstChild(), RegExpBundle.message("inspection.warning.redundant.nested.character.class"),
new RedundantNestedCharacterClassFix());
}
}
else if (parent instanceof RegExpIntersection) {
final PsiElement grandParent = parent.getParent();
if (grandParent instanceof RegExpClass) {
final RegExpClass parentClass = (RegExpClass)grandParent;
if (parentClass.isNegated() == regExpClass.isNegated()) {
myHolder.registerProblem(regExpClass.getFirstChild(), RegExpBundle.message("inspection.warning.redundant.nested.character.class"),
new RedundantNestedCharacterClassFix());
}
}
}
}
private static class RedundantNestedCharacterClassFix implements LocalQuickFix {
@Override
public @NlsContexts.ListItem @NotNull String getFamilyName() {
return RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement().getParent();
if (element instanceof RegExpClass) {
final RegExpClass regExpClass = (RegExpClass)element;
final RegExpClassElement[] elements = regExpClass.getElements();
final PsiElement parent = regExpClass.getParent();
for (RegExpClassElement classElement : elements) {
parent.addBefore(classElement, regExpClass);
}
regExpClass.delete();
}
}
}
}
}
@@ -0,0 +1,37 @@
// Copyright 2000-2020 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 org.intellij.lang.regexp.inspection;
import com.intellij.codeInspection.LocalInspectionTool;
import org.intellij.lang.regexp.RegExpBundle;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class RedundantNestedCharacterClassInspectionTest extends RegExpInspectionTestCase {
public void testConjunction() {
quickfixTest("[a<warning descr=\"Redundant nested character class\"><caret>[</warning>b]]", "[ab]",
RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents"));
}
public void testIntersection() {
quickfixTest("[a-z&&<warning descr=\"Redundant nested character class\"><caret>[</warning>aeoiuy]]", "[a-z&&aeoiuy]",
RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents"));
}
public void testNegation() {
quickfixTest("[^*=<warning descr=\"Redundant nested character class\"><caret>[</warning>^&]]", "[^*=&]",
RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents"));
}
public void testNoWarn() {
highlightTest("[a-z&&[^aeouiy]]" );
}
@Override
protected @NotNull LocalInspectionTool getInspection() {
return new RedundantNestedCharacterClassInspection();
}
}