From 7ddc0910f51df717ab3ec1f111ec6db3f8e85b2e Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Thu, 4 May 2017 14:57:23 +0200 Subject: [PATCH] RegExp: new "Anonymous capturing group or numeric back reference" inspection --- RegExpSupport/src/META-INF/RegExpPlugin.xml | 3 + .../AnonymousGroup.html | 11 +++ .../lang/regexp/RegExpLanguageHost.java | 11 ++- .../lang/regexp/RegExpLanguageHosts.java | 11 +++ .../inspection/AnonymousGroupInspection.java | 77 +++++++++++++++++++ .../com/intellij/psi/impl/JavaRegExpHost.java | 11 +++ .../codeInsight/RegExpHighlightingTest.java | 6 ++ .../groovy/regexp/GroovyRegExpHost.java | 18 +++++ .../impl/PyStringLiteralExpressionImpl.java | 11 ++- 9 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 RegExpSupport/src/inspectionDescriptions/AnonymousGroup.html create mode 100644 RegExpSupport/src/org/intellij/lang/regexp/inspection/AnonymousGroupInspection.java diff --git a/RegExpSupport/src/META-INF/RegExpPlugin.xml b/RegExpSupport/src/META-INF/RegExpPlugin.xml index f34ef8958cb1..ac3cfebb9be0 100644 --- a/RegExpSupport/src/META-INF/RegExpPlugin.xml +++ b/RegExpSupport/src/META-INF/RegExpPlugin.xml @@ -45,5 +45,8 @@ + diff --git a/RegExpSupport/src/inspectionDescriptions/AnonymousGroup.html b/RegExpSupport/src/inspectionDescriptions/AnonymousGroup.html new file mode 100644 index 000000000000..62a4c3ef170a --- /dev/null +++ b/RegExpSupport/src/inspectionDescriptions/AnonymousGroup.html @@ -0,0 +1,11 @@ + + +Reports anonymous capturing groups and numeric back references in a RegExp. +These are only reported when the RegExp dialect supports named group and named group references. +Named groups and named back references improve code readability and are recommended to use instead. +When a capture is not needed, matching can be more performant and use less memory by using a non-capturing group, +i.e. (?:xxx) instead of (xxx). + +

