diff --git a/RegExpSupport/resources/META-INF/RegExpPlugin.xml b/RegExpSupport/resources/META-INF/RegExpPlugin.xml index 96f0d73ec80c..dc012b8e9110 100644 --- a/RegExpSupport/resources/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/resources/META-INF/RegExpPlugin.xml @@ -71,6 +71,9 @@ + diff --git a/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantNestedCharacterClass.html b/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantNestedCharacterClass.html new file mode 100644 index 000000000000..151c53d3c666 --- /dev/null +++ b/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantNestedCharacterClass.html @@ -0,0 +1,8 @@ + + +Reports unnecessary nested character classes. +For example [a-c[x-z]], which is equivalent too [a-cx-z]. + +

New in 2020.2 + + \ No newline at end of file diff --git a/RegExpSupport/resources/messages/RegExpBundle.properties b/RegExpSupport/resources/messages/RegExpBundle.properties index a213da895e9a..d0efe8fc5618 100644 --- a/RegExpSupport/resources/messages/RegExpBundle.properties +++ b/RegExpSupport/resources/messages/RegExpBundle.properties @@ -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 #ref in RegExp inspection.warning.potential.exponential.backtracking=Potential exponential backtracking inspection.warning.redundant.character.escape.0.in.regexp=Redundant character escape {0} 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! diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspection.java new file mode 100644 index 000000000000..5e751f51613c --- /dev/null +++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspection.java @@ -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(); + } + } + } + } +} diff --git a/RegExpSupport/test/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspectionTest.java b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspectionTest.java new file mode 100644 index 000000000000..b7161e5154ff --- /dev/null +++ b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RedundantNestedCharacterClassInspectionTest.java @@ -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[b]]", "[ab]", + RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents")); + } + + public void testIntersection() { + quickfixTest("[a-z&&[aeoiuy]]", "[a-z&&aeoiuy]", + RegExpBundle.message("inspection.quick.fix.replace.redundant.character.class.with.contents")); + } + + public void testNegation() { + quickfixTest("[^*=[^&]]", "[^*=&]", + 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(); + } +}