This commit is contained in:
Auguste Lalande 2025-12-16 16:39:34 -05:00 committed by GitHub
commit 78da98fd22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 814 additions and 34 deletions

View File

@ -81,6 +81,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::UndocumentedPublicPackage, Rule::UndocumentedPublicPackage,
]); ]);
let enforce_pydoclint = checker.any_rule_enabled(&[ let enforce_pydoclint = checker.any_rule_enabled(&[
Rule::UndocumentedParam,
Rule::DocstringExtraneousParameter, Rule::DocstringExtraneousParameter,
Rule::DocstringMissingReturns, Rule::DocstringMissingReturns,
Rule::DocstringExtraneousReturns, Rule::DocstringExtraneousReturns,

View File

@ -12,6 +12,7 @@ mod tests {
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::pydocstyle; use crate::rules::pydocstyle;
use crate::rules::pydocstyle::settings::Convention; use crate::rules::pydocstyle::settings::Convention;
use crate::settings::types::PreviewMode;
use crate::test::test_path; use crate::test::test_path;
use crate::{assert_diagnostics, settings}; use crate::{assert_diagnostics, settings};
@ -99,4 +100,107 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics); assert_diagnostics!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test_case(Rule::UndocumentedParam, Path::new("canonical_google_examples.py"))]
#[test_case(Rule::UndocumentedParam, Path::new("canonical_numpy_examples.py"))]
#[test_case(Rule::UndocumentedParam, Path::new("sections.py"))]
fn undocumented_param(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pydocstyle").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
pydocstyle: pydocstyle::settings::Settings {
..pydocstyle::settings::Settings::default()
},
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test]
fn d417_unspecified() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
// When inferring the convention, we'll see a few false negatives.
// See: https://github.com/PyCQA/pydocstyle/issues/459.
pydocstyle: pydocstyle::settings::Settings::default(),
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn d417_unspecified_ignore_var_parameters() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
pydocstyle: pydocstyle::settings::Settings::default(),
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn d417_google() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
// With explicit Google convention, we should flag every function.
pydocstyle: pydocstyle::settings::Settings {
convention: Some(Convention::Google),
..pydocstyle::settings::Settings::default()
},
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn d417_google_ignore_var_parameters() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
pydocstyle: pydocstyle::settings::Settings {
convention: Some(Convention::Google),
ignore_var_parameters: true,
..pydocstyle::settings::Settings::default()
},
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
#[test]
fn d417_numpy() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
// With explicit numpy convention, we shouldn't flag anything.
pydocstyle: pydocstyle::settings::Settings {
convention: Some(Convention::Numpy),
..pydocstyle::settings::Settings::default()
},
..settings::LinterSettings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_diagnostics!(diagnostics);
Ok(())
}
} }

View File

