diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF066_custom_property_decorator.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF066_custom_property_decorator.py new file mode 100644 index 0000000000..705506d37e --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF066_custom_property_decorator.py @@ -0,0 +1,15 @@ +from my_library import custom_property + + +class Example: + @custom_property + def missing_return(self): # ERROR: No return + x = 1 + + @custom_property + def with_return(self): # OK: Has return + return 1 + + @property + def builtin_property(self): # ERROR: No return (builtin @property still works) + x = 1 diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 48c6f7c80c..f20fd77020 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -19,6 +19,7 @@ mod tests { use crate::pyproject_toml::lint_pyproject_toml; use crate::registry::Rule; + use crate::rules::pydocstyle::settings::Settings as PydocstyleSettings; use crate::settings::LinterSettings; use crate::settings::types::{CompiledPerFileIgnoreList, PerFileIgnore, PreviewMode}; use crate::test::{test_path, test_resource_path}; @@ -235,6 +236,24 @@ mod tests { Ok(()) } + #[test] + fn property_without_return_custom_decorator() -> Result<()> { + let diagnostics = test_path( + Path::new("ruff/RUF066_custom_property_decorator.py"), + &LinterSettings { + pydocstyle: PydocstyleSettings { + property_decorators: ["my_library.custom_property".to_string()] + .into_iter() + .collect(), + ..PydocstyleSettings::default() + }, + ..LinterSettings::for_rule(Rule::PropertyWithoutReturn) + }, + )?; + assert_diagnostics!(diagnostics); + Ok(()) + } + #[test] fn confusables() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/ruff/rules/property_without_return.rs b/crates/ruff_linter/src/rules/ruff/rules/property_without_return.rs index 3027eb876b..91619a5b98 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/property_without_return.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/property_without_return.rs @@ -29,6 +29,10 @@ use crate::{FixAvailability, Violation}; /// return f"{self.first_name} {self.last_name}" /// ``` /// +/// ## Options +/// +/// - `lint.pydocstyle.property-decorators` +/// /// ## References /// - [Python documentation: The property class](https://docs.python.org/3/library/functions.html#property) #[derive(ViolationMetadata)] @@ -62,7 +66,8 @@ pub(crate) fn property_without_return(checker: &Checker, function_def: &StmtFunc .. } = function_def; - if !visibility::is_property(decorator_list, [], semantic) + let extra_property_decorators = checker.settings().pydocstyle.property_decorators(); + if !visibility::is_property(decorator_list, extra_property_decorators, semantic) || visibility::is_overload(decorator_list, semantic) || function_type::is_stub(function_def, semantic) { diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__property_without_return_custom_decorator.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__property_without_return_custom_decorator.snap new file mode 100644 index 0000000000..5ebc44d4af --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__property_without_return_custom_decorator.snap @@ -0,0 +1,21 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +--- +RUF066 `missing_return` is a property without a `return` statement + --> RUF066_custom_property_decorator.py:6:9 + | +4 | class Example: +5 | @custom_property +6 | def missing_return(self): # ERROR: No return + | ^^^^^^^^^^^^^^ +7 | x = 1 + | + +RUF066 `builtin_property` is a property without a `return` statement + --> RUF066_custom_property_decorator.py:14:9 + | +13 | @property +14 | def builtin_property(self): # ERROR: No return (builtin @property still works) + | ^^^^^^^^^^^^^^^^ +15 | x = 1 + |