mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
[DOC201
] Permit explicit None
in functions that only return None
(#13064)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
parent
e6d0c4a65d
commit
96b42b0c8f
5 changed files with 297 additions and 14 deletions
|
@ -119,3 +119,91 @@ class A(metaclass=abc.abcmeta):
|
||||||
def f(self):
|
def f(self):
|
||||||
"""Lorem ipsum."""
|
"""Lorem ipsum."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# OK - implicit None early return
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - explicit None early return
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - explicit None early return w/o useful type annotations
|
||||||
|
def foo(obj):
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - multiple explicit None early returns
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
if obj == "None":
|
||||||
|
return
|
||||||
|
if obj == 0:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - non-early return explicit None
|
||||||
|
def foo(x: int) -> int | None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x (int): An interger.
|
||||||
|
"""
|
||||||
|
if x < 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - non-early return explicit None w/o useful type annotations
|
||||||
|
def foo(x):
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x (int): An interger.
|
||||||
|
"""
|
||||||
|
if x < 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - only returns None, but return annotation is not None
|
||||||
|
def foo(s: str) -> str | None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): A string.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
|
@ -85,3 +85,105 @@ class A(metaclass=abc.abcmeta):
|
||||||
def f(self):
|
def f(self):
|
||||||
"""Lorem ipsum."""
|
"""Lorem ipsum."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# OK - implicit None early return
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj : object
|
||||||
|
An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - explicit None early return
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj : object
|
||||||
|
An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - explicit None early return w/o useful type annotations
|
||||||
|
def foo(obj):
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj : object
|
||||||
|
An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# OK - multiple explicit None early returns
|
||||||
|
def foo(obj: object) -> None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
obj : object
|
||||||
|
An object.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
if obj == "None":
|
||||||
|
return
|
||||||
|
if obj == 0:
|
||||||
|
return None
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - non-early return explicit None
|
||||||
|
def foo(x: int) -> int | None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
x : int
|
||||||
|
An interger.
|
||||||
|
"""
|
||||||
|
if x < 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - non-early return explicit None w/o useful type annotations
|
||||||
|
def foo(x):
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
x : int
|
||||||
|
An interger.
|
||||||
|
"""
|
||||||
|
if x < 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
# DOC201 - only returns None, but return annotation is not None
|
||||||
|
def foo(s: str) -> str | None:
|
||||||
|
"""A very helpful docstring.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
x : str
|
||||||
|
A string.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
|
@ -25,7 +25,8 @@ use crate::rules::pydocstyle::settings::Convention;
|
||||||
/// Docstrings missing return sections are a sign of incomplete documentation
|
/// Docstrings missing return sections are a sign of incomplete documentation
|
||||||
/// or refactors.
|
/// or refactors.
|
||||||
///
|
///
|
||||||
/// This rule is not enforced for abstract methods and stubs functions.
|
/// This rule is not enforced for abstract methods, stubs functions, or
|
||||||
|
/// functions that only return `None`.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
|
@ -494,13 +495,26 @@ fn parse_entries_numpy(content: &str) -> Vec<QualifiedName> {
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An individual documentable statement in a function body.
|
/// An individual `yield` expression in a function body.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Entry {
|
struct YieldEntry {
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ranged for Entry {
|
impl Ranged for YieldEntry {
|
||||||
|
fn range(&self) -> TextRange {
|
||||||
|
self.range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An individual `return` statement in a function body.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ReturnEntry {
|
||||||
|
range: TextRange,
|
||||||
|
is_none_return: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ranged for ReturnEntry {
|
||||||
fn range(&self) -> TextRange {
|
fn range(&self) -> TextRange {
|
||||||
self.range
|
self.range
|
||||||
}
|
}
|
||||||
|
@ -522,15 +536,15 @@ impl Ranged for ExceptionEntry<'_> {
|
||||||
/// A summary of documentable statements from the function body
|
/// A summary of documentable statements from the function body
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BodyEntries<'a> {
|
struct BodyEntries<'a> {
|
||||||
returns: Vec<Entry>,
|
returns: Vec<ReturnEntry>,
|
||||||
yields: Vec<Entry>,
|
yields: Vec<YieldEntry>,
|
||||||
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An AST visitor to extract a summary of documentable statements from a function body.
|
/// An AST visitor to extract a summary of documentable statements from a function body.
|
||||||
struct BodyVisitor<'a> {
|
struct BodyVisitor<'a> {
|
||||||
returns: Vec<Entry>,
|
returns: Vec<ReturnEntry>,
|
||||||
yields: Vec<Entry>,
|
yields: Vec<YieldEntry>,
|
||||||
currently_suspended_exceptions: Option<&'a ast::Expr>,
|
currently_suspended_exceptions: Option<&'a ast::Expr>,
|
||||||
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
||||||
semantic: &'a SemanticModel<'a>,
|
semantic: &'a SemanticModel<'a>,
|
||||||
|
@ -623,9 +637,12 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> {
|
||||||
}
|
}
|
||||||
Stmt::Return(ast::StmtReturn {
|
Stmt::Return(ast::StmtReturn {
|
||||||
range,
|
range,
|
||||||
value: Some(_),
|
value: Some(value),
|
||||||
}) => {
|
}) => {
|
||||||
self.returns.push(Entry { range: *range });
|
self.returns.push(ReturnEntry {
|
||||||
|
range: *range,
|
||||||
|
is_none_return: value.is_none_literal_expr(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => return,
|
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => return,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -640,10 +657,10 @@ impl<'a> Visitor<'a> for BodyVisitor<'a> {
|
||||||
range,
|
range,
|
||||||
value: Some(_),
|
value: Some(_),
|
||||||
}) => {
|
}) => {
|
||||||
self.yields.push(Entry { range: *range });
|
self.yields.push(YieldEntry { range: *range });
|
||||||
}
|
}
|
||||||
Expr::YieldFrom(ast::ExprYieldFrom { range, .. }) => {
|
Expr::YieldFrom(ast::ExprYieldFrom { range, .. }) => {
|
||||||
self.yields.push(Entry { range: *range });
|
self.yields.push(YieldEntry { range: *range });
|
||||||
}
|
}
|
||||||
Expr::Lambda(_) => return,
|
Expr::Lambda(_) => return,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -737,8 +754,22 @@ pub(crate) fn check_docstring(
|
||||||
let extra_property_decorators = checker.settings.pydocstyle.property_decorators();
|
let extra_property_decorators = checker.settings.pydocstyle.property_decorators();
|
||||||
if !definition.is_property(extra_property_decorators, checker.semantic()) {
|
if !definition.is_property(extra_property_decorators, checker.semantic()) {
|
||||||
if let Some(body_return) = body_entries.returns.first() {
|
if let Some(body_return) = body_entries.returns.first() {
|
||||||
let diagnostic = Diagnostic::new(DocstringMissingReturns, body_return.range());
|
match function_def.returns.as_deref() {
|
||||||
diagnostics.push(diagnostic);
|
Some(returns) if !Expr::is_none_literal_expr(returns) => diagnostics.push(
|
||||||
|
Diagnostic::new(DocstringMissingReturns, body_return.range()),
|
||||||
|
),
|
||||||
|
None if body_entries
|
||||||
|
.returns
|
||||||
|
.iter()
|
||||||
|
.any(|entry| !entry.is_none_return) =>
|
||||||
|
{
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
DocstringMissingReturns,
|
||||||
|
body_return.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,3 +38,34 @@ DOC201_google.py:121:9: DOC201 `return` is not documented in docstring
|
||||||
| ^^^^^^^^^^^ DOC201
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
|
||||||
= help: Add a "Returns" section to the docstring
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_google.py:184:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
182 | """
|
||||||
|
183 | if x < 0:
|
||||||
|
184 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
185 | else:
|
||||||
|
186 | return x
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_google.py:197:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
195 | """
|
||||||
|
196 | if x < 0:
|
||||||
|
197 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
198 | else:
|
||||||
|
199 | return x
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_google.py:209:5: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
207 | s (str): A string.
|
||||||
|
208 | """
|
||||||
|
209 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
|
@ -27,3 +27,34 @@ DOC201_numpy.py:87:9: DOC201 `return` is not documented in docstring
|
||||||
| ^^^^^^^^^^^ DOC201
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
|
||||||
= help: Add a "Returns" section to the docstring
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_numpy.py:160:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
158 | """
|
||||||
|
159 | if x < 0:
|
||||||
|
160 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
161 | else:
|
||||||
|
162 | return x
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_numpy.py:175:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
173 | """
|
||||||
|
174 | if x < 0:
|
||||||
|
175 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
176 | else:
|
||||||
|
177 | return x
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_numpy.py:189:5: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
187 | A string.
|
||||||
|
188 | """
|
||||||
|
189 | return None
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue