mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
regexp: lookbehind error highlighting
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -95,4 +95,28 @@ public interface RegExpLanguageHost {
|
||||
default Number getQuantifierValue(@NotNull RegExpNumber number) {
|
||||
return Double.parseDouble(number.getText());
|
||||
}
|
||||
|
||||
default Lookbehind supportsLookbehind(@NotNull RegExpGroup lookbehindGroup) {
|
||||
return Lookbehind.FULL; // to not break existing implementations, although rarely actually supported.
|
||||
}
|
||||
|
||||
enum Lookbehind {
|
||||
/** Lookbehind not supported. */
|
||||
NOT_SUPPORTED,
|
||||
|
||||
/**
|
||||
* Alternation inside lookbehind (a|b|c) branches must have same length,
|
||||
* finite repetition with identical min, max values (a{3} or a{3,3}) allowed.
|
||||
*/
|
||||
FIXED_LENGTH_ALTERNATION,
|
||||
|
||||
/** Alternation (a|bc|def) branches inside look behind may have different length */
|
||||
VARIABLE_LENGTH_ALTERNATION,
|
||||
|
||||
/** 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -162,6 +162,14 @@ public final class RegExpLanguageHosts extends ClassExtension<RegExpLanguageHost
|
||||
return host != null && host.isValidNamedCharacter(namedCharacter);
|
||||
}
|
||||
|
||||
public RegExpLanguageHost.Lookbehind supportsLookbehind(RegExpGroup group) {
|
||||
final RegExpLanguageHost host = findRegExpHost(group);
|
||||
if (host == null) {
|
||||
return RegExpLanguageHost.Lookbehind.FULL;
|
||||
}
|
||||
return host.supportsLookbehind(group);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Number getQuantifierValue(@NotNull RegExpNumber valueElement) {
|
||||
final RegExpLanguageHost host = findRegExpHost(valueElement);
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.intellij.psi.StringEscapesTokenTypes;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.intellij.lang.regexp.RegExpLanguageHost;
|
||||
import org.intellij.lang.regexp.RegExpLanguageHosts;
|
||||
import org.intellij.lang.regexp.RegExpTT;
|
||||
import org.intellij.lang.regexp.psi.*;
|
||||
@@ -295,6 +296,16 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
|
||||
final ASTNode node = group.getNode().findChildByType(RegExpTT.NAME);
|
||||
if (node != null) myHolder.createErrorAnnotation(node, "Invalid group name");
|
||||
}
|
||||
final RegExpGroup.Type groupType = group.getType();
|
||||
if (groupType == RegExpGroup.Type.POSITIVE_LOOKBEHIND || groupType == RegExpGroup.Type.NEGATIVE_LOOKBEHIND) {
|
||||
final RegExpLanguageHost.Lookbehind support = myLanguageHosts.supportsLookbehind(group);
|
||||
if (support == RegExpLanguageHost.Lookbehind.NOT_SUPPORTED) {
|
||||
myHolder.createErrorAnnotation(group, "Look-behind groups not supported in this regex dialect");
|
||||
}
|
||||
else {
|
||||
group.accept(new LookbehindVisitor(support, myHolder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -426,4 +437,152 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class LookbehindVisitor extends RegExpRecursiveElementVisitor {
|
||||
|
||||
private final RegExpLanguageHost.Lookbehind mySupport;
|
||||
private final AnnotationHolder myHolder;
|
||||
private int myLength = 0;
|
||||
private boolean myStop = false;
|
||||
|
||||
LookbehindVisitor(RegExpLanguageHost.Lookbehind support, AnnotationHolder holder) {
|
||||
mySupport = support;
|
||||
myHolder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpElement(RegExpElement element) {
|
||||
if (myStop) {
|
||||
return;
|
||||
}
|
||||
super.visitRegExpElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpChar(RegExpChar ch) {
|
||||
super.visitRegExpChar(ch);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSimpleClass(RegExpSimpleClass simpleClass) {
|
||||
super.visitSimpleClass(simpleClass);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpClass(RegExpClass expClass) {
|
||||
super.visitRegExpClass(expClass);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpProperty(RegExpProperty property) {
|
||||
super.visitRegExpProperty(property);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpNamedCharacter(RegExpNamedCharacter namedCharacter) {
|
||||
super.visitRegExpNamedCharacter(namedCharacter);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpBackref(RegExpBackref backref) {
|
||||
super.visitRegExpBackref(backref);
|
||||
if (mySupport != RegExpLanguageHost.Lookbehind.FULL) {
|
||||
stopAndReportError(backref, "Group reference not allowed inside lookbehind");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpPattern(RegExpPattern pattern) {
|
||||
if (mySupport != RegExpLanguageHost.Lookbehind.FIXED_LENGTH_ALTERNATION) {
|
||||
super.visitRegExpPattern(pattern);
|
||||
return;
|
||||
}
|
||||
final int length = myLength;
|
||||
int branchLength = -1;
|
||||
final RegExpBranch[] branches = pattern.getBranches();
|
||||
for (RegExpBranch branch : branches) {
|
||||
myLength = 0;
|
||||
super.visitRegExpBranch(branch);
|
||||
if (branchLength == -1) {
|
||||
branchLength = myLength;
|
||||
} else if (branchLength != myLength) {
|
||||
stopAndReportError(pattern, "Alternation alternatives needs to have the same length inside lookbehind");
|
||||
return;
|
||||
}
|
||||
}
|
||||
myLength = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpQuantifier(RegExpQuantifier quantifier) {
|
||||
super.visitRegExpQuantifier(quantifier);
|
||||
if (mySupport == RegExpLanguageHost.Lookbehind.FULL) {
|
||||
return;
|
||||
}
|
||||
if (quantifier.isCounted()) {
|
||||
final RegExpNumber minElement = quantifier.getMin();
|
||||
final RegExpNumber maxElement = quantifier.getMax();
|
||||
if (minElement != null && maxElement != null) {
|
||||
try {
|
||||
final long min = Long.parseLong(minElement.getText());
|
||||
final long max = Long.parseLong(maxElement.getText());
|
||||
if (min == max) {
|
||||
myLength += min;
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException ignore) {}
|
||||
}
|
||||
if (mySupport != RegExpLanguageHost.Lookbehind.FINITE_REPETITION) {
|
||||
stopAndReportError(quantifier, "Unequal min and max in counted quantifier not allowed inside lookbehind");
|
||||
}
|
||||
}
|
||||
else {
|
||||
final ASTNode token = quantifier.getToken();
|
||||
assert token != null;
|
||||
final String tokenText = token.getText();
|
||||
if ("?".equals(tokenText) && mySupport == RegExpLanguageHost.Lookbehind.FINITE_REPETITION) {
|
||||
return;
|
||||
}
|
||||
stopAndReportError(quantifier, tokenText + " repetition not allowed inside lookbehind");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpBoundary(RegExpBoundary boundary) {
|
||||
super.visitRegExpBoundary(boundary);
|
||||
// is zero length
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpNamedGroupRef(RegExpNamedGroupRef groupRef) {
|
||||
super.visitRegExpNamedGroupRef(groupRef);
|
||||
if (mySupport != RegExpLanguageHost.Lookbehind.FULL) {
|
||||
stopAndReportError(groupRef, "Named group reference not allowed inside lookbehind");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpPyCondRef(RegExpPyCondRef condRef) {
|
||||
super.visitRegExpPyCondRef(condRef);
|
||||
if (mySupport != RegExpLanguageHost.Lookbehind.FULL) {
|
||||
stopAndReportError(condRef, "Conditional group reference not allowed inside lookbehind");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPosixBracketExpression(RegExpPosixBracketExpression posixBracketExpression) {
|
||||
super.visitPosixBracketExpression(posixBracketExpression);
|
||||
myLength++;
|
||||
}
|
||||
|
||||
public void stopAndReportError(RegExpElement element, String message) {
|
||||
myHolder.createErrorAnnotation(element, message);
|
||||
myStop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +328,11 @@ public class JavaRegExpHost implements RegExpLanguageHost {
|
||||
return UnicodeCharacterNames.getCodePoint(namedCharacter.getName()) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lookbehind supportsLookbehind(@NotNull RegExpGroup lookbehindGroup) {
|
||||
return Lookbehind.FINITE_REPETITION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getQuantifierValue(@NotNull RegExpNumber number) {
|
||||
try {
|
||||
|
||||
@@ -212,6 +212,14 @@ public class RegExpHighlightingTest extends LightCodeInsightFixtureTestCase {
|
||||
doTest("(?i)<error descr=\"Dangling metacharacter\">{5,6}</error>");
|
||||
}
|
||||
|
||||
public void testLookbehind() {
|
||||
doTest("(?<!(aa)<error descr=\"* repetition not allowed inside lookbehind\">*</error>)");
|
||||
doTest("(?<!(aa)<error descr=\"+ repetition not allowed inside lookbehind\">+</error>)");
|
||||
doTest("(?<!(aa)?)");
|
||||
doTest("(?<!(aa){2,6})");
|
||||
doTest("(one)(?<!<error descr=\"Group reference not allowed inside lookbehind\">\\1</error>)");
|
||||
}
|
||||
|
||||
private void doTest(@Language("RegExp") String code) {
|
||||
code = StringUtil.escapeBackSlashes(code);
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, "class X {{ java.util.regex.Pattern.compile(\"" + code + "\"); }}");
|
||||
|
||||
Reference in New Issue
Block a user