New in 2017.2 + + \ No newline at end of file diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHost.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHost.java index 61f3d12ac341..f21add85d2ee 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHost.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHost.java @@ -20,16 +20,25 @@ import org.intellij.lang.regexp.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.EnumSet; + /** * @author yole */ public interface RegExpLanguageHost { + + EnumSet EMPTY_NAMED_GROUP_TYPES = EnumSet.noneOf(RegExpGroup.Type.class); + boolean characterNeedsEscaping(char c); boolean supportsPerl5EmbeddedComments(); boolean supportsPossessiveQuantifiers(); boolean supportsPythonConditionalRefs(); boolean supportsNamedGroupSyntax(RegExpGroup group); boolean supportsNamedGroupRefSyntax(RegExpNamedGroupRef ref); + @NotNull + default EnumSet getSupportedNamedGroupTypes(RegExpElement context) { + return EMPTY_NAMED_GROUP_TYPES; + } boolean supportsExtendedHexCharacter(RegExpChar regExpChar); default boolean isValidGroupName(String name, @NotNull PsiElement context) { @@ -116,7 +125,7 @@ public interface RegExpLanguageHost { /** Finite repetition inside lookbehind with different minimum, maximum values allowed */ FINITE_REPETITION, - /** Full regex syntax inside lookbehind ,i.e. star (*) and plus (*) repetition and backreferences, allowed. */ + /** Full regex syntax inside lookbehind, i.e. star (*) and plus (*) repetition and backreferences, allowed. */ FULL } } diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHosts.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHosts.java index e9d43a4f3bcd..8f1977230d59 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHosts.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLanguageHosts.java @@ -24,6 +24,9 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; +import java.util.Collections; + /** * @author yole */ @@ -106,6 +109,14 @@ public final class RegExpLanguageHosts extends ClassExtension getSupportedNamedGroupTypes(RegExpElement context) { + final RegExpLanguageHost host = findRegExpHost(context); + if (host == null) { + return Collections.emptySet(); + } + return host.getSupportedNamedGroupTypes(context); + } + public boolean isValidGroupName(String name, @Nullable final PsiElement context) { final RegExpLanguageHost host = findRegExpHost(context); return host != null && host.isValidGroupName(name, context); diff --git a/RegExpSupport/src/org/intellij/lang/regexp/inspection/AnonymousGroupInspection.java b/RegExpSupport/src/org/intellij/lang/regexp/inspection/AnonymousGroupInspection.java new file mode 100644 index 000000000000..e2749042960b --- /dev/null +++ b/RegExpSupport/src/org/intellij/lang/regexp/inspection/AnonymousGroupInspection.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.intellij.lang.regexp.inspection; + +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.PsiElementVisitor; +import org.intellij.lang.regexp.RegExpLanguageHosts; +import org.intellij.lang.regexp.psi.RegExpBackref; +import org.intellij.lang.regexp.psi.RegExpElementVisitor; +import org.intellij.lang.regexp.psi.RegExpGroup; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * @author Bas Leijdekkers + */ +public class AnonymousGroupInspection extends LocalInspectionTool { + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Anonymous capturing group or numeric back reference"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new AnonymousGroupVisitor(holder); + } + + private static class AnonymousGroupVisitor extends RegExpElementVisitor { + + private final ProblemsHolder myHolder; + + public AnonymousGroupVisitor(ProblemsHolder holder) { + myHolder = holder; + } + + @Override + public void visitRegExpGroup(RegExpGroup group) { + if (group.getType() != RegExpGroup.Type.CAPTURING_GROUP) { + return; + } + final Collection types = RegExpLanguageHosts.getInstance().getSupportedNamedGroupTypes(group); + if (types.isEmpty()) { + return; + } + myHolder.registerProblem(group.getFirstChild(), "Anonymous capturing group"); + } + + @Override + public void visitRegExpBackref(RegExpBackref backref) { + final Collection types = RegExpLanguageHosts.getInstance().getSupportedNamedGroupTypes(backref); + if (types.isEmpty()) { + return; + } + myHolder.registerProblem(backref, "Numeric back reference"); + } + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/JavaRegExpHost.java b/java/java-impl/src/com/intellij/psi/impl/JavaRegExpHost.java index c14300e573a1..2e745a2669f5 100644 --- a/java/java-impl/src/com/intellij/psi/impl/JavaRegExpHost.java +++ b/java/java-impl/src/com/intellij/psi/impl/JavaRegExpHost.java @@ -31,6 +31,7 @@ import org.intellij.lang.regexp.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.EnumSet; import java.util.Locale; /** @@ -38,6 +39,7 @@ import java.util.Locale; */ public class JavaRegExpHost implements RegExpLanguageHost { + protected static final EnumSet SUPPORTED_NAMED_GROUP_TYPES = EnumSet.of(RegExpGroup.Type.NAMED_GROUP); private final DefaultRegExpPropertiesProvider myPropertiesProvider; private final String[][] myPropertyNames = { @@ -171,6 +173,15 @@ public class JavaRegExpHost implements RegExpLanguageHost { return ref.isNamedGroupRef() && hasAtLeastJdkVersion(ref, JavaSdkVersion.JDK_1_7); } + @NotNull + @Override + public EnumSet getSupportedNamedGroupTypes(RegExpElement context) { + if (!hasAtLeastJdkVersion(context, JavaSdkVersion.JDK_1_7)) { + return EMPTY_NAMED_GROUP_TYPES; + } + return SUPPORTED_NAMED_GROUP_TYPES; + } + @Override public boolean isValidGroupName(String name, @NotNull PsiElement context) { for (int i = 0, length = name.length(); i < length; i++) { diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java index 222877772b18..0d3f44eb8833 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java @@ -22,6 +22,7 @@ import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import org.intellij.lang.annotations.Language; +import org.intellij.lang.regexp.inspection.AnonymousGroupInspection; import org.jetbrains.annotations.NotNull; /** @@ -30,6 +31,11 @@ import org.jetbrains.annotations.NotNull; @SuppressWarnings("Annotator") public class RegExpHighlightingTest extends LightCodeInsightFixtureTestCase { + public void testAnonymousCapturingGroupInspection() { + myFixture.enableInspections(new AnonymousGroupInspection()); + doTest("(moo)\\1"); + } + public void testSingleRepetition() { doTest("a{1}"); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/regexp/GroovyRegExpHost.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/regexp/GroovyRegExpHost.java index 57e5071e7d9c..c1f1b98b78ac 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/regexp/GroovyRegExpHost.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/regexp/GroovyRegExpHost.java @@ -17,13 +17,21 @@ package org.jetbrains.plugins.groovy.regexp; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.JavaRegExpHost; +import org.intellij.lang.regexp.RegExpLanguageHost; import org.intellij.lang.regexp.psi.RegExpChar; +import org.intellij.lang.regexp.psi.RegExpElement; import org.intellij.lang.regexp.psi.RegExpGroup; import org.intellij.lang.regexp.psi.RegExpNamedGroupRef; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; + /** * @author Bas Leijdekkers */ @@ -47,6 +55,16 @@ public class GroovyRegExpHost extends JavaRegExpHost { return false; } + @NotNull + @Override + public EnumSet getSupportedNamedGroupTypes(RegExpElement context) { + final String version = getGroovyVersion(context); + if (version == null || version.compareTo(GroovyConfigUtils.GROOVY2_0) < 0) { + return EMPTY_NAMED_GROUP_TYPES; + } + return SUPPORTED_NAMED_GROUP_TYPES; + } + @Override public boolean supportsExtendedHexCharacter(RegExpChar regExpChar) { final String version = getGroovyVersion(regExpChar); diff --git a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java index f548ac0e93f9..e454bbcd9d63 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java @@ -34,10 +34,7 @@ import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; import org.intellij.lang.regexp.DefaultRegExpPropertiesProvider; import org.intellij.lang.regexp.RegExpLanguageHost; -import org.intellij.lang.regexp.psi.RegExpChar; -import org.intellij.lang.regexp.psi.RegExpGroup; -import org.intellij.lang.regexp.psi.RegExpNamedGroupRef; -import org.intellij.lang.regexp.psi.RegExpNumber; +import org.intellij.lang.regexp.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -458,6 +455,12 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt return ref.isPythonNamedGroupRef(); } + @NotNull + @Override + public EnumSet getSupportedNamedGroupTypes(RegExpElement context) { + return EnumSet.of(RegExpGroup.Type.PYTHON_NAMED_GROUP); + } + @Override public boolean supportsExtendedHexCharacter(RegExpChar regExpChar) { return false;