mirror of https://github.com/astral-sh/ruff
[pyupgrade] Automatically rewrite format-strings to f-strings (#1905)
This commit is contained in:
parent
a4862857de
commit
1730f2a603
|
|
@ -725,6 +725,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||||
| UP028 | RewriteYieldFrom | Replace `yield` over `for` loop with `yield from` | 🛠 |
|
| UP028 | RewriteYieldFrom | Replace `yield` over `for` loop with `yield from` | 🛠 |
|
||||||
| UP029 | UnnecessaryBuiltinImport | Unnecessary builtin import: `...` | 🛠 |
|
| UP029 | UnnecessaryBuiltinImport | Unnecessary builtin import: `...` | 🛠 |
|
||||||
| UP030 | FormatLiterals | Use implicit references for positional format fields | 🛠 |
|
| UP030 | FormatLiterals | Use implicit references for positional format fields | 🛠 |
|
||||||
|
| UP032 | FString | Use f-string instead of `format` call | 🛠 |
|
||||||
|
|
||||||
### pep8-naming (N)
|
### pep8-naming (N)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
###
|
||||||
|
# Errors
|
||||||
|
###
|
||||||
|
|
||||||
|
"{} {}".format(a, b)
|
||||||
|
|
||||||
|
"{1} {0}".format(a, b)
|
||||||
|
|
||||||
|
"{x.y}".format(x=z)
|
||||||
|
|
||||||
|
"{.x} {.y}".format(a, b)
|
||||||
|
|
||||||
|
"{} {}".format(a.b, c.d)
|
||||||
|
|
||||||
|
"{}".format(a())
|
||||||
|
|
||||||
|
"{}".format(a.b())
|
||||||
|
|
||||||
|
"{}".format(a.b().c())
|
||||||
|
|
||||||
|
"hello {}!".format(name)
|
||||||
|
|
||||||
|
"{}{b}{}".format(a, c, b=b)
|
||||||
|
|
||||||
|
"{}".format(0x0)
|
||||||
|
|
||||||
|
"{} {}".format(a, b)
|
||||||
|
|
||||||
|
"""{} {}""".format(a, b)
|
||||||
|
|
||||||
|
"foo{}".format(1)
|
||||||
|
|
||||||
|
r"foo{}".format(1)
|
||||||
|
|
||||||
|
x = "{a}".format(a=1)
|
||||||
|
|
||||||
|
print("foo {} ".format(x))
|
||||||
|
|
||||||
|
"{a[b]}".format(a=a)
|
||||||
|
|
||||||
|
"{a.a[b]}".format(a=a)
|
||||||
|
|
||||||
|
"{}{{}}{}".format(escaped, y)
|
||||||
|
|
||||||
|
"{}".format(a)
|
||||||
|
|
||||||
|
###
|
||||||
|
# Non-errors
|
||||||
|
###
|
||||||
|
|
||||||
|
# False-negative: RustPython doesn't parse the `\N{snowman}`.
|
||||||
|
"\N{snowman} {}".format(a)
|
||||||
|
|
||||||
|
"{".format(a)
|
||||||
|
|
||||||
|
"}".format(a)
|
||||||
|
|
||||||
|
"{} {}".format(*a)
|
||||||
|
|
||||||
|
"{0} {0}".format(arg)
|
||||||
|
|
||||||
|
"{x} {x}".format(arg)
|
||||||
|
|
||||||
|
"{x.y} {x.z}".format(arg)
|
||||||
|
|
||||||
|
b"{} {}".format(a, b)
|
||||||
|
|
||||||
|
"{:{}}".format(x, y)
|
||||||
|
|
||||||
|
"{}{}".format(a)
|
||||||
|
|
||||||
|
"" "{}".format(a["\\"])
|
||||||
|
|
||||||
|
"{}".format(a["b"])
|
||||||
|
|
||||||
|
r'"\N{snowman} {}".format(a)'
|
||||||
|
|
||||||
|
"{a}" "{b}".format(a=1, b=1)
|
||||||
|
|
||||||
|
|
||||||
|
async def c():
|
||||||
|
return "{}".format(await 3)
|
||||||
|
|
||||||
|
|
||||||
|
async def c():
|
||||||
|
return "{}".format(1 + await 3)
|
||||||
|
|
@ -1687,6 +1687,7 @@
|
||||||
"UP029",
|
"UP029",
|
||||||
"UP03",
|
"UP03",
|
||||||
"UP030",
|
"UP030",
|
||||||
|
"UP032",
|
||||||
"W",
|
"W",
|
||||||
"W2",
|
"W2",
|
||||||
"W29",
|
"W29",
|
||||||
|
|
|
||||||
|
|
@ -1867,6 +1867,7 @@ where
|
||||||
|| self.settings.enabled.contains(&RuleCode::F525)
|
|| self.settings.enabled.contains(&RuleCode::F525)
|
||||||
// pyupgrade
|
// pyupgrade
|
||||||
|| self.settings.enabled.contains(&RuleCode::UP030)
|
|| self.settings.enabled.contains(&RuleCode::UP030)
|
||||||
|
|| self.settings.enabled.contains(&RuleCode::UP032)
|
||||||
{
|
{
|
||||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||||
if let ExprKind::Constant {
|
if let ExprKind::Constant {
|
||||||
|
|
@ -1890,8 +1891,8 @@ where
|
||||||
}
|
}
|
||||||
Ok(summary) => {
|
Ok(summary) => {
|
||||||
if self.settings.enabled.contains(&RuleCode::F522) {
|
if self.settings.enabled.contains(&RuleCode::F522) {
|
||||||
pyflakes::rules::string_dot_format_extra_named_arguments(self,
|
pyflakes::rules::string_dot_format_extra_named_arguments(
|
||||||
&summary, keywords, location,
|
self, &summary, keywords, location,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1917,6 +1918,10 @@ where
|
||||||
if self.settings.enabled.contains(&RuleCode::UP030) {
|
if self.settings.enabled.contains(&RuleCode::UP030) {
|
||||||
pyupgrade::rules::format_literals(self, &summary, expr);
|
pyupgrade::rules::format_literals(self, &summary, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.settings.enabled.contains(&RuleCode::UP032) {
|
||||||
|
pyupgrade::rules::f_strings(self, &summary, expr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,7 @@ pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
|
||||||
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
|
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
|
||||||
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
|
"u\"", "u'", "r\"", "r'", "u\"", "u'", "r\"", "r'", "U\"", "U'", "R\"", "R'", "\"", "'",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub const TRIPLE_QUOTE_SUFFIXES: &[&str] = &["\"\"\"", "'''"];
|
||||||
|
|
||||||
|
pub const SINGLE_QUOTE_SUFFIXES: &[&str] = &["\"", "'"];
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,7 @@ ruff_macros::define_rule_mapping!(
|
||||||
UP028 => violations::RewriteYieldFrom,
|
UP028 => violations::RewriteYieldFrom,
|
||||||
UP029 => violations::UnnecessaryBuiltinImport,
|
UP029 => violations::UnnecessaryBuiltinImport,
|
||||||
UP030 => violations::FormatLiterals,
|
UP030 => violations::FormatLiterals,
|
||||||
|
UP032 => violations::FString,
|
||||||
// pydocstyle
|
// pydocstyle
|
||||||
D100 => violations::PublicModule,
|
D100 => violations::PublicModule,
|
||||||
D101 => violations::PublicClass,
|
D101 => violations::PublicClass,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@ pub fn leading_quote(content: &str) -> Option<&str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the trailing quote string for a docstring (e.g., `"""`).
|
||||||
|
pub fn trailing_quote(content: &str) -> Option<&&str> {
|
||||||
|
constants::TRIPLE_QUOTE_SUFFIXES
|
||||||
|
.iter()
|
||||||
|
.chain(constants::SINGLE_QUOTE_SUFFIXES)
|
||||||
|
.find(|&pattern| content.ends_with(pattern))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the index of the first logical line in a string.
|
/// Return the index of the first logical line in a string.
|
||||||
pub fn logical_line(content: &str) -> Option<usize> {
|
pub fn logical_line(content: &str) -> Option<usize> {
|
||||||
// Find the first logical line.
|
// Find the first logical line.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ mod tests {
|
||||||
#[test_case(RuleCode::UP029, Path::new("UP029.py"); "UP029")]
|
#[test_case(RuleCode::UP029, Path::new("UP029.py"); "UP029")]
|
||||||
#[test_case(RuleCode::UP030, Path::new("UP030_0.py"); "UP030_0")]
|
#[test_case(RuleCode::UP030, Path::new("UP030_0.py"); "UP030_0")]
|
||||||
#[test_case(RuleCode::UP030, Path::new("UP030_1.py"); "UP030_1")]
|
#[test_case(RuleCode::UP030, Path::new("UP030_1.py"); "UP030_1")]
|
||||||
|
#[test_case(RuleCode::UP032, Path::new("UP032.py"); "UP032")]
|
||||||
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
|
fn rules(rule_code: RuleCode, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
use rustpython_ast::{Constant, Expr, ExprKind, KeywordData};
|
||||||
|
use rustpython_common::format::{
|
||||||
|
FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate,
|
||||||
|
};
|
||||||
|
use rustpython_parser::lexer;
|
||||||
|
use rustpython_parser::lexer::Tok;
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::fix::Fix;
|
||||||
|
use crate::registry::{Diagnostic, RuleCode};
|
||||||
|
use crate::rules::pydocstyle::helpers::{leading_quote, trailing_quote};
|
||||||
|
use crate::rules::pyflakes::format::FormatSummary;
|
||||||
|
use crate::violations;
|
||||||
|
|
||||||
|
/// Like [`FormatSummary`], but maps positional and keyword arguments to their
|
||||||
|
/// values. For example, given `{a} {b}".format(a=1, b=2)`, `FormatFunction`
|
||||||
|
/// would include `"a"` and `'b'` in `kwargs`, mapped to `1` and `2`
|
||||||
|
/// respectively.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FormatSummaryValues<'a> {
|
||||||
|
args: Vec<String>,
|
||||||
|
kwargs: FxHashMap<&'a str, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FormatSummaryValues<'a> {
|
||||||
|
fn try_from_expr(checker: &'a Checker, expr: &'a Expr) -> Option<Self> {
|
||||||
|
let mut extracted_args: Vec<String> = Vec::new();
|
||||||
|
let mut extracted_kwargs: FxHashMap<&str, String> = FxHashMap::default();
|
||||||
|
if let ExprKind::Call { args, keywords, .. } = &expr.node {
|
||||||
|
for arg in args {
|
||||||
|
let arg = checker
|
||||||
|
.locator
|
||||||
|
.slice_source_code_range(&Range::from_located(arg));
|
||||||
|
if contains_invalids(&arg) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
extracted_args.push(arg.to_string());
|
||||||
|
}
|
||||||
|
for keyword in keywords {
|
||||||
|
let KeywordData { arg, value } = &keyword.node;
|
||||||
|
if let Some(key) = arg {
|
||||||
|
let kwarg = checker
|
||||||
|
.locator
|
||||||
|
.slice_source_code_range(&Range::from_located(value));
|
||||||
|
if contains_invalids(&kwarg) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
extracted_kwargs.insert(key, kwarg.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if extracted_args.is_empty() && extracted_kwargs.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
args: extracted_args,
|
||||||
|
kwargs: extracted_kwargs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_next(&mut self) -> Option<String> {
|
||||||
|
if self.args.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.args.remove(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_arg(&mut self, index: usize) -> Option<String> {
|
||||||
|
if self.args.len() > index {
|
||||||
|
Some(self.args.remove(index))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_kwarg(&mut self, key: &str) -> Option<String> {
|
||||||
|
self.kwargs.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the string contains characters that are forbidden in
|
||||||
|
/// argument identifier.
|
||||||
|
fn contains_invalids(string: &str) -> bool {
|
||||||
|
string.contains('*')
|
||||||
|
|| string.contains('\'')
|
||||||
|
|| string.contains('"')
|
||||||
|
|| string.contains("await")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate an f-string from an [`Expr`].
|
||||||
|
fn try_convert_to_f_string(checker: &Checker, expr: &Expr) -> Option<String> {
|
||||||
|
let ExprKind::Call { func, .. } = &expr.node else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let ExprKind::Attribute { value, .. } = &func.node else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if !matches!(
|
||||||
|
&value.node,
|
||||||
|
ExprKind::Constant {
|
||||||
|
value: Constant::Str(..),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(mut summary) = FormatSummaryValues::try_from_expr(checker, expr) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = checker
|
||||||
|
.locator
|
||||||
|
.slice_source_code_range(&Range::from_located(value));
|
||||||
|
|
||||||
|
// Tokenize: we need to avoid trying to fix implicit string concatenations.
|
||||||
|
if lexer::make_tokenizer(&contents)
|
||||||
|
.flatten()
|
||||||
|
.filter(|(_, tok, _)| matches!(tok, Tok::String { .. }))
|
||||||
|
.count()
|
||||||
|
> 1
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip the unicode prefix. It's redundant in Python 3, and invalid when used
|
||||||
|
// with f-strings.
|
||||||
|
let contents = if contents.starts_with('U') || contents.starts_with('u') {
|
||||||
|
&contents[1..]
|
||||||
|
} else {
|
||||||
|
&contents
|
||||||
|
};
|
||||||
|
if contents.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the leading and trailing quotes.
|
||||||
|
let Some(leading_quote) = leading_quote(contents) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Some(trailing_quote) = trailing_quote(contents) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let contents = &contents[leading_quote.len()..contents.len() - trailing_quote.len()];
|
||||||
|
|
||||||
|
// Parse the format string.
|
||||||
|
let Ok(format_string) = FormatString::from_str(contents) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut converted = String::with_capacity(contents.len());
|
||||||
|
for part in format_string.format_parts {
|
||||||
|
match part {
|
||||||
|
FormatPart::Field {
|
||||||
|
field_name,
|
||||||
|
preconversion_spec,
|
||||||
|
format_spec,
|
||||||
|
} => {
|
||||||
|
converted.push('{');
|
||||||
|
|
||||||
|
let field = FieldName::parse(&field_name).ok()?;
|
||||||
|
match field.field_type {
|
||||||
|
FieldType::Auto => {
|
||||||
|
let Some(arg) = summary.consume_next() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
converted.push_str(&arg);
|
||||||
|
}
|
||||||
|
FieldType::Index(index) => {
|
||||||
|
let Some(arg) = summary.consume_arg(index) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
converted.push_str(&arg);
|
||||||
|
}
|
||||||
|
FieldType::Keyword(name) => {
|
||||||
|
let Some(arg) = summary.consume_kwarg(&name) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
converted.push_str(&arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in field.parts {
|
||||||
|
match part {
|
||||||
|
FieldNamePart::Attribute(name) => {
|
||||||
|
converted.push('.');
|
||||||
|
converted.push_str(&name);
|
||||||
|
}
|
||||||
|
FieldNamePart::Index(index) => {
|
||||||
|
converted.push('[');
|
||||||
|
converted.push_str(index.to_string().as_str());
|
||||||
|
converted.push(']');
|
||||||
|
}
|
||||||
|
FieldNamePart::StringIndex(index) => {
|
||||||
|
converted.push('[');
|
||||||
|
converted.push_str(&index);
|
||||||
|
converted.push(']');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(preconversion_spec) = preconversion_spec {
|
||||||
|
converted.push('!');
|
||||||
|
converted.push(preconversion_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !format_spec.is_empty() {
|
||||||
|
converted.push(':');
|
||||||
|
converted.push_str(&format_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
converted.push('}');
|
||||||
|
}
|
||||||
|
FormatPart::Literal(value) => {
|
||||||
|
if value.starts_with('{') {
|
||||||
|
converted.push('{');
|
||||||
|
}
|
||||||
|
converted.push_str(&value);
|
||||||
|
if value.ends_with('}') {
|
||||||
|
converted.push('}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the format string.
|
||||||
|
let mut contents = String::with_capacity(1 + converted.len());
|
||||||
|
contents.push('f');
|
||||||
|
contents.push_str(leading_quote);
|
||||||
|
contents.push_str(&converted);
|
||||||
|
contents.push_str(trailing_quote);
|
||||||
|
Some(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UP032
|
||||||
|
pub(crate) fn f_strings(checker: &mut Checker, summary: &FormatSummary, expr: &Expr) {
|
||||||
|
if summary.has_nested_parts {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid refactoring multi-line strings.
|
||||||
|
if expr.location.row() != expr.end_location.unwrap().row() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently, the only issue we know of is in LibCST:
|
||||||
|
// https://github.com/Instagram/LibCST/issues/846
|
||||||
|
let Some(contents) = try_convert_to_f_string(checker, expr) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Avoid refactors that increase the resulting string length.
|
||||||
|
let existing = checker
|
||||||
|
.locator
|
||||||
|
.slice_source_code_range(&Range::from_located(expr));
|
||||||
|
if contents.len() > existing.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut diagnostic = Diagnostic::new(violations::FString, Range::from_located(expr));
|
||||||
|
if checker.patch(&RuleCode::UP032) {
|
||||||
|
diagnostic.amend(Fix::replacement(
|
||||||
|
contents,
|
||||||
|
expr.location,
|
||||||
|
expr.end_location.unwrap(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ pub(crate) use convert_named_tuple_functional_to_class::convert_named_tuple_func
|
||||||
pub(crate) use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
|
pub(crate) use convert_typed_dict_functional_to_class::convert_typed_dict_functional_to_class;
|
||||||
pub(crate) use datetime_utc_alias::datetime_utc_alias;
|
pub(crate) use datetime_utc_alias::datetime_utc_alias;
|
||||||
pub(crate) use deprecated_unittest_alias::deprecated_unittest_alias;
|
pub(crate) use deprecated_unittest_alias::deprecated_unittest_alias;
|
||||||
|
pub(crate) use f_strings::f_strings;
|
||||||
pub(crate) use format_literals::format_literals;
|
pub(crate) use format_literals::format_literals;
|
||||||
pub(crate) use native_literals::native_literals;
|
pub(crate) use native_literals::native_literals;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
@ -31,7 +32,7 @@ pub(crate) use use_pep604_annotation::use_pep604_annotation;
|
||||||
pub(crate) use useless_metaclass_type::useless_metaclass_type;
|
pub(crate) use useless_metaclass_type::useless_metaclass_type;
|
||||||
pub(crate) use useless_object_inheritance::useless_object_inheritance;
|
pub(crate) use useless_object_inheritance::useless_object_inheritance;
|
||||||
|
|
||||||
use crate::ast::helpers::{self};
|
use crate::ast::helpers;
|
||||||
use crate::ast::types::{Range, Scope, ScopeKind};
|
use crate::ast::types::{Range, Scope, ScopeKind};
|
||||||
use crate::fix::Fix;
|
use crate::fix::Fix;
|
||||||
use crate::registry::Diagnostic;
|
use crate::registry::Diagnostic;
|
||||||
|
|
@ -41,6 +42,7 @@ mod convert_named_tuple_functional_to_class;
|
||||||
mod convert_typed_dict_functional_to_class;
|
mod convert_typed_dict_functional_to_class;
|
||||||
mod datetime_utc_alias;
|
mod datetime_utc_alias;
|
||||||
mod deprecated_unittest_alias;
|
mod deprecated_unittest_alias;
|
||||||
|
mod f_strings;
|
||||||
mod format_literals;
|
mod format_literals;
|
||||||
mod native_literals;
|
mod native_literals;
|
||||||
mod open_alias;
|
mod open_alias;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,362 @@
|
||||||
|
---
|
||||||
|
source: src/rules/pyupgrade/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 5
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 5
|
||||||
|
column: 20
|
||||||
|
fix:
|
||||||
|
content: "f\"{a} {b}\""
|
||||||
|
location:
|
||||||
|
row: 5
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 5
|
||||||
|
column: 20
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 7
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 7
|
||||||
|
column: 22
|
||||||
|
fix:
|
||||||
|
content: "f\"{b} {a}\""
|
||||||
|
location:
|
||||||
|
row: 7
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 7
|
||||||
|
column: 22
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 9
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 9
|
||||||
|
column: 19
|
||||||
|
fix:
|
||||||
|
content: "f\"{z.y}\""
|
||||||
|
location:
|
||||||
|
row: 9
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 9
|
||||||
|
column: 19
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 11
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 11
|
||||||
|
column: 24
|
||||||
|
fix:
|
||||||
|
content: "f\"{a.x} {b.y}\""
|
||||||
|
location:
|
||||||
|
row: 11
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 11
|
||||||
|
column: 24
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 13
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 24
|
||||||
|
fix:
|
||||||
|
content: "f\"{a.b} {c.d}\""
|
||||||
|
location:
|
||||||
|
row: 13
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 13
|
||||||
|
column: 24
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 15
|
||||||
|
column: 16
|
||||||
|
fix:
|
||||||
|
content: "f\"{a()}\""
|
||||||
|
location:
|
||||||
|
row: 15
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 15
|
||||||
|
column: 16
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 17
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 17
|
||||||
|
column: 18
|
||||||
|
fix:
|
||||||
|
content: "f\"{a.b()}\""
|
||||||
|
location:
|
||||||
|
row: 17
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 17
|
||||||
|
column: 18
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 19
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 19
|
||||||
|
column: 22
|
||||||
|
fix:
|
||||||
|
content: "f\"{a.b().c()}\""
|
||||||
|
location:
|
||||||
|
row: 19
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 19
|
||||||
|
column: 22
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 21
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 21
|
||||||
|
column: 24
|
||||||
|
fix:
|
||||||
|
content: "f\"hello {name}!\""
|
||||||
|
location:
|
||||||
|
row: 21
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 21
|
||||||
|
column: 24
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 27
|
||||||
|
fix:
|
||||||
|
content: "f\"{a}{b}{c}\""
|
||||||
|
location:
|
||||||
|
row: 23
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 23
|
||||||
|
column: 27
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 25
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 25
|
||||||
|
column: 16
|
||||||
|
fix:
|
||||||
|
content: "f\"{0x0}\""
|
||||||
|
location:
|
||||||
|
row: 25
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 25
|
||||||
|
column: 16
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 27
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 27
|
||||||
|
column: 20
|
||||||
|
fix:
|
||||||
|
content: "f\"{a} {b}\""
|
||||||
|
location:
|
||||||
|
row: 27
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 27
|
||||||
|
column: 20
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 29
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 24
|
||||||
|
fix:
|
||||||
|
content: "f\"\"\"{a} {b}\"\"\""
|
||||||
|
location:
|
||||||
|
row: 29
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 24
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 31
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 31
|
||||||
|
column: 17
|
||||||
|
fix:
|
||||||
|
content: "f\"foo{1}\""
|
||||||
|
location:
|
||||||
|
row: 31
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 31
|
||||||
|
column: 17
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 33
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 33
|
||||||
|
column: 18
|
||||||
|
fix:
|
||||||
|
content: "fr\"foo{1}\""
|
||||||
|
location:
|
||||||
|
row: 33
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 33
|
||||||
|
column: 18
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 35
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 35
|
||||||
|
column: 21
|
||||||
|
fix:
|
||||||
|
content: "f\"{1}\""
|
||||||
|
location:
|
||||||
|
row: 35
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 35
|
||||||
|
column: 21
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 37
|
||||||
|
column: 6
|
||||||
|
end_location:
|
||||||
|
row: 37
|
||||||
|
column: 25
|
||||||
|
fix:
|
||||||
|
content: "f\"foo {x} \""
|
||||||
|
location:
|
||||||
|
row: 37
|
||||||
|
column: 6
|
||||||
|
end_location:
|
||||||
|
row: 37
|
||||||
|
column: 25
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 39
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 39
|
||||||
|
column: 20
|
||||||
|
fix:
|
||||||
|
content: "f\"{a[b]}\""
|
||||||
|
location:
|
||||||
|
row: 39
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 39
|
||||||
|
column: 20
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 41
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 41
|
||||||
|
column: 22
|
||||||
|
fix:
|
||||||
|
content: "f\"{a.a[b]}\""
|
||||||
|
location:
|
||||||
|
row: 41
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 41
|
||||||
|
column: 22
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 43
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 43
|
||||||
|
column: 29
|
||||||
|
fix:
|
||||||
|
content: "f\"{escaped}{{}}{y}\""
|
||||||
|
location:
|
||||||
|
row: 43
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 43
|
||||||
|
column: 29
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
FString: ~
|
||||||
|
location:
|
||||||
|
row: 45
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 14
|
||||||
|
fix:
|
||||||
|
content: "f\"{a}\""
|
||||||
|
location:
|
||||||
|
row: 45
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 45
|
||||||
|
column: 14
|
||||||
|
parent: ~
|
||||||
|
|
||||||
|
|
@ -61,6 +61,15 @@ impl Default for Quote {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Quote> for char {
|
||||||
|
fn from(val: Quote) -> Self {
|
||||||
|
match val {
|
||||||
|
Quote::Single => '\'',
|
||||||
|
Quote::Double => '"',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&Quote> for vendor::str::Quote {
|
impl From<&Quote> for vendor::str::Quote {
|
||||||
fn from(val: &Quote) -> Self {
|
fn from(val: &Quote) -> Self {
|
||||||
match val {
|
match val {
|
||||||
|
|
|
||||||
|
|
@ -3797,6 +3797,23 @@ impl AlwaysAutofixableViolation for FormatLiterals {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
pub struct FString;
|
||||||
|
);
|
||||||
|
impl AlwaysAutofixableViolation for FString {
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"Use f-string instead of `format` call".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title(&self) -> String {
|
||||||
|
"Convert to f-string".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder() -> Self {
|
||||||
|
FString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pydocstyle
|
// pydocstyle
|
||||||
|
|
||||||
define_violation!(
|
define_violation!(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue