RegExp: new "Anonymous capturing group or numeric back reference" inspection

This commit is contained in:
Bas Leijdekkers
2017-05-04 15:31:32 +02:00
parent 16ac96d3a0
commit 7ddc0910f5
9 changed files with 154 additions and 5 deletions
@@ -45,5 +45,8 @@
<localInspection groupName="RegExp" language="RegExp" shortName="EmptyAlternationBranch" displayName="Empty branch in alternation"
enabledByDefault="true" level="WARNING"
implementationClass="org.intellij.lang.regexp.inspection.EmptyAlternationBranchInspection"/>
<localInspection groupName="RegExp" language="RegExp" shortName="AnonymousGroup"
displayName="Anonymous capturing group or numeric back reference" enabledByDefault="false" level="WARNING"
implementationClass="org.intellij.lang.regexp.inspection.AnonymousGroupInspection"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,11 @@
<html>
<body>
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. <b>(?:xxx)</b> instead of <b>(xxx)</b>.
<!-- tooltip end -->
<p><small>New in 2017.2</small>
</body>
</html>
@@ -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<RegExpGroup.Type> 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<RegExpGroup.Type> 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
}
}
@@ -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<RegExpLanguageHost
}
}
public Collection<RegExpGroup.Type> 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);
@@ -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<RegExpGroup.Type> types = RegExpLanguageHosts.getInstance().getSupportedNamedGroupTypes(group);
if (types.isEmpty()) {
return;
}
myHolder.registerProblem(group.getFirstChild(), "Anonymous capturing group");
}
@Override
public void visitRegExpBackref(RegExpBackref backref) {
final Collection<RegExpGroup.Type> types = RegExpLanguageHosts.getInstance().getSupportedNamedGroupTypes(backref);
if (types.isEmpty()) {
return;
}
myHolder.registerProblem(backref, "Numeric back reference");
}
}
}
@@ -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<RegExpGroup.Type> 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<RegExpGroup.Type> 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++) {
@@ -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("<warning descr=\"Anonymous capturing group\">(</warning>moo)<warning descr=\"Numeric back reference\">\\1</warning>");
}
public void testSingleRepetition() {
doTest("a<weak_warning descr=\"Single repetition\">{1}</weak_warning>");
}
@@ -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<RegExpGroup.Type> 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);
@@ -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<RegExpGroup.Type> getSupportedNamedGroupTypes(RegExpElement context) {
return EnumSet.of(RegExpGroup.Type.PYTHON_NAMED_GROUP);
}
@Override
public boolean supportsExtendedHexCharacter(RegExpChar regExpChar) {
return false;