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"
_ = "abc" + foo + 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::{
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, ruff,
flake8_future_annotations, flake8_gettext, flake8_logging_format, flake8_pie, flake8_print,
flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify, flake8_tidy_imports,
flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle, pyflakes, pygrep_hooks,
pylint, pyupgrade, ruff,
};
use crate::settings::types::PythonVersion;
@ -1055,13 +1055,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Expr::BinOp(ast::ExprBinOp {
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) {
ruff::rules::collection_literal_concatenation(checker, expr);
}

View File

@ -119,8 +119,8 @@ pub(crate) fn check_tokens(
}
if settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
Rule::SingleLineImplicitStringConcatenation,
]) {
flake8_implicit_str_concat::rules::implicit(
&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(&[
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,

View File

@ -258,6 +258,7 @@ impl Rule {
| Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::CommentedOutCode
| Rule::ExplicitStringConcatenation
| Rule::ExtraneousParentheses
| Rule::InvalidCharacterBackspace
| 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_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
/// ## What it does
/// Checks for string literals that are explicitly concatenated (using the
@ -39,34 +42,31 @@ impl Violation for ExplicitStringConcatenation {
}
/// ISC003
pub(crate) fn explicit(expr: &Expr, locator: &Locator) -> Option<Diagnostic> {
if let Expr::BinOp(ast::ExprBinOp {
left,
op,
right,
range,
}) = expr
pub(crate) fn explicit(diagnostics: &mut Vec<Diagnostic>, tokens: &[LexResult]) {
for ((a_tok, a_range), (b_tok, _), (c_tok, _), (d_tok, d_range)) in tokens
.iter()
.flatten()
.filter(|(tok, _)| !tok.is_comment())
.tuple_windows()
{
if matches!(op, Operator::Add) {
if matches!(
left.as_ref(),
Expr::JoinedStr(_)
| Expr::Constant(ast::ExprConstant {
value: Constant::Str(..) | Constant::Bytes(..),
..
})
) && matches!(
right.as_ref(),
Expr::JoinedStr(_)
| Expr::Constant(ast::ExprConstant {
value: Constant::Str(..) | Constant::Bytes(..),
..
})
) && locator.contains_line_break(*range)
{
return Some(Diagnostic::new(ExplicitStringConcatenation, expr.range()));
(a_tok, b_tok, c_tok, d_tok),
(
Tok::String { .. },
Tok::NonLogicalNewline,
Tok::Plus,
Tok::String { .. }
) | (
Tok::String { .. },
Tok::Plus,
Tok::NonLogicalNewline,
Tok::String { .. }
)
) {
diagnostics.push(Diagnostic::new(
ExplicitStringConcatenation,
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 | )
|
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 | )
|
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 | )
|