WI-60779 Support PCRE "pseudo" conditions DEFINE and VERSION as conditions in conditional groups.

GitOrigin-RevId: bdf83d81e0235826d7980739025f90581bc4222c
This commit is contained in:
Filippova Maria
2022-06-24 13:57:29 +03:00
committed by intellij-monorepo-bot
parent c2abcf40d8
commit a58330a1c1
12 changed files with 579 additions and 423 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -117,6 +117,11 @@ public enum RegExpCapability {
* \g{[integer]} \g[unsigned integer]
*/
PCRE_BACK_REFERENCES,
/**
* Allow PCRE conditions DEFINE and VERSION[>]?=n.m in conditional groups
*/
PCRE_CONDITIONS,
;
static final EnumSet<RegExpCapability> DEFAULT_CAPABILITIES = EnumSet.of(NESTED_CHARACTER_CLASSES,
ALLOW_HORIZONTAL_WHITESPACE_CLASS,

View File

@@ -456,7 +456,12 @@ public class RegExpParser implements PsiParser, LightPsiParser {
}
else {
if (RegExpTT.GROUP_BEGIN == type) {
parseGroupReferenceCondition(builder, RegExpTT.GROUP_END);
if (builder.lookAhead(1) == RegExpTT.PCRE_CONDITION) {
parsePcreConditionalGroup(builder);
}
else {
parseGroupReferenceCondition(builder, RegExpTT.GROUP_END);
}
}
else if (RegExpTT.QUOTED_CONDITION_BEGIN == type) {
parseGroupReferenceCondition(builder, RegExpTT.QUOTED_CONDITION_END);
@@ -502,6 +507,14 @@ public class RegExpParser implements PsiParser, LightPsiParser {
checkMatches(builder, RegExpTT.GROUP_END, RegExpBundle.message("parse.error.unclosed.group"));
}
private void parsePcreConditionalGroup(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
builder.advanceLexer();
parseGroupEnd(builder);
marker.done(RegExpElementTypes.GROUP);
}
private static void parseNamedGroupRef(PsiBuilder builder, PsiBuilder.Marker marker, IElementType type) {
builder.advanceLexer();
checkMatches(builder, RegExpTT.NAME, RegExpBundle.message("parse.error.group.name.expected"));

View File

@@ -168,6 +168,9 @@ public interface RegExpTT {
/** \g'name' */
IElementType RUBY_QUOTED_NAMED_GROUP_CALL = new RegExpElementType("RUBY_QUOTED_NAMED_GROUP_CALL");
/** DEFINE|VERSION[>]=n.m */
IElementType PCRE_CONDITION = new RegExpElementType("PCRE_CONDITION");
TokenSet CHARACTERS = TokenSet.create(CHARACTER,
ESC_CTRL_CHARACTER,
ESC_CHARACTER,

View File

@@ -5,8 +5,10 @@ import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import org.intellij.lang.regexp.RegExpBundle;
import org.intellij.lang.regexp.psi.*;
import org.intellij.lang.regexp.psi.impl.RegExpGroupImpl;
import org.jetbrains.annotations.NotNull;
/**
@@ -53,7 +55,7 @@ public class SuspiciousBackrefInspection extends LocalInspectionTool {
return;
}
final RegExpBranch branch = PsiTreeUtil.getParentOfType(target, RegExpBranch.class);
if (!PsiTreeUtil.isAncestor(branch, groupRef, true)) {
if (!PsiTreeUtil.isAncestor(branch, groupRef, true) && !isPcreCondition(branch)) {
final String message =
RegExpBundle.message("inspection.warning.group.back.reference.are.in.different.branches", groupRef.getGroupName());
myHolder.registerProblem(groupRef, message);
@@ -63,5 +65,14 @@ public class SuspiciousBackrefInspection extends LocalInspectionTool {
myHolder.registerProblem(groupRef, message);
}
}
private static boolean isPcreCondition(RegExpBranch branch) {
if (branch == null) return false;
if (branch.getParent() instanceof RegExpConditional) {
RegExpGroup groupCondition = ObjectUtils.tryCast(((RegExpConditional)branch.getParent()).getCondition(), RegExpGroup.class);
return groupCondition != null && RegExpGroupImpl.isPcreConditionalGroup(groupCondition.getNode());
}
return false;
}
}
}

View File

@@ -98,6 +98,10 @@ public class RegExpGroupImpl extends RegExpElementImpl implements RegExpGroup {
throw new AssertionError();
}
public static boolean isPcreConditionalGroup(ASTNode node) {
return node != null && node.findChildByType(RegExpTT.PCRE_CONDITION) != null;
}
@Override
public String getGroupName() {
final ASTNode nameNode = getNode().findChildByType(RegExpTT.NAME);

View File

@@ -44,6 +44,7 @@ import static org.intellij.lang.regexp.RegExpCapability.*;
private boolean allowOneHexCharEscape;
private boolean allowMysqlBracketExpressions;
private boolean allowPcreBackReferences;
private boolean allowPcreConditions;
private int maxOctal = 0777;
private int minOctalDigits = 1;
private boolean whitespaceInClass;
@@ -65,6 +66,7 @@ import static org.intellij.lang.regexp.RegExpCapability.*;
this.allowTransformationEscapes = capabilities.contains(TRANSFORMATION_ESCAPES);
this.allowMysqlBracketExpressions = capabilities.contains(MYSQL_BRACKET_EXPRESSIONS);
this.allowPcreBackReferences = capabilities.contains(PCRE_BACK_REFERENCES);
this.allowPcreConditions = capabilities.contains(PCRE_CONDITIONS);
if (capabilities.contains(MAX_OCTAL_177)) {
maxOctal = 0177;
}
@@ -156,6 +158,8 @@ TRANSFORMATION= "l" | "L" | "U" | "E"
HEX_CHAR=[0-9a-fA-F]
PCRE_CONDITION=DEFINE|VERSION>?=\d*[.]?\d{0,2}
/* 999 back references should be enough for everybody */
BACK_REFERENCES_GROUP = [1-9][0-9]{0,2}
@@ -486,6 +490,7 @@ BACK_REFERENCES_GROUP = [1-9][0-9]{0,2}
}
<CONDITIONAL2> {
{PCRE_CONDITION} { return allowPcreConditions ? RegExpTT.PCRE_CONDITION : RegExpTT.NAME; }
{GROUP_NAME} { return RegExpTT.NAME; }
[:digit:]+ { return RegExpTT.NUMBER; }
"')" { yybegin(YYINITIAL); return RegExpTT.QUOTED_CONDITION_END; }

View File

@@ -32,6 +32,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.regexp.*;
import org.intellij.lang.regexp.psi.*;
import org.intellij.lang.regexp.psi.impl.RegExpGroupImpl;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -232,7 +233,7 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
public void visitRegExpGroup(RegExpGroup group) {
final RegExpPattern pattern = group.getPattern();
final RegExpBranch[] branches = pattern.getBranches();
if (isEmpty(branches) && group.getNode().getLastChildNode().getElementType() == RegExpTT.GROUP_END) {
if (!RegExpGroupImpl.isPcreConditionalGroup(group.getNode()) && isEmpty(branches) && group.getNode().getLastChildNode().getElementType() == RegExpTT.GROUP_END) {
// catches "()" as well as "(|)"
myHolder.newAnnotation(HighlightSeverity.WARNING, RegExpBundle.message("error.empty.group")).create();
}

View File

@@ -331,6 +331,21 @@ public class RegExpLexerTest extends LexerTestCase {
doTest("(a)\\g-105", null, lexer);
}
public void testPcreConditionDefine() {
final RegExpLexer lexer = new RegExpLexer(EnumSet.of(PCRE_CONDITIONS));
doTest("(?(DEFINE)(?<Name>\\w+))(?P>Name)", null, lexer);
}
public void testPcreConditionVersion() {
final RegExpLexer lexer = new RegExpLexer(EnumSet.of(PCRE_CONDITIONS));
doTest("(?(VERSION>=10.7)yes|no)", null, lexer);
}
public void testNoPcreCondition() {
final RegExpLexer lexer = new RegExpLexer(EnumSet.noneOf(RegExpCapability.class));
doTest("(?(DEFINE)(?<Name>\\w+))(?P>Name)", null, lexer);
}
public void testNoNestedCharacterClasses1() {
final RegExpLexer lexer = new RegExpLexer(EnumSet.noneOf(RegExpCapability.class));
doTest("[[\\]]", "CLASS_BEGIN ('[')\n" +

View File

@@ -0,0 +1,14 @@
CONDITIONAL ('(?')
GROUP_BEGIN ('(')
NAME ('DEFINE')
GROUP_END (')')
RUBY_NAMED_GROUP ('(?<')
NAME ('Name')
GT ('>')
CHAR_CLASS ('\w')
PLUS ('+')
GROUP_END (')')
GROUP_END (')')
PCRE_RECURSIVE_NAMED_GROUP ('(?P>')
NAME ('Name')
GROUP_END (')')

View File

@@ -0,0 +1,14 @@
CONDITIONAL ('(?')
GROUP_BEGIN ('(')
PCRE_CONDITION ('DEFINE')
GROUP_END (')')
RUBY_NAMED_GROUP ('(?<')
NAME ('Name')
GT ('>')
CHAR_CLASS ('\w')
PLUS ('+')
GROUP_END (')')
GROUP_END (')')
PCRE_RECURSIVE_NAMED_GROUP ('(?P>')
NAME ('Name')
GROUP_END (')')

View File

@@ -0,0 +1,11 @@
CONDITIONAL ('(?')
GROUP_BEGIN ('(')
PCRE_CONDITION ('VERSION>=10.7')
GROUP_END (')')
CHARACTER ('y')
CHARACTER ('e')
CHARACTER ('s')
UNION ('|')
CHARACTER ('n')
CHARACTER ('o')
GROUP_END (')')