diff --git a/python/src/com/jetbrains/python/codeInsight/PySubstitutionChunkReference.java b/python/src/com/jetbrains/python/codeInsight/PySubstitutionChunkReference.java index 898c1f9aa335..0dbd8765dd47 100644 --- a/python/src/com/jetbrains/python/codeInsight/PySubstitutionChunkReference.java +++ b/python/src/com/jetbrains/python/codeInsight/PySubstitutionChunkReference.java @@ -23,6 +23,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.jetbrains.python.inspections.PyStringFormatParser; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,13 +31,15 @@ import org.jetbrains.annotations.Nullable; public class PySubstitutionChunkReference extends PsiReferenceBase implements PsiReferenceEx{ private final int myPosition; private final PyStringFormatParser.SubstitutionChunk myChunk; + private final boolean myIsPercent; private boolean myIgnoreUnresolved = false; public PySubstitutionChunkReference(@NotNull final PyStringLiteralExpression element, - @NotNull final PyStringFormatParser.SubstitutionChunk chunk, final int position) { + @NotNull final PyStringFormatParser.SubstitutionChunk chunk, final int position, boolean isPercent) { super(element, getKeyWordRange(element, chunk)); myChunk = chunk; myPosition = position; + myIsPercent = isPercent; } @Nullable @@ -64,8 +67,7 @@ public class PySubstitutionChunkReference extends PsiReferenceBase myPosition) { - result = elements[myPosition]; - } - } - else if (containedExpression instanceof PyBinaryExpression && ((PyBinaryExpression)containedExpression).isOperator("+")) { - result = processNotNestedBinaryExpression((PyBinaryExpression)containedExpression); - } - else if (containedExpression instanceof PyReferenceExpression) { - myIgnoreUnresolved = true; + containedExpression = PyPsiUtils.flattenParens(expression); + } + if (containedExpression instanceof PyTupleExpression) { + final PyExpression[] elements = ((PySequenceExpression)containedExpression).getElements(); + if (elements.length > myPosition) { + result = elements[myPosition]; } } - else if (expression instanceof PyReferenceExpression) { + else if (containedExpression instanceof PyBinaryExpression && ((PyBinaryExpression)containedExpression).isOperator("+")) { + result = resolveNotNestedBinaryExpression((PyBinaryExpression)containedExpression); + } + else if (containedExpression instanceof PyLiteralExpression && myPosition == 0) { + return expression; + } + else if (containedExpression instanceof PyCallExpression) { + return resolveCallExpression((PyCallExpression)expression); + } + else if (containedExpression instanceof PyReferenceExpression) { myIgnoreUnresolved = true; } return result; } @Nullable - private PsiElement processNotNestedBinaryExpression(PyBinaryExpression containedExpression) { + private PsiElement resolveNotNestedBinaryExpression(PyBinaryExpression containedExpression) { PyExpression left = containedExpression.getLeftExpression(); PyExpression right = containedExpression.getRightExpression(); if (left instanceof PyParenthesizedExpression) { - PyExpression leftTuple = getContainedExpression((PyParenthesizedExpression)left); + PyExpression leftTuple = PyPsiUtils.flattenParens(left); if (leftTuple instanceof PyTupleExpression) { PyExpression[] leftTupleElements = ((PyTupleExpression)leftTuple).getElements(); int leftTupleLength = leftTupleElements.length; @@ -172,7 +180,7 @@ public class PySubstitutionChunkReference extends PsiReferenceBase chunks = PyStringFormatParser.filterSubstitutions( PyStringFormatParser.parseNewStyleFormat(element.getStringValue())); - return getReferencesFromChunks(element, chunks); + return getReferencesFromChunks(element, chunks, false); } private static PsiReference[] getReferencesFromPercentString(@NotNull final PyStringLiteralExpression element) { final List chunks = PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parsePercentFormat(element.getStringValue())); - return getReferencesFromChunks(element, chunks); + return getReferencesFromChunks(element, chunks, true); } @NotNull private static PsiReference[] getReferencesFromChunks(@NotNull final PyStringLiteralExpression element, - @NotNull final List chunks) { + @NotNull final List chunks, + boolean isPercent) { final PsiReference[] result = new PsiReference[chunks.size()]; if (!element.isDocString()) { for (int i = 0; i < chunks.size(); i++) { final PyStringFormatParser.SubstitutionChunk chunk = chunks.get(i); - result[i] = new PySubstitutionChunkReference(element, chunk, i); + result[i] = new PySubstitutionChunkReference(element, chunk, i, isPercent); } } return result; diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyLiteralExpressionProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyLiteralExpressionProcessor.java index c4fed0238705..8910129755b5 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyLiteralExpressionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyLiteralExpressionProcessor.java @@ -17,18 +17,16 @@ package com.jetbrains.python.refactoring.rename; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.usageView.UsageInfo; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; -import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.PyLiteralExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class RenamePyLiteralExpressionProcessor extends RenamePyElementProcessor { - private static final Class[] UNSUPPORTED = {PyNumericLiteralExpression.class, PyNoneLiteralExpression.class, PyBoolLiteralExpression.class}; @Override public boolean canProcessElement(@NotNull PsiElement element) { return PsiTreeUtil.instanceOf(element, PyLiteralExpression.class); @@ -37,13 +35,7 @@ public class RenamePyLiteralExpressionProcessor extends RenamePyElementProcessor @Override public void renameElement(PsiElement element, String newName, UsageInfo[] usages, @Nullable RefactoringElementListener listener) throws IncorrectOperationException { - if (PsiTreeUtil.instanceOf(element, UNSUPPORTED)) throw new IncorrectOperationException(); - ((PyStringLiteralExpression)element).updateText("\"" + newName + "\""); - for (UsageInfo usageInfo: usages) { - PsiReference reference = usageInfo.getReference(); - if (reference == null) return; - reference.handleElementRename("\"" + newName + "\""); - } + throw new IncorrectOperationException(); } @Override diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringKeyword.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringKeyword.py new file mode 100644 index 000000000000..454a39f84094 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringKeyword.py @@ -0,0 +1 @@ +'{foo}'.format(boo=1) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDict.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDict.py new file mode 100644 index 000000000000..6248dd51403d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDict.py @@ -0,0 +1 @@ +'{foo}'.format(**{"boo": 1}) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDictCall.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDictCall.py new file mode 100644 index 000000000000..dfbb03d2a4f1 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedDictCall.py @@ -0,0 +1 @@ +'{foo}'.format(**dict(t=1)) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedFunctionCall.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedFunctionCall.py new file mode 100644 index 000000000000..8aafa5a24125 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedFunctionCall.py @@ -0,0 +1,5 @@ +def f(): + return dict(foo=0) + + +'{foo}'.format(**f()) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedReference.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedReference.py new file mode 100644 index 000000000000..c3e726eb7ab4 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPackedReference.py @@ -0,0 +1,2 @@ +ref = {"fst": 1, "snd": 2} +print "first is {fst}, second is {snd}".format(**ref) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPositional.py b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPositional.py new file mode 100644 index 000000000000..fa8faafa8734 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/formatStringPositional.py @@ -0,0 +1 @@ +v = '{}'.format() \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringFunctionCall.py b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringFunctionCall.py new file mode 100644 index 000000000000..c7dc76fd26dc --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringFunctionCall.py @@ -0,0 +1,4 @@ +def f(): + return [1] + +"%s" % f() \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringKeyword.py b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringKeyword.py new file mode 100644 index 000000000000..f4152f4c7ce3 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringKeyword.py @@ -0,0 +1 @@ +v = "first is %(fst)s" % {"snd": 2} \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringPositional.py b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringPositional.py new file mode 100644 index 000000000000..36ad546c900a --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringPositional.py @@ -0,0 +1 @@ +v = '%s' % (1) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringReference.py b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringReference.py new file mode 100644 index 000000000000..6f3a61ff0013 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/percentStringReference.py @@ -0,0 +1,2 @@ +d = {"fst": 1, "snd": 2} +print "first is %(fst)s, second is %(snd)s" % d \ No newline at end of file diff --git a/python/testData/refactoring/rename/formatStringDictLiteral.py b/python/testData/refactoring/rename/formatStringDictLiteral.py new file mode 100644 index 000000000000..7431aef24f14 --- /dev/null +++ b/python/testData/refactoring/rename/formatStringDictLiteral.py @@ -0,0 +1 @@ +"I want to rename this{to_be_renamed}".format(**{"to_be_renamed": "value"}) \ No newline at end of file diff --git a/python/testData/refactoring/rename/formatStringNumericLiteralExpression.py b/python/testData/refactoring/rename/formatStringNumericLiteralExpression.py new file mode 100644 index 000000000000..ee028401dae1 --- /dev/null +++ b/python/testData/refactoring/rename/formatStringNumericLiteralExpression.py @@ -0,0 +1 @@ +print "first is {}, second is {}".format(1, 2) \ No newline at end of file diff --git a/python/testData/resolve/FormatDoubleStarArgument.py b/python/testData/resolve/FormatDoubleStarArgument.py deleted file mode 100644 index 7d836c3ea1dd..000000000000 --- a/python/testData/resolve/FormatDoubleStarArgument.py +++ /dev/null @@ -1 +0,0 @@ -print "first is {fst}, second is {snd}".format(**{"fst": "f", "snd": "s"}) \ No newline at end of file diff --git a/python/testData/resolve/FormatStringPackedDictCall.py b/python/testData/resolve/FormatStringPackedDictCall.py new file mode 100644 index 000000000000..674703380784 --- /dev/null +++ b/python/testData/resolve/FormatStringPackedDictCall.py @@ -0,0 +1 @@ +'{foo}'.format(**dict(foo="fo")) diff --git a/python/testData/resolve/PercentStringDictCall.py b/python/testData/resolve/PercentStringDictCall.py new file mode 100644 index 000000000000..f5250ac2e19d --- /dev/null +++ b/python/testData/resolve/PercentStringDictCall.py @@ -0,0 +1 @@ +"first is %(fst)s" % dict(fst="hello") \ No newline at end of file diff --git a/python/testData/resolve/PercentStringWithOneStringArgument.py b/python/testData/resolve/PercentStringWithOneStringArgument.py new file mode 100644 index 000000000000..9ccdf86b06e9 --- /dev/null +++ b/python/testData/resolve/PercentStringWithOneStringArgument.py @@ -0,0 +1 @@ +v = "%s" % "hello" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 126892f9af5e..d7cbfb6c97ac 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -611,70 +611,75 @@ public class PyResolveTest extends PyResolveTestCase { assertResolvesTo(PyFunction.class, "__rmatmul__"); } - //PY-2478 + //PY-2748 public void testFormatStringKWArgs() { PsiElement target = resolve(); assertTrue(target instanceof PyKeywordArgument); assertEquals("fst", ((PyKeywordArgument)target).getKeyword()); } - //PY-2478 + //PY-2748 public void testFormatPositionalArgs() { PsiElement target = resolve(); assertTrue(target instanceof PyReferenceExpression); assertEquals("string", target.getText()); } - //PY-2478 + //PY-2748 public void testFormatArgsAndKWargs() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); } - //PY-2478 + //PY-2748 public void testFormatArgsAndKWargs1() { PsiElement target = resolve(); assertTrue(target instanceof PyKeywordArgument); assertEquals("kwd", ((PyKeywordArgument)target).getKeyword()); } - + + //PY-2748 public void testFormatStringWithPackedDictAsArgument() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); assertEquals("\"fst\"", target.getText()); } + //PY-2748 public void testFormatStringWithPackedListAsArgument() { PsiElement target = resolve(); assertTrue(target instanceof PyNumericLiteralExpression); assertEquals("1", target.getText()); } + //PY-2748 public void testFormatStringWithPackedTupleAsArgument() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); assertEquals("\"snd\"", target.getText()); } - + + //PY-2748 public void testFormatStringWithBinExprAsArg() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); assertEquals("\"snd\"", target.getText()); } - + + //PY-2748 public void testFormatStringWithRefAsArgument() { PsiElement target = resolve(); assertEquals(null, target); } - //PY-2478 + //PY-2748 public void testPercentPositionalArgs() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); } - //PY-2478 + //PY-2748 public void testPercentKeyWordArgs() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); @@ -692,25 +697,44 @@ public class PyResolveTest extends PyResolveTestCase { assertEquals("snd", ((PyStringLiteralExpression)target).getStringValue()); } - //PY-2478 + //PY-2748 public void testPercentStringBinaryStatementArg() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); assertEquals("1", ((PyStringLiteralExpression)target).getStringValue()); } - //PY-2478 + //PY-2748 public void testPercentStringArgWithRedundantParentheses() { PsiElement target = resolve(); assertTrue(target instanceof PyStringLiteralExpression); assertEquals("1", ((PyStringLiteralExpression)target).getStringValue()); } - + + //PY-2748 public void testPercentStringWithRefAsArgument() { PsiElement target = resolve(); assertEquals(null, target); } - + + //PY-2748 + public void testPercentStringWithOneStringArgument() { + PsiElement target = resolve(); + assertEquals("hello", ((PyStringLiteralExpression)target).getStringValue()); + } + + //PY-2748 + public void testFormatStringPackedDictCall() { + PsiElement target = resolve(); + assertEquals("fo", ((PyStringLiteralExpression)((PyKeywordArgument)target).getValueExpression()).getStringValue()); + } + + //PY-2748 + public void testPercentStringDictCall() { + PsiElement target = resolve(); + assertEquals("hello", ((PyStringLiteralExpression)((PyKeywordArgument)target).getValueExpression()).getStringValue()); + } + public void testGlobalNotDefinedAtTopLevel() { assertResolvesTo(PyTargetExpression.class, "foo"); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 19b1c24fb1bf..5eb1fe6ca0be 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -537,6 +537,56 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-2748 + public void testFormatStringPackedDictCall() { + doTest(); + } + + // PY-2748 + public void testFormatStringPackedDict() { + doTest(); + } + + // PY-2748 + public void testFormatStringPositional() { + doTest(); + } + + // PY-2748 + public void testFormatStringKeyword() { + doTest(); + } + + // PY-2748 + public void testPercentStringPositional() { + doTest(); + } + + // PY-2748 + public void testPercentStringKeyword() { + doTest(); + } + + // PY-2748 + public void testFormatStringPackedFunctionCall() { + doTest(); + } + + // PY-2748 + public void testPercentStringFunctionCall() { + doTest(); + } + + // PY-2748 + public void testFormatStringPackedReference() { + doTest(); + } + + // PY-2748 + public void testPercentStringReference() { + doTest(); + } + // PY-18254 public void testVarargsAnnotatedWithFunctionComment() { doTest(); diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java index 0da795a760f2..88bbd18ca102 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java @@ -236,11 +236,39 @@ public class PyRenameTest extends PyTestCase { renameWithDocStringFormat(DocStringFormat.NUMPY, "bar"); } - //PY-2478 + //PY-2748 public void testFormatStringKeyword() { doTest("renamed"); } + //PY-2748 + public void testFormatStringDictLiteral() { + myFixture.configureByFile(RENAME_DATA_PATH + getTestName(true) + ".py"); + try { + myFixture.renameElementAtCaret("renamed"); + } + catch (RuntimeException e) { + if ("com.intellij.util.IncorrectOperationException".equals(e.getMessage())) { + return; + } + } + fail(); + } + + //PY-2748 + public void testFormatStringNumericLiteralExpression() { + myFixture.configureByFile(RENAME_DATA_PATH + getTestName(true) + ".py"); + try { + myFixture.renameElementAtCaret("renamed"); + } + catch (RuntimeException e) { + if ("com.intellij.util.IncorrectOperationException".equals(e.getMessage())) { + return; + } + } + fail(); + } + private void renameWithDocStringFormat(DocStringFormat format, final String newName) { runWithDocStringFormat(format, new Runnable() { public void run() {