From 604d8dc4b3525f8b189aa0e53aec223a8bf74286 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 22 Feb 2011 17:12:45 +0100 Subject: [PATCH] StringConstantAnnotator performs all checks per node, doesn't do lexer's job again (PY-2802) --- .../impl/PyStringLiteralExpressionImpl.java | 11 ++- .../validation/StringConstantAnnotator.java | 86 ++++++------------- .../highlighting/oddNumberOfQuotes.py | 1 + .../python/PythonHighlightingTest.java | 4 + .../com/jetbrains/python/PythonLexerTest.java | 4 + 5 files changed, 43 insertions(+), 63 deletions(-) create mode 100644 python/testData/highlighting/oddNumberOfQuotes.py diff --git a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java index 83d0f14fefe9..b698101f1c61 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java @@ -75,9 +75,7 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt } private static TextRange getNodeTextRange(final String text) { - int startOffset = 0; - startOffset = skipEncodingPrefix(text, startOffset); - startOffset = skipRawPrefix(text, startOffset); + int startOffset = getPrefixLength(text); int delimiterLength = 1; final String afterPrefix = text.substring(startOffset); if (afterPrefix.startsWith("\"\"\"") || afterPrefix.startsWith("'''")) { @@ -92,6 +90,13 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt return new TextRange(startOffset, endOffset); } + public static int getPrefixLength(String text) { + int startOffset = 0; + startOffset = skipEncodingPrefix(text, startOffset); + startOffset = skipRawPrefix(text, startOffset); + return startOffset; + } + private static int skipRawPrefix(String text, int startOffset) { char c = Character.toUpperCase(text.charAt(startOffset)); if (c == 'R') { diff --git a/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java b/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java index b1bdfcbd3ab2..1f3bebfae41a 100644 --- a/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java +++ b/python/src/com/jetbrains/python/validation/StringConstantAnnotator.java @@ -2,8 +2,8 @@ package com.jetbrains.python.validation; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.PyStringLiteralExpression; +import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; import java.util.List; @@ -17,73 +17,39 @@ public class StringConstantAnnotator extends PyAnnotator { private static final String TRIPLE_QUOTES = "\"\"\""; private static final String TRIPLE_APOS = "'''"; - //public static final String PREMATURE_Q = "Premature closing quote"; public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) { - String s = node.getText(); - String msg = ""; - boolean ok = true; - boolean esc = false; - int index = 0; - // skip 'unicode' and 'raw' modifiers - char first_quote = s.charAt(index); - if (Character.toLowerCase(first_quote) == 'u' || Character.toLowerCase(first_quote) == 'b') index += 1; - first_quote = s.charAt(index); - if ((first_quote == 'r') || (first_quote == 'R')) index += 1; - List stringNodes = node.getStringNodes(); - int thisNodeIndex = index; for (ASTNode stringNode : stringNodes) { - if (checkTripleQuotedString(stringNode.getPsi(), stringNode.getText(), thisNodeIndex, TRIPLE_QUOTES)) return; - if (checkTripleQuotedString(stringNode.getPsi(), stringNode.getText(), thisNodeIndex, TRIPLE_APOS)) return; - thisNodeIndex = 0; - } - - first_quote = s.charAt(index); - // s can't begin with a non-quote, else parser would not say it's a string - index += 1; - if (index >= s.length()) { // sole opening quote - msg = MISSING_Q + " [" + first_quote + "]"; - ok = false; - } - else { - while (ok && (index < s.length()-1)) { - char c = s.charAt(index); - if (esc) esc = false; - else { - if (c == '\\') esc = true; - // a line may consist of multiple fragments with different quote chars (PY-299) - else if (c == '\'' || c == '\"') { - if (first_quote == '\0') - first_quote = c; - else if (c == first_quote) - first_quote = '\0'; - } - - /* - else { // impossible with current lexer, but who knows :) - msg = PREMATURE_Q + " [" + first_quote + "]"; - ok = false; - } - */ - } - index += 1; + boolean foundError; + String nodeText = stringNode.getText(); + int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); + String unprefixed = nodeText.substring(index); + if (StringUtil.startsWith(unprefixed, TRIPLE_QUOTES)) { + foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_QUOTES); } - if (ok && (esc || (s.charAt(index) != first_quote))) { - msg = MISSING_Q + " [" + first_quote + "]"; - ok = false; + else if (StringUtil.startsWith(unprefixed, TRIPLE_APOS)) { + foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_APOS); } - } - // - if (! ok) { - getHolder().createErrorAnnotation(node, msg); + else { + foundError = checkQuotedString(stringNode, unprefixed); + } + if (foundError) break; } } - private boolean checkTripleQuotedString(PsiElement node, String s, int index, final String quotes) { - if (StringUtil.startsWith(s.substring(index, s.length()), quotes)) { - if (s.length() < 6 + index || !s.endsWith(quotes)) { - getHolder().createErrorAnnotation(node, "Missing closing triple quotes"); - } + private boolean checkQuotedString(ASTNode stringNode, String nodeText) { + char firstQuote = nodeText.charAt(0); + int lastChar = nodeText.length()-1; + if (lastChar == 0 || nodeText.charAt(lastChar) != firstQuote || nodeText.charAt(lastChar-1) == '\\') { + getHolder().createErrorAnnotation(stringNode, MISSING_Q + " [" + firstQuote + "]"); + return true; + } + return false; + } + + private boolean checkTripleQuotedString(ASTNode stringNode, String text, final String quotes) { + if (text.length() < 6 || !text.endsWith(quotes)) { + getHolder().createErrorAnnotation(stringNode, "Missing closing triple quotes"); return true; } return false; diff --git a/python/testData/highlighting/oddNumberOfQuotes.py b/python/testData/highlighting/oddNumberOfQuotes.py new file mode 100644 index 000000000000..17111418ff54 --- /dev/null +++ b/python/testData/highlighting/oddNumberOfQuotes.py @@ -0,0 +1 @@ +'''a'''' diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java index 1f319ab31189..f8952e4ee4a6 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -138,6 +138,10 @@ public class PythonHighlightingTest extends PyLightFixtureTestCase { doTest(); } + public void testOddNumberOfQuotes() { // PY-2802 + doTest(true, false); + } + public void testUnsupportedFeaturesInPython3() { doTest(LanguageLevel.PYTHON30, true, false); } diff --git a/python/testSrc/com/jetbrains/python/PythonLexerTest.java b/python/testSrc/com/jetbrains/python/PythonLexerTest.java index 322ff23b144f..82d3ccee77a1 100644 --- a/python/testSrc/com/jetbrains/python/PythonLexerTest.java +++ b/python/testSrc/com/jetbrains/python/PythonLexerTest.java @@ -166,6 +166,10 @@ public class PythonLexerTest extends PyLexerTestCase { doTest(THREE_QUOTES + " foo \"\\" + THREE_QUOTES + " bar " + THREE_QUOTES, "Py:STRING_LITERAL"); } + public void testOddNumberOfQuotes() { // PY-2802 + doTest("'''foo''''", "Py:STRING_LITERAL", "Py:STRING_LITERAL"); + } + public void testDedentBeforeComment() { // PY-2209 & friends doTest("class UserProfile:\n" + " pass\n" +