Move f-string identification into rule module (#3838)

This commit is contained in:
Charlie Marsh 2023-03-31 23:10:11 -04:00 committed by GitHub
parent 66d72b1c7b
commit b6276e2d95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 43 additions and 40 deletions

View file

@ -1,8 +1,9 @@
use rustpython_parser::ast::{Expr, ExprKind}; use rustpython_parser::ast::{Expr, ExprKind, Location};
use rustpython_parser::{lexer, Mode, StringKind, Tok};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::find_useless_f_strings; use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range; use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
@ -47,6 +48,45 @@ impl AlwaysAutofixableViolation for FStringMissingPlaceholders {
} }
} }
/// Find f-strings that don't contain any formatted values in a `JoinedStr`.
fn find_useless_f_strings<'a>(
expr: &'a Expr,
locator: &'a Locator,
) -> impl Iterator<Item = (Range, Range)> + 'a {
let contents = locator.slice(expr);
lexer::lex_located(contents, Mode::Module, expr.location)
.flatten()
.filter_map(|(location, tok, end_location)| match tok {
Tok::String {
kind: StringKind::FString | StringKind::RawFString,
..
} => {
let first_char = locator.slice(Range {
location,
end_location: Location::new(location.row(), location.column() + 1),
});
// f"..." => f_position = 0
// fr"..." => f_position = 0
// rf"..." => f_position = 1
let f_position = usize::from(!(first_char == "f" || first_char == "F"));
Some((
Range {
location: Location::new(location.row(), location.column() + f_position),
end_location: Location::new(
location.row(),
location.column() + f_position + 1,
),
},
Range {
location,
end_location,
},
))
}
_ => None,
})
}
fn unescape_f_string(content: &str) -> String { fn unescape_f_string(content: &str) -> String {
content.replace("{{", "{").replace("}}", "}") content.replace("{{", "{").replace("}}", "}")
} }

View file

@ -9,7 +9,7 @@ use rustpython_parser::ast::{
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
Located, Location, MatchCase, Pattern, PatternKind, Stmt, StmtKind, Located, Location, MatchCase, Pattern, PatternKind, Stmt, StmtKind,
}; };
use rustpython_parser::{lexer, Mode, StringKind, Tok}; use rustpython_parser::{lexer, Mode, Tok};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use crate::context::Context; use crate::context::Context;
@ -1055,43 +1055,6 @@ pub fn except_range(handler: &Excepthandler, locator: &Locator) -> Range {
range range
} }
/// Find f-strings that don't contain any formatted values in a `JoinedStr`.
pub fn find_useless_f_strings(expr: &Expr, locator: &Locator) -> Vec<(Range, Range)> {
let contents = locator.slice(expr);
lexer::lex_located(contents, Mode::Module, expr.location)
.flatten()
.filter_map(|(location, tok, end_location)| match tok {
Tok::String {
kind: StringKind::FString | StringKind::RawFString,
..
} => {
let first_char = locator.slice(Range {
location,
end_location: Location::new(location.row(), location.column() + 1),
});
// f"..." => f_position = 0
// fr"..." => f_position = 0
// rf"..." => f_position = 1
let f_position = usize::from(!(first_char == "f" || first_char == "F"));
Some((
Range {
location: Location::new(location.row(), location.column() + f_position),
end_location: Location::new(
location.row(),
location.column() + f_position + 1,
),
},
Range {
location,
end_location,
},
))
}
_ => None,
})
.collect()
}
/// Return the `Range` of `else` in `For`, `AsyncFor`, and `While` statements. /// Return the `Range` of `else` in `For`, `AsyncFor`, and `While` statements.
pub fn else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> { pub fn else_range(stmt: &Stmt, locator: &Locator) -> Option<Range> {
match &stmt.node { match &stmt.node {