mirror of https://github.com/astral-sh/ruff
Implement `property-decorators` configuration option for pydocstyle (#3311)
This commit is contained in:
parent
ffdf6e35e6
commit
508bc605a5
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
from functools import cached_property
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
# Bad examples
|
||||
|
||||
def bad_liouiwnlkjl():
|
||||
|
|
@ -76,6 +78,11 @@ class Thingy:
|
|||
"""This property method docstring does not need to be written in imperative mood."""
|
||||
return self._beep
|
||||
|
||||
@GObject.Property
|
||||
def good_custom_property(self):
|
||||
"""This property method docstring does not need to be written in imperative mood."""
|
||||
return self._beep
|
||||
|
||||
@cached_property
|
||||
def good_cached_property(self):
|
||||
"""This property method docstring does not need to be written in imperative mood."""
|
||||
|
|
|
|||
|
|
@ -5593,7 +5593,11 @@ impl<'a> Checker<'a> {
|
|||
pydocstyle::rules::ends_with_period(self, &docstring);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::NonImperativeMood) {
|
||||
pydocstyle::rules::non_imperative_mood(self, &docstring);
|
||||
pydocstyle::rules::non_imperative_mood(
|
||||
self,
|
||||
&docstring,
|
||||
&self.settings.pydocstyle.property_decorators,
|
||||
);
|
||||
}
|
||||
if self.settings.rules.enabled(&Rule::NoSignature) {
|
||||
pydocstyle::rules::no_signature(self, &docstring);
|
||||
|
|
|
|||
|
|
@ -577,6 +577,7 @@ mod tests {
|
|||
pydocstyle: Some(pydocstyle::settings::Options {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: None,
|
||||
property_decorators: None,
|
||||
}),
|
||||
..default_options([Linter::Pydocstyle.into()])
|
||||
});
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ mod tests {
|
|||
pydocstyle: Settings {
|
||||
convention: None,
|
||||
ignore_decorators: BTreeSet::from_iter(["functools.wraps".to_string()]),
|
||||
property_decorators: BTreeSet::from_iter([
|
||||
"gi.repository.GObject.Property".to_string()
|
||||
]),
|
||||
},
|
||||
..settings::Settings::for_rule(rule_code)
|
||||
},
|
||||
|
|
@ -95,6 +98,7 @@ mod tests {
|
|||
pydocstyle: Settings {
|
||||
convention: None,
|
||||
ignore_decorators: BTreeSet::new(),
|
||||
property_decorators: BTreeSet::new(),
|
||||
},
|
||||
..settings::Settings::for_rule(Rule::UndocumentedParam)
|
||||
},
|
||||
|
|
@ -112,6 +116,7 @@ mod tests {
|
|||
pydocstyle: Settings {
|
||||
convention: Some(Convention::Google),
|
||||
ignore_decorators: BTreeSet::new(),
|
||||
property_decorators: BTreeSet::new(),
|
||||
},
|
||||
..settings::Settings::for_rule(Rule::UndocumentedParam)
|
||||
},
|
||||
|
|
@ -129,6 +134,7 @@ mod tests {
|
|||
pydocstyle: Settings {
|
||||
convention: Some(Convention::Numpy),
|
||||
ignore_decorators: BTreeSet::new(),
|
||||
property_decorators: BTreeSet::new(),
|
||||
},
|
||||
..settings::Settings::for_rule(Rule::UndocumentedParam)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use std::collections::BTreeSet;
|
||||
|
||||
use imperative::Mood;
|
||||
use once_cell::sync::Lazy;
|
||||
use ruff_macros::{define_violation, derive_message_formats};
|
||||
|
||||
use crate::ast::cast;
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::helpers::to_call_path;
|
||||
use crate::ast::types::{CallPath, Range};
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
||||
use crate::registry::Diagnostic;
|
||||
|
|
@ -14,7 +17,11 @@ use crate::visibility::{is_property, is_test};
|
|||
static MOOD: Lazy<Mood> = Lazy::new(Mood::new);
|
||||
|
||||
/// D401
|
||||
pub fn non_imperative_mood(checker: &mut Checker, docstring: &Docstring) {
|
||||
pub fn non_imperative_mood(
|
||||
checker: &mut Checker,
|
||||
docstring: &Docstring,
|
||||
property_decorators: &BTreeSet<String>,
|
||||
) {
|
||||
let (
|
||||
DefinitionKind::Function(parent)
|
||||
| DefinitionKind::NestedFunction(parent)
|
||||
|
|
@ -22,7 +29,15 @@ pub fn non_imperative_mood(checker: &mut Checker, docstring: &Docstring) {
|
|||
) = &docstring.kind else {
|
||||
return;
|
||||
};
|
||||
if is_test(cast::name(parent)) || is_property(checker, cast::decorator_list(parent)) {
|
||||
|
||||
let property_decorators = property_decorators
|
||||
.iter()
|
||||
.map(|decorator| to_call_path(decorator))
|
||||
.collect::<Vec<CallPath>>();
|
||||
|
||||
if is_test(cast::name(parent))
|
||||
|| is_property(checker, cast::decorator_list(parent), &property_decorators)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,12 +97,25 @@ pub struct Options {
|
|||
/// specified decorators. Unlike the `pydocstyle`, Ruff accepts an array
|
||||
/// of fully-qualified module identifiers, instead of a regular expression.
|
||||
pub ignore_decorators: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
property-decorators = ["gi.repository.GObject.Property"]
|
||||
"#
|
||||
)]
|
||||
/// Consider any method decorated with one of these decorators as a property,
|
||||
/// and consequently allow a docstring which is not in imperative mood.
|
||||
/// Unlike pydocstyle, supplying this option doesn't disable standard
|
||||
/// property decorators - `@property` and `@cached_property`.
|
||||
pub property_decorators: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Hash)]
|
||||
pub struct Settings {
|
||||
pub convention: Option<Convention>,
|
||||
pub ignore_decorators: BTreeSet<String>,
|
||||
pub property_decorators: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl From<Options> for Settings {
|
||||
|
|
@ -110,6 +123,9 @@ impl From<Options> for Settings {
|
|||
Self {
|
||||
convention: options.convention,
|
||||
ignore_decorators: BTreeSet::from_iter(options.ignore_decorators.unwrap_or_default()),
|
||||
property_decorators: BTreeSet::from_iter(
|
||||
options.property_decorators.unwrap_or_default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +135,7 @@ impl From<Settings> for Options {
|
|||
Self {
|
||||
convention: settings.convention,
|
||||
ignore_decorators: Some(settings.ignore_decorators.into_iter().collect()),
|
||||
property_decorators: Some(settings.property_decorators.into_iter().collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,74 @@
|
|||
---
|
||||
source: src/rules/pydocstyle/mod.rs
|
||||
source: crates/ruff/src/rules/pydocstyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NonImperativeMood: Returns foo.
|
||||
location:
|
||||
row: 8
|
||||
row: 10
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
row: 10
|
||||
column: 22
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: Constructor for a foo.
|
||||
location:
|
||||
row: 12
|
||||
row: 14
|
||||
column: 4
|
||||
end_location:
|
||||
row: 12
|
||||
row: 14
|
||||
column: 32
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: Constructor for a boa.
|
||||
location:
|
||||
row: 16
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 20
|
||||
row: 22
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: Runs something
|
||||
location:
|
||||
row: 24
|
||||
row: 26
|
||||
column: 4
|
||||
end_location:
|
||||
row: 24
|
||||
row: 26
|
||||
column: 24
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: "Runs other things, nested"
|
||||
location:
|
||||
row: 27
|
||||
row: 29
|
||||
column: 8
|
||||
end_location:
|
||||
row: 27
|
||||
row: 29
|
||||
column: 39
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: Writes a logical line that
|
||||
location:
|
||||
row: 33
|
||||
row: 35
|
||||
column: 4
|
||||
end_location:
|
||||
row: 35
|
||||
row: 37
|
||||
column: 7
|
||||
fix: ~
|
||||
parent: ~
|
||||
- kind:
|
||||
NonImperativeMood: This method docstring should be written in imperative mood.
|
||||
location:
|
||||
row: 72
|
||||
row: 74
|
||||
column: 8
|
||||
end_location:
|
||||
row: 72
|
||||
row: 74
|
||||
column: 73
|
||||
fix: ~
|
||||
parent: ~
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use std::path::Path;
|
|||
use rustpython_parser::ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::{collect_call_path, map_callable};
|
||||
use crate::ast::types::CallPath;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::docstrings::definition::Documentable;
|
||||
|
||||
|
|
@ -77,13 +78,22 @@ pub fn is_abstract(checker: &Checker, decorator_list: &[Expr]) -> bool {
|
|||
}
|
||||
|
||||
/// Returns `true` if a function definition is a `@property`.
|
||||
pub fn is_property(checker: &Checker, decorator_list: &[Expr]) -> bool {
|
||||
/// `extra_properties` can be used to check additional non-standard
|
||||
/// `@property`-like decorators.
|
||||
pub fn is_property(
|
||||
checker: &Checker,
|
||||
decorator_list: &[Expr],
|
||||
extra_properties: &[CallPath],
|
||||
) -> bool {
|
||||
decorator_list.iter().any(|expr| {
|
||||
checker
|
||||
.resolve_call_path(map_callable(expr))
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["", "property"]
|
||||
|| call_path.as_slice() == ["functools", "cached_property"]
|
||||
|| extra_properties
|
||||
.iter()
|
||||
.any(|extra_property| extra_property.as_slice() == call_path.as_slice())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1268,6 +1268,16 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"property-decorators": {
|
||||
"description": "Consider any method decorated with one of these decorators as a property, and consequently allow a docstring which is not in imperative mood. Unlike pydocstyle, supplying this option doesn't disable standard property decorators - `@property` and `@cached_property`.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
|
|||
Loading…
Reference in New Issue