diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF009_attrs.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF009_attrs.py index 8f2a5c9fdd..526c12ab88 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF009_attrs.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF009_attrs.py @@ -125,3 +125,20 @@ class J: class K: f: F = F() g: G = G() + + +# Regression test for https://github.com/astral-sh/ruff/issues/19014 +# These are all valid field calls and should not cause diagnostics. +@attr.define +class TestAttrField: + attr_field_factory: list[int] = attr.field(factory=list) + attr_field_default: list[int] = attr.field(default=attr.Factory(list)) + attr_factory: list[int] = attr.Factory(list) + attr_ib: list[int] = attr.ib(factory=list) + attr_attr: list[int] = attr.attr(factory=list) + attr_attrib: list[int] = attr.attrib(factory=list) + + +@attr.attributes +class TestAttrAttributes: + x: list[int] = list() # RUF009 diff --git a/crates/ruff_linter/src/rules/ruff/helpers.rs b/crates/ruff_linter/src/rules/ruff/helpers.rs index c1426de616..535492816b 100644 --- a/crates/ruff_linter/src/rules/ruff/helpers.rs +++ b/crates/ruff_linter/src/rules/ruff/helpers.rs @@ -25,14 +25,16 @@ fn is_stdlib_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool { .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["dataclasses", "field"])) } -/// Returns `true` if the given [`Expr`] is a call to `attr.ib()` or `attrs.field()`. +/// Returns `true` if the given [`Expr`] is a call to an `attrs` field function. fn is_attrs_field(func: &Expr, semantic: &SemanticModel) -> bool { semantic .resolve_qualified_name(func) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), - ["attrs", "field" | "Factory"] | ["attr", "ib"] + ["attrs", "field" | "Factory"] + // See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L33 + | ["attr", "ib" | "attr" | "attrib" | "field" | "Factory"] ) }) } @@ -120,7 +122,8 @@ pub(super) fn dataclass_kind<'a>( match qualified_name.segments() { ["attrs" | "attr", func @ ("define" | "frozen" | "mutable")] - | ["attr", func @ ("s" | "attrs")] => { + // See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L32 + | ["attr", func @ ("s" | "attributes" | "attrs")] => { // `.define`, `.frozen` and `.mutable` all default `auto_attribs` to `None`, // whereas `@attr.s` implicitly sets `auto_attribs=False`. // https://www.attrs.org/en/stable/api.html#attrs.define diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF009_RUF009_attrs.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF009_RUF009_attrs.py.snap index 58cadaeeb2..08faee3866 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF009_RUF009_attrs.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF009_RUF009_attrs.py.snap @@ -98,3 +98,11 @@ RUF009_attrs.py:127:12: RUF009 Do not perform function call `G` in dataclass def 127 | g: G = G() | ^^^ RUF009 | + +RUF009_attrs.py:144:20: RUF009 Do not perform function call `list` in dataclass defaults + | +142 | @attr.attributes +143 | class TestAttrAttributes: +144 | x: list[int] = list() # RUF009 + | ^^^^^^ RUF009 + |