diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py index 8b2811eb63467e..640d1fb42b87c8 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.py @@ -32,6 +32,7 @@ def f8(x: bytes = b"50 character byte stringgggggggggggggggggggggggggg\xff") -> foo: str = "50 character stringggggggggggggggggggggggggggggggg" bar: str = "51 character stringgggggggggggggggggggggggggggggggg" +baz: str = f"51 character stringgggggggggggggggggggggggggggggggg" baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi index 71064d9bdbd026..e87388ec9acbbc 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi +++ b/crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI053.pyi @@ -29,6 +29,10 @@ baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053 +ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK + +fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053 + class Demo: """Docstrings are excluded from this rule. Some padding.""" # OK diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 4994dfda95b8a3..9f084ae352b756 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -3,6 +3,7 @@ use ruff_python_literal::cformat::{CFormatError, CFormatErrorType}; use ruff_diagnostics::Diagnostic; +use ruff_python_ast::node::AstNode; use ruff_python_ast::types::Node; use ruff_python_semantic::analyze::typing; use ruff_python_semantic::ScopeKind; @@ -968,8 +969,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { ruff::rules::explicit_f_string_type_conversion(checker, expr, elements); } for element in elements { - if let ast::FStringElement::Literal(ast::FStringLiteralElement { value, range }) = - element + if let ast::FStringElement::Literal( + literal_element @ ast::FStringLiteralElement { value, range }, + ) = element { if checker.enabled(Rule::HardcodedBindAllInterfaces) { if let Some(diagnostic) = @@ -981,6 +983,15 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::HardcodedTempFile) { flake8_bandit::rules::hardcoded_tmp_directory(checker, *range, value); } + + if checker.source_type.is_stub() { + if checker.enabled(Rule::StringOrBytesTooLong) { + flake8_pyi::rules::string_or_bytes_too_long( + checker, + &literal_element.as_any_node_ref(), + ); + } + } } } } @@ -1242,18 +1253,22 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { flake8_pyi::rules::numeric_literal_too_long(checker, expr); } } - Expr::Constant(ast::ExprConstant { - value: Constant::Bytes(_), - range: _, - }) => { + Expr::Constant( + constant @ ast::ExprConstant { + value: Constant::Bytes(_), + range: _, + }, + ) => { if checker.source_type.is_stub() && checker.enabled(Rule::StringOrBytesTooLong) { - flake8_pyi::rules::string_or_bytes_too_long(checker, expr); + flake8_pyi::rules::string_or_bytes_too_long(checker, &constant.as_any_node_ref()); } } - Expr::Constant(ast::ExprConstant { - value: Constant::Str(value), - range: _, - }) => { + Expr::Constant( + constant @ ast::ExprConstant { + value: Constant::Str(value), + range: _, + }, + ) => { if checker.enabled(Rule::HardcodedBindAllInterfaces) { if let Some(diagnostic) = flake8_bandit::rules::hardcoded_bind_all_interfaces(value, expr.range()) @@ -1269,7 +1284,10 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { } if checker.source_type.is_stub() { if checker.enabled(Rule::StringOrBytesTooLong) { - flake8_pyi::rules::string_or_bytes_too_long(checker, expr); + flake8_pyi::rules::string_or_bytes_too_long( + checker, + &constant.as_any_node_ref(), + ); } } } diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs index bbf20bda6273cf..185c80418b4f8c 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/string_or_bytes_too_long.rs @@ -1,8 +1,9 @@ -use ruff_python_ast::{self as ast, Constant, Expr}; +use ruff_python_ast::{self as ast, Constant}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::is_docstring_stmt; +use ruff_python_ast::node::AnyNodeRef; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -42,32 +43,35 @@ impl AlwaysAutofixableViolation for StringOrBytesTooLong { } /// PYI053 -pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) { +pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, node: &AnyNodeRef) { // Ignore docstrings. if is_docstring_stmt(checker.semantic().current_statement()) { return; } - let length = match expr { - Expr::Constant(ast::ExprConstant { + let length = match node { + AnyNodeRef::ExprConstant(ast::ExprConstant { value: Constant::Str(s), .. }) => s.chars().count(), - Expr::Constant(ast::ExprConstant { + AnyNodeRef::ExprConstant(ast::ExprConstant { value: Constant::Bytes(bytes), .. }) => bytes.len(), + AnyNodeRef::FStringLiteralElement(ast::FStringLiteralElement { value, .. }) => { + value.chars().count() + } _ => return, }; if length <= 50 { return; } - let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, expr.range()); + let mut diagnostic = Diagnostic::new(StringOrBytesTooLong, node.range()); if checker.patch(diagnostic.kind.rule()) { diagnostic.set_fix(Fix::suggested(Edit::range_replacement( "...".to_string(), - expr.range(), + node.range(), ))); } checker.diagnostics.push(diagnostic); diff --git a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI053_PYI053.pyi.snap b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI053_PYI053.pyi.snap index 5b9f3aa1a13323..aa4b57d184df47 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI053_PYI053.pyi.snap +++ b/crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI053_PYI053.pyi.snap @@ -90,7 +90,7 @@ PYI053.pyi:30:14: PYI053 [*] String and bytes literals longer than 50 characters 30 | qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053 31 | -32 | class Demo: +32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK | = help: Replace with `...` @@ -101,7 +101,28 @@ PYI053.pyi:30:14: PYI053 [*] String and bytes literals longer than 50 characters 30 |-qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053 30 |+qux: bytes = ... # Error: PYI053 31 31 | -32 32 | class Demo: -33 33 | """Docstrings are excluded from this rule. Some padding.""" # OK +32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK +33 33 | + +PYI053.pyi:34:15: PYI053 [*] String and bytes literals longer than 50 characters are not permitted + | +32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK +33 | +34 | fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI053 +35 | +36 | class Demo: + | + = help: Replace with `...` + +ℹ Suggested fix +31 31 | +32 32 | ffoo: str = f"50 character stringggggggggggggggggggggggggggggggg" # OK +33 33 | +34 |-fbar: str = f"51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI053 + 34 |+fbar: str = f"..." # Error: PYI053 +35 35 | +36 36 | class Demo: +37 37 | """Docstrings are excluded from this rule. Some padding.""" # OK