PY-21244 Suppress intention if references for some nested fields are unresolved
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Tue, 1 Nov 2016 14:19:38 +0000 (17:19 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Tue, 8 Nov 2016 15:20:22 +0000 (18:20 +0300)
python/src/com/jetbrains/python/codeInsight/intentions/PyConvertToFStringIntention.java
python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodParentFieldUnresolved.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/intentions/PyConvertToFStringIntentionTest.java

index 57a663c513e9a8d56a8db04c7009ed4867a8ba7f..22cf27e448bedffd9a0ef3476a9cb68a5019f5b2 100644 (file)
@@ -25,6 +25,7 @@ import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.ObjectUtils;
 import com.jetbrains.python.PyBundle;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.PyTokenTypes;
@@ -85,22 +86,21 @@ public class PyConvertToFStringIntention extends PyBaseIntentionAction {
         return false;
       }
 
-      final List<FormatStringChunk> chunks = percentOperator ? PyStringFormatParser.parsePercentFormat(stringText)
-                                                             : PyStringFormatParser.parseNewStyleFormat(stringText);
-      final List<SubstitutionChunk> substitutions = PyStringFormatParser.filterSubstitutions(chunks);
-
-      // TODO handle dynamic format spec in both formatting styles
-      final boolean hasDynamicFormatting;
+      final List<SubstitutionChunk> substitutions;
       if (percentOperator) {
-        hasDynamicFormatting = substitutions.stream().anyMatch(s -> "*".equals(s.getWidth()) || "*".equals(s.getPrecision()));
+        final List<FormatStringChunk> chunks = PyStringFormatParser.parsePercentFormat(stringText);
+        substitutions = PyStringFormatParser.filterSubstitutions(chunks);
       }
       else {
-        hasDynamicFormatting = false;
+        substitutions = new ArrayList<>(PyNewStyleStringFormatParser.parse(stringText).getFields());
       }
-      if (hasDynamicFormatting) return false;
 
-      final PySubstitutionChunkReference[] references =
-        PythonFormattedStringReferenceProvider.getReferencesFromChunks(pyString, substitutions, percentOperator);
+      // TODO handle dynamic width and precision in old-style/"percent" formatting
+      if (percentOperator && substitutions.stream().anyMatch(s -> "*".equals(s.getWidth()) || "*".equals(s.getPrecision()))) {
+        return false;
+      }
+
+      final PySubstitutionChunkReference[] references = createChunkReferences(substitutions, pyString, percentOperator);
 
       final PsiElement valuesSource;
       if (percentOperator) {
@@ -134,6 +134,20 @@ public class PyConvertToFStringIntention extends PyBaseIntentionAction {
     return false;
   }
 
+  @NotNull
+  private static PySubstitutionChunkReference[] createChunkReferences(@NotNull List<SubstitutionChunk> substitutions,
+                                                                      @NotNull PyStringLiteralExpression pyString,
+                                                                      boolean percentOperator) {
+    if (percentOperator) {
+      return PythonFormattedStringReferenceProvider.getReferencesFromChunks(pyString, substitutions, true);
+    }
+    return substitutions.stream()
+      .filter(PyNewStyleStringFormatParser.Field.class::isInstance)
+      .map(PyNewStyleStringFormatParser.Field.class::cast)
+      .map(field -> new PySubstitutionChunkReference(pyString, field, ObjectUtils.chooseNotNull(field.getAutoPosition(), 0), false))
+      .toArray(PySubstitutionChunkReference[]::new);
+  }
+
   private static boolean expressionCanBeInlined(@NotNull PyStringLiteralExpression host, @NotNull PyExpression target) {
     // Cannot inline multi-line expressions or expressions that contains backslashes (yet)
     if (target.textContains('\\') || target.textContains('\n')) return false;
diff --git a/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodParentFieldUnresolved.py b/python/testData/intentions/PyConvertToFStringIntentionTest/formatMethodParentFieldUnresolved.py
new file mode 100644 (file)
index 0000000..e92f2f7
--- /dev/null
@@ -0,0 +1 @@
+'{foo:{bar}}'.format(bar=42)
\ No newline at end of file
index 7d1f2528a489976e2df658bac83664455fa5f925..e796cf0def5066ab495294170c42892b525017bc 100644 (file)
@@ -163,4 +163,9 @@ public class PyConvertToFStringIntentionTest extends PyIntentionTestCase {
   public void testFormatMethodNestedFields3() {
     doTest();
   }
+
+  // PY-21244
+  public void testFormatMethodParentFieldUnresolved() {
+    doNegativeTest();
+  }
 }