@ -1,9 +1,11 @@
use itertools::Itertools; use itertools::Itertools;
use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::helpers::{map_callable, map_subscript}; use ruff_python_ast::helpers::{map_callable, map_subscript};
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::QualifiedName; use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr, Stmt, visitor}; use ruff_python_ast::{self as ast, Expr, Stmt, visitor};
use ruff_python_semantic::analyze::visibility::is_staticmethod;
use ruff_python_semantic::analyze::{function_type, visibility}; use ruff_python_semantic::analyze::{function_type, visibility};
use ruff_python_semantic::{Definition, SemanticModel}; use ruff_python_semantic::{Definition, SemanticModel};
use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::identifiers::is_identifier;
@ -17,6 +19,7 @@ use crate::docstrings::Docstring;
use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind}; use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind};
use crate::docstrings::styles::SectionStyle; use crate::docstrings::styles::SectionStyle;
use crate::registry::Rule; use crate::registry::Rule;
use crate::rules::pydocstyle::rules::UndocumentedParam;
use crate::rules::pydocstyle::settings::Convention; use crate::rules::pydocstyle::settings::Convention;
/// ## What it does /// ## What it does
@ -464,6 +467,7 @@ impl GenericSection {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ParameterEntry<'a> { struct ParameterEntry<'a> {
name: &'a str, name: &'a str,
has_definition: bool,
range: TextRange, range: TextRange,
} }
@ -523,6 +527,16 @@ impl<'a> ParametersSection<'a> {
range: section.section_name_range(), range: section.section_name_range(),
} }
} }
fn extend_from_section(&mut self, section: &SectionContext<'a>, style: Option<SectionStyle>) {
let mut new_entries = parse_parameters(
section.following_lines_str(),
section.following_range().start(),
style,
);
self.parameters.append(&mut new_entries);
}
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -538,10 +552,22 @@ impl<'a> DocstringSections<'a> {
let mut docstring_sections = Self::default(); let mut docstring_sections = Self::default();
for section in sections { for section in sections {
match section.kind() { match section.kind() {
SectionKind::Args | SectionKind::Arguments | SectionKind::Parameters => { SectionKind::Args
| SectionKind::Arguments
| SectionKind::Parameters
| SectionKind::KeywordArgs
| SectionKind::KeywordArguments
| SectionKind::OtherArgs
| SectionKind::OtherArguments
| SectionKind::OtherParams
| SectionKind::OtherParameters => {
if let Some(ref mut parameters_section) = docstring_sections.parameters {
parameters_section.extend_from_section(&section, style);
} else {
docstring_sections.parameters = docstring_sections.parameters =
Some(ParametersSection::from_section(&section, style)); Some(ParametersSection::from_section(&section, style));
} }
}
SectionKind::Raises => { SectionKind::Raises => {
docstring_sections.raises = Some(RaisesSection::from_section(&section, style)); docstring_sections.raises = Some(RaisesSection::from_section(&section, style));
} }
@ -569,11 +595,12 @@ fn parse_parameters(
) -> Vec<ParameterEntry<'_>> { ) -> Vec<ParameterEntry<'_>> {
match style { match style {
Some(SectionStyle::Google) => parse_parameters_google(content, content_start), Some(SectionStyle::Google) => parse_parameters_google(content, content_start),
Some(SectionStyle::Numpy) => parse_parameters_numpy(content, content_start), Some(SectionStyle::Numpy) => parse_parameters_numpy(content, content_start),
None => { None => {
let entries = parse_parameters_google(content, content_start); let entries = parse_parameters_numpy(content, content_start);
if entries.is_empty() { if entries.is_empty() {
parse_parameters_numpy(content, content_start) parse_parameters_google(content, content_start)
} else { } else {
entries entries
} }
@ -591,7 +618,11 @@ fn parse_parameters(
fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> { fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> {
let mut entries: Vec<ParameterEntry> = Vec::new(); let mut entries: Vec<ParameterEntry> = Vec::new();
// Find first entry to determine indentation // Find first entry to determine indentation
let Some(first_arg) = content.lines().next() else { let Some((_, first_arg)) = content
.lines()
.enumerate()
.find(|(_, line)| !line.trim().is_empty())
else {
return entries; return entries;
}; };
let indentation = &first_arg[..first_arg.len() - first_arg.trim_start().len()]; let indentation = &first_arg[..first_arg.len() - first_arg.trim_start().len()];
@ -607,7 +638,7 @@ fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<Parame
.next() .next()
.is_some_and(|first_char| !first_char.is_whitespace()) .is_some_and(|first_char| !first_char.is_whitespace())
{ {
let Some((before_colon, _)) = entry.split_once(':') else { let Some((before_colon, after_colon)) = entry.split_once(':') else {
continue; continue;
}; };
if let Some(param) = before_colon.split_whitespace().next() { if let Some(param) = before_colon.split_whitespace().next() {
@ -615,9 +646,11 @@ fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<Parame
if is_identifier(param_name) { if is_identifier(param_name) {
let param_start = line_start + indentation.text_len(); let param_start = line_start + indentation.text_len();
let param_end = param_start + param.text_len(); let param_end = param_start + param.text_len();
let has_definition = !after_colon.trim().is_empty();
entries.push(ParameterEntry { entries.push(ParameterEntry {
name: param_name, name: param_name,
has_definition,
range: TextRange::new( range: TextRange::new(
content_start + param_start, content_start + param_start,
content_start + param_end, content_start + param_end,
@ -625,6 +658,13 @@ fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<Parame
}); });
} }
} }
} else {
// this is a follow up of the previous entry.
if !entry.trim().is_empty() {
if let Some(last) = entries.last_mut() {
last.has_definition = true;
}
}
} }
} }
} }
@ -644,10 +684,14 @@ fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<Parame
fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> { fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> {
let mut entries: Vec<ParameterEntry> = Vec::new(); let mut entries: Vec<ParameterEntry> = Vec::new();
let mut lines = content.lines(); let mut lines = content.lines();
let Some(dashes) = lines.next() else { let Some(dashes_line) = lines.next() else {
return entries; return entries;
}; };
let indentation = &dashes[..dashes.len() - dashes.trim_start().len()]; let dashes = dashes_line.trim_start();
if dashes.is_empty() || !dashes.chars().all(|c| c == '-') {
return entries;
}
let indentation = &dashes_line[..dashes_line.len() - dashes.len()];
let mut current_pos = content.full_line_end(dashes.text_len()); let mut current_pos = content.full_line_end(dashes.text_len());
for potential in lines { for potential in lines {
@ -662,7 +706,6 @@ fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<Paramet
{ {
if let Some(before_colon) = entry.split(':').next() { if let Some(before_colon) = entry.split(':').next() {
let param_line = before_colon.trim_end(); let param_line = before_colon.trim_end();
// Split on commas to handle comma-separated parameters // Split on commas to handle comma-separated parameters
let mut current_offset = TextSize::from(0); let mut current_offset = TextSize::from(0);
for param_part in param_line.split(',') { for param_part in param_line.split(',') {
@ -678,6 +721,7 @@ fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<Paramet
entries.push(ParameterEntry { entries.push(ParameterEntry {
name: param_name, name: param_name,
has_definition: true,
range: TextRange::at( range: TextRange::at(
content_start + param_start, content_start + param_start,
param_part_trimmed.text_len(), param_part_trimmed.text_len(),
@ -703,9 +747,9 @@ fn parse_raises(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName
Some(SectionStyle::Google) => parse_raises_google(content), Some(SectionStyle::Google) => parse_raises_google(content),
Some(SectionStyle::Numpy) => parse_raises_numpy(content), Some(SectionStyle::Numpy) => parse_raises_numpy(content),
None => { None => {
let entries = parse_raises_google(content); let entries = parse_raises_numpy(content);
if entries.is_empty() { if entries.is_empty() {
parse_raises_numpy(content) parse_raises_google(content)
} else { } else {
entries entries
} }
@ -763,10 +807,14 @@ fn parse_raises_google(content: &str) -> Vec<QualifiedName<'_>> {
fn parse_raises_numpy(content: &str) -> Vec<QualifiedName<'_>> { fn parse_raises_numpy(content: &str) -> Vec<QualifiedName<'_>> {
let mut entries: Vec<QualifiedName> = Vec::new(); let mut entries: Vec<QualifiedName> = Vec::new();
let mut lines = content.lines(); let mut lines = content.lines();
let Some(dashes) = lines.next() else { let Some(dashes_line) = lines.next() else {
return entries; return entries;
}; };
let indentation = &dashes[..dashes.len() - dashes.trim_start().len()]; let dashes = dashes_line.trim_start();
if dashes.is_empty() || !dashes.chars().all(|c| c == '-') {
return entries;
}
let indentation = &dashes_line[..dashes_line.len() - dashes.len()];
for potential in lines { for potential in lines {
if let Some(entry) = potential.strip_prefix(indentation) { if let Some(entry) = potential.strip_prefix(indentation) {
// Check for Sphinx directives (lines starting with ..) - these indicate the end of the // Check for Sphinx directives (lines starting with ..) - these indicate the end of the
@ -1166,14 +1214,41 @@ fn is_generator_function_annotated_as_returning_none(
.is_some_and(GeneratorOrIteratorArguments::indicates_none_returned) .is_some_and(GeneratorOrIteratorArguments::indicates_none_returned)
} }
fn parameters_from_signature<'a>(docstring: &'a Docstring) -> Vec<&'a str> { #[derive(Debug)]
struct SignatureParameter<'a> {
name: &'a str,
is_vararg: bool,
is_kwarg: bool,
}
fn parameters_from_signature<'a>(docstring: &'a Docstring) -> Vec<SignatureParameter<'a>> {
let mut parameters = Vec::new(); let mut parameters = Vec::new();
let Some(function) = docstring.definition.as_function_def() else { let Some(function) = docstring.definition.as_function_def() else {
return parameters; return parameters;
}; };
for param in &function.parameters { for param in function.parameters.iter_non_variadic_params() {
parameters.push(param.name()); parameters.push(SignatureParameter {
name: param.name(),
is_vararg: false,
is_kwarg: false,
});
} }
if let Some(param) = function.parameters.vararg.as_ref() {
parameters.push(SignatureParameter {
name: param.name(),
is_vararg: true,
is_kwarg: false,
});
}
if let Some(param) = function.parameters.kwarg.as_ref() {
parameters.push(SignatureParameter {
name: param.name(),
is_vararg: false,
is_kwarg: true,
});
}
parameters parameters
} }
@ -1209,10 +1284,6 @@ pub(crate) fn check_docstring(
let semantic = checker.semantic(); let semantic = checker.semantic();
if function_type::is_stub(function_def, semantic) {
return;
}
// Prioritize the specified convention over the determined style. // Prioritize the specified convention over the determined style.
let docstring_sections = match convention { let docstring_sections = match convention {
Some(Convention::Google) => { Some(Convention::Google) => {
@ -1232,6 +1303,57 @@ pub(crate) fn check_docstring(
let signature_parameters = parameters_from_signature(docstring); let signature_parameters = parameters_from_signature(docstring);
// DOC101
if checker.settings().preview.is_enabled() {
if checker.is_rule_enabled(Rule::UndocumentedParam) {
if let Some(parameters_section) = docstring_sections.parameters.as_ref() {
let mut missing_parameters = Vec::new();
// Here we check if the function is a method (and not a staticmethod)
// in which case we skip the first argument which should be `self` or
// `cls`, and does not need to be documented.
for signature_param in signature_parameters.iter().skip(usize::from(
docstring.definition.is_method()
&& !is_staticmethod(&function_def.decorator_list, semantic),
)) {
if !(checker.settings().pydocstyle.ignore_var_parameters()
&& (signature_param.is_vararg || signature_param.is_kwarg)
|| signature_param.name.starts_with('_')
|| parameters_section.parameters.iter().any(|param| {
param.name == signature_param.name && param.has_definition
}))
{
let name = signature_param.name;
if signature_param.is_vararg {
missing_parameters.push(format!("*{name}"));
} else if signature_param.is_kwarg {
missing_parameters.push(format!("**{name}"));
} else {
missing_parameters.push(name.to_string());
}
}
}
if !missing_parameters.is_empty() {
if let Some(definition) = docstring.definition.name() {
let names = missing_parameters.into_iter().sorted().collect();
checker.report_diagnostic(
UndocumentedParam {
definition: definition.to_string(),
names,
},
function_def.identifier(),
);
}
}
}
}
}
if function_type::is_stub(function_def, semantic) {
return;
}
// DOC201 // DOC201
if checker.is_rule_enabled(Rule::DocstringMissingReturns) { if checker.is_rule_enabled(Rule::DocstringMissingReturns) {
if should_document_returns(function_def) if should_document_returns(function_def)
@ -1326,7 +1448,10 @@ pub(crate) fn check_docstring(
if function_def.parameters.vararg.is_none() && function_def.parameters.kwarg.is_none() { if function_def.parameters.vararg.is_none() && function_def.parameters.kwarg.is_none() {
if let Some(docstring_params) = docstring_sections.parameters { if let Some(docstring_params) = docstring_sections.parameters {
for docstring_param in &docstring_params.parameters { for docstring_param in &docstring_params.parameters {
if !signature_parameters.contains(&docstring_param.name) { if !signature_parameters
.iter()
.any(|param| param.name == docstring_param.name)
{
checker.report_diagnostic( checker.report_diagnostic(
DocstringExtraneousParameter { DocstringExtraneousParameter {
id: docstring_param.name.to_string(), id: docstring_param.name.to_string(),

View File

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---

View File

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---

View File

@ -0,0 +1,102 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
D417 Missing argument description in the docstring for `bar`: `y`
--> sections.py:292:9
|
290 | x = 1
291 |
292 | def bar(y=2): # noqa: D207, D213, D406, D407
| ^^^
293 | """Nested function test for docstrings.
|
D417 Missing argument description in the docstring for `test_missing_google_args`: `y`
--> sections.py:309:5
|
307 | "(argument(s) y are missing descriptions in "
308 | "'test_missing_google_args' docstring)")
309 | def test_missing_google_args(x=1, y=2, _private=3): # noqa: D406, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^
310 | """Toggle the gizmo.
|
D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
--> sections.py:333:9
|
331 | "(argument(s) test, y, z are missing descriptions in "
332 | "'test_missing_args' docstring)", arg_count=5)
333 | def test_missing_args(self, test, x, y, z=3, _private_arg=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^
334 | """Test a valid args section.
|
D417 Missing argument descriptions in the docstring for `test_missing_args_class_method`: `test`, `y`, `z`
--> sections.py:345:9
|
343 | "(argument(s) test, y, z are missing descriptions in "
344 | "'test_missing_args_class_method' docstring)", arg_count=5)
345 | def test_missing_args_class_method(cls, test, x, y, _, z=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
346 | """Test a valid args section.
|
D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `y`, `z`
--> sections.py:358:9
|
356 | "(argument(s) a, y, z are missing descriptions in "
357 | "'test_missing_args_static_method' docstring)", arg_count=4)
358 | def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
359 | """Test a valid args section.
|
D417 Missing argument descriptions in the docstring for `test_missing_docstring`: `a`, `b`
--> sections.py:370:9
|
368 | "(argument(s) a, b are missing descriptions in "
369 | "'test_missing_docstring' docstring)", arg_count=2)
370 | def test_missing_docstring(a, b): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^
371 | """Test a valid args section.
|
D417 Missing argument description in the docstring for `test_missing_numpy_args`: `y`
--> sections.py:398:5
|
396 | "(argument(s) y are missing descriptions in "
397 | "'test_missing_numpy_args' docstring)")
398 | def test_missing_numpy_args(_private_arg=0, x=1, y=2): # noqa: D406, D407
| ^^^^^^^^^^^^^^^^^^^^^^^
399 | """Toggle the gizmo.
|
D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
--> sections.py:434:9
|
432 | "(argument(s) test, y, z are missing descriptions in "
433 | "'test_missing_args' docstring)", arg_count=5)
434 | def test_missing_args(self, test, x, y, z=3, t=1, _private=0): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^
435 | """Test a valid args section.
|
D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `z`
--> sections.py:468:9
|
466 | "(argument(s) a, z are missing descriptions in "
467 | "'test_missing_args_static_method' docstring)", arg_count=3)
468 | def test_missing_args_static_method(a, x, y, z=3, t=1): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
469 | """Test a valid args section.
|
D417 Missing argument description in the docstring for `test_incorrect_indent`: `y`
--> sections.py:498:9
|
496 | "(argument(s) y are missing descriptions in "
497 | "'test_incorrect_indent' docstring)", arg_count=3)
498 | def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407
| ^^^^^^^^^^^^^^^^^^^^^
499 | """Reproducing issue #437.
|

View File

@ -0,0 +1,113 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:1:5
|
1 | def f(x, y, z):
| ^
2 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:14:5
|
14 | def f(x, y, z):
| ^
15 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:27:5
|
27 | def f(x, y, z):
| ^
28 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:39:5
|
39 | def f(x, y, z):
| ^
40 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:52:5
|
52 | def f(x, y, z):
| ^
53 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:65:5
|
65 | def f(x, y, z):
| ^
66 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:77:5
|
77 | def f(x, y, z):
| ^
78 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:98:5
|
98 | def f(x, *args, **kwargs):
| ^
99 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `*args`
--> D417.py:108:5
|
108 | def f(x, *args, **kwargs):
| ^
109 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:131:5
|
129 | return x, y, z
130 |
131 | def f(x):
| ^
132 | """Do something with valid description.
|
D417 Missing argument description in the docstring for `select_data`: `auto_save`
--> D417.py:155:5
|
155 | def select_data(
| ^^^^^^^^^^^
156 | query: str,
157 | args: tuple,
|
D417 Missing argument description in the docstring for `f`: `**kwargs`
--> D417.py:172:5
|
170 | """
171 |
172 | def f(x, *args, **kwargs):
| ^
173 | """Do something.
|
D417 Missing argument description in the docstring for `should_fail`: `Args`
--> D417.py:199:5
|
198 | # undocumented argument with the same name as a section
199 | def should_fail(payload, Args):
| ^^^^^^^^^^^
200 | """
201 | Send a message.
|

View File

@ -0,0 +1,95 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:1:5
|
1 | def f(x, y, z):
| ^
2 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:14:5
|
14 | def f(x, y, z):
| ^
15 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:27:5
|
27 | def f(x, y, z):
| ^
28 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:39:5
|
39 | def f(x, y, z):
| ^
40 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:52:5
|
52 | def f(x, y, z):
| ^
53 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:65:5
|
65 | def f(x, y, z):
| ^
66 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:77:5
|
77 | def f(x, y, z):
| ^
78 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:98:5
|
98 | def f(x, *args, **kwargs):
| ^
99 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:131:5
|
129 | return x, y, z
130 |
131 | def f(x):
| ^
132 | """Do something with valid description.
|
D417 Missing argument description in the docstring for `select_data`: `auto_save`
--> D417.py:155:5
|
155 | def select_data(
| ^^^^^^^^^^^
156 | query: str,
157 | args: tuple,
|
D417 Missing argument description in the docstring for `should_fail`: `Args`
--> D417.py:199:5
|
198 | # undocumented argument with the same name as a section
199 | def should_fail(payload, Args):
| ^^^^^^^^^^^
200 | """
201 | Send a message.
|

View File

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---

View File

@ -0,0 +1,113 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:1:5
|
1 | def f(x, y, z):
| ^
2 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:14:5
|
14 | def f(x, y, z):
| ^
15 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:27:5
|
27 | def f(x, y, z):
| ^
28 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:39:5
|
39 | def f(x, y, z):
| ^
40 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:52:5
|
52 | def f(x, y, z):
| ^
53 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:65:5
|
65 | def f(x, y, z):
| ^
66 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:77:5
|
77 | def f(x, y, z):
| ^
78 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:98:5
|
98 | def f(x, *args, **kwargs):
| ^
99 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `*args`
--> D417.py:108:5
|
108 | def f(x, *args, **kwargs):
| ^
109 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:131:5
|
129 | return x, y, z
130 |
131 | def f(x):
| ^
132 | """Do something with valid description.
|
D417 Missing argument description in the docstring for `select_data`: `auto_save`
--> D417.py:155:5
|
155 | def select_data(
| ^^^^^^^^^^^
156 | query: str,
157 | args: tuple,
|
D417 Missing argument description in the docstring for `f`: `**kwargs`
--> D417.py:172:5
|
170 | """
171 |
172 | def f(x, *args, **kwargs):
| ^
173 | """Do something.
|
D417 Missing argument description in the docstring for `should_fail`: `Args`
--> D417.py:199:5
|
198 | # undocumented argument with the same name as a section
199 | def should_fail(payload, Args):
| ^^^^^^^^^^^
200 | """
201 | Send a message.
|

View File

@ -0,0 +1,113 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:1:5
|
1 | def f(x, y, z):
| ^
2 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:14:5
|
14 | def f(x, y, z):
| ^
15 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:27:5
|
27 | def f(x, y, z):
| ^
28 | """Do something.
|
D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
--> D417.py:39:5
|
39 | def f(x, y, z):
| ^
40 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:52:5
|
52 | def f(x, y, z):
| ^
53 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:65:5
|
65 | def f(x, y, z):
| ^
66 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `y`
--> D417.py:77:5
|
77 | def f(x, y, z):
| ^
78 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:98:5
|
98 | def f(x, *args, **kwargs):
| ^
99 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `*args`
--> D417.py:108:5
|
108 | def f(x, *args, **kwargs):
| ^
109 | """Do something.
|
D417 Missing argument description in the docstring for `f`: `x`
--> D417.py:131:5
|
129 | return x, y, z
130 |
131 | def f(x):
| ^
132 | """Do something with valid description.
|
D417 Missing argument description in the docstring for `select_data`: `auto_save`
--> D417.py:155:5
|
155 | def select_data(
| ^^^^^^^^^^^
156 | query: str,
157 | args: tuple,
|
D417 Missing argument description in the docstring for `f`: `**kwargs`
--> D417.py:172:5
|
170 | """
171 |
172 | def f(x, *args, **kwargs):
| ^
173 | """Do something.
|
D417 Missing argument description in the docstring for `should_fail`: `Args`
--> D417.py:199:5
|
198 | # undocumented argument with the same name as a section
199 | def should_fail(payload, Args):
| ^^^^^^^^^^^
200 | """
201 | Send a message.
|

View File

@ -1243,9 +1243,9 @@ impl AlwaysFixableViolation for MissingSectionNameColon {
#[violation_metadata(stable_since = "v0.0.73")] #[violation_metadata(stable_since = "v0.0.73")]
pub(crate) struct UndocumentedParam { pub(crate) struct UndocumentedParam {
/// The name of the function being documented. /// The name of the function being documented.
definition: String, pub(crate) definition: String,
/// The names of the undocumented parameters. /// The names of the undocumented parameters.
names: Vec<String>, pub(crate) names: Vec<String>,
} }
impl Violation for UndocumentedParam { impl Violation for UndocumentedParam {
@ -1828,6 +1828,7 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
} }
} }
if checker.settings().preview.is_disabled() {
if !missing_arg_names.is_empty() { if !missing_arg_names.is_empty() {
if let Some(definition) = docstring.definition.name() { if let Some(definition) = docstring.definition.name() {
let names = missing_arg_names.into_iter().sorted().collect(); let names = missing_arg_names.into_iter().sorted().collect();
@ -1841,6 +1842,7 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
} }
} }
} }
}
/// Returns `true` if the parameter is annotated with `typing.Unpack` /// Returns `true` if the parameter is annotated with `typing.Unpack`
fn has_unpack_annotation(checker: &Checker, parameter: &Parameter) -> bool { fn has_unpack_annotation(checker: &Checker, parameter: &Parameter) -> bool {