mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
RegExp: character range may contain only characters, including named characters
This commit is contained in:
@@ -18,6 +18,7 @@ package org.intellij.lang.regexp;
|
||||
import com.intellij.lexer.FlexAdapter;
|
||||
import com.intellij.lexer.Lexer;
|
||||
import com.intellij.lexer.LookAheadLexer;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.EnumSet;
|
||||
@@ -30,7 +31,8 @@ public class RegExpLexer extends LookAheadLexer {
|
||||
|
||||
@Override
|
||||
protected void lookAhead(@NotNull Lexer baseLexer) {
|
||||
if (!RegExpTT.CHARACTERS.contains(baseLexer.getTokenType())) {
|
||||
final IElementType tokenType = baseLexer.getTokenType();
|
||||
if (!RegExpTT.CHARACTERS.contains(tokenType) && tokenType != RegExpTT.RBRACE) {
|
||||
advanceLexer(baseLexer);
|
||||
if (baseLexer.getTokenType() == RegExpTT.MINUS) {
|
||||
advanceAs(baseLexer, RegExpTT.CHARACTER);
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.intellij.lang.regexp;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.PsiBuilder;
|
||||
import com.intellij.lang.PsiParser;
|
||||
import com.intellij.psi.StringEscapesTokenTypes;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -251,8 +250,8 @@ public class RegExpParser implements PsiParser {
|
||||
else if (token == RegExpTT.BRACKET_EXPRESSION_BEGIN) {
|
||||
parseBracketExpression(builder);
|
||||
}
|
||||
else if (RegExpTT.CHARACTERS.contains(token)) {
|
||||
parseSimpleClassdef(builder);
|
||||
else if (RegExpTT.CHARACTERS.contains(token) || token == RegExpTT.NAMED_CHARACTER) {
|
||||
parseCharacterRange(builder);
|
||||
}
|
||||
else if (token == RegExpTT.CHAR_CLASS) {
|
||||
final PsiBuilder.Marker m = builder.mark();
|
||||
@@ -262,9 +261,6 @@ public class RegExpParser implements PsiParser {
|
||||
else if (token == RegExpTT.PROPERTY) {
|
||||
parseProperty(builder);
|
||||
}
|
||||
else if (token == RegExpTT.NAMED_CHARACTER) {
|
||||
parseNamedCharacter(builder);
|
||||
}
|
||||
else {
|
||||
return count > 0;
|
||||
}
|
||||
@@ -283,49 +279,42 @@ public class RegExpParser implements PsiParser {
|
||||
marker.done(RegExpElementTypes.POSIX_BRACKET_EXPRESSION);
|
||||
}
|
||||
|
||||
private void parseSimpleClassdef(PsiBuilder builder) {
|
||||
assert RegExpTT.CHARACTERS.contains(builder.getTokenType());
|
||||
|
||||
final PsiBuilder.Marker marker = builder.mark();
|
||||
makeChar(builder);
|
||||
private void parseCharacterRange(PsiBuilder builder) {
|
||||
final PsiBuilder.Marker rangeMarker = builder.mark();
|
||||
parseCharacter(builder);
|
||||
|
||||
if (builder.getTokenType() == RegExpTT.MINUS) {
|
||||
final PsiBuilder.Marker m = builder.mark();
|
||||
final PsiBuilder.Marker minusMarker = builder.mark();
|
||||
builder.advanceLexer();
|
||||
|
||||
final IElementType t = builder.getTokenType();
|
||||
if (RegExpTT.CHARACTERS.contains(t) || t == RegExpTT.CHAR_CLASS) {
|
||||
m.drop();
|
||||
makeChar(builder);
|
||||
marker.done(RegExpElementTypes.CHAR_RANGE);
|
||||
if (RegExpTT.CHARACTERS.contains(t) || t == RegExpTT.NAMED_CHARACTER) {
|
||||
minusMarker.drop();
|
||||
parseCharacter(builder);
|
||||
rangeMarker.done(RegExpElementTypes.CHAR_RANGE);
|
||||
}
|
||||
else {
|
||||
marker.drop();
|
||||
m.done(t == RegExpTT.CHAR_CLASS ? RegExpElementTypes.SIMPLE_CLASS : RegExpElementTypes.CHAR);
|
||||
|
||||
if (t == RegExpTT.CLASS_END) { // [a-]
|
||||
return;
|
||||
rangeMarker.drop();
|
||||
minusMarker.done(RegExpElementTypes.CHAR);
|
||||
}
|
||||
else if (t == RegExpTT.CLASS_BEGIN) { // [a-[b]]
|
||||
if (parseClassdef(builder)) {
|
||||
return;
|
||||
}
|
||||
else if (t == RegExpTT.CLASS_BEGIN) { // [a-[b]]\
|
||||
rangeMarker.drop();
|
||||
minusMarker.done(RegExpElementTypes.CHAR);
|
||||
parseClassdef(builder);
|
||||
}
|
||||
else {
|
||||
minusMarker.drop();
|
||||
builder.error("Illegal character range");
|
||||
rangeMarker.done(RegExpElementTypes.CHAR_RANGE);
|
||||
}
|
||||
builder.error("Illegal character range");
|
||||
}
|
||||
}
|
||||
else {
|
||||
marker.drop();
|
||||
rangeMarker.drop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeChar(PsiBuilder builder) {
|
||||
final IElementType t = builder.getTokenType();
|
||||
final PsiBuilder.Marker m = builder.mark();
|
||||
builder.advanceLexer();
|
||||
m.done(t == RegExpTT.CHAR_CLASS ? RegExpElementTypes.SIMPLE_CLASS : RegExpElementTypes.CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* GROUP ::= "(" PATTERN ")" | TERM
|
||||
* TERM ::= "." | "$" | "^" | CHAR | CLASS | BACKREF
|
||||
@@ -363,9 +352,9 @@ public class RegExpParser implements PsiParser {
|
||||
marker.done(RegExpElementTypes.SET_OPTIONS);
|
||||
}
|
||||
}
|
||||
else if (type == StringEscapesTokenTypes.INVALID_CHARACTER_ESCAPE_TOKEN || RegExpTT.CHARACTERS.contains(type)) {
|
||||
builder.advanceLexer();
|
||||
marker.done(RegExpElementTypes.CHAR);
|
||||
else if (RegExpTT.CHARACTERS.contains(type) || type == RegExpTT.NAMED_CHARACTER) {
|
||||
marker.drop();
|
||||
parseCharacter(builder);
|
||||
}
|
||||
else if (RegExpTT.BOUNDARIES.contains(type)) {
|
||||
builder.advanceLexer();
|
||||
@@ -407,10 +396,6 @@ public class RegExpParser implements PsiParser {
|
||||
marker.drop();
|
||||
parseProperty(builder);
|
||||
}
|
||||
else if (type == RegExpTT.NAMED_CHARACTER) {
|
||||
marker.drop();
|
||||
parseNamedCharacter(builder);
|
||||
}
|
||||
else if (type == RegExpTT.DOT || type == RegExpTT.CHAR_CLASS) {
|
||||
builder.advanceLexer();
|
||||
marker.done(RegExpElementTypes.SIMPLE_CLASS);
|
||||
@@ -495,13 +480,19 @@ public class RegExpParser implements PsiParser {
|
||||
marker.done(RegExpElementTypes.PROPERTY);
|
||||
}
|
||||
|
||||
private static void parseNamedCharacter(PsiBuilder builder) {
|
||||
private static void parseCharacter(PsiBuilder builder) {
|
||||
final PsiBuilder.Marker marker = builder.mark();
|
||||
builder.advanceLexer();
|
||||
checkMatches(builder, RegExpTT.LBRACE, "'{' expected");
|
||||
checkMatches(builder, RegExpTT.NAME, "Unicode character name expected");
|
||||
checkMatches(builder, RegExpTT.RBRACE, "'}' expected");
|
||||
marker.done(RegExpElementTypes.NAMED_CHARACTER);
|
||||
if (builder.getTokenType() == RegExpTT.NAMED_CHARACTER) {
|
||||
builder.advanceLexer();
|
||||
checkMatches(builder, RegExpTT.LBRACE, "'{' expected");
|
||||
checkMatches(builder, RegExpTT.NAME, "Unicode character name expected");
|
||||
checkMatches(builder, RegExpTT.RBRACE, "'}' expected");
|
||||
marker.done(RegExpElementTypes.NAMED_CHARACTER);
|
||||
}
|
||||
else {
|
||||
builder.advanceLexer();
|
||||
marker.done(RegExpElementTypes.CHAR);
|
||||
}
|
||||
}
|
||||
|
||||
private static void patternExpected(PsiBuilder builder) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2011 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.
|
||||
@@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.intellij.lang.regexp.psi.RegExpChar;
|
||||
import org.intellij.lang.regexp.psi.RegExpNamedCharacter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,8 @@ public class RegExpWordSelectionFilter implements Condition<PsiElement> {
|
||||
@Override
|
||||
public boolean value(@NotNull PsiElement element) {
|
||||
final ASTNode node = element.getNode();
|
||||
if ((node != null && node.getElementType() == RegExpTT.CHARACTER) || element instanceof RegExpChar) {
|
||||
if ((node != null && node.getElementType() == RegExpTT.CHARACTER) ||
|
||||
(element instanceof RegExpChar && !(element instanceof RegExpNamedCharacter))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Represents a simple, escaped, encoded or named character
|
||||
*/
|
||||
public interface RegExpChar extends RegExpAtom, RegExpClassElement, RegExpCharRange.Endpoint {
|
||||
public interface RegExpChar extends RegExpAtom, RegExpClassElement {
|
||||
/** Character type */
|
||||
enum Type {
|
||||
/** a plain character ("a") */
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
package org.intellij.lang.regexp.psi;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a character range as in [a-z].
|
||||
*/
|
||||
public interface RegExpCharRange extends RegExpElement, RegExpClassElement {
|
||||
interface Endpoint extends RegExpClassElement { }
|
||||
public interface RegExpCharRange extends RegExpClassElement {
|
||||
|
||||
@NotNull
|
||||
Endpoint getFrom();
|
||||
RegExpChar getFrom();
|
||||
|
||||
@NotNull
|
||||
Endpoint getTo();
|
||||
@Nullable
|
||||
RegExpChar getTo();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Represents a simple character class.
|
||||
*/
|
||||
public interface RegExpSimpleClass extends RegExpAtom, RegExpClassElement, RegExpCharRange.Endpoint {
|
||||
public interface RegExpSimpleClass extends RegExpAtom, RegExpClassElement {
|
||||
enum Kind {
|
||||
/** . */ ANY,
|
||||
/** \d */ DIGIT,
|
||||
|
||||
@@ -17,35 +17,32 @@ package org.intellij.lang.regexp.psi.impl;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.intellij.lang.regexp.RegExpElementTypes;
|
||||
import org.intellij.lang.regexp.psi.RegExpChar;
|
||||
import org.intellij.lang.regexp.psi.RegExpCharRange;
|
||||
import org.intellij.lang.regexp.psi.RegExpElementVisitor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RegExpCharRangeImpl extends RegExpElementImpl implements RegExpCharRange {
|
||||
private static final TokenSet E = TokenSet.create(RegExpElementTypes.CHAR, RegExpElementTypes.SIMPLE_CLASS);
|
||||
|
||||
public RegExpCharRangeImpl(ASTNode astNode) {
|
||||
super(astNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Endpoint getFrom() {
|
||||
return (Endpoint)getCharNode(0);
|
||||
}
|
||||
@NotNull
|
||||
public Endpoint getTo() {
|
||||
return (Endpoint)getCharNode(1);
|
||||
public RegExpChar getFrom() {
|
||||
return (RegExpChar)getFirstChild();
|
||||
}
|
||||
|
||||
private PsiElement getCharNode(int idx) {
|
||||
final ASTNode[] ch = getNode().getChildren(E);
|
||||
assert ch.length == 2;
|
||||
return ch[idx].getPsi();
|
||||
@Override
|
||||
@Nullable
|
||||
public RegExpChar getTo() {
|
||||
final PsiElement child = getLastChild();
|
||||
return child instanceof RegExpChar ? (RegExpChar)child : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(RegExpElementVisitor visitor) {
|
||||
visitor.visitRegExpCharRange(this);
|
||||
}
|
||||
|
||||
@@ -83,16 +83,10 @@ public final class RegExpAnnotator extends RegExpElementVisitor implements Annot
|
||||
|
||||
@Override
|
||||
public void visitRegExpCharRange(RegExpCharRange range) {
|
||||
final RegExpCharRange.Endpoint from = range.getFrom();
|
||||
final RegExpCharRange.Endpoint to = range.getTo();
|
||||
if (from instanceof RegExpChar && to instanceof RegExpChar) {
|
||||
checkRange(range, ((RegExpChar)from).getValue(), ((RegExpChar)to).getValue());
|
||||
}
|
||||
else if (to instanceof RegExpSimpleClass) {
|
||||
myHolder.createErrorAnnotation(to, "Character class not allowed inside character range");
|
||||
}
|
||||
else if (from.getText().equals(to.getText())) {
|
||||
myHolder.createWarningAnnotation(range, "Redundant character range");
|
||||
final RegExpChar from = range.getFrom();
|
||||
final RegExpChar to = range.getTo();
|
||||
if (to != null) {
|
||||
checkRange(range, from.getValue(), to.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ public class RegExpParsingTest extends ParsingTestCase {
|
||||
public void testCharclasses68() throws IOException { doCodeTest("[\\b]"); }
|
||||
public void testCharClasses69() throws IOException { doCodeTest("\\p{^L}"); }
|
||||
public void testCharClasses70() throws IOException { doCodeTest("[&&&&a]"); }
|
||||
public void testCharClasses71() throws IOException { doCodeTest("[a-\\Qz\\E]"); }
|
||||
|
||||
public void testGroups1() throws IOException { doCodeTest("()ef"); }
|
||||
public void testGroups2() throws IOException { doCodeTest("()*"); }
|
||||
@@ -248,6 +249,7 @@ public class RegExpParsingTest extends ParsingTestCase {
|
||||
public void testNamedchars12() throws IOException { doCodeTest("\\p{InArabic Extended-A}"); }
|
||||
public void testNamedchars13() throws IOException { doCodeTest("\\N{Mahjong Tile Winter}"); }
|
||||
public void testNamedchars14() throws IOException { doCodeTest("[\\N{Mahjong Tile Winter}]"); }
|
||||
public void testNamedchars15() throws IOException { doCodeTest("[\\N{LATIN SMALL LETTER A}-\\N{LATIN SMALL LETTER Z}]"); }
|
||||
|
||||
public void testBackrefs1() throws IOException { doCodeTest("(ac*)c*d[ac]*\\1"); }
|
||||
public void testBackrefs2() throws IOException { doCodeTest("(.)=\\1"); }
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
REGEXP_FILE
|
||||
RegExpPatternImpl: <[a-\Qz\E]>
|
||||
RegExpBranchImpl: <[a-\Qz\E]>
|
||||
RegExpClassImpl: <[a-\Qz\E]>
|
||||
PsiElement(CLASS_BEGIN)('[')
|
||||
RegExpCharRangeImpl: <a-\Qz>
|
||||
RegExpCharImpl: <a>
|
||||
PsiElement(CHARACTER)('a')
|
||||
PsiElement(MINUS)('-')
|
||||
PsiWhiteSpace('\Q')
|
||||
RegExpCharImpl: <z>
|
||||
PsiElement(CHARACTER)('z')
|
||||
PsiWhiteSpace('\E')
|
||||
PsiElement(CLASS_END)(']')
|
||||
@@ -3,10 +3,12 @@ REGEXP_FILE
|
||||
RegExpBranchImpl: <[a-\w]>
|
||||
RegExpClassImpl: <[a-\w]>
|
||||
PsiElement(CLASS_BEGIN)('[')
|
||||
RegExpCharRangeImpl: <a-\w>
|
||||
RegExpCharRangeImpl: <a->
|
||||
RegExpCharImpl: <a>
|
||||
PsiElement(CHARACTER)('a')
|
||||
PsiElement(MINUS)('-')
|
||||
RegExpSimpleClassImpl: <\w>
|
||||
PsiElement(CHAR_CLASS)('\w')
|
||||
PsiErrorElement:Illegal character range
|
||||
<empty list>
|
||||
RegExpSimpleClassImpl: <\w>
|
||||
PsiElement(CHAR_CLASS)('\w')
|
||||
PsiElement(CLASS_END)(']')
|
||||
@@ -6,12 +6,12 @@ REGEXP_FILE
|
||||
RegExpClassImpl: <[b-&&[cd]]>
|
||||
PsiElement(CLASS_BEGIN)('[')
|
||||
RegExpIntersectionImpl: <b-&&[cd]>
|
||||
RegExpCharImpl: <b>
|
||||
PsiElement(CHARACTER)('b')
|
||||
RegExpCharImpl: <->
|
||||
RegExpCharRangeImpl: <b->
|
||||
RegExpCharImpl: <b>
|
||||
PsiElement(CHARACTER)('b')
|
||||
PsiElement(MINUS)('-')
|
||||
PsiErrorElement:Illegal character range
|
||||
<empty list>
|
||||
PsiErrorElement:Illegal character range
|
||||
<empty list>
|
||||
PsiElement(ANDAND)('&&')
|
||||
RegExpClassImpl: <[cd]>
|
||||
PsiElement(CLASS_BEGIN)('[')
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
REGEXP_FILE
|
||||
RegExpPatternImpl: <[\N{LATIN SMALL LETTER A}-\N{LATIN SMALL LETTER Z}]>
|
||||
RegExpBranchImpl: <[\N{LATIN SMALL LETTER A}-\N{LATIN SMALL LETTER Z}]>
|
||||
RegExpClassImpl: <[\N{LATIN SMALL LETTER A}-\N{LATIN SMALL LETTER Z}]>
|
||||
PsiElement(CLASS_BEGIN)('[')
|
||||
RegExpCharRangeImpl: <\N{LATIN SMALL LETTER A}-\N{LATIN SMALL LETTER Z}>
|
||||
RegExpNamedCharacterImpl: <\N{LATIN SMALL LETTER A}>
|
||||
PsiElement(NAMED_CHARACTER)('\N')
|
||||
PsiElement(LBRACE)('{')
|
||||
PsiElement(NAME)('LATIN SMALL LETTER A')
|
||||
PsiElement(RBRACE)('}')
|
||||
PsiElement(MINUS)('-')
|
||||
RegExpNamedCharacterImpl: <\N{LATIN SMALL LETTER Z}>
|
||||
PsiElement(NAMED_CHARACTER)('\N')
|
||||
PsiElement(LBRACE)('{')
|
||||
PsiElement(NAME)('LATIN SMALL LETTER Z')
|
||||
PsiElement(RBRACE)('}')
|
||||
PsiElement(CLASS_END)(']')
|
||||
@@ -130,6 +130,11 @@ public class RegExpHighlightingTest extends LightCodeInsightFixtureTestCase {
|
||||
doTest("[<error descr=\"Illegal character range (to < from)\">z-a</error>]");
|
||||
}
|
||||
|
||||
public void testIllegalCharacterRange4() {
|
||||
IdeaTestUtil.setTestVersion(JavaSdkVersion.JDK_1_9, myFixture.getModule(), getTestRootDisposable());
|
||||
doTest("[<error descr=\"Illegal character range (to < from)\">\\N{LATIN SMALL LETTER Z}-\\N{LATIN SMALL LETTER A}</error>]");
|
||||
}
|
||||
|
||||
public void testLegalCharacterRange() {
|
||||
// Cyrillic Capital Letter Zemlya - Unicode Han Character 'to peel, pare' (Unicode Supplementary Character)
|
||||
// without code point support 0x20731 wraps to 0x731 which would produce a "Illegal character range (to < from)" error
|
||||
|
||||
Reference in New Issue
Block a user