mirror of https://github.com/astral-sh/ruff
Implement Flynt static string join transform as FLY002 (#4196)
This commit is contained in:
parent
61f21a6513
commit
efdf383f5e
|
|
@ -0,0 +1,18 @@
|
||||||
|
import secrets
|
||||||
|
from random import random, choice
|
||||||
|
|
||||||
|
a = "Hello"
|
||||||
|
ok1 = " ".join([a, " World"]) # OK
|
||||||
|
ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
|
||||||
|
nok1 = "x".join({"4", "5", "yee"}) # Not OK (set)
|
||||||
|
nok2 = a.join(["1", "2", "3"]) # Not OK (not a static joiner)
|
||||||
|
nok3 = "a".join(a) # Not OK (not a static joinee)
|
||||||
|
nok4 = "a".join([a, a, *a]) # Not OK (not a static length)
|
||||||
|
nok5 = "a".join([choice("flarp")]) # Not OK (not a simple call)
|
||||||
|
nok6 = "a".join(x for x in "feefoofum") # Not OK (generator)
|
||||||
|
nok7 = "a".join([f"foo{8}", "bar"]) # Not OK (contains an f-string)
|
||||||
|
|
@ -45,9 +45,9 @@ use crate::rules::{
|
||||||
flake8_django, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
|
flake8_django, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
|
||||||
flake8_import_conventions, flake8_logging_format, flake8_pie, flake8_print, flake8_pyi,
|
flake8_import_conventions, flake8_logging_format, flake8_pie, flake8_print, flake8_pyi,
|
||||||
flake8_pytest_style, flake8_raise, flake8_return, flake8_self, flake8_simplify,
|
flake8_pytest_style, flake8_raise, flake8_return, flake8_self, flake8_simplify,
|
||||||
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, flake8_use_pathlib, mccabe,
|
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, flake8_use_pathlib, flynt,
|
||||||
numpy, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint,
|
mccabe, numpy, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks,
|
||||||
pyupgrade, ruff, tryceratops,
|
pylint, pyupgrade, ruff, tryceratops,
|
||||||
};
|
};
|
||||||
use crate::settings::types::PythonVersion;
|
use crate::settings::types::PythonVersion;
|
||||||
use crate::settings::{flags, Settings};
|
use crate::settings::{flags, Settings};
|
||||||
|
|
@ -2471,6 +2471,8 @@ where
|
||||||
// pyupgrade
|
// pyupgrade
|
||||||
Rule::FormatLiterals,
|
Rule::FormatLiterals,
|
||||||
Rule::FString,
|
Rule::FString,
|
||||||
|
// flynt
|
||||||
|
Rule::StaticJoinToFString,
|
||||||
]) {
|
]) {
|
||||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||||
if let ExprKind::Constant {
|
if let ExprKind::Constant {
|
||||||
|
|
@ -2478,7 +2480,12 @@ where
|
||||||
..
|
..
|
||||||
} = &value.node
|
} = &value.node
|
||||||
{
|
{
|
||||||
if attr == "format" {
|
if attr == "join" {
|
||||||
|
// "...".join(...) call
|
||||||
|
if self.settings.rules.enabled(Rule::StaticJoinToFString) {
|
||||||
|
flynt::rules::static_join_to_fstring(self, expr, value);
|
||||||
|
}
|
||||||
|
} else if attr == "format" {
|
||||||
// "...".format(...) call
|
// "...".format(...) call
|
||||||
let location = expr.range();
|
let location = expr.range();
|
||||||
match pyflakes::format::FormatSummary::try_from(value.as_ref()) {
|
match pyflakes::format::FormatSummary::try_from(value.as_ref()) {
|
||||||
|
|
|
||||||
|
|
@ -737,6 +737,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
|
||||||
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
|
(Flake8Django, "012") => Rule::DjangoUnorderedBodyContentInModel,
|
||||||
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
|
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
|
||||||
|
|
||||||
|
// flynt
|
||||||
|
// Reserved: (Flynt, "001") => Rule::StringConcatenationToFString,
|
||||||
|
(Flynt, "002") => Rule::StaticJoinToFString,
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -670,6 +670,8 @@ ruff_macros::register_rules!(
|
||||||
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
|
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
|
||||||
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
|
rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
|
||||||
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
|
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
|
||||||
|
// flynt
|
||||||
|
rules::flynt::rules::StaticJoinToFString,
|
||||||
);
|
);
|
||||||
|
|
||||||
pub trait AsRule {
|
pub trait AsRule {
|
||||||
|
|
@ -825,6 +827,9 @@ pub enum Linter {
|
||||||
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
|
/// [tryceratops](https://pypi.org/project/tryceratops/1.1.0/)
|
||||||
#[prefix = "TRY"]
|
#[prefix = "TRY"]
|
||||||
Tryceratops,
|
Tryceratops,
|
||||||
|
/// [flynt](https://pypi.org/project/flynt/)
|
||||||
|
#[prefix = "FLY"]
|
||||||
|
Flynt,
|
||||||
/// NumPy-specific rules
|
/// NumPy-specific rules
|
||||||
#[prefix = "NPY"]
|
#[prefix = "NPY"]
|
||||||
Numpy,
|
Numpy,
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,16 @@ use ruff_macros::CacheKey;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
|
const RULESET_SIZE: usize = 10;
|
||||||
|
|
||||||
/// A set of [`Rule`]s.
|
/// A set of [`Rule`]s.
|
||||||
///
|
///
|
||||||
/// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set.
|
/// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set.
|
||||||
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
|
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
|
||||||
pub struct RuleSet([u64; 10]);
|
pub struct RuleSet([u64; RULESET_SIZE]);
|
||||||
|
|
||||||
impl RuleSet {
|
impl RuleSet {
|
||||||
const EMPTY: [u64; 10] = [0; 10];
|
const EMPTY: [u64; RULESET_SIZE] = [0; RULESET_SIZE];
|
||||||
|
|
||||||
// 64 fits into a u16 without truncation
|
// 64 fits into a u16 without truncation
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
use ruff_python_ast::helpers::create_expr;
|
||||||
|
use rustpython_parser::ast::{Constant, Expr, ExprKind};
|
||||||
|
|
||||||
|
/// Wrap an expression in a `FormattedValue` with no special formatting.
|
||||||
|
fn to_formatted_value_expr(inner: &Expr) -> Expr {
|
||||||
|
create_expr(ExprKind::FormattedValue {
|
||||||
|
value: Box::new(inner.clone()),
|
||||||
|
conversion: 0,
|
||||||
|
format_spec: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a string to a constant string expression.
|
||||||
|
pub(crate) fn to_constant_string(s: &str) -> Expr {
|
||||||
|
create_expr(ExprKind::Constant {
|
||||||
|
value: Constant::Str(s.to_owned()),
|
||||||
|
kind: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Figure out if `expr` represents a "simple" call
|
||||||
|
/// (i.e. one that can be safely converted to a formatted value).
|
||||||
|
fn is_simple_call(expr: &Expr) -> bool {
|
||||||
|
match &expr.node {
|
||||||
|
ExprKind::Call {
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
} => args.is_empty() && keywords.is_empty() && is_simple_callee(func),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Figure out if `func` represents a "simple" callee (a bare name, or a chain of simple
|
||||||
|
/// attribute accesses).
|
||||||
|
fn is_simple_callee(func: &Expr) -> bool {
|
||||||
|
match &func.node {
|
||||||
|
ExprKind::Name { .. } => true,
|
||||||
|
ExprKind::Attribute { value, .. } => is_simple_callee(value),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an expression to a f-string element (if it looks like a good idea).
|
||||||
|
pub(crate) fn to_fstring_elem(expr: &Expr) -> Option<Expr> {
|
||||||
|
match &expr.node {
|
||||||
|
// These are directly handled by `unparse_fstring_elem`:
|
||||||
|
ExprKind::Constant {
|
||||||
|
value: Constant::Str(_),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ExprKind::JoinedStr { .. }
|
||||||
|
| ExprKind::FormattedValue { .. } => Some(expr.clone()),
|
||||||
|
// These should be pretty safe to wrap in a formatted value.
|
||||||
|
ExprKind::Constant {
|
||||||
|
value:
|
||||||
|
Constant::Int(_) | Constant::Float(_) | Constant::Bool(_) | Constant::Complex { .. },
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ExprKind::Name { .. }
|
||||||
|
| ExprKind::Attribute { .. } => Some(to_formatted_value_expr(expr)),
|
||||||
|
ExprKind::Call { .. } if is_simple_call(expr) => Some(to_formatted_value_expr(expr)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
//! Rules from [flynt](https://pypi.org/project/flynt/).
|
||||||
|
mod helpers;
|
||||||
|
pub(crate) mod rules;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::registry::Rule;
|
||||||
|
use crate::test::test_path;
|
||||||
|
use crate::{assert_messages, settings};
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
use test_case::test_case;
|
||||||
|
|
||||||
|
#[test_case(Rule::StaticJoinToFString, Path::new("FLY002.py"); "FLY002")]
|
||||||
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flynt").join(path).as_path(),
|
||||||
|
&settings::Settings::for_rule(rule_code),
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod static_join_to_fstring;
|
||||||
|
|
||||||
|
pub use static_join_to_fstring::{static_join_to_fstring, StaticJoinToFString};
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
use rustpython_parser::ast::{Expr, ExprKind};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::helpers::{create_expr, unparse_expr};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::registry::AsRule;
|
||||||
|
use crate::rules::flynt::helpers;
|
||||||
|
|
||||||
|
#[violation]
|
||||||
|
pub struct StaticJoinToFString {
|
||||||
|
pub expr: String,
|
||||||
|
pub fixable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for StaticJoinToFString {
|
||||||
|
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||||
|
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let StaticJoinToFString { expr, .. } = self;
|
||||||
|
format!("Consider `{expr}` instead of string join")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
|
self.fixable
|
||||||
|
.then_some(|StaticJoinToFString { expr, .. }| format!("Replace with `{expr}`"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_static_length(elts: &[Expr]) -> bool {
|
||||||
|
elts.iter()
|
||||||
|
.all(|e| !matches!(e.node, ExprKind::Starred { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_fstring(joiner: &str, joinees: &[Expr]) -> Option<Expr> {
|
||||||
|
let mut fstring_elems = Vec::with_capacity(joinees.len() * 2);
|
||||||
|
let mut first = true;
|
||||||
|
|
||||||
|
for expr in joinees {
|
||||||
|
if matches!(expr.node, ExprKind::JoinedStr { .. }) {
|
||||||
|
// Oops, already an f-string. We don't know how to handle those
|
||||||
|
// gracefully right now.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
fstring_elems.push(helpers::to_constant_string(joiner));
|
||||||
|
}
|
||||||
|
fstring_elems.push(helpers::to_fstring_elem(expr)?);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
Some(create_expr(ExprKind::JoinedStr {
|
||||||
|
values: fstring_elems,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn static_join_to_fstring(checker: &mut Checker, expr: &Expr, joiner: &str) {
|
||||||
|
let ExprKind::Call {
|
||||||
|
func: _,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
} = &expr.node else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !keywords.is_empty() || args.len() != 1 {
|
||||||
|
// If there are kwargs or more than one argument,
|
||||||
|
// this is some non-standard string join call.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the elements to join; skip e.g. generators, sets, etc.
|
||||||
|
let joinees = match &args[0].node {
|
||||||
|
ExprKind::List { elts, .. } if is_static_length(elts) => elts,
|
||||||
|
ExprKind::Tuple { elts, .. } if is_static_length(elts) => elts,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to build the fstring (internally checks whether e.g. the elements are
|
||||||
|
// convertible to f-string parts).
|
||||||
|
let Some(new_expr) = build_fstring(joiner, joinees) else { return };
|
||||||
|
|
||||||
|
let contents = unparse_expr(&new_expr, checker.stylist);
|
||||||
|
|
||||||
|
let mut diagnostic = Diagnostic::new(
|
||||||
|
StaticJoinToFString {
|
||||||
|
expr: contents.clone(),
|
||||||
|
fixable: true,
|
||||||
|
},
|
||||||
|
expr.range(),
|
||||||
|
);
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
diagnostic.set_fix(Edit::range_replacement(contents, expr.range()));
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flynt/mod.rs
|
||||||
|
---
|
||||||
|
FLY002.py:5:7: FLY002 [*] Consider `f"{a} World"` instead of string join
|
||||||
|
|
|
||||||
|
5 | a = "Hello"
|
||||||
|
6 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
7 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
8 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
|
|
||||||
|
= help: Replace with `f"{a} World"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
2 2 | from random import random, choice
|
||||||
|
3 3 |
|
||||||
|
4 4 | a = "Hello"
|
||||||
|
5 |-ok1 = " ".join([a, " World"]) # OK
|
||||||
|
5 |+ok1 = f"{a} World" # OK
|
||||||
|
6 6 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
7 7 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
8 8 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
|
||||||
|
FLY002.py:6:7: FLY002 [*] Consider `f"Finally, {a} World"` instead of string join
|
||||||
|
|
|
||||||
|
6 | a = "Hello"
|
||||||
|
7 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
8 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
9 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
10 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
|
|
||||||
|
= help: Replace with `f"Finally, {a} World"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
3 3 |
|
||||||
|
4 4 | a = "Hello"
|
||||||
|
5 5 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
6 |-ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
6 |+ok2 = f"Finally, {a} World" # OK
|
||||||
|
7 7 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
8 8 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
9 9 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
|
||||||
|
FLY002.py:7:7: FLY002 [*] Consider `f"1x2x3"` instead of string join
|
||||||
|
|
|
||||||
|
7 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
8 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
9 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
10 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
11 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
|
|
||||||
|
= help: Replace with `f"1x2x3"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
4 4 | a = "Hello"
|
||||||
|
5 5 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
6 6 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
7 |-ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
7 |+ok3 = f"1x2x3" # OK
|
||||||
|
8 8 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
9 9 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
10 10 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
|
||||||
|
FLY002.py:8:7: FLY002 [*] Consider `f"{1}y{2}y{3}"` instead of string join
|
||||||
|
|
|
||||||
|
8 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
9 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
10 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
11 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
12 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
|
|
||||||
|
= help: Replace with `f"{1}y{2}y{3}"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
5 5 | ok1 = " ".join([a, " World"]) # OK
|
||||||
|
6 6 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
7 7 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
8 |-ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
8 |+ok4 = f"{1}y{2}y{3}" # Technically OK, though would've been an error originally
|
||||||
|
9 9 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
10 10 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
11 11 |
|
||||||
|
|
||||||
|
FLY002.py:9:7: FLY002 [*] Consider `f"{random()}a{random()}"` instead of string join
|
||||||
|
|
|
||||||
|
9 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
10 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
11 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
12 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
|
|
||||||
|
= help: Replace with `f"{random()}a{random()}"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
6 6 | ok2 = "".join(["Finally, ", a, " World"]) # OK
|
||||||
|
7 7 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
8 8 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
9 |-ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
9 |+ok5 = f"{random()}a{random()}" # OK (simple calls)
|
||||||
|
10 10 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
11 11 |
|
||||||
|
12 12 | nok1 = "x".join({"4", "5", "yee"}) # Not OK (set)
|
||||||
|
|
||||||
|
FLY002.py:10:7: FLY002 [*] Consider `f"{secrets.token_urlsafe()}a{secrets.token_hex()}"` instead of string join
|
||||||
|
|
|
||||||
|
10 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
11 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
12 | ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FLY002
|
||||||
|
13 |
|
||||||
|
14 | nok1 = "x".join({"4", "5", "yee"}) # Not OK (set)
|
||||||
|
|
|
||||||
|
= help: Replace with `f"{secrets.token_urlsafe()}a{secrets.token_hex()}"`
|
||||||
|
|
||||||
|
ℹ Suggested fix
|
||||||
|
7 7 | ok3 = "x".join(("1", "2", "3")) # OK
|
||||||
|
8 8 | ok4 = "y".join([1, 2, 3]) # Technically OK, though would've been an error originally
|
||||||
|
9 9 | ok5 = "a".join([random(), random()]) # OK (simple calls)
|
||||||
|
10 |-ok6 = "a".join([secrets.token_urlsafe(), secrets.token_hex()]) # OK (attr calls)
|
||||||
|
10 |+ok6 = f"{secrets.token_urlsafe()}a{secrets.token_hex()}" # OK (attr calls)
|
||||||
|
11 11 |
|
||||||
|
12 12 | nok1 = "x".join({"4", "5", "yee"}) # Not OK (set)
|
||||||
|
13 13 | nok2 = a.join(["1", "2", "3"]) # Not OK (not a static joiner)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub mod flake8_tidy_imports;
|
||||||
pub mod flake8_type_checking;
|
pub mod flake8_type_checking;
|
||||||
pub mod flake8_unused_arguments;
|
pub mod flake8_unused_arguments;
|
||||||
pub mod flake8_use_pathlib;
|
pub mod flake8_use_pathlib;
|
||||||
|
pub mod flynt;
|
||||||
pub mod isort;
|
pub mod isort;
|
||||||
pub mod mccabe;
|
pub mod mccabe;
|
||||||
pub mod numpy;
|
pub mod numpy;
|
||||||
|
|
|
||||||
|
|
@ -1829,6 +1829,10 @@
|
||||||
"FBT001",
|
"FBT001",
|
||||||
"FBT002",
|
"FBT002",
|
||||||
"FBT003",
|
"FBT003",
|
||||||
|
"FLY",
|
||||||
|
"FLY0",
|
||||||
|
"FLY00",
|
||||||
|
"FLY002",
|
||||||
"G",
|
"G",
|
||||||
"G0",
|
"G0",
|
||||||
"G00",
|
"G00",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue