Skip to content

Commit

Permalink
[pydoclint] Allow ignoring one line docstrings for DOC rules (#13302
Browse files Browse the repository at this point in the history
)

## Summary

Add a setting to allow ignoring one line docstrings for the pydoclint
rules.

Resolves #13086

Part of #12434

## Test Plan

Run tests with setting enabled.

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
  • Loading branch information
augustelalande and dylwil3 authored Jan 16, 2025
1 parent 177bf72 commit e84c824
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 3 deletions.
29 changes: 29 additions & 0 deletions crates/ruff_linter/src/rules/pydoclint/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Rules from [pydoclint](https://pypi.org/project/pydoclint/).
pub(crate) mod rules;
pub mod settings;

#[cfg(test)]
mod tests {
Expand All @@ -15,6 +16,8 @@ mod tests {
use crate::test::test_path;
use crate::{assert_messages, settings};

use super::settings::Settings;

#[test_case(Rule::DocstringMissingException, Path::new("DOC501.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
Expand Down Expand Up @@ -69,4 +72,30 @@ mod tests {
assert_messages!(snapshot, diagnostics);
Ok(())
}

#[test_case(Rule::DocstringMissingReturns, Path::new("DOC201_google.py"))]
#[test_case(Rule::DocstringMissingYields, Path::new("DOC402_google.py"))]
#[test_case(Rule::DocstringMissingException, Path::new("DOC501_google.py"))]
fn rules_google_style_ignore_one_line(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"{}_{}_ignore_one_line",
rule_code.as_ref(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pydoclint").join(path).as_path(),
&settings::LinterSettings {
pydoclint: Settings {
ignore_one_line_docstrings: true,
},
pydocstyle: pydocstyle::settings::Settings {
convention: Some(Convention::Google),
..pydocstyle::settings::Settings::default()
},
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}
18 changes: 18 additions & 0 deletions crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, visitor, Expr, Stmt};
use ruff_python_semantic::analyze::{function_type, visibility};
use ruff_python_semantic::{Definition, SemanticModel};
use ruff_source_file::NewlineWithTrailingNewline;
use ruff_text_size::{Ranged, TextRange};

use crate::checkers::ast::Checker;
Expand Down Expand Up @@ -843,6 +844,19 @@ fn is_generator_function_annotated_as_returning_none(
.is_some_and(GeneratorOrIteratorArguments::indicates_none_returned)
}

fn is_one_line(docstring: &Docstring) -> bool {
let mut non_empty_line_count = 0;
for line in NewlineWithTrailingNewline::from(docstring.body().as_str()) {
if !line.trim().is_empty() {
non_empty_line_count += 1;
}
if non_empty_line_count > 1 {
return false;
}
}
true
}

/// DOC201, DOC202, DOC402, DOC403, DOC501, DOC502
pub(crate) fn check_docstring(
checker: &mut Checker,
Expand All @@ -858,6 +872,10 @@ pub(crate) fn check_docstring(
return;
};

if checker.settings.pydoclint.ignore_one_line_docstrings && is_one_line(docstring) {
return;
}

let semantic = checker.semantic();

if function_type::is_stub(function_def, semantic) {
Expand Down
21 changes: 21 additions & 0 deletions crates/ruff_linter/src/rules/pydoclint/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Settings for the `pydoclint` plugin.
use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};

#[derive(Debug, Clone, Default, CacheKey)]
pub struct Settings {
pub ignore_one_line_docstrings: bool,
}

impl Display for Settings {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
display_settings! {
formatter = f,
namespace = "linter.pydoclint",
fields = [self.ignore_one_line_docstrings]
}
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
DOC501_google.py:34:5: DOC501 Raised exception `FasterThanLightError` missing from docstring
|
32 | # DOC501
33 | def calculate_speed(distance: float, time: float) -> float:
34 | / """Calculate speed as distance divided by time.
35 | |
36 | | Args:
37 | | distance: Distance traveled.
38 | | time: Time spent traveling.
39 | |
40 | | Returns:
41 | | Speed as distance divided by time.
42 | | """
| |_______^ DOC501
43 | try:
44 | return distance / time
|
= help: Add `FasterThanLightError` to the docstring

DOC501_google.py:51:5: DOC501 Raised exception `ValueError` missing from docstring
|
49 | # DOC501
50 | def calculate_speed(distance: float, time: float) -> float:
51 | / """Calculate speed as distance divided by time.
52 | |
53 | | Args:
54 | | distance: Distance traveled.
55 | | time: Time spent traveling.
56 | |
57 | | Returns:
58 | | Speed as distance divided by time.
59 | | """
| |_______^ DOC501
60 | try:
61 | return distance / time
|
= help: Add `ValueError` to the docstring

DOC501_google.py:51:5: DOC501 Raised exception `FasterThanLightError` missing from docstring
|
49 | # DOC501
50 | def calculate_speed(distance: float, time: float) -> float:
51 | / """Calculate speed as distance divided by time.
52 | |
53 | | Args:
54 | | distance: Distance traveled.
55 | | time: Time spent traveling.
56 | |
57 | | Returns:
58 | | Speed as distance divided by time.
59 | | """
| |_______^ DOC501
60 | try:
61 | return distance / time
|
= help: Add `FasterThanLightError` to the docstring

DOC501_google.py:106:5: DOC501 Raised exception `AnotherError` missing from docstring
|
104 | # DOC501
105 | def calculate_speed(distance: float, time: float) -> float:
106 | / """Calculate speed as distance divided by time.
107 | |
108 | | Args:
109 | | distance: Distance traveled.
110 | | time: Time spent traveling.
111 | |
112 | | Returns:
113 | | Speed as distance divided by time.
114 | | """
| |_______^ DOC501
115 | raise AnotherError
|
= help: Add `AnotherError` to the docstring

DOC501_google.py:120:5: DOC501 Raised exception `AnotherError` missing from docstring
|
118 | # DOC501
119 | def calculate_speed(distance: float, time: float) -> float:
120 | / """Calculate speed as distance divided by time.
121 | |
122 | | Args:
123 | | distance: Distance traveled.
124 | | time: Time spent traveling.
125 | |
126 | | Returns:
127 | | Speed as distance divided by time.
128 | | """
| |_______^ DOC501
129 | raise AnotherError()
|
= help: Add `AnotherError` to the docstring

DOC501_google.py:134:5: DOC501 Raised exception `SomeError` missing from docstring
|
132 | # DOC501
133 | def foo(bar: int):
134 | / """Foo.
135 | |
136 | | Args:
137 | | bar: Bar.
138 | | """
| |_______^ DOC501
139 | raise something.SomeError
|
= help: Add `SomeError` to the docstring

DOC501_google.py:197:5: DOC501 Raised exception `ZeroDivisionError` missing from docstring
|
195 | # DOC501
196 | def calculate_speed(distance: float, time: float) -> float:
197 | / """Calculate speed as distance divided by time.
198 | |
199 | | Args:
200 | | distance: Distance traveled.
201 | | time: Time spent traveling.
202 | |
203 | | Returns:
204 | | Speed as distance divided by time.
205 | |
206 | | Raises:
207 | | TypeError: if you didn't pass a number for both parameters
208 | | """
| |_______^ DOC501
209 | try:
210 | return distance / time
|
= help: Add `ZeroDivisionError` to the docstring

DOC501_google.py:238:5: DOC501 Raised exception `TypeError` missing from docstring
|
237 | def foo():
238 | / """Foo.
239 | |
240 | | Returns:
241 | | 42: int.
242 | | """
| |_______^ DOC501
243 | if True:
244 | raise TypeError # DOC501
|
= help: Add `TypeError` to the docstring

DOC501_google.py:238:5: DOC501 Raised exception `ValueError` missing from docstring
|
237 | def foo():
238 | / """Foo.
239 | |
240 | | Returns:
241 | | 42: int.
242 | | """
| |_______^ DOC501
243 | if True:
244 | raise TypeError # DOC501
|
= help: Add `ValueError` to the docstring
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
DOC201_google.py:3:5: DOC201 `return` is not documented in docstring
|
1 | # DOC201
2 | def foo(num: int) -> str:
3 | / """
4 | | Do something
5 | |
6 | | Args:
7 | | num (int): A number
8 | | """
| |_______^ DOC201
9 | return 'test'
|
= help: Add a "Returns" section to the docstring

DOC201_google.py:44:9: DOC201 `return` is not documented in docstring
|
42 | # DOC201
43 | def bar(self) -> str:
44 | / """
45 | | Do something
46 | |
47 | | Args:
48 | | num (int): A number
49 | | """
| |___________^ DOC201
50 | return 'test'
|
= help: Add a "Returns" section to the docstring

DOC201_google.py:178:5: DOC201 `return` is not documented in docstring
|
176 | # DOC201 - non-early return explicit None
177 | def foo(x: int) -> int | None:
178 | / """A very helpful docstring.
179 | |
180 | | Args:
181 | | x (int): An integer.
182 | | """
| |_______^ DOC201
183 | if x < 0:
184 | return None
|
= help: Add a "Returns" section to the docstring

DOC201_google.py:191:5: DOC201 `return` is not documented in docstring
|
189 | # DOC201 - non-early return explicit None w/o useful type annotations
190 | def foo(x):
191 | / """A very helpful docstring.
192 | |
193 | | Args:
194 | | x (int): An integer.
195 | | """
| |_______^ DOC201
196 | if x < 0:
197 | return None
|
= help: Add a "Returns" section to the docstring

DOC201_google.py:204:5: DOC201 `return` is not documented in docstring
|
202 | # DOC201 - only returns None, but return annotation is not None
203 | def foo(s: str) -> str | None:
204 | / """A very helpful docstring.
205 | |
206 | | Args:
207 | | s (str): A string.
208 | | """
| |_______^ DOC201
209 | return None
|
= help: Add a "Returns" section to the docstring
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
---
DOC402_google.py:3:5: DOC402 `yield` is not documented in docstring
|
1 | # DOC402
2 | def foo(num: int) -> str:
3 | / """
4 | | Do something
5 | |
6 | | Args:
7 | | num (int): A number
8 | | """
| |_______^ DOC402
9 | yield 'test'
|
= help: Add a "Yields" section to the docstring

DOC402_google.py:44:9: DOC402 `yield` is not documented in docstring
|
42 | # DOC402
43 | def bar(self) -> str:
44 | / """
45 | | Do something
46 | |
47 | | Args:
48 | | num (int): A number
49 | | """
| |___________^ DOC402
50 | yield 'test'
|
= help: Add a "Yields" section to the docstring
Loading

0 comments on commit e84c824

Please sign in to comment.