PY-16553 Add trailing comma when converting single element collection to tuple
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Mon, 2 May 2016 16:55:31 +0000 (19:55 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Tue, 3 May 2016 11:54:00 +0000 (14:54 +0300)
python/src/com/jetbrains/python/codeInsight/intentions/PyBaseConvertCollectionLiteralIntention.java
python/src/com/jetbrains/python/codeInsight/intentions/PyConvertLiteralToTupleIntention.java
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple.py [new file with mode: 0644]
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple_after.py [new file with mode: 0644]
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple.py [new file with mode: 0644]
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple_after.py [new file with mode: 0644]
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple.py [new file with mode: 0644]
python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple_after.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/intentions/PyConvertCollectionLiteralIntentionTest.java

index daa669af001e32fa081700975a0d599a0b7921bd..44557bcd0e915a069c95fce2bf95043dc9f35539 100644 (file)
@@ -18,7 +18,7 @@ package com.jetbrains.python.codeInsight.intentions;
 import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.TextRange;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.util.PsiTreeUtil;
@@ -98,22 +98,30 @@ public abstract class PyBaseConvertCollectionLiteralIntention extends BaseIntent
       replacedElement = literal;
     }
 
-    final String innerText = stripLiteralBraces(replacedElement);
+    final TextRange contentRange = getRangeOfContentWithoutBraces(replacedElement);
     final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
+    final String contentToWrap = prepareContent(replacedElement, literal, contentRange);
     final PyExpression newLiteral = elementGenerator.createExpressionFromText(LanguageLevel.forElement(file),
-                                                                              myLeftBrace + innerText + myRightBrace);
+                                                                              myLeftBrace + contentToWrap + myRightBrace);
     replacedElement.replace(newLiteral);
   }
 
   @NotNull
