From 677bd7e889590d370aaa5439b7b3d187c3c6cce2 Mon Sep 17 00:00:00 2001 From: Filippova Maria Date: Mon, 25 Apr 2022 23:25:50 +0300 Subject: [PATCH] IDEA-292945 New Inspection: \d is redundant with \w in the same class (\D with \W) GitOrigin-RevId: 7c2e57a4e3bf8a61e08bf326460576766f851a98 --- .../resources/META-INF/RegExpPlugin.xml | 3 + .../RegExpRedundantClassElement.html | 16 ++++ .../messages/RegExpBundle.properties | 4 + ...RegExpRedundantClassElementInspection.java | 83 +++++++++++++++++++ ...xpRedundantClassElementInspectionTest.java | 27 ++++++ 5 files changed, 133 insertions(+) create mode 100644 RegExpSupport/resources/inspectionDescriptions/RegExpRedundantClassElement.html create mode 100644 RegExpSupport/src/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspection.java create mode 100644 RegExpSupport/test/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspectionTest.java diff --git a/RegExpSupport/resources/META-INF/RegExpPlugin.xml b/RegExpSupport/resources/META-INF/RegExpPlugin.xml index c2a96352bdd1..f74c9fbf7246 100644 --- a/RegExpSupport/resources/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/resources/META-INF/RegExpPlugin.xml @@ -80,5 +80,8 @@ + diff --git a/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantClassElement.html b/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantClassElement.html new file mode 100644 index 000000000000..36bd5e9d02ed --- /dev/null +++ b/RegExpSupport/resources/inspectionDescriptions/RegExpRedundantClassElement.html @@ -0,0 +1,16 @@ + + +Reports redundant \d or [:digit:] that are used in one class with \w or [:word:] +(\D with \W) and can be removed. +

Example:

+

+  [\w\d]
+
+

After the quick-fix is applied:

+

+  [\w]
+
+ +

New in 2022.2 + + \ No newline at end of file diff --git a/RegExpSupport/resources/messages/RegExpBundle.properties b/RegExpSupport/resources/messages/RegExpBundle.properties index 97c151baecb2..24f407e6e638 100644 --- a/RegExpSupport/resources/messages/RegExpBundle.properties +++ b/RegExpSupport/resources/messages/RegExpBundle.properties @@ -78,6 +78,7 @@ inspection.name.simplifiable.expression=Regular expression can be simplified inspection.name.single.character.alternation=Single character alternation inspection.name.suspicious.backref=Suspicious back reference inspection.name.unnecessary.non.capturing.group=Unnecessary non-capturing group +inspection.name.redundant.digit.class.element=Redundant '\\d', '[:digit:]', or '\\D' class elements inspection.option.ignore.escaped.closing.brackets=Ignore escaped closing brackets '}' and ']' inspection.quick.fix.remove.duplicate.0.from.character.class=Remove duplicate ''{0}'' from character class inspection.quick.fix.remove.duplicate.branch=Remove duplicate branch @@ -85,6 +86,8 @@ 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.remove.unnecessary.non.capturing.group=Unwrap unnecessary non-capturing group +inspection.quick.fix.remove.redundant.0.class.element=Remove redundant ''{0}'' +inspection.quick.fix.remove.redundant.class.element=Remove redundant class element 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 @@ -105,6 +108,7 @@ inspection.warning.octal.escape.code.ref.code.in.regexp=Octal escape #ref< 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.redundant.class.element=Redundant ''{0}'' in RegExp inspection.warning.single.character.alternation.in.regexp=Single character alternation in RegExp inspection.warning.unnecessary.non.capturing.group=Unnecessary non-capturing group {0} intention.name.check.regexp=Check RegExp diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspection.java new file mode 100644 index 000000000000..fbc052cbb37c --- /dev/null +++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspection.java @@ -0,0 +1,83 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +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.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import org.intellij.lang.regexp.RegExpBundle; +import org.intellij.lang.regexp.psi.*; +import org.jetbrains.annotations.NotNull; + +import static org.intellij.lang.regexp.psi.RegExpSimpleClass.Kind.*; +import static org.intellij.lang.regexp.psi.RegExpSimpleClass.Kind.NON_WORD; + +public class RegExpRedundantClassElementInspection extends LocalInspectionTool { + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new RegExpElementVisitor() { + @Override + public void visitRegExpClass(RegExpClass regExpClass) { + RegExpClass regExp = ObjectUtils.tryCast(regExpClass, RegExpClass.class); + if (regExp == null) return; + RegExpClassElement[] classElements = regExp.getElements(); + boolean containsNonWordCharacterClass = + ContainerUtil.exists(classElements, RegExpRedundantClassElementInspection::isAnyNonWordCharacter); + boolean containsWordCharacterClass = + ContainerUtil.exists(classElements, RegExpRedundantClassElementInspection::isAnyWordCharacter); + for (RegExpClassElement element : classElements) { + if (containsWordCharacterClass && isAnyDigit(element) || containsNonWordCharacterClass && isAnyNonDigit(element)) { + String elementText = element.getText(); + holder.registerProblem(element, RegExpBundle.message("inspection.warning.redundant.class.element", elementText), new RemoveRedundantClassElement(elementText)); + } + } + } + }; + } + + private static boolean isAnyDigit(RegExpClassElement classElement) { + return classElement instanceof RegExpSimpleClass && ((RegExpSimpleClass)classElement).getKind().equals(DIGIT) || + classElement instanceof RegExpPosixBracketExpression && ((RegExpPosixBracketExpression)classElement).getClassName().equals("digit"); + } + + private static boolean isAnyNonDigit(RegExpClassElement classElement) { + return classElement instanceof RegExpSimpleClass && ((RegExpSimpleClass)classElement).getKind().equals(NON_DIGIT); + } + + private static boolean isAnyWordCharacter(RegExpClassElement classElement) { + return classElement instanceof RegExpSimpleClass && ((RegExpSimpleClass)classElement).getKind().equals(WORD) || + classElement instanceof RegExpPosixBracketExpression && ((RegExpPosixBracketExpression)classElement).getClassName().equals("word"); + } + + private static boolean isAnyNonWordCharacter(RegExpClassElement classElement) { + return classElement instanceof RegExpSimpleClass && ((RegExpSimpleClass)classElement).getKind().equals(NON_WORD); + } + + private static class RemoveRedundantClassElement implements LocalQuickFix { + private final String myClassElementText; + + private RemoveRedundantClassElement(String text) { myClassElementText = text; } + + @Override + public @NotNull String getFamilyName() { + return RegExpBundle.message("inspection.quick.fix.remove.redundant.class.element"); + } + + @Override + public @NotNull String getName() { + return RegExpBundle.message("inspection.quick.fix.remove.redundant.0.class.element", myClassElementText); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element == null) return; + element.delete(); + } + } +} diff --git a/RegExpSupport/test/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspectionTest.java b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspectionTest.java new file mode 100644 index 000000000000..640c2887a3b0 --- /dev/null +++ b/RegExpSupport/test/org/intellij/lang/regexp/inspection/RegExpRedundantClassElementInspectionTest.java @@ -0,0 +1,27 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.intellij.lang.regexp.inspection; + +import com.intellij.codeInspection.LocalInspectionTool; +import org.intellij.lang.regexp.RegExpBundle; +import org.jetbrains.annotations.NotNull; + +public class RegExpRedundantClassElementInspectionTest extends RegExpInspectionTestCase { + @Override + protected @NotNull LocalInspectionTool getInspection() { + return new RegExpRedundantClassElementInspection(); + } + + public void testAnyDigit() { + quickfixTest("[\\w.\\d]", + "[\\w.]", RegExpBundle.message("inspection.quick.fix.remove.redundant.0.class.element", "\\d")); + } + + public void testAnyNonDigit() { + quickfixTest("^[\\W.\\D]", + "^[\\W.]", RegExpBundle.message("inspection.quick.fix.remove.redundant.0.class.element", "\\D")); + } + + public void testNoHighlighting() { + highlightTest("\\w{2}[.,]?[\\d\\W]"); + } +}