mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
StringConstantAnnotator performs all checks per node, doesn't do lexer's job again (PY-2802)
This commit is contained in:
@@ -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') {
|
||||
|
||||
@@ -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<ASTNode> 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;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
'''a'''<error descr="Missing closing quote [']">'</error>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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" +
|
||||
|
||||
Reference in New Issue
Block a user