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 @@
\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.