Ignore precedence when identifying explicit concatenations

This commit is contained in:
Charlie Marsh 2023-07-25 21:09:57 -04:00
parent da33c26238
commit c8cbf45ec2
7 changed files with 89 additions and 41 deletions

View File

@ -59,3 +59,13 @@ _ = "abc" + "def" + foo
_ = foo + bar + "abc" _ = foo + bar + "abc"
_ = "abc" + foo + bar _ = "abc" + foo + bar
_ = foo + "abc" + bar _ = foo + "abc" + bar
_ = (
a + f"abc" +
"def"
)
_ = (
f"abc" +
"def" + a
)

View File

@ -12,10 +12,10 @@ use crate::registry::Rule;
use crate::rules::{ use crate::rules::{
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django, flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format, flake8_future_annotations, flake8_gettext, flake8_logging_format, flake8_pie, flake8_print,
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify, flake8_tidy_imports,
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks,
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff, pylint, pyupgrade, ruff,
}; };
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
@ -1055,13 +1055,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Expr::BinOp(ast::ExprBinOp { Expr::BinOp(ast::ExprBinOp {
op: Operator::Add, .. op: Operator::Add, ..
}) => { }) => {
if checker.enabled(Rule::ExplicitStringConcatenation) {
if let Some(diagnostic) =
flake8_implicit_str_concat::rules::explicit(expr, checker.locator)
{
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::CollectionLiteralConcatenation) { if checker.enabled(Rule::CollectionLiteralConcatenation) {
ruff::rules::collection_literal_concatenation(checker, expr); ruff::rules::collection_literal_concatenation(checker, expr);
} }

View File

@ -119,8 +119,8 @@ pub(crate) fn check_tokens(
} }
if settings.rules.any_enabled(&[ if settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation, Rule::MultiLineImplicitStringConcatenation,
Rule::SingleLineImplicitStringConcatenation,
]) { ]) {
flake8_implicit_str_concat::rules::implicit( flake8_implicit_str_concat::rules::implicit(
&mut diagnostics, &mut diagnostics,
@ -130,6 +130,10 @@ pub(crate) fn check_tokens(
); );
} }
if settings.rules.enabled(Rule::ExplicitStringConcatenation) {
flake8_implicit_str_concat::rules::explicit(&mut diagnostics, tokens);
}
if settings.rules.any_enabled(&[ if settings.rules.any_enabled(&[
Rule::MissingTrailingComma, Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple, Rule::TrailingCommaOnBareTuple,

View File

@ -258,6 +258,7 @@ impl Rule {
| Rule::BlanketNOQA | Rule::BlanketNOQA
| Rule::BlanketTypeIgnore | Rule::BlanketTypeIgnore
| Rule::CommentedOutCode | Rule::CommentedOutCode
| Rule::ExplicitStringConcatenation
| Rule::ExtraneousParentheses | Rule::ExtraneousParentheses
| Rule::InvalidCharacterBackspace | Rule::InvalidCharacterBackspace
| Rule::InvalidCharacterEsc | Rule::InvalidCharacterEsc

View File

@ -1,8 +1,11 @@
use rustpython_parser::ast::{self, Constant, Expr, Operator, Ranged}; use itertools::Itertools;
use ruff_text_size::TextRange;
use rustpython_parser::ast::Ranged;
use rustpython_parser::lexer::LexResult;
use rustpython_parser::Tok;
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
/// ## What it does /// ## What it does
/// Checks for string literals that are explicitly concatenated (using the /// Checks for string literals that are explicitly concatenated (using the
@ -39,34 +42,31 @@ impl Violation for ExplicitStringConcatenation {
} }
/// ISC003 /// ISC003
pub(crate) fn explicit(expr: &Expr, locator: &Locator) -> Option<Diagnostic> { pub(crate) fn explicit(diagnostics: &mut Vec<Diagnostic>, tokens: &[LexResult]) {
if let Expr::BinOp(ast::ExprBinOp { for ((a_tok, a_range), (b_tok, _), (c_tok, _), (d_tok, d_range)) in tokens
left, .iter()
op, .flatten()
right, .filter(|(tok, _)| !tok.is_comment())
range, .tuple_windows()
}) = expr
{ {
if matches!(op, Operator::Add) {
if matches!( if matches!(
left.as_ref(), (a_tok, b_tok, c_tok, d_tok),
Expr::JoinedStr(_) (
| Expr::Constant(ast::ExprConstant { Tok::String { .. },
value: Constant::Str(..) | Constant::Bytes(..), Tok::NonLogicalNewline,
.. Tok::Plus,
}) Tok::String { .. }
) && matches!( ) | (
right.as_ref(), Tok::String { .. },
Expr::JoinedStr(_) Tok::Plus,
| Expr::Constant(ast::ExprConstant { Tok::NonLogicalNewline,
value: Constant::Str(..) | Constant::Bytes(..), Tok::String { .. }
.. )
}) ) {
) && locator.contains_line_break(*range) diagnostics.push(Diagnostic::new(
{ ExplicitStringConcatenation,
return Some(Diagnostic::new(ExplicitStringConcatenation, expr.range())); TextRange::new(a_range.start(), d_range.end()),
));
} }
} }
} }
None
}

View File

@ -31,4 +31,24 @@ ISC.py:19:3: ISC003 Explicitly concatenated string should be implicitly concaten
21 | ) 21 | )
| |
ISC.py:64:7: ISC003 Explicitly concatenated string should be implicitly concatenated
|
63 | _ = (
64 | a + f"abc" +
| _______^
65 | | "def"
| |_______^ ISC003
66 | )
|
ISC.py:69:3: ISC003 Explicitly concatenated string should be implicitly concatenated
|
68 | _ = (
69 | f"abc" +
| ___^
70 | | "def" + a
| |_______^ ISC003
71 | )
|

View File

@ -31,4 +31,24 @@ ISC.py:19:3: ISC003 Explicitly concatenated string should be implicitly concaten
21 | ) 21 | )
| |
ISC.py:64:7: ISC003 Explicitly concatenated string should be implicitly concatenated
|
63 | _ = (
64 | a + f"abc" +
| _______^
65 | | "def"
| |_______^ ISC003
66 | )
|
ISC.py:69:3: ISC003 Explicitly concatenated string should be implicitly concatenated
|
68 | _ = (
69 | f"abc" +
| ___^
70 | | "def" + a
| |_______^ ISC003
71 | )
|