PY-20987 Consider multiple "glued" string nodes in the spell checker
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 5 Oct 2016 11:22:10 +0000 (14:22 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 5 Oct 2016 13:11:37 +0000 (16:11 +0300)
python/src/com/jetbrains/python/spellchecker/PythonSpellcheckerStrategy.java
python/testData/inspections/spelling/gluedStringNodesAfterFirstRawWithBackslashes.py [new file with mode: 0644]
python/testData/inspections/spelling/gluedStringNodesAfterFirstWithPrefix.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PySpellCheckerTest.java

index 948d14aaf5419833e0eaecdbefed44bc6bfbb36d..2cc3ff78a9219266eb99a1397082cd0b99d88596 100644 (file)
  */
 package com.jetbrains.python.spellchecker;
 
+import com.intellij.lang.ASTNode;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.PsiElement;
 import com.intellij.spellchecker.inspections.PlainTextSplitter;
 import com.intellij.spellchecker.inspections.Splitter;
 import com.intellij.spellchecker.tokenizer.SpellcheckingStrategy;
 import com.intellij.spellchecker.tokenizer.TokenConsumer;
 import com.intellij.spellchecker.tokenizer.Tokenizer;
+import com.intellij.util.containers.ContainerUtil;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.PythonStringUtil;
 import com.jetbrains.python.inspections.PyStringFormatParser;
@@ -32,6 +35,8 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
 
+import static com.jetbrains.python.psi.PyUtil.StringNodeInfo;
+
 /**
  * @author yole
  */
@@ -40,16 +45,17 @@ public class PythonSpellcheckerStrategy extends SpellcheckingStrategy {
     @Override
     public void tokenize(@NotNull PyStringLiteralExpression element, TokenConsumer consumer) {
       final Splitter splitter = PlainTextSplitter.getInstance();
-      final String text = element.getText();
-      final String prefix = PythonStringUtil.getPrefix(text);
-      if (text.indexOf('\\') >= 0 && !PythonStringUtil.isRawPrefix(prefix)) {
+      final List<ASTNode> strNodes = element.getStringNodes();
+      final List<String> prefixes = ContainerUtil.mapNotNull(strNodes, n -> StringUtil.nullize(new StringNodeInfo(n).getPrefix()));
+      
+      if (element.textContains('\\') && !prefixes.stream().anyMatch(PythonStringUtil::isRawPrefix)) {
         for (Pair<TextRange, String> fragment : element.getDecodedFragments()) {
           final String value = fragment.getSecond();
           final int startOffset = fragment.getFirst().getStartOffset();
           consumer.consumeToken(element, value, false, startOffset, TextRange.allOf(value), splitter);
         }
       }
-      else if (!prefix.isEmpty()) {
+      else if (!prefixes.isEmpty()) {
         for (TextRange valueTextRange : element.getStringValueTextRanges()) {
           final String value = valueTextRange.substring(element.getText());
           final int startOffset = valueTextRange.getStartOffset();
diff --git a/python/testData/inspections/spelling/gluedStringNodesAfterFirstRawWithBackslashes.py b/python/testData/inspections/spelling/gluedStringNodesAfterFirstRawWithBackslashes.py
new file mode 100644 (file)
index 0000000..8e8e211
--- /dev/null
@@ -0,0 +1 @@
+s = 'foo' r'\teapot'
\ No newline at end of file
diff --git a/python/testData/inspections/spelling/gluedStringNodesAfterFirstWithPrefix.py b/python/testData/inspections/spelling/gluedStringNodesAfterFirstWithPrefix.py
new file mode 100644 (file)
index 0000000..3ff69df
--- /dev/null
@@ -0,0 +1 @@
+s = '<TYPO descr="Typo: In word 'potat'">potat</TYPO> <TYPO descr="Typo: In word 'potat'">potat</TYPO>' u'<TYPO descr="Typo: In word 'potat'">potat</TYPO> <TYPO descr="Typo: In word 'potat'">potat</TYPO>' r'<TYPO descr="Typo: In word 'potat'">potat</TYPO> <TYPO descr="Typo: In word 'potat'">potat</TYPO>'
\ No newline at end of file
index 0643ff96556d7bfdd9fe1fce4c53af650b511aba..84f4e4c6ec51abd3dfe0cd0bc3c3b15bf0bcffd0 100644 (file)
@@ -48,6 +48,16 @@ public class PySpellCheckerTest extends PyTestCase {
     runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
   }
 
+  // PY-20987
+  public void testGluedStringNodesAfterFirstWithPrefix() {
+    doTest();
+  }
+
+  // PY-20987
+  public void testGluedStringNodesAfterFirstRawWithBackslashes() {
+    doTest();
+  }
+
   private void doTest() {
     myFixture.enableInspections(SpellCheckingInspection.class);
     myFixture.configureByFile("inspections/spelling/" + getTestName(true) + ".py");