From cfd609357933da47742fa928dd7155915d28dba7 Mon Sep 17 00:00:00 2001 From: InSync Date: Tue, 31 Dec 2024 18:16:55 +0700 Subject: [PATCH] [`pydocstyle`] Add setting to ignore missing documentation for`*args` and `**kwargs` parameters (`D417`) (#15210) Co-authored-by: Micha Reiser --- .../test/fixtures/pydocstyle/D417.py | 9 ++ crates/ruff_linter/src/rules/pydoclint/mod.rs | 10 ++- .../ruff_linter/src/rules/pydocstyle/mod.rs | 55 ++++++++++-- .../src/rules/pydocstyle/rules/sections.rs | 37 +++++---- .../src/rules/pydocstyle/settings.rs | 27 +++--- ...rules__pydocstyle__tests__d417_google.snap | 9 ++ ...ts__d417_google_ignore_var_parameters.snap | 67 +++++++++++++++ ...__pydocstyle__tests__d417_unspecified.snap | 9 ++ ...417_unspecified_ignore_var_parameters.snap | 83 +++++++++++++++++++ crates/ruff_workspace/src/options.rs | 22 +++-- ruff.schema.json | 7 ++ 11 files changed, 285 insertions(+), 50 deletions(-) create mode 100644 crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google_ignore_var_parameters.snap create mode 100644 crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified_ignore_var_parameters.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D417.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D417.py index e9106dc385..d3f5910f35 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D417.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D417.py @@ -168,3 +168,12 @@ def select_data( database: Which database to connect to ("origin" or "destination"). """ + +def f(x, *args, **kwargs): + """Do something. + + Args: + x: the value + *args: var-arguments + """ + return x \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pydoclint/mod.rs b/crates/ruff_linter/src/rules/pydoclint/mod.rs index 68565de689..c35d585b0e 100644 --- a/crates/ruff_linter/src/rules/pydoclint/mod.rs +++ b/crates/ruff_linter/src/rules/pydoclint/mod.rs @@ -37,7 +37,10 @@ mod tests { let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { - pydocstyle: pydocstyle::settings::Settings::new(Some(Convention::Google), [], []), + pydocstyle: pydocstyle::settings::Settings { + convention: Some(Convention::Google), + ..pydocstyle::settings::Settings::default() + }, ..settings::LinterSettings::for_rule(rule_code) }, )?; @@ -56,7 +59,10 @@ mod tests { let diagnostics = test_path( Path::new("pydoclint").join(path).as_path(), &settings::LinterSettings { - pydocstyle: pydocstyle::settings::Settings::new(Some(Convention::Numpy), [], []), + pydocstyle: pydocstyle::settings::Settings { + convention: Some(Convention::Numpy), + ..pydocstyle::settings::Settings::default() + }, ..settings::LinterSettings::for_rule(rule_code) }, )?; diff --git a/crates/ruff_linter/src/rules/pydocstyle/mod.rs b/crates/ruff_linter/src/rules/pydocstyle/mod.rs index 6e2be1ab7f..08731c7868 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/mod.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/mod.rs @@ -12,11 +12,10 @@ mod tests { use crate::registry::Rule; + use super::settings::{Convention, Settings}; use crate::test::test_path; use crate::{assert_messages, settings}; - use super::settings::{Convention, Settings}; - #[test_case(Rule::MissingBlankLineAfterLastSection, Path::new("sections.py"))] #[test_case(Rule::NoBlankLineAfterSection, Path::new("sections.py"))] #[test_case(Rule::MissingBlankLineAfterLastSection, Path::new("D413.py"))] @@ -100,11 +99,13 @@ mod tests { let diagnostics = test_path( Path::new("pydocstyle").join(path).as_path(), &settings::LinterSettings { - pydocstyle: Settings::new( - None, - ["functools.wraps".to_string()], - ["gi.repository.GObject.Property".to_string()], - ), + pydocstyle: Settings { + ignore_decorators: ["functools.wraps".to_string()].into_iter().collect(), + property_decorators: ["gi.repository.GObject.Property".to_string()] + .into_iter() + .collect(), + ..Settings::default() + }, ..settings::LinterSettings::for_rule(rule_code) }, )?; @@ -137,13 +138,46 @@ mod tests { Ok(()) } + #[test] + fn d417_unspecified_ignore_var_parameters() -> Result<()> { + let diagnostics = test_path( + Path::new("pydocstyle/D417.py"), + &settings::LinterSettings { + pydocstyle: Settings::default(), + ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + #[test] fn d417_google() -> Result<()> { let diagnostics = test_path( Path::new("pydocstyle/D417.py"), &settings::LinterSettings { // With explicit Google convention, we should flag every function. - pydocstyle: Settings::new(Some(Convention::Google), [], []), + pydocstyle: Settings { + convention: Some(Convention::Google), + ..Settings::default() + }, + ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + + #[test] + fn d417_google_ignore_var_parameters() -> Result<()> { + let diagnostics = test_path( + Path::new("pydocstyle/D417.py"), + &settings::LinterSettings { + pydocstyle: Settings { + convention: Some(Convention::Google), + ignore_var_parameters: true, + ..Settings::default() + }, ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) }, )?; @@ -157,7 +191,10 @@ mod tests { Path::new("pydocstyle/D417.py"), &settings::LinterSettings { // With explicit numpy convention, we shouldn't flag anything. - pydocstyle: Settings::new(Some(Convention::Numpy), [], []), + pydocstyle: Settings { + convention: Some(Convention::Numpy), + ..Settings::default() + }, ..settings::LinterSettings::for_rule(Rule::UndocumentedParam) }, )?; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index de2e41f619..e0ad726471 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -1215,6 +1215,7 @@ impl AlwaysFixableViolation for MissingSectionNameColon { /// /// ## Options /// - `lint.pydocstyle.convention` +/// - `lint.pydocstyle.ignore-var-parameters` /// /// ## References /// - [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/) @@ -1810,24 +1811,26 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: & // Check specifically for `vararg` and `kwarg`, which can be prefixed with a // single or double star, respectively. - if let Some(arg) = function.parameters.vararg.as_ref() { - let arg_name = arg.name.as_str(); - let starred_arg_name = format!("*{arg_name}"); - if !arg_name.starts_with('_') - && !docstrings_args.contains(arg_name) - && !docstrings_args.contains(&starred_arg_name) - { - missing_arg_names.insert(starred_arg_name); + if !checker.settings.pydocstyle.ignore_var_parameters() { + if let Some(arg) = function.parameters.vararg.as_ref() { + let arg_name = arg.name.as_str(); + let starred_arg_name = format!("*{arg_name}"); + if !arg_name.starts_with('_') + && !docstrings_args.contains(arg_name) + && !docstrings_args.contains(&starred_arg_name) + { + missing_arg_names.insert(starred_arg_name); + } } - } - if let Some(arg) = function.parameters.kwarg.as_ref() { - let arg_name = arg.name.as_str(); - let starred_arg_name = format!("**{arg_name}"); - if !arg_name.starts_with('_') - && !docstrings_args.contains(arg_name) - && !docstrings_args.contains(&starred_arg_name) - { - missing_arg_names.insert(starred_arg_name); + if let Some(arg) = function.parameters.kwarg.as_ref() { + let arg_name = arg.name.as_str(); + let starred_arg_name = format!("**{arg_name}"); + if !arg_name.starts_with('_') + && !docstrings_args.contains(arg_name) + && !docstrings_args.contains(&starred_arg_name) + { + missing_arg_names.insert(starred_arg_name); + } } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/settings.rs b/crates/ruff_linter/src/rules/pydocstyle/settings.rs index 933c5b2364..48e1e36bdb 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/settings.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/settings.rs @@ -87,25 +87,13 @@ impl fmt::Display for Convention { #[derive(Debug, Clone, Default, CacheKey)] pub struct Settings { - convention: Option, - ignore_decorators: BTreeSet, - property_decorators: BTreeSet, + pub convention: Option, + pub ignore_decorators: BTreeSet, + pub property_decorators: BTreeSet, + pub ignore_var_parameters: bool, } impl Settings { - #[must_use] - pub fn new( - convention: Option, - ignore_decorators: impl IntoIterator, - property_decorators: impl IntoIterator, - ) -> Self { - Self { - convention, - ignore_decorators: ignore_decorators.into_iter().collect(), - property_decorators: property_decorators.into_iter().collect(), - } - } - pub fn convention(&self) -> Option { self.convention } @@ -117,6 +105,10 @@ impl Settings { pub fn property_decorators(&self) -> DecoratorIterator { DecoratorIterator::new(&self.property_decorators) } + + pub fn ignore_var_parameters(&self) -> bool { + self.ignore_var_parameters + } } impl fmt::Display for Settings { @@ -127,7 +119,8 @@ impl fmt::Display for Settings { fields = [ self.convention | optional, self.ignore_decorators | set, - self.property_decorators | set + self.property_decorators | set, + self.ignore_var_parameters ] } Ok(()) diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google.snap index e62ac506a5..38a96aa54c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google.snap @@ -72,3 +72,12 @@ D417.py:155:5: D417 Missing argument description in the docstring for `select_da 156 | query: str, 157 | args: tuple, | + +D417.py:172:5: D417 Missing argument description in the docstring for `f`: `**kwargs` + | +170 | """ +171 | +172 | def f(x, *args, **kwargs): + | ^ D417 +173 | """Do something. + | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google_ignore_var_parameters.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google_ignore_var_parameters.snap new file mode 100644 index 0000000000..fd0c4d5dd0 --- /dev/null +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_google_ignore_var_parameters.snap @@ -0,0 +1,67 @@ +--- +source: crates/ruff_linter/src/rules/pydocstyle/mod.rs +snapshot_kind: text +--- +D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +1 | def f(x, y, z): + | ^ D417 +2 | """Do something. + | + +D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +14 | def f(x, y, z): + | ^ D417 +15 | """Do something. + | + +D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +27 | def f(x, y, z): + | ^ D417 +28 | """Do something. + | + +D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +39 | def f(x, y, z): + | ^ D417 +40 | """Do something. + | + +D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y` + | +52 | def f(x, y, z): + | ^ D417 +53 | """Do something. + | + +D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y` + | +65 | def f(x, y, z): + | ^ D417 +66 | """Do something. + | + +D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y` + | +77 | def f(x, y, z): + | ^ D417 +78 | """Do something. + | + +D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x` + | +98 | def f(x, *args, **kwargs): + | ^ D417 +99 | """Do something. + | + +D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save` + | +155 | def select_data( + | ^^^^^^^^^^^ D417 +156 | query: str, +157 | args: tuple, + | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified.snap index e62ac506a5..38a96aa54c 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified.snap @@ -72,3 +72,12 @@ D417.py:155:5: D417 Missing argument description in the docstring for `select_da 156 | query: str, 157 | args: tuple, | + +D417.py:172:5: D417 Missing argument description in the docstring for `f`: `**kwargs` + | +170 | """ +171 | +172 | def f(x, *args, **kwargs): + | ^ D417 +173 | """Do something. + | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified_ignore_var_parameters.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified_ignore_var_parameters.snap new file mode 100644 index 0000000000..38a96aa54c --- /dev/null +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__d417_unspecified_ignore_var_parameters.snap @@ -0,0 +1,83 @@ +--- +source: crates/ruff_linter/src/rules/pydocstyle/mod.rs +snapshot_kind: text +--- +D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +1 | def f(x, y, z): + | ^ D417 +2 | """Do something. + | + +D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +14 | def f(x, y, z): + | ^ D417 +15 | """Do something. + | + +D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +27 | def f(x, y, z): + | ^ D417 +28 | """Do something. + | + +D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z` + | +39 | def f(x, y, z): + | ^ D417 +40 | """Do something. + | + +D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y` + | +52 | def f(x, y, z): + | ^ D417 +53 | """Do something. + | + +D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y` + | +65 | def f(x, y, z): + | ^ D417 +66 | """Do something. + | + +D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y` + | +77 | def f(x, y, z): + | ^ D417 +78 | """Do something. + | + +D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x` + | +98 | def f(x, *args, **kwargs): + | ^ D417 +99 | """Do something. + | + +D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*args` + | +108 | def f(x, *args, **kwargs): + | ^ D417 +109 | """Do something. + | + +D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save` + | +155 | def select_data( + | ^^^^^^^^^^^ D417 +156 | query: str, +157 | args: tuple, + | + +D417.py:172:5: D417 Missing argument description in the docstring for `f`: `**kwargs` + | +170 | """ +171 | +172 | def f(x, *args, **kwargs): + | ^ D417 +173 | """Do something. + | diff --git a/crates/ruff_workspace/src/options.rs b/crates/ruff_workspace/src/options.rs index 28f228de58..9e022a62bb 100644 --- a/crates/ruff_workspace/src/options.rs +++ b/crates/ruff_workspace/src/options.rs @@ -2,7 +2,7 @@ use regex::Regex; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use serde::de::{self}; use serde::{Deserialize, Deserializer, Serialize}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; use strum::IntoEnumIterator; @@ -2836,6 +2836,16 @@ pub struct PydocstyleOptions { "# )] pub property_decorators: Option>, + + /// If set to `true`, ignore missing documentation for `*args` and `**kwargs` parameters. + #[option( + default = r#"false"#, + value_type = "bool", + example = r#" + ignore_var_parameters = true + "# + )] + pub ignore_var_parameters: Option, } impl PydocstyleOptions { @@ -2844,12 +2854,14 @@ impl PydocstyleOptions { convention, ignore_decorators, property_decorators, + ignore_var_parameters: ignore_variadics, } = self; - pydocstyle::settings::Settings::new( + pydocstyle::settings::Settings { convention, - ignore_decorators.unwrap_or_default(), - property_decorators.unwrap_or_default(), - ) + ignore_decorators: BTreeSet::from_iter(ignore_decorators.unwrap_or_default()), + property_decorators: BTreeSet::from_iter(property_decorators.unwrap_or_default()), + ignore_var_parameters: ignore_variadics.unwrap_or_default(), + } } } diff --git a/ruff.schema.json b/ruff.schema.json index e92605f140..320f9efe76 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -2549,6 +2549,13 @@ "type": "string" } }, + "ignore-var-parameters": { + "description": "If set to `true`, ignore missing documentation for `*args` and `**kwargs` parameters.", + "type": [ + "boolean", + "null" + ] + }, "property-decorators": { "description": "A list of decorators that, when applied to a method, indicate that the method should be treated as a property (in addition to the builtin `@property` and standard-library `@functools.cached_property`).\n\nFor example, Ruff will expect that any method decorated by a decorator in this list can use a non-imperative summary line.", "type": [