diff --git a/resources/test/fixtures/canonical_google_examples.py b/resources/test/fixtures/canonical_google_examples.py index 301f83c46f..f9c3e7566b 100644 --- a/resources/test/fixtures/canonical_google_examples.py +++ b/resources/test/fixtures/canonical_google_examples.py @@ -44,7 +44,7 @@ expectation.expected.add(( @expect("D407: Missing dashed underline after section ('Returns')", arg_count=3) @expect("D413: Missing blank line after last section ('Raises')", arg_count=3) -def fetch_bigtable_rows(big_table, keys, other_silly_variable=None): +def fetch_bigtable_rows(big_table, keys, other_silly_variable=None, **kwargs): """Fetches rows from a Bigtable. Retrieves rows pertaining to the given keys from the Table instance @@ -57,6 +57,7 @@ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None): to fetch. other_silly_variable: Another optional variable, that has a much longer name than the other args, and which does nothing. + **kwargs: More keyword arguments. Returns: A dict mapping keys to the corresponding table row data diff --git a/resources/test/fixtures/canonical_numpy_examples.py b/resources/test/fixtures/canonical_numpy_examples.py index f0732b3790..1c2149f816 100644 --- a/resources/test/fixtures/canonical_numpy_examples.py +++ b/resources/test/fixtures/canonical_numpy_examples.py @@ -73,7 +73,7 @@ expectation.expected.add(( "(found 'A')", arg_count=3) @expect("D413: Missing blank line after last section ('Examples')", arg_count=3) -def foo(var1, var2, long_var_name='hi'): +def foo(var1, var2, long_var_name='hi', **kwargs): r"""A one-line summary that does not use variable names. Several sentences providing an extended description. Refer to @@ -91,6 +91,8 @@ def foo(var1, var2, long_var_name='hi'): detail, e.g. ``(N,) ndarray`` or ``array_like``. long_var_name : {'hi', 'ho'}, optional Choices in brackets, default first when optional. + **kwargs : int + More keyword arguments. Returns ------- diff --git a/src/pydocstyle/plugins.rs b/src/pydocstyle/plugins.rs index 630f61416b..a303a2784a 100644 --- a/src/pydocstyle/plugins.rs +++ b/src/pydocstyle/plugins.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use once_cell::sync::Lazy; use regex::Regex; use rustc_hash::FxHashSet; -use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind}; +use rustpython_ast::{Constant, ExprKind, Location, StmtKind}; use crate::ast::types::Range; use crate::ast::whitespace; @@ -1303,8 +1303,9 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args: args: arguments, .. } = &parent.node { - // Collect all the arguments into a single vector. - let mut all_arguments: Vec<&Arg> = arguments + // Look for arguments that weren't included in the docstring. + let mut missing_arg_names: BTreeSet = BTreeSet::default(); + for arg in arguments .args .iter() .chain(arguments.posonlyargs.iter()) @@ -1316,33 +1317,38 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args: && !is_staticmethod(parent), ), ) - .collect(); + { + let arg_name = arg.node.arg.as_str(); + if !arg_name.starts_with('_') && !docstrings_args.contains(&arg_name) { + missing_arg_names.insert(arg_name.to_string()); + } + } + + // Check specifically for `vararg` and `kwarg`, which can be prefixed with a single or + // double star, respectively. if let Some(arg) = &arguments.vararg { - all_arguments.push(arg); + let arg_name = arg.node.arg.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.as_str()) + { + missing_arg_names.insert(starred_arg_name); + } } if let Some(arg) = &arguments.kwarg { - all_arguments.push(arg); - } - - // Look for arguments that weren't included in the docstring. - let mut missing_args: BTreeSet<&str> = BTreeSet::default(); - for arg in all_arguments { let arg_name = arg.node.arg.as_str(); - if arg_name.starts_with('_') { - continue; + let starred_arg_name = format!("**{arg_name}"); + if !arg_name.starts_with('_') + && !docstrings_args.contains(&arg_name) + && !docstrings_args.contains(&starred_arg_name.as_str()) + { + missing_arg_names.insert(starred_arg_name); } - if docstrings_args.contains(&arg_name) { - continue; - } - missing_args.insert(arg_name); } - if !missing_args.is_empty() { - let names = missing_args - .into_iter() - .map(String::from) - .sorted() - .collect(); + if !missing_arg_names.is_empty() { + let names = missing_arg_names.into_iter().sorted().collect(); checker.add_check(Check::new( CheckKind::DocumentAllArguments(names), Range::from_located(parent),