mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-21161 Convert to f-string literal for printf-style formatting
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
f"Pi is {math.pi:03.5}"
|
||||
f'{"eggs"}'
|
||||
@@ -0,0 +1,2 @@
|
||||
"Pi is {:03.5}".format(math.pi)
|
||||
'%(spam)s' % {'spam': 'eggs'}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention converts string formatting via format() method and format
|
||||
operator to Python 3.6 f-string literals.
|
||||
<!-- tooltip end -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -310,6 +310,11 @@
|
||||
<className>com.jetbrains.python.codeInsight.intentions.PyYieldFromIntention</className>
|
||||
<category>Python</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.jetbrains.python.codeInsight.intentions.PyConvertToFStringIntention</className>
|
||||
<category>Python</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.jetbrains.python.codeInsight.intentions.PyConvertTypeCommentToVariableAnnotationIntention</className>
|
||||
|
||||
@@ -219,7 +219,11 @@ INTN.string.concatenation.to.format=Replace string concatenation with format ope
|
||||
INTN.replace.plus.with.format.operator=Replace + with string formatting operator
|
||||
INTN.replace.plus.with.str.format=Replace + with str.format method call
|
||||
|
||||
# ConvertFormatOperatorToMethodIntention
|
||||
INTN.format.operator.to.method=Convert format operator usage to str.format method call
|
||||
|
||||
# PyConvertToFStringIntention
|
||||
INTN.convert.to.fstring.literal=Convert to f-string literal
|
||||
INTN.replace.with.method=Replace with str.format method call
|
||||
|
||||
INTN.flip.comparison=Flip comparison
|
||||
|
||||
+6
-6
@@ -37,23 +37,23 @@ public class PythonFormattedStringReferenceProvider extends PsiReferenceProvider
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiReference[] getReferencesFromFormatString(@NotNull final PyStringLiteralExpression element) {
|
||||
private static PySubstitutionChunkReference[] getReferencesFromFormatString(@NotNull final PyStringLiteralExpression element) {
|
||||
final List<PyStringFormatParser.SubstitutionChunk> chunks = PyStringFormatParser.filterSubstitutions(
|
||||
PyStringFormatParser.parseNewStyleFormat(element.getStringValue()));
|
||||
return getReferencesFromChunks(element, chunks, false);
|
||||
}
|
||||
|
||||
private static PsiReference[] getReferencesFromPercentString(@NotNull final PyStringLiteralExpression element) {
|
||||
private static PySubstitutionChunkReference[] getReferencesFromPercentString(@NotNull final PyStringLiteralExpression element) {
|
||||
final List<PyStringFormatParser.SubstitutionChunk>
|
||||
chunks = PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parsePercentFormat(element.getStringValue()));
|
||||
return getReferencesFromChunks(element, chunks, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiReference[] getReferencesFromChunks(@NotNull final PyStringLiteralExpression element,
|
||||
@NotNull final List<PyStringFormatParser.SubstitutionChunk> chunks,
|
||||
boolean isPercent) {
|
||||
final PsiReference[] result = new PsiReference[chunks.size()];
|
||||
public static PySubstitutionChunkReference[] getReferencesFromChunks(@NotNull final PyStringLiteralExpression element,
|
||||
@NotNull final List<PyStringFormatParser.SubstitutionChunk> chunks,
|
||||
boolean isPercent) {
|
||||
final PySubstitutionChunkReference[] result = new PySubstitutionChunkReference[chunks.size()];
|
||||
for (int i = 0; i < chunks.size(); i++) {
|
||||
final PyStringFormatParser.SubstitutionChunk chunk = chunks.get(i);
|
||||
result[i] = new PySubstitutionChunkReference(element, chunk, i, isPercent);
|
||||
|
||||
+25
-11
@@ -36,6 +36,7 @@ import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -148,17 +149,7 @@ public class ConvertFormatOperatorToMethodIntention extends PyBaseIntentionActio
|
||||
// don't convert %s -> !s, for %s is the normal way to output the default representation
|
||||
out.append(":");
|
||||
if (f_modifier != null) {
|
||||
// in strict order
|
||||
if (has(f_modifier, '-')) out.append("<"); // left align
|
||||
else if ("s".equals(fConversion) && !StringUtil.isEmptyOrSpaces(f_width)) {
|
||||
// "%20s" aligns right, "{0:20s}" aligns left; to preserve align, make it explicit
|
||||
out.append(">");
|
||||
}
|
||||
if (has(f_modifier, '+')) out.append("+"); // signed
|
||||
else if (has(f_modifier, ' ')) out.append(" "); // default-signed
|
||||
if (has(f_modifier, '#')) out.append("#"); // alt numbers
|
||||
if (has(f_modifier, '0')) out.append("0"); // padding
|
||||
// anything else can't be here
|
||||
out.append(convertFormatSpec(f_modifier, f_width, fConversion));
|
||||
}
|
||||
if (f_width != null) {
|
||||
out.append(f_width);
|
||||
@@ -209,6 +200,29 @@ public class ConvertFormatOperatorToMethodIntention extends PyBaseIntentionActio
|
||||
return new Pair<>(result, usesNamedFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String convertFormatSpec(@NotNull String modifier,
|
||||
@Nullable String widthAndPrecision,
|
||||
@Nullable String conversionChar) {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
// in strict order
|
||||
if (has(modifier, '-')) {
|
||||
result.append("<"); // left align
|
||||
}
|
||||
else if ("s".equals(conversionChar) && !StringUtil.isEmptyOrSpaces(widthAndPrecision)) {
|
||||
// "%20s" aligns right, "{0:20s}" aligns left; to preserve align, make it explicit
|
||||
result.append(">");
|
||||
}
|
||||
if (has(modifier, '+')) {
|
||||
result.append("+"); // signed
|
||||
}
|
||||
else if (has(modifier, ' ')) result.append(" "); // default-signed
|
||||
if (has(modifier, '#')) result.append("#"); // alt numbers
|
||||
if (has(modifier, '0')) result.append("0"); // padding
|
||||
// anything else can't be here
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static boolean has(String where, char what) {
|
||||
return where.indexOf(what) >= 0;
|
||||
}
|
||||
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.codeInsight.intentions;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
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.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.codeInsight.PySubstitutionChunkReference;
|
||||
import com.jetbrains.python.codeInsight.PythonFormattedStringReferenceProvider;
|
||||
import com.jetbrains.python.inspections.PyStringFormatParser;
|
||||
import com.jetbrains.python.inspections.PyStringFormatParser.ConstantChunk;
|
||||
import com.jetbrains.python.inspections.PyStringFormatParser.FormatStringChunk;
|
||||
import com.jetbrains.python.inspections.PyStringFormatParser.SubstitutionChunk;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.PyUtil.StringNodeInfo;
|
||||
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyConvertToFStringIntention extends PyBaseIntentionAction {
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return PyBundle.message("INTN.convert.to.fstring.literal");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
return getFamilyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
if (!(file instanceof PyFile) || !LanguageLevel.forElement(file).isAtLeast(LanguageLevel.PYTHON36)) return false;
|
||||
|
||||
final Pair<PyStringLiteralExpression, Boolean> pair = findTargetStringUnderCaret(editor, file);
|
||||
if (pair == null) return false;
|
||||
|
||||
final PyStringLiteralExpression pyString = pair.getFirst();
|
||||
final boolean percentOperator = pair.getSecond();
|
||||
|
||||
// TODO handle "glued" literals
|
||||
if (pyString != null && pyString.getStringNodes().size() == 1) {
|
||||
final String stringText = pyString.getText();
|
||||
final String prefix = PyStringLiteralUtil.getPrefix(stringText);
|
||||
if (PyStringLiteralUtil.isBytesPrefix(prefix) || PyStringLiteralUtil.isFormattedPrefix(prefix)) {
|
||||
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;
|
||||
if (percentOperator) {
|
||||
hasDynamicFormatting = substitutions.stream().anyMatch(s -> "*".equals(s.getWidth()) || "*".equals(s.getPrecision()));
|
||||
}
|
||||
else {
|
||||
hasDynamicFormatting = false;
|
||||
}
|
||||
if (hasDynamicFormatting) return false;
|
||||
|
||||
final PySubstitutionChunkReference[] references =
|
||||
PythonFormattedStringReferenceProvider.getReferencesFromChunks(pyString, substitutions, percentOperator);
|
||||
|
||||
final PsiElement valuesSource;
|
||||
if (percentOperator) {
|
||||
final PyBinaryExpression binaryExpression = as(pyString.getParent(), PyBinaryExpression.class);
|
||||
assert binaryExpression != null;
|
||||
valuesSource = binaryExpression.getRightExpression();
|
||||
}
|
||||
else {
|
||||
final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(pyString, PyCallExpression.class);
|
||||
assert callExpression != null;
|
||||
valuesSource = callExpression.getArgumentList();
|
||||
}
|
||||
|
||||
return Arrays.stream(references)
|
||||
.map(PyConvertToFStringIntention::getActualReplacementExpression)
|
||||
.allMatch(element -> element != null &&
|
||||
!(element instanceof PyStarExpression) &&
|
||||
!(element instanceof PyStarArgument) &&
|
||||
PsiTreeUtil.isAncestor(valuesSource, element, false) &&
|
||||
expressionCanBeInlined(pyString, element));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
return adjustQuotesInside((PyExpression)target.copy(), host) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiElement adjustQuotesInside(@NotNull PyExpression element, @NotNull PyStringLiteralExpression host) {
|
||||
final StringNodeInfo hostInfo = new StringNodeInfo(host.getStringNodes().get(0));
|
||||
final char hostQuote = hostInfo.getSingleQuote();
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(host.getProject());
|
||||
|
||||
final Collection<PyStringLiteralExpression> innerStrings = PsiTreeUtil.collectElementsOfType(element, PyStringLiteralExpression.class);
|
||||
for (PyStringLiteralExpression literal : innerStrings) {
|
||||
final List<ASTNode> nodes = literal.getStringNodes();
|
||||
// TODO figure out what to do with those
|
||||
if (nodes.size() > 1) {
|
||||
return null;
|
||||
}
|
||||
final StringNodeInfo info = new StringNodeInfo(nodes.get(0));
|
||||
// Nest string contain the same type of quote as host string inside, and we cannot escape inside f-string -- retreat
|
||||
final String content = info.getContent();
|
||||
if (content.indexOf(hostQuote) >= 0) {
|
||||
return null;
|
||||
}
|
||||
if (!info.isTerminated()) {
|
||||
return null;
|
||||
}
|
||||
if (info.getSingleQuote() == hostQuote) {
|
||||
final char targetSingleQuote = hostQuote == '"' ? '\'' : '"';
|
||||
final String targetQuote = info.getQuote().replace(hostQuote, targetSingleQuote);
|
||||
final String stringWithSwappedQuotes = info.getPrefix() + targetQuote + content + targetQuote;
|
||||
final PsiElement replaced = literal.replace(generator.createStringLiteralAlreadyEscaped(stringWithSwappedQuotes));
|
||||
if (literal == element) {
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<PyStringLiteralExpression, Boolean> findTargetStringUnderCaret(@NotNull Editor editor, @NotNull PsiFile file) {
|
||||
final PsiElement anchor = file.findElementAt(editor.getCaretModel().getOffset());
|
||||
if (anchor == null) return null;
|
||||
|
||||
final PyBinaryExpression binaryExpr = PsiTreeUtil.getParentOfType(anchor, PyBinaryExpression.class);
|
||||
if (binaryExpr != null && binaryExpr.getOperator() == PyTokenTypes.PERC) {
|
||||
final PyStringLiteralExpression pyString = as(binaryExpr.getLeftExpression(), PyStringLiteralExpression.class);
|
||||
if (pyString != null) {
|
||||
return Pair.create(pyString, true);
|
||||
}
|
||||
}
|
||||
final PyCallExpression callExpr = PsiTreeUtil.getParentOfType(anchor, PyCallExpression.class);
|
||||
if (callExpr != null) {
|
||||
final PyReferenceExpression callee = as(callExpr.getCallee(), PyReferenceExpression.class);
|
||||
if (callee != null) {
|
||||
final PyStringLiteralExpression pyString = as(callee.getQualifier(), PyStringLiteralExpression.class);
|
||||
if (pyString != null) {
|
||||
return Pair.create(pyString, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInvoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
final Pair<PyStringLiteralExpression, Boolean> pair = findTargetStringUnderCaret(editor, file);
|
||||
assert pair != null;
|
||||
final Boolean percentOperator = pair.getSecond();
|
||||
if (percentOperator) {
|
||||
convertPercentOperatorFormatting(pair.getFirst());
|
||||
}
|
||||
else {
|
||||
convertFormatMethodFormatting(pair.getFirst());
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertPercentOperatorFormatting(@NotNull PyStringLiteralExpression pyString) {
|
||||
final String stringText = pyString.getText();
|
||||
final Pair<String, String> quotes = PyStringLiteralUtil.getQuotes(stringText);
|
||||
assert quotes != null;
|
||||
final StringBuilder result = new StringBuilder();
|
||||
result.append("f");
|
||||
result.append(quotes.getFirst().replaceAll("[uU]", ""));
|
||||
final List<FormatStringChunk> chunks = PyStringFormatParser.parsePercentFormat(stringText);
|
||||
final TextRange contentRange = PyStringLiteralExpressionImpl.getNodeTextRange(stringText);
|
||||
int subsChunkPosition = 0;
|
||||
for (FormatStringChunk chunk : chunks) {
|
||||
if (chunk instanceof ConstantChunk) {
|
||||
final TextRange rangeWithoutQuotes = chunk.getTextRange().intersection(contentRange);
|
||||
assert rangeWithoutQuotes != null;
|
||||
result.append(rangeWithoutQuotes.substring(stringText));
|
||||
}
|
||||
else {
|
||||
final SubstitutionChunk subsChunk = (SubstitutionChunk)chunk;
|
||||
final char conversionChar = subsChunk.getConversionType();
|
||||
final String widthAndPrecision;
|
||||
if (StringUtil.isNotEmpty(subsChunk.getWidth()) || StringUtil.isNotEmpty(subsChunk.getPrecision())) {
|
||||
widthAndPrecision = StringUtil.notNullize(subsChunk.getWidth()) + "." + StringUtil.notNullize(subsChunk.getPrecision());
|
||||
}
|
||||
else {
|
||||
widthAndPrecision = "";
|
||||
}
|
||||
final String conversionFlags = subsChunk.getConversionFlags();
|
||||
|
||||
result.append("{");
|
||||
final PySubstitutionChunkReference reference = new PySubstitutionChunkReference(pyString, subsChunk, subsChunkPosition, true);
|
||||
final PyExpression resolveResult = getActualReplacementExpression(reference);
|
||||
assert resolveResult != null;
|
||||
|
||||
final PsiElement adjusted = adjustQuotesInside(resolveResult, pyString);
|
||||
if (adjusted == null) return;
|
||||
|
||||
result.append(adjusted.getText());
|
||||
|
||||
// TODO mostly duplicates the logic of ConvertFormatOperatorToMethodIntention
|
||||
if (conversionChar == 'r') {
|
||||
result.append("!r");
|
||||
}
|
||||
|
||||
if ((conversionChar != 'r' && conversionChar != 's')
|
||||
|| StringUtil.isNotEmpty(conversionFlags)
|
||||
|| StringUtil.isNotEmpty(widthAndPrecision)) {
|
||||
result.append(":");
|
||||
}
|
||||
|
||||
if (StringUtil.isNotEmpty(conversionFlags)) {
|
||||
final String conversionStr = String.valueOf(conversionChar);
|
||||
result.append(ConvertFormatOperatorToMethodIntention.convertFormatSpec(conversionFlags, widthAndPrecision, conversionStr));
|
||||
}
|
||||
|
||||
if (StringUtil.isNotEmpty(widthAndPrecision)) {
|
||||
result.append(widthAndPrecision);
|
||||
}
|
||||
|
||||
if ('i' == conversionChar || 'u' == conversionChar) {
|
||||
result.append("d");
|
||||
}
|
||||
else if ('s' != conversionChar && 'r' != conversionChar) {
|
||||
result.append(conversionChar);
|
||||
}
|
||||
result.append("}");
|
||||
subsChunkPosition++;
|
||||
}
|
||||
}
|
||||
result.append(quotes.getSecond());
|
||||
|
||||
final PyBinaryExpression expressionToReplace = PsiTreeUtil.getParentOfType(pyString, PyBinaryExpression.class);
|
||||
assert expressionToReplace != null;
|
||||
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(pyString.getProject());
|
||||
final PyExpression fString = generator.createExpressionFromText(LanguageLevel.PYTHON36, result.toString());
|
||||
expressionToReplace.replace(fString);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyExpression getActualReplacementExpression(@NotNull PySubstitutionChunkReference reference) {
|
||||
final PsiElement resolveResult = reference.resolve();
|
||||
if (resolveResult == null) {
|
||||
return null;
|
||||
}
|
||||
final PyKeywordArgument argument = as(resolveResult, PyKeywordArgument.class);
|
||||
if (argument != null) {
|
||||
return argument.getValueExpression();
|
||||
}
|
||||
final PyKeyValueExpression parent = as(resolveResult.getParent(), PyKeyValueExpression.class);
|
||||
if (parent != null && parent.getKey() == resolveResult) {
|
||||
return parent.getValue();
|
||||
}
|
||||
return as(resolveResult, PyExpression.class);
|
||||
}
|
||||
|
||||
private void convertFormatMethodFormatting(@NotNull PyStringLiteralExpression pyString) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
b'{}'.format(42)
|
||||
@@ -0,0 +1 @@
|
||||
'{}' '{}'.format('foo', 42)
|
||||
+1
@@ -0,0 +1 @@
|
||||
"%.*s" % (5, 'spam')
|
||||
+1
@@ -0,0 +1 @@
|
||||
"%*s" % (5, 'spam')
|
||||
+1
@@ -0,0 +1 @@
|
||||
'%s' % "'"
|
||||
+1
@@ -0,0 +1 @@
|
||||
'%s' % '\n'
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
'%s' % func(1,
|
||||
2)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
'%s %s %s' % (
|
||||
d[r'foo'][b'bar']['baz'],
|
||||
u'''[]''',
|
||||
f('foo', g(rf'bar'))
|
||||
)
|
||||
+1
@@ -0,0 +1 @@
|
||||
f'{d[r"foo"][b"bar"]["baz"]} {u"""[]"""} {f("foo", g(rf"bar"))}'
|
||||
+1
@@ -0,0 +1 @@
|
||||
'%(foo)d %(bar)s' % dict(foo=.25, bar='spam'.upper())
|
||||
+1
@@ -0,0 +1 @@
|
||||
f'{.25:d} {"spam".upper()}'
|
||||
+1
@@ -0,0 +1 @@
|
||||
"%(foo)s %(bar)r" % {'foo': 'bar', 'bar': [1, 2, 3]}
|
||||
+1
@@ -0,0 +1 @@
|
||||
f"{'bar'} {[1, 2, 3]!r}"
|
||||
+1
@@ -0,0 +1 @@
|
||||
u'%s %d %f' % ("foo", 42, 2.5)
|
||||
+1
@@ -0,0 +1 @@
|
||||
f'{"foo"} {42:d} {2.5:f}'
|
||||
+1
@@ -0,0 +1 @@
|
||||
u'%04.5r' % 42
|
||||
+1
@@ -0,0 +1 @@
|
||||
f'{42!r:04.5}'
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.intentions;
|
||||
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyConvertToFStringIntentionTest extends PyIntentionTestCase {
|
||||
|
||||
private void doTest() {
|
||||
doTest(PyBundle.message("INTN.convert.to.fstring.literal"), LanguageLevel.PYTHON36);
|
||||
}
|
||||
|
||||
private void doNegativeTest() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36, () -> doNegativeTest(PyBundle.message("INTN.convert.to.fstring.literal")));
|
||||
}
|
||||
|
||||
public void testFormatMethodByteString() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testFormatMethodGluedString() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorDynamicWidth() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorDynamicPrecision() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorSingleExpression() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorSimpleTuple() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorSimpleDictLiteral() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorSimpleDictConstructorCall() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorQuotesInsideInlinedExpressions() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorMultilineExpression() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorExpressionWithBackslash() {
|
||||
doNegativeTest();
|
||||
}
|
||||
|
||||
public void testPercentOperatorExpressionContainBadQuotes() {
|
||||
doNegativeTest();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user