From 143337cad96c35a3eec69896ad5ab0b02e0d7ed7 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Tue, 27 Oct 2020 15:03:49 +0100 Subject: [PATCH] RegExp: new "Unnecessary non-capturing group" inspection (IDEA-228664) GitOrigin-RevId: d6288b1241bf4253030c6f91ecb73f17a6ea4710 --- .../resources/META-INF/RegExpPlugin.xml | 3 + .../RegExpUnnecessaryNonCapturingGroup.html | 11 +++ .../messages/RegExpBundle.properties | 3 + ...nnecessaryNonCapturingGroupInspection.java | 95 +++++++++++++++++++ ...essaryNonCapturingGroupInspectionTest.java | 48 ++++++++++ 5 files changed, 160 insertions(+) create mode 100644 RegExpSupport/resources/inspectionDescriptions/RegExpUnnecessaryNonCapturingGroup.html create mode 100644 RegExpSupport/src/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspection.java create mode 100644 RegExpSupport/test/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspectionTest.java diff --git a/RegExpSupport/resources/META-INF/RegExpPlugin.xml b/RegExpSupport/resources/META-INF/RegExpPlugin.xml index ae910d4a9c0b..6dd0016171bf 100644 --- a/RegExpSupport/resources/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/resources/META-INF/RegExpPlugin.xml @@ -74,6 +74,9 @@ + diff --git a/RegExpSupport/resources/inspectionDescriptions/RegExpUnnecessaryNonCapturingGroup.html b/RegExpSupport/resources/inspectionDescriptions/RegExpUnnecessaryNonCapturingGroup.html new file mode 100644 index 000000000000..627aaa4cc82c --- /dev/null +++ b/RegExpSupport/resources/inspectionDescriptions/RegExpUnnecessaryNonCapturingGroup.html @@ -0,0 +1,11 @@ + + +Reports unnecessary non-capturing groups. +For example
+Everybody be cool, (?:this) is a robbery!
+is equivalent too
+Everybody be cool, this is a robbery!. + +

New in 2021.1 + + \ No newline at end of file diff --git a/RegExpSupport/resources/messages/RegExpBundle.properties b/RegExpSupport/resources/messages/RegExpBundle.properties index a7f6edf0cfe5..87ebb0aa49db 100644 --- a/RegExpSupport/resources/messages/RegExpBundle.properties +++ b/RegExpSupport/resources/messages/RegExpBundle.properties @@ -73,11 +73,13 @@ 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.name.unnecessary.non.capturing.group=Unnecessary non-capturing group inspection.quick.fix.remove.duplicate.0.from.character.class=Remove duplicate ''{0}'' from character class inspection.quick.fix.remove.duplicate.branch=Remove duplicate branch inspection.quick.fix.remove.duplicate.element.from.character.class=Remove duplicate element from character class 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.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 @@ -95,6 +97,7 @@ inspection.warning.potential.exponential.backtracking=Potential exponential back 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 +inspection.warning.unnecessary.non.capturing.group=Unnecessary non-capturing group {0} intention.name.check.regexp=Check RegExp intention.name.simplify.quantifier=Simplify quantifier label.regexp=&RegExp: diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspection.java new file mode 100644 index 000000000000..dbbb1b79385c --- /dev/null +++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspection.java @@ -0,0 +1,95 @@ +// 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.codeInspection.util.IntentionFamilyName; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import org.intellij.lang.regexp.RegExpBundle; +import org.intellij.lang.regexp.psi.RegExpBranch; +import org.intellij.lang.regexp.psi.RegExpElementVisitor; +import org.intellij.lang.regexp.psi.RegExpGroup; +import org.intellij.lang.regexp.psi.RegExpPattern; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class UnnecessaryNonCapturingGroupInspection extends LocalInspectionTool { + + @Override + public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new UnnecessaryNonCapturingGroupVisitor(holder); + } + + private static class UnnecessaryNonCapturingGroupVisitor extends RegExpElementVisitor { + + private final ProblemsHolder myHolder; + + private UnnecessaryNonCapturingGroupVisitor(ProblemsHolder holder) { + myHolder = holder; + } + + @Override + public void visitRegExpGroup(RegExpGroup group) { + super.visitRegExpGroup(group); + if (group.getType() != RegExpGroup.Type.NON_CAPTURING) { + return; + } + final PsiElement parent = group.getParent(); + if (parent instanceof RegExpBranch) { + if (hasOneBranch(group.getPattern())) { + registerProblem(group); + } + else { + final PsiElement grandParent = parent.getParent(); + if (grandParent instanceof RegExpPattern && hasSingleAtom((RegExpPattern)grandParent)) { + registerProblem(group); + } + } + } + else if (hasSingleAtom(group.getPattern())) { + registerProblem(group); + } + } + + void registerProblem(RegExpGroup group) { + myHolder.registerProblem(group.getFirstChild(), + RegExpBundle.message("inspection.warning.unnecessary.non.capturing.group", group.getText()), + new UnnecessaryNonCapturingGroupFix()); + } + } + + private static boolean hasOneBranch(RegExpPattern pattern) { + return pattern != null && pattern.getBranches().length == 1; + } + + private static boolean hasSingleAtom(RegExpPattern pattern) { + if (pattern == null) { + return false; + } + final RegExpBranch[] branches = pattern.getBranches(); + return branches.length == 1 && branches[0].getAtoms().length == 1; + } + + private static class UnnecessaryNonCapturingGroupFix implements LocalQuickFix { + @Override + public @IntentionFamilyName @NotNull String getFamilyName() { + return RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement().getParent(); + if (!(element instanceof RegExpGroup)) { + return; + } + final RegExpGroup group = (RegExpGroup)element; + RegExpReplacementUtil.replaceInContext(group, group.getPattern().getUnescapedText()); + } + } +} diff --git a/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspectionTest.java b/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspectionTest.java new file mode 100644 index 000000000000..d52594d50bea --- /dev/null +++ b/RegExpSupport/test/org/intellij/lang/regexp/inspection/UnnecessaryNonCapturingGroupInspectionTest.java @@ -0,0 +1,48 @@ +// 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 UnnecessaryNonCapturingGroupInspectionTest extends RegExpInspectionTestCase { + + public void testSimple() { + quickfixTest("abc (?:def) ghi", + "abc def ghi", + RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group")); + } + + public void testNoWarnOnRegularGroup() { + highlightTest("abc (def) ghi"); + } + + public void testNoWarnOnAlternation() { + highlightTest("aa(?:bb|bbb)cccc"); + } + + public void testTopLevelAlternation() { + quickfixTest("(?:xx|xy)", "xx|xy", + RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group")); + } + + public void testSingleAtom() { + quickfixTest("aaa(?:b)+aaa", + "aaab+aaa", + RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group")); + } + + public void testCorrectEscaping() { + quickfixTest("(?:[\\w-]+:)[\\w-]+", + "[\\w-]+:[\\w-]+", + RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group")); + } + + @Override + protected @NotNull LocalInspectionTool getInspection() { + return new UnnecessaryNonCapturingGroupInspection(); + } +} \ No newline at end of file