PY-59594 PEP 701: Make the intention for flipping string quotes aware of the syntax

(cherry picked from commit a62f78cfdee24eccf3fcf2d341807565f26d2fbd)

IJ-MR-115763

GitOrigin-RevId: 059a289a9006d592a88a677137647c0660c68fc2
This commit is contained in:
Mikhail Golubev
2023-09-26 11:27:36 +03:00
committed by intellij-monorepo-bot
parent b3351b941e
commit 0d34a0e88f
18 changed files with 58 additions and 12 deletions

View File

@@ -11,6 +11,7 @@ import java.util.Collection;
import static com.jetbrains.python.psi.PyUtil.as;
public final class PyQuotesUtil {
private PyQuotesUtil() {
}
@@ -20,7 +21,7 @@ public final class PyQuotesUtil {
PyFormattedStringElement parentFString = PsiTreeUtil.getParentOfType(stringElement, PyFormattedStringElement.class, true,
PyStatement.class);
char targetQuote = PyStringLiteralUtil.flipQuote(stringElement.getQuote().charAt(0));
if (parentFString != null) {
if (parentFString != null && LanguageLevel.forElement(stringElement).isOlderThan(LanguageLevel.PYTHON312)) {
boolean parentFStringUsesTargetQuotes = parentFString.getQuote().equals(Character.toString(targetQuote));
if (parentFStringUsesTargetQuotes) return false;
boolean conversionIntroducesBackslashEscapedQuote = stringElement.textContains(targetQuote);
@@ -51,11 +52,17 @@ public final class PyQuotesUtil {
processStringElementText(builder, stringElement.getText(), originalQuote);
}
else {
boolean fStringCanIncludeArbitraryStringLiterals = LanguageLevel.forElement(stringElement).isAtLeast(LanguageLevel.PYTHON312);
stringElement.acceptChildren(new PyRecursiveElementVisitor() {
@Override
public void visitElement(@NotNull PsiElement element) {
if (element instanceof PyStringElement) {
processStringElement(builder, (PyStringElement)element);
if (fStringCanIncludeArbitraryStringLiterals) {
builder.append(element.getText());
}
else {
processStringElement(builder, (PyStringElement)element);
}
}
else if (PyTokenTypes.FSTRING_TOKENS.contains(element.getNode().getElementType())) {
processStringElementText(builder, element.getText(), originalQuote);

View File

@@ -0,0 +1 @@
s = f"<caret>foo{'bar'}baz"

View File

@@ -0,0 +1 @@
s = f'<caret>foo{"bar"}baz'

View File

@@ -1 +1 @@
s = f'<caret>foo{"bar"}baz'
s = f'foo{'bar'}baz'

View File

@@ -0,0 +1,3 @@
s = (f<caret>'{"foo"}'
f'{"bar"}'
f'{"baz"}')

View File

@@ -0,0 +1,3 @@
s = (f"{'foo'}"
f"{'bar'}"
f"{'baz'}")

View File

@@ -1,3 +1,3 @@
s = (f"{'foo'}"
f"{'bar'}"
f"{'baz'}")
s = (f"{"foo"}"
f"{"bar"}"
f"{"baz"}")

View File

@@ -0,0 +1 @@
s = f"""foo{'<caret>ba"r'}baz"""

View File

@@ -0,0 +1 @@
s = f"""foo{"ba\"r"}baz"""

View File

@@ -0,0 +1 @@
s = f"foo{'<caret>bar'}baz"

View File

@@ -0,0 +1 @@
s = f"foo{"bar"}baz"

View File

@@ -0,0 +1 @@
s = rf'f<caret>oo{"bar"}'

View File

@@ -0,0 +1 @@
s = rf"f<caret>oo{'bar'}"

View File

@@ -1 +1 @@
s = rf"f<caret>oo{'bar'}"
s = rf"f<caret>oo{"bar"}"

View File

@@ -224,22 +224,37 @@ public class PyIntentionTest extends PyTestCase {
}
// PY-30798
public void testConvertingRawFStringQuotesBefore312() {
runWithLanguageLevel(LanguageLevel.PYTHON311, () -> doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double")));
}
// PY-59594
public void testConvertingRawFStringQuotes() {
doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
}
// PY-30798
public void testConvertingQuotesNotSuggestedForStringInsideFStringWithOppositeQuotes() {
doNegativeTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
public void testConvertingQuotesNotSuggestedForStringInsideFStringWithOppositeQuotesBefore312() {
runWithLanguageLevel(LanguageLevel.PYTHON311, () -> doNegativeTest(PyPsiBundle.message("INTN.quoted.string.single.to.double")));
}
// PY-59594
public void testConvertingQuotesOfStringInsideFStringWithOppositeQuotes() {
doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
}
// PY-30798
public void testConvertingQuotesNotSuggestedForStringInsideFStringThatWouldRequireEscapingInsideFragment() {
doNegativeTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
public void testConvertingQuotesNotSuggestedForStringContainingQuotesOfParentFStringBefore312() {
runWithLanguageLevel(LanguageLevel.PYTHON311, () -> doNegativeTest(PyPsiBundle.message("INTN.quoted.string.single.to.double")));
}
// PY-59594
public void testConvertingQuotesOfStringContainingQuotesOfParentFString() {
doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
}
// PY-30798
public void testConvertingQuotesNotSuggestedForFStringContainingStringWithInconvertibleQuotes() {
public void testConvertingQuotesNotSuggestedForFStringContainingTripleQuotedString() {
doNegativeTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
}
@@ -249,6 +264,11 @@ public class PyIntentionTest extends PyTestCase {
}
// PY-30798
public void testConvertingQuotesOfFStringContainingOtherStringsBefore312() {
runWithLanguageLevel(LanguageLevel.PYTHON311, () -> doTest(PyPsiBundle.message("INTN.quoted.string.double.to.single")));
}
// PY-59594
public void testConvertingQuotesOfFStringContainingOtherStrings() {
doTest(PyPsiBundle.message("INTN.quoted.string.double.to.single"));
}
@@ -259,6 +279,11 @@ public class PyIntentionTest extends PyTestCase {
}
// PY-30798
public void testConvertingQuotesOfGluedFStringContainingOtherStringsBefore312() {
runWithLanguageLevel(LanguageLevel.PYTHON311, () -> doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double")));
}
// PY-59594
public void testConvertingQuotesOfGluedFStringContainingOtherStrings() {
doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
}