mirror of https://github.com/astral-sh/ruff
Don't enforce returns and yields in abstract methods (#12771)
## Summary Closes https://github.com/astral-sh/ruff/issues/12685.
This commit is contained in:
parent
2abfab0f9b
commit
1f51048fa4
|
|
@ -108,3 +108,14 @@ def f(num: int):
|
||||||
num (int): A number
|
num (int): A number
|
||||||
"""
|
"""
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class A(metaclass=abc.abcmeta):
|
||||||
|
# DOC201
|
||||||
|
@abc.abstractmethod
|
||||||
|
def f(self):
|
||||||
|
"""Lorem ipsum."""
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,14 @@ class Bar:
|
||||||
A number
|
A number
|
||||||
"""
|
"""
|
||||||
return 'test'
|
return 'test'
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class A(metaclass=abc.abcmeta):
|
||||||
|
# DOC201
|
||||||
|
@abc.abstractmethod
|
||||||
|
def f(self):
|
||||||
|
"""Lorem ipsum."""
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,17 @@ class C:
|
||||||
x
|
x
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class A(metaclass=abc.abcmeta):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def f(self):
|
||||||
|
"""Lorem ipsum
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The values
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,19 @@ class Bar:
|
||||||
A number
|
A number
|
||||||
"""
|
"""
|
||||||
print('test')
|
print('test')
|
||||||
|
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
|
||||||
|
class A(metaclass=abc.abcmeta):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def f(self):
|
||||||
|
"""Lorem ipsum
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict:
|
||||||
|
The values
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use ruff_python_ast::helpers::map_callable;
|
||||||
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, visitor, Expr, Stmt};
|
use ruff_python_ast::{self as ast, visitor, Expr, Stmt};
|
||||||
use ruff_python_semantic::analyze::function_type;
|
use ruff_python_semantic::analyze::{function_type, visibility};
|
||||||
use ruff_python_semantic::{Definition, SemanticModel};
|
use ruff_python_semantic::{Definition, SemanticModel};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
|
|
@ -25,6 +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.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def calculate_speed(distance: float, time: float) -> float:
|
/// def calculate_speed(distance: float, time: float) -> float:
|
||||||
|
|
@ -73,6 +75,8 @@ impl Violation for DocstringMissingReturns {
|
||||||
/// Functions without an explicit return should not have a returns section
|
/// Functions without an explicit return should not have a returns section
|
||||||
/// in their docstrings.
|
/// in their docstrings.
|
||||||
///
|
///
|
||||||
|
/// This rule is not enforced for stub functions.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def say_hello(n: int) -> None:
|
/// def say_hello(n: int) -> None:
|
||||||
|
|
@ -121,6 +125,8 @@ impl Violation for DocstringExtraneousReturns {
|
||||||
/// Docstrings missing yields sections are a sign of incomplete documentation
|
/// Docstrings missing yields sections are a sign of incomplete documentation
|
||||||
/// or refactors.
|
/// or refactors.
|
||||||
///
|
///
|
||||||
|
/// This rule is not enforced for abstract methods and stubs functions.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def count_to_n(n: int) -> int:
|
/// def count_to_n(n: int) -> int:
|
||||||
|
|
@ -169,6 +175,8 @@ impl Violation for DocstringMissingYields {
|
||||||
/// Functions which don't yield anything should not have a yields section
|
/// Functions which don't yield anything should not have a yields section
|
||||||
/// in their docstrings.
|
/// in their docstrings.
|
||||||
///
|
///
|
||||||
|
/// This rule is not enforced for stub functions.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def say_hello(n: int) -> None:
|
/// def say_hello(n: int) -> None:
|
||||||
|
|
@ -218,6 +226,8 @@ impl Violation for DocstringExtraneousYields {
|
||||||
/// it can be misleading to users and/or a sign of incomplete documentation or
|
/// it can be misleading to users and/or a sign of incomplete documentation or
|
||||||
/// refactors.
|
/// refactors.
|
||||||
///
|
///
|
||||||
|
/// This rule is not enforced for abstract methods and stubs functions.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def calculate_speed(distance: float, time: float) -> float:
|
/// def calculate_speed(distance: float, time: float) -> float:
|
||||||
|
|
@ -282,6 +292,8 @@ impl Violation for DocstringMissingException {
|
||||||
/// Some conventions prefer non-explicit exceptions be omitted from the
|
/// Some conventions prefer non-explicit exceptions be omitted from the
|
||||||
/// docstring.
|
/// docstring.
|
||||||
///
|
///
|
||||||
|
/// This rule is not enforced for stub functions.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
/// def calculate_speed(distance: float, time: float) -> float:
|
/// def calculate_speed(distance: float, time: float) -> float:
|
||||||
|
|
@ -343,7 +355,7 @@ impl Violation for DocstringExtraneousException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A generic docstring section.
|
/// A generic docstring section.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct GenericSection {
|
struct GenericSection {
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
|
|
@ -363,7 +375,7 @@ impl GenericSection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Raises docstring section.
|
/// A "Raises" section in a docstring.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RaisesSection<'a> {
|
struct RaisesSection<'a> {
|
||||||
raised_exceptions: Vec<QualifiedName<'a>>,
|
raised_exceptions: Vec<QualifiedName<'a>>,
|
||||||
|
|
@ -378,7 +390,7 @@ impl Ranged for RaisesSection<'_> {
|
||||||
|
|
||||||
impl<'a> RaisesSection<'a> {
|
impl<'a> RaisesSection<'a> {
|
||||||
/// Return the raised exceptions for the docstring, or `None` if the docstring does not contain
|
/// Return the raised exceptions for the docstring, or `None` if the docstring does not contain
|
||||||
/// a `Raises` section.
|
/// a "Raises" section.
|
||||||
fn from_section(section: &SectionContext<'a>, style: Option<SectionStyle>) -> Self {
|
fn from_section(section: &SectionContext<'a>, style: Option<SectionStyle>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
raised_exceptions: parse_entries(section.following_lines_str(), style),
|
raised_exceptions: parse_entries(section.following_lines_str(), style),
|
||||||
|
|
@ -415,7 +427,7 @@ impl<'a> DocstringSections<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse the entries in a `Raises` section of a docstring.
|
/// Parse the entries in a "Raises" section of a docstring.
|
||||||
///
|
///
|
||||||
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
|
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
|
||||||
/// entries are found.
|
/// entries are found.
|
||||||
|
|
@ -519,7 +531,7 @@ struct BodyEntries<'a> {
|
||||||
struct BodyVisitor<'a> {
|
struct BodyVisitor<'a> {
|
||||||
returns: Vec<Entry>,
|
returns: Vec<Entry>,
|
||||||
yields: Vec<Entry>,
|
yields: Vec<Entry>,
|
||||||
currently_suspended_exceptions: Option<&'a ast::Expr>,
|
currently_suspended_exceptions: Option<&'a Expr>,
|
||||||
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
raised_exceptions: Vec<ExceptionEntry<'a>>,
|
||||||
semantic: &'a SemanticModel<'a>,
|
semantic: &'a SemanticModel<'a>,
|
||||||
}
|
}
|
||||||
|
|
@ -732,17 +744,6 @@ pub(crate) fn check_docstring(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOC202
|
|
||||||
if checker.enabled(Rule::DocstringExtraneousReturns) {
|
|
||||||
if let Some(ref docstring_returns) = docstring_sections.returns {
|
|
||||||
if body_entries.returns.is_empty() {
|
|
||||||
let diagnostic =
|
|
||||||
Diagnostic::new(DocstringExtraneousReturns, docstring_returns.range());
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DOC402
|
// DOC402
|
||||||
if checker.enabled(Rule::DocstringMissingYields) {
|
if checker.enabled(Rule::DocstringMissingYields) {
|
||||||
if !yields_documented(docstring, &docstring_sections, convention) {
|
if !yields_documented(docstring, &docstring_sections, convention) {
|
||||||
|
|
@ -753,17 +754,6 @@ pub(crate) fn check_docstring(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOC403
|
|
||||||
if checker.enabled(Rule::DocstringExtraneousYields) {
|
|
||||||
if let Some(docstring_yields) = docstring_sections.yields {
|
|
||||||
if body_entries.yields.is_empty() {
|
|
||||||
let diagnostic =
|
|
||||||
Diagnostic::new(DocstringExtraneousYields, docstring_yields.range());
|
|
||||||
diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DOC501
|
// DOC501
|
||||||
if checker.enabled(Rule::DocstringMissingException) {
|
if checker.enabled(Rule::DocstringMissingException) {
|
||||||
for body_raise in &body_entries.raised_exceptions {
|
for body_raise in &body_entries.raised_exceptions {
|
||||||
|
|
@ -794,28 +784,54 @@ pub(crate) fn check_docstring(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOC502
|
// Avoid applying "extraneous" rules to abstract methods. An abstract method's docstring _could_
|
||||||
if checker.enabled(Rule::DocstringExtraneousException) {
|
// document that it raises an exception without including the exception in the implementation.
|
||||||
if let Some(docstring_raises) = docstring_sections.raises {
|
if !visibility::is_abstract(&function_def.decorator_list, checker.semantic()) {
|
||||||
let mut extraneous_exceptions = Vec::new();
|
// DOC202
|
||||||
for docstring_raise in &docstring_raises.raised_exceptions {
|
if checker.enabled(Rule::DocstringExtraneousReturns) {
|
||||||
if !body_entries.raised_exceptions.iter().any(|exception| {
|
if let Some(ref docstring_returns) = docstring_sections.returns {
|
||||||
exception
|
if body_entries.returns.is_empty() {
|
||||||
.qualified_name
|
let diagnostic =
|
||||||
.segments()
|
Diagnostic::new(DocstringExtraneousReturns, docstring_returns.range());
|
||||||
.ends_with(docstring_raise.segments())
|
diagnostics.push(diagnostic);
|
||||||
}) {
|
|
||||||
extraneous_exceptions.push(docstring_raise.to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !extraneous_exceptions.is_empty() {
|
}
|
||||||
let diagnostic = Diagnostic::new(
|
|
||||||
DocstringExtraneousException {
|
// DOC403
|
||||||
ids: extraneous_exceptions,
|
if checker.enabled(Rule::DocstringExtraneousYields) {
|
||||||
},
|
if let Some(docstring_yields) = docstring_sections.yields {
|
||||||
docstring_raises.range(),
|
if body_entries.yields.is_empty() {
|
||||||
);
|
let diagnostic =
|
||||||
diagnostics.push(diagnostic);
|
Diagnostic::new(DocstringExtraneousYields, docstring_yields.range());
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOC502
|
||||||
|
if checker.enabled(Rule::DocstringExtraneousException) {
|
||||||
|
if let Some(docstring_raises) = docstring_sections.raises {
|
||||||
|
let mut extraneous_exceptions = Vec::new();
|
||||||
|
for docstring_raise in &docstring_raises.raised_exceptions {
|
||||||
|
if !body_entries.raised_exceptions.iter().any(|exception| {
|
||||||
|
exception
|
||||||
|
.qualified_name
|
||||||
|
.segments()
|
||||||
|
.ends_with(docstring_raise.segments())
|
||||||
|
}) {
|
||||||
|
extraneous_exceptions.push(docstring_raise.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !extraneous_exceptions.is_empty() {
|
||||||
|
let diagnostic = Diagnostic::new(
|
||||||
|
DocstringExtraneousException {
|
||||||
|
ids: extraneous_exceptions,
|
||||||
|
},
|
||||||
|
docstring_raises.range(),
|
||||||
|
);
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,12 @@ DOC201_google.py:71:9: DOC201 `return` is not documented in docstring
|
||||||
73 | print("I never return")
|
73 | print("I never return")
|
||||||
|
|
|
|
||||||
= help: Add a "Returns" section to the docstring
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
||||||
|
DOC201_google.py:121:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
119 | def f(self):
|
||||||
|
120 | """Lorem ipsum."""
|
||||||
|
121 | return True
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,12 @@ DOC201_numpy.py:62: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:87:9: DOC201 `return` is not documented in docstring
|
||||||
|
|
|
||||||
|
85 | def f(self):
|
||||||
|
86 | """Lorem ipsum."""
|
||||||
|
87 | return True
|
||||||
|
| ^^^^^^^^^^^ DOC201
|
||||||
|
|
|
||||||
|
= help: Add a "Returns" section to the docstring
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue