diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLexer.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLexer.java index 0f3d20c8b8d1..c9a8d03eb130 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpLexer.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpLexer.java @@ -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); diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java index 60e897f7c96d..9a062c8e3ae5 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpParser.java @@ -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) { diff --git a/RegExpSupport/src/org/intellij/lang/regexp/RegExpWordSelectionFilter.java b/RegExpSupport/src/org/intellij/lang/regexp/RegExpWordSelectionFilter.java index 6ac84b85c7e1..ed7a2193a18d 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/RegExpWordSelectionFilter.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/RegExpWordSelectionFilter.java @@ -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 { @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; diff --git a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpChar.java b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpChar.java index 4af07e5e5205..865960e6599a 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpChar.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpChar.java @@ -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") */ diff --git a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpCharRange.java b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpCharRange.java index 437cfc3f1d0a..a841e054ab2e 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpCharRange.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpCharRange.java @@ -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(); } diff --git a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpSimpleClass.java b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpSimpleClass.java index 8fe679c3aabe..a62eeb46b412 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpSimpleClass.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/psi/RegExpSimpleClass.java @@ -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, diff --git a/RegExpSupport/src/org/intellij/lang/regexp/psi/impl/RegExpCharRangeImpl.java b/RegExpSupport/src/org/intellij/lang/regexp/psi/impl/RegExpCharRangeImpl.java index 426fb5734576..4b8121399ddf 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/psi/impl/RegExpCharRangeImpl.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/psi/impl/RegExpCharRangeImpl.java @@ -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); } diff --git a/RegExpSupport/src/org/intellij/lang/regexp/validation/RegExpAnnotator.java b/RegExpSupport/src/org/intellij/lang/regexp/validation/RegExpAnnotator.java index 6df47e75d60f..ab05d58741f0 100644 --- a/RegExpSupport/src/org/intellij/lang/regexp/validation/RegExpAnnotator.java +++ b/RegExpSupport/src/org/intellij/lang/regexp/validation/RegExpAnnotator.java @@ -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()); } } diff --git a/RegExpSupport/test/org/intellij/lang/regexp/RegExpParsingTest.java b/RegExpSupport/test/org/intellij/lang/regexp/RegExpParsingTest.java index 0b5a040a90ea..a81dd79e2622 100644 --- a/RegExpSupport/test/org/intellij/lang/regexp/RegExpParsingTest.java +++ b/RegExpSupport/test/org/intellij/lang/regexp/RegExpParsingTest.java @@ -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"); } diff --git a/RegExpSupport/testData/psi/CharClasses71.txt b/RegExpSupport/testData/psi/CharClasses71.txt new file mode 100644 index 000000000000..76e4927404ef --- /dev/null +++ b/RegExpSupport/testData/psi/CharClasses71.txt @@ -0,0 +1,14 @@ +REGEXP_FILE + RegExpPatternImpl: <[a-\Qz\E]> + RegExpBranchImpl: <[a-\Qz\E]> + RegExpClassImpl: <[a-\Qz\E]> + PsiElement(CLASS_BEGIN)('[') + RegExpCharRangeImpl: + RegExpCharImpl: + PsiElement(CHARACTER)('a') + PsiElement(MINUS)('-') + PsiWhiteSpace('\Q') + RegExpCharImpl: + PsiElement(CHARACTER)('z') + PsiWhiteSpace('\E') + PsiElement(CLASS_END)(']') \ No newline at end of file diff --git a/RegExpSupport/testData/psi/Charclasses62.txt b/RegExpSupport/testData/psi/Charclasses62.txt index 7cc112d48cc3..3ba9832bc479 100644 --- a/RegExpSupport/testData/psi/Charclasses62.txt +++ b/RegExpSupport/testData/psi/Charclasses62.txt @@ -3,10 +3,12 @@ REGEXP_FILE RegExpBranchImpl: <[a-\w]> RegExpClassImpl: <[a-\w]> PsiElement(CLASS_BEGIN)('[') - RegExpCharRangeImpl: + RegExpCharRangeImpl: RegExpCharImpl: PsiElement(CHARACTER)('a') PsiElement(MINUS)('-') - RegExpSimpleClassImpl: <\w> - PsiElement(CHAR_CLASS)('\w') + PsiErrorElement:Illegal character range + + RegExpSimpleClassImpl: <\w> + PsiElement(CHAR_CLASS)('\w') PsiElement(CLASS_END)(']') \ No newline at end of file diff --git a/RegExpSupport/testData/psi/Charclasses9.txt b/RegExpSupport/testData/psi/Charclasses9.txt index 979286ca22b7..903fc0aa7848 100644 --- a/RegExpSupport/testData/psi/Charclasses9.txt +++ b/RegExpSupport/testData/psi/Charclasses9.txt @@ -6,12 +6,12 @@ REGEXP_FILE RegExpClassImpl: <[b-&&[cd]]> PsiElement(CLASS_BEGIN)('[') RegExpIntersectionImpl: - RegExpCharImpl: - PsiElement(CHARACTER)('b') - RegExpCharImpl: <-> + RegExpCharRangeImpl: + RegExpCharImpl: + PsiElement(CHARACTER)('b') PsiElement(MINUS)('-') - PsiErrorElement:Illegal character range - + PsiErrorElement:Illegal character range + PsiElement(ANDAND)('&&') RegExpClassImpl: <[cd]> PsiElement(CLASS_BEGIN)('[') diff --git a/RegExpSupport/testData/psi/Namedchars15.txt b/RegExpSupport/testData/psi/Namedchars15.txt new file mode 100644 index 000000000000..1598e677baff --- /dev/null +++ b/RegExpSupport/testData/psi/Namedchars15.txt @@ -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)(']') \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java index bf02dcc08c39..0b16a9f2b813 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/RegExpHighlightingTest.java @@ -130,6 +130,11 @@ public class RegExpHighlightingTest extends LightCodeInsightFixtureTestCase { doTest("[z-a]"); } + public void testIllegalCharacterRange4() { + IdeaTestUtil.setTestVersion(JavaSdkVersion.JDK_1_9, myFixture.getModule(), getTestRootDisposable()); + doTest("[\\N{LATIN SMALL LETTER Z}-\\N{LATIN SMALL LETTER A}]"); + } + 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