diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_10.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_10.py index e49918308c..c527182cab 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_10.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_10.py @@ -3,7 +3,7 @@ MY_SET = {"ABC", "DEF"} # plain set MY_LIST = [1, 2, 3] # plain list MY_DICT = {"key": "value"} # plain dict -# NOT triggering B006 (should trigger) +# NOT triggering B006 (correct - UPPER_CASE constants are excluded per PEP 8) def func_A(s: set[str] = MY_SET): return s @@ -11,11 +11,11 @@ def func_A(s: set[str] = MY_SET): def func_B(s: set[str] = {"ABC", "DEF"}): return s -# Should trigger B006 +# NOT triggering B006 (correct - UPPER_CASE constants are excluded per PEP 8) def func_C(items: list[int] = MY_LIST): return items -# Should trigger B006 +# NOT triggering B006 (correct - UPPER_CASE constants are excluded per PEP 8) def func_D(data: dict[str, str] = MY_DICT): return data diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index 578e8c638f..01a7fe2314 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -10,6 +10,7 @@ use ruff_python_semantic::analyze::function_type::is_stub; use ruff_python_semantic::analyze::typing::{ find_binding_value, is_immutable_annotation, is_mutable_expr, }; +use ruff_python_stdlib::str; use ruff_python_trivia::{indentation_at_offset, textwrap}; use ruff_source_file::LineRanges; use ruff_text_size::Ranged; @@ -145,6 +146,10 @@ fn is_guaranteed_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool { } Expr::Named(ast::ExprNamed { value, .. }) => is_guaranteed_mutable_expr(value, semantic), Expr::Name(name) => { + // Exclude UPPER_CASE constants (PEP 8 convention - meant to be read-only) + if str::is_cased_uppercase(&name.id) { + return false; + } // Resolve module-level constants that are bound to mutable objects let Some(binding_id) = semantic.only_binding(name) else { return false; diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_10.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_10.py.snap index c256bfd8ec..3282c97ae8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_10.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_10.py.snap @@ -19,5 +19,5 @@ help: Replace with `None`; initialize within function 13 + s = {"ABC", "DEF"} 14 | return s 15 | -16 | # Should trigger B006 +16 | # NOT triggering B006 (correct - UPPER_CASE constants are excluded per PEP 8) note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_10.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_10.py.snap index 006f0e328c..3282c97ae8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_10.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_10.py.snap @@ -1,27 +1,6 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B006 [*] Do not use mutable data structures for argument defaults - --> B006_10.py:7:26 - | -6 | # NOT triggering B006 (should trigger) -7 | def func_A(s: set[str] = MY_SET): - | ^^^^^^ -8 | return s - | -help: Replace with `None`; initialize within function -4 | MY_DICT = {"key": "value"} # plain dict -5 | -6 | # NOT triggering B006 (should trigger) - - def func_A(s: set[str] = MY_SET): -7 + def func_A(s: set[str] = None): -8 + if s is None: -9 + s = MY_SET -10 | return s -11 | -12 | # Triggering B006 (correct) -note: This is an unsafe fix and may change runtime behavior - B006 [*] Do not use mutable data structures for argument defaults --> B006_10.py:11:26 | @@ -40,46 +19,5 @@ help: Replace with `None`; initialize within function 13 + s = {"ABC", "DEF"} 14 | return s 15 | -16 | # Should trigger B006 -note: This is an unsafe fix and may change runtime behavior - -B006 [*] Do not use mutable data structures for argument defaults - --> B006_10.py:15:31 - | -14 | # Should trigger B006 -15 | def func_C(items: list[int] = MY_LIST): - | ^^^^^^^ -16 | return items - | -help: Replace with `None`; initialize within function -12 | return s -13 | -14 | # Should trigger B006 - - def func_C(items: list[int] = MY_LIST): -15 + def func_C(items: list[int] = None): -16 + if items is None: -17 + items = MY_LIST -18 | return items -19 | -20 | # Should trigger B006 -note: This is an unsafe fix and may change runtime behavior - -B006 [*] Do not use mutable data structures for argument defaults - --> B006_10.py:19:35 - | -18 | # Should trigger B006 -19 | def func_D(data: dict[str, str] = MY_DICT): - | ^^^^^^^ -20 | return data - | -help: Replace with `None`; initialize within function -16 | return items -17 | -18 | # Should trigger B006 - - def func_D(data: dict[str, str] = MY_DICT): -19 + def func_D(data: dict[str, str] = None): -20 + if data is None: -21 + data = MY_DICT -22 | return data -23 | +16 | # NOT triggering B006 (correct - UPPER_CASE constants are excluded per PEP 8) note: This is an unsafe fix and may change runtime behavior