-  private static String stripLiteralBraces(@NotNull PsiElement literal) {
+  protected String prepareContent(@NotNull PsiElement replacedElement, 
+                                  @NotNull PySequenceExpression collection, 
+                                  @NotNull TextRange contentRange) {
+    return contentRange.substring(replacedElement.getText());
+  }
+
+  @NotNull
+  private static TextRange getRangeOfContentWithoutBraces(@NotNull PsiElement literal) {
     if (literal instanceof PyTupleExpression) {
-      return literal.getText();
+      return TextRange.create(0, literal.getTextLength());
     }
 
-    final PsiElement firstChild = literal.getFirstChild();
-
     final String replacedText = literal.getText();
+    
+    final PsiElement firstChild = literal.getFirstChild();
     final int contentStartOffset;
     if (PyTokenTypes.OPEN_BRACES.contains(firstChild.getNode().getElementType())) {
       contentStartOffset = firstChild.getTextLength();
@@ -131,7 +139,7 @@ public abstract class PyBaseConvertCollectionLiteralIntention extends BaseIntent
       contentEndOffset = replacedText.length();
     }
 
-    return literal.getText().substring(contentStartOffset, contentEndOffset);
+    return TextRange.create(contentStartOffset, contentEndOffset);
   }
 
   @Nullable
@@ -142,12 +150,9 @@ public abstract class PyBaseConvertCollectionLiteralIntention extends BaseIntent
     if (seqExpr != null) {
       return seqExpr;
     }
-    final PyParenthesizedExpression paren = (PyParenthesizedExpression)PsiTreeUtil.findFirstParent(curElem, new Condition<PsiElement>() {
-      @Override
-      public boolean value(PsiElement element) {
-        final PyParenthesizedExpression parenthesizedExpr = as(element, PyParenthesizedExpression.class);
-        return parenthesizedExpr != null && parenthesizedExpr.getContainedExpression() instanceof PyTupleExpression;
-      }
+    final PyParenthesizedExpression paren = (PyParenthesizedExpression)PsiTreeUtil.findFirstParent(curElem, element -> {
+      final PyParenthesizedExpression parenthesizedExpr = as(element, PyParenthesizedExpression.class);
+      return parenthesizedExpr != null && parenthesizedExpr.getContainedExpression() instanceof PyTupleExpression;
     });
     return paren != null ? ((PyTupleExpression)paren.getContainedExpression()) : null;
   }
index 91a62f7dc9ee1b71908e4ab13fc27bba22dceeb8..56d68c7ac7752f8d05f89313bb05f66afebb8ec7 100644 (file)
  */
 package com.jetbrains.python.codeInsight.intentions;
 
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.tree.IElementType;
+import com.jetbrains.python.PyTokenTypes;
+import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.PySequenceExpression;
 import com.jetbrains.python.psi.PyTupleExpression;
+import com.jetbrains.python.psi.impl.PyPsiUtils;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * @author Mikhail Golubev
@@ -24,4 +32,44 @@ public class PyConvertLiteralToTupleIntention extends PyBaseConvertCollectionLit
   public PyConvertLiteralToTupleIntention() {
     super(PyTupleExpression.class, "tuple", "(", ")");
   }
+
+
+  @NotNull
+  @Override
+  protected String prepareContent(@NotNull PsiElement replacedElement, 
+                                  @NotNull PySequenceExpression collection, 
+                                  @NotNull TextRange contentRange) {
+    assert !(collection instanceof PyTupleExpression);
+
+    final String contentWithoutBraces = super.prepareContent(replacedElement, collection, contentRange);
+    
+    final PyExpression[] elements = collection.getElements();
+    if (elements.length != 1) {
+      return contentWithoutBraces;
+    }
+    
+    final PsiElement lastChild = collection.getLastChild();
+    boolean endsWithComma = false;
+    final IElementType lastChildType = lastChild.getNode().getElementType();
+    if (lastChildType == PyTokenTypes.COMMA) {
+      endsWithComma = true;
+    }
+    else if (PyTokenTypes.CLOSE_BRACES.contains(lastChildType)) {
+      final PsiElement prev = PyPsiUtils.getPrevNonWhitespaceSibling(lastChild);
+      if (prev != null && prev.getNode().getElementType() == PyTokenTypes.COMMA) {
+        endsWithComma = true;
+      }
+    }
+    if (endsWithComma) {
+      return contentWithoutBraces;
+    }
+
+    final PyExpression singleElem = elements[0];
+    final int commaOffset = singleElem.getTextRange().getEndOffset() - replacedElement.getTextRange().getStartOffset();
+
+    final String wholeText = replacedElement.getText();
+    return wholeText.substring(contentRange.getStartOffset(), commaOffset) + 
+           "," + 
+           wholeText.substring(commaOffset, contentRange.getEndOffset());
+  }
 }
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple.py
new file mode 100644 (file)
index 0000000..6b15da4
--- /dev/null
@@ -0,0 +1 @@
+[42
\ No newline at end of file
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple_after.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementIncompleteListToTuple_after.py
new file mode 100644 (file)
index 0000000..ed36fce
--- /dev/null
@@ -0,0 +1 @@
+(42,)
\ No newline at end of file
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple.py
new file mode 100644 (file)
index 0000000..4e64032
--- /dev/null
@@ -0,0 +1,3 @@
+[<caret>
+    42
+]
\ No newline at end of file
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple_after.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListToTuple_after.py
new file mode 100644 (file)
index 0000000..9aaa347
--- /dev/null
@@ -0,0 +1,3 @@
+(
+    42,
+)
\ No newline at end of file
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple.py
new file mode 100644 (file)
index 0000000..51c3943
--- /dev/null
@@ -0,0 +1,3 @@
+[
+    42  # foo
+]
diff --git a/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple_after.py b/python/testData/intentions/PyConvertCollectionLiteralIntentionTest/convertOneElementListWithCommentToTuple_after.py
new file mode 100644 (file)
index 0000000..958910e
--- /dev/null
@@ -0,0 +1,3 @@
+(
+    42,  # foo
+)
index 7b10e2bd57cf16df5599857231ad7dbb2a519ab1..efc2e267cdad657fe335dbc7309f5c2b9bf534dd 100644 (file)
@@ -108,4 +108,19 @@ public class PyConvertCollectionLiteralIntentionTest extends PyIntentionTestCase
   public void testConvertLiteralPreservesFormattingAndComments() {
     doIntentionTest(CONVERT_TUPLE_TO_LIST);
   }
+
+  // PY-16553
+  public void testConvertOneElementListToTuple() {
+    doIntentionTest(CONVERT_LIST_TO_TUPLE);
+  }
+  
+  // PY-16553
+  public void testConvertOneElementIncompleteListToTuple() {
+    doIntentionTest(CONVERT_LIST_TO_TUPLE);
+  }
+
+  // PY-16553
+  public void testConvertOneElementListWithCommentToTuple() {
+    doIntentionTest(CONVERT_LIST_TO_TUPLE);
+  }
 }