RegExp: new "Unnecessary non-capturing group" inspection (IDEA-228664)

GitOrigin-RevId: d6288b1241bf4253030c6f91ecb73f17a6ea4710
This commit is contained in:
Bas Leijdekkers
2020-10-27 15:03:49 +01:00
committed by intellij-monorepo-bot
parent 75339de4c1
commit 143337cad9
5 changed files with 160 additions and 0 deletions

View File

@@ -74,6 +74,9 @@
<localInspection language="RegExp" shortName="RegExpRedundantNestedCharacterClass" enabledByDefault="true" level="WARNING"
bundle="messages.RegExpBundle" groupKey="inspection.group.name.regexp" key="inspection.name.redundant.nested.character.class"
implementationClass="org.intellij.lang.regexp.inspection.RedundantNestedCharacterClassInspection"/>
<localInspection language="RegExp" shortName="RegExpUnnecessaryNonCapturingGroup" enabledByDefault="true" level="WARNING"
bundle="messages.RegExpBundle" groupKey="inspection.group.name.regexp" key="inspection.name.unnecessary.non.capturing.group"
implementationClass="org.intellij.lang.regexp.inspection.UnnecessaryNonCapturingGroupInspection"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,11 @@
<html>
<body>
Reports unnecessary non-capturing groups.
For example<br>
<code>Everybody be cool, (?:this) is a robbery!</code><br>
is equivalent too<br>
<code>Everybody be cool, this is a robbery!</code>.
<!-- tooltip end -->
<p><small>New in 2021.1</small>
</body>
</html>

View File

@@ -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 <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
inspection.warning.unnecessary.non.capturing.group=Unnecessary non-capturing group <code>{0}</code>
intention.name.check.regexp=Check RegExp
intention.name.simplify.quantifier=Simplify quantifier
label.regexp=&RegExp:

View File

@@ -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());
}
}
}

View File

@@ -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 <warning descr=\"Unnecessary non-capturing group '(?:def)'\"><caret>(?:</warning>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("<warning descr=\"Unnecessary non-capturing group '(?:xx|xy)'\">(?:</warning>xx|xy)", "xx|xy",
RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group"));
}
public void testSingleAtom() {
quickfixTest("aaa<warning descr=\"Unnecessary non-capturing group '(?:b)'\">(?:<caret></warning>b)+aaa",
"aaab+aaa",
RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group"));
}
public void testCorrectEscaping() {
quickfixTest("<warning descr=\"Unnecessary non-capturing group '(?:[\\w-]+:)'\"><caret>(?:</warning>[\\w-]+:)[\\w-]+",
"[\\w-]+:[\\w-]+",
RegExpBundle.message("inspection.quick.fix.remove.unnecessary.non.capturing.group"));
}
@Override
protected @NotNull LocalInspectionTool getInspection() {
return new UnnecessaryNonCapturingGroupInspection();
}
}