mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:43 +00:00
Implement docstring argument tracking for NumPy-style docstrings (#425)
This commit is contained in:
parent
6fb82ab763
commit
9bbfd1d3b2
11 changed files with 471 additions and 38 deletions
25
README.md
25
README.md
|
@ -321,34 +321,37 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||||
| D106 | PublicNestedClass | Missing docstring in public nested class | | |
|
| D106 | PublicNestedClass | Missing docstring in public nested class | | |
|
||||||
| D107 | PublicInit | Missing docstring in __init__ | | |
|
| D107 | PublicInit | Missing docstring in __init__ | | |
|
||||||
| D200 | FitsOnOneLine | One-line docstring should fit on one line | | |
|
| D200 | FitsOnOneLine | One-line docstring should fit on one line | | |
|
||||||
|
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) | | |
|
||||||
|
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | | |
|
||||||
|
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | |
|
||||||
|
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | |
|
||||||
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | |
|
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | |
|
||||||
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | |
|
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | |
|
||||||
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | |
|
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | |
|
||||||
|
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | |
|
||||||
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | | |
|
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | | |
|
||||||
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
|
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
|
||||||
|
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | | |
|
||||||
|
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | | |
|
||||||
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
|
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
|
||||||
| D400 | EndsInPeriod | First line should end with a period | | |
|
| D400 | EndsInPeriod | First line should end with a period | | |
|
||||||
| D402 | NoSignature | First line should not be the function's 'signature' | | |
|
| D402 | NoSignature | First line should not be the function's 'signature' | | |
|
||||||
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | |
|
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | |
|
||||||
| D404 | NoThisPrefix | First word of the docstring should not be `This` | | |
|
| D404 | NoThisPrefix | First word of the docstring should not be `This` | | |
|
||||||
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
|
|
||||||
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
|
|
||||||
| D419 | NonEmpty | Docstring is empty | | |
|
|
||||||
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) | | |
|
|
||||||
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | | |
|
|
||||||
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | |
|
|
||||||
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | |
|
|
||||||
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | |
|
|
||||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | | |
|
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | | |
|
||||||
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | | |
|
|
||||||
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | | |
|
|
||||||
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | | |
|
|
||||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | | |
|
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | | |
|
||||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | | |
|
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | | |
|
||||||
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | | |
|
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | | |
|
||||||
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | | |
|
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | | |
|
||||||
|
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | | |
|
||||||
|
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | | |
|
||||||
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | | |
|
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | | |
|
||||||
|
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | | |
|
||||||
| D414 | NonEmptySection | Section has no content ("Returns") | | |
|
| D414 | NonEmptySection | Section has no content ("Returns") | | |
|
||||||
|
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
|
||||||
|
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | | |
|
||||||
|
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
|
||||||
|
| D419 | NonEmpty | Docstring is empty | | |
|
||||||
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
|
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
163
resources/test/fixtures/canonical_numpy_examples.py
vendored
Normal file
163
resources/test/fixtures/canonical_numpy_examples.py
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
"""This is the docstring for the example.py module. Modules names should
|
||||||
|
have short, all-lowercase names. The module name may have underscores if
|
||||||
|
this improves readability.
|
||||||
|
|
||||||
|
Every module should have a docstring at the very top of the file. The
|
||||||
|
module's docstring may extend over multiple lines. If your docstring does
|
||||||
|
extend over multiple lines, the closing three quotation marks must be on
|
||||||
|
a line by itself, preferably preceded by a blank line.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Example source file from the official "numpydoc docstring guide"
|
||||||
|
# documentation (with the modification of commenting out all the original
|
||||||
|
# ``import`` lines, plus adding this note and ``Expectation`` code):
|
||||||
|
# * As HTML: https://numpydoc.readthedocs.io/en/latest/example.html
|
||||||
|
# * Source Python:
|
||||||
|
# https://github.com/numpy/numpydoc/blob/master/doc/example.py
|
||||||
|
|
||||||
|
# from __future__ import division, absolute_import, print_function
|
||||||
|
#
|
||||||
|
# import os # standard library imports first
|
||||||
|
#
|
||||||
|
# Do NOT import using *, e.g. from numpy import *
|
||||||
|
#
|
||||||
|
# Import the module using
|
||||||
|
#
|
||||||
|
# import numpy
|
||||||
|
#
|
||||||
|
# instead or import individual functions as needed, e.g
|
||||||
|
#
|
||||||
|
# from numpy import array, zeros
|
||||||
|
#
|
||||||
|
# If you prefer the use of abbreviated module names, we suggest the
|
||||||
|
# convention used by NumPy itself::
|
||||||
|
#
|
||||||
|
# import numpy as np
|
||||||
|
# import matplotlib as mpl
|
||||||
|
# import matplotlib.pyplot as plt
|
||||||
|
#
|
||||||
|
# These abbreviated names are not to be used in docstrings; users must
|
||||||
|
# be able to paste and execute docstrings after importing only the
|
||||||
|
# numpy module itself, unabbreviated.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from .expected import Expectation
|
||||||
|
|
||||||
|
expectation = Expectation()
|
||||||
|
expect = expectation.expect
|
||||||
|
|
||||||
|
# module docstring expected violations:
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D205: 1 blank line required between summary line and description "
|
||||||
|
"(found 0)"))
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D213: Multi-line docstring summary should start at the second line"))
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D400: First line should end with a period (not 'd')"))
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D404: First word of the docstring should not be `This`"))
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D415: First line should end with a period, question mark, or exclamation "
|
||||||
|
"point (not 'd')"))
|
||||||
|
|
||||||
|
|
||||||
|
@expect("D213: Multi-line docstring summary should start at the second line",
|
||||||
|
arg_count=3)
|
||||||
|
@expect("D401: First line should be in imperative mood; try rephrasing "
|
||||||
|
"(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'):
|
||||||
|
r"""A one-line summary that does not use variable names.
|
||||||
|
|
||||||
|
Several sentences providing an extended description. Refer to
|
||||||
|
variables using back-ticks, e.g. `var`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
var1 : array_like
|
||||||
|
Array_like means all those objects -- lists, nested lists, etc. --
|
||||||
|
that can be converted to an array. We can also refer to
|
||||||
|
variables like `var1`.
|
||||||
|
var2 : int
|
||||||
|
The type above can either refer to an actual Python type
|
||||||
|
(e.g. ``int``), or describe the type of the variable in more
|
||||||
|
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||||
|
long_var_name : {'hi', 'ho'}, optional
|
||||||
|
Choices in brackets, default first when optional.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
type
|
||||||
|
Explanation of anonymous return value of type ``type``.
|
||||||
|
describe : type
|
||||||
|
Explanation of return value named `describe`.
|
||||||
|
out : type
|
||||||
|
Explanation of `out`.
|
||||||
|
type_without_description
|
||||||
|
|
||||||
|
Other Parameters
|
||||||
|
----------------
|
||||||
|
only_seldom_used_keywords : type
|
||||||
|
Explanation
|
||||||
|
common_parameters_listed_above : type
|
||||||
|
Explanation
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
BadException
|
||||||
|
Because you shouldn't have done that.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
numpy.array : Relationship (optional).
|
||||||
|
numpy.ndarray : Relationship (optional), which could be fairly long, in
|
||||||
|
which case the line wraps here.
|
||||||
|
numpy.dot, numpy.linalg.norm, numpy.eye
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
Notes about the implementation algorithm (if needed).
|
||||||
|
|
||||||
|
This can have multiple paragraphs.
|
||||||
|
|
||||||
|
You may include some math:
|
||||||
|
|
||||||
|
.. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n}
|
||||||
|
|
||||||
|
And even use a Greek symbol like :math:`\omega` inline.
|
||||||
|
|
||||||
|
References
|
||||||
|
----------
|
||||||
|
Cite the relevant literature, e.g. [1]_. You may also cite these
|
||||||
|
references in the notes section above.
|
||||||
|
|
||||||
|
.. [1] O. McNoleg, "The integration of GIS, remote sensing,
|
||||||
|
expert systems and adaptive co-kriging for environmental habitat
|
||||||
|
modelling of the Highland Haggis using object-oriented, fuzzy-logic
|
||||||
|
and neural-network techniques," Computers & Geosciences, vol. 22,
|
||||||
|
pp. 585-588, 1996.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
These are written in doctest format, and should illustrate how to
|
||||||
|
use the function.
|
||||||
|
|
||||||
|
>>> a = [1, 2, 3]
|
||||||
|
>>> print([x + 3 for x in a])
|
||||||
|
[4, 5, 6]
|
||||||
|
>>> print("a\nb")
|
||||||
|
a
|
||||||
|
b
|
||||||
|
"""
|
||||||
|
# After closing class docstring, there should be one blank line to
|
||||||
|
# separate following codes (according to PEP257).
|
||||||
|
# But for function, method and module, there should be no blank lines
|
||||||
|
# after closing the docstring.
|
||||||
|
pass
|
|
@ -1657,7 +1657,6 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
fn handle_node_delete(&mut self, expr: &Expr) {
|
fn handle_node_delete(&mut self, expr: &Expr) {
|
||||||
if let ExprKind::Name { id, .. } = &expr.node {
|
if let ExprKind::Name { id, .. } = &expr.node {
|
||||||
// Check if we're on a conditional branch.
|
|
||||||
if operations::on_conditional_branch(&self.parent_stack, &self.parents) {
|
if operations::on_conditional_branch(&self.parent_stack, &self.parents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1996,20 +1995,23 @@ impl<'a> Checker<'a> {
|
||||||
if self.settings.enabled.contains(&CheckCode::D418) {
|
if self.settings.enabled.contains(&CheckCode::D418) {
|
||||||
docstring_checks::if_needed(self, &docstring);
|
docstring_checks::if_needed(self, &docstring);
|
||||||
}
|
}
|
||||||
if self.settings.enabled.contains(&CheckCode::D407)
|
if self.settings.enabled.contains(&CheckCode::D212)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
|| self.settings.enabled.contains(&CheckCode::D214)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D215)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D405)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D406)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D407)
|
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D212)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D408)
|
|| self.settings.enabled.contains(&CheckCode::D408)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D409)
|
|| self.settings.enabled.contains(&CheckCode::D409)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D412)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D405)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D413)
|
|
||||||
|| self.settings.enabled.contains(&CheckCode::D410)
|
|| self.settings.enabled.contains(&CheckCode::D410)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D411)
|
|| self.settings.enabled.contains(&CheckCode::D411)
|
||||||
|| self.settings.enabled.contains(&CheckCode::D406)
|
|| self.settings.enabled.contains(&CheckCode::D412)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D413)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||||
|
|| self.settings.enabled.contains(&CheckCode::D417)
|
||||||
{
|
{
|
||||||
docstring_checks::check_sections(self, &docstring);
|
docstring_checks::check_sections(self, &docstring);
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,8 @@ pub enum CheckCode {
|
||||||
D211,
|
D211,
|
||||||
D212,
|
D212,
|
||||||
D213,
|
D213,
|
||||||
|
D214,
|
||||||
|
D215,
|
||||||
D300,
|
D300,
|
||||||
D400,
|
D400,
|
||||||
D402,
|
D402,
|
||||||
|
@ -190,6 +192,7 @@ pub enum CheckCode {
|
||||||
D413,
|
D413,
|
||||||
D414,
|
D414,
|
||||||
D415,
|
D415,
|
||||||
|
D417,
|
||||||
D418,
|
D418,
|
||||||
D419,
|
D419,
|
||||||
// Meta
|
// Meta
|
||||||
|
@ -298,6 +301,7 @@ pub enum CheckKind {
|
||||||
BlankLineBeforeSection(String),
|
BlankLineBeforeSection(String),
|
||||||
CapitalizeSectionName(String),
|
CapitalizeSectionName(String),
|
||||||
DashedUnderlineAfterSection(String),
|
DashedUnderlineAfterSection(String),
|
||||||
|
DocumentAllArguments(Vec<String>),
|
||||||
EndsInPeriod,
|
EndsInPeriod,
|
||||||
EndsInPunctuation,
|
EndsInPunctuation,
|
||||||
FirstLineCapitalized,
|
FirstLineCapitalized,
|
||||||
|
@ -326,8 +330,10 @@ pub enum CheckKind {
|
||||||
PublicModule,
|
PublicModule,
|
||||||
PublicNestedClass,
|
PublicNestedClass,
|
||||||
PublicPackage,
|
PublicPackage,
|
||||||
|
SectionNotOverIndented(String),
|
||||||
SectionUnderlineAfterName(String),
|
SectionUnderlineAfterName(String),
|
||||||
SectionUnderlineMatchesSectionLength(String),
|
SectionUnderlineMatchesSectionLength(String),
|
||||||
|
SectionUnderlineNotOverIndented(String),
|
||||||
SkipDocstring,
|
SkipDocstring,
|
||||||
UsesTripleQuotes,
|
UsesTripleQuotes,
|
||||||
// Meta
|
// Meta
|
||||||
|
@ -489,6 +495,11 @@ impl CheckCode {
|
||||||
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
||||||
CheckCode::D418 => CheckKind::SkipDocstring,
|
CheckCode::D418 => CheckKind::SkipDocstring,
|
||||||
CheckCode::D419 => CheckKind::NonEmpty,
|
CheckCode::D419 => CheckKind::NonEmpty,
|
||||||
|
CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
|
||||||
|
CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
|
||||||
|
CheckCode::D417 => {
|
||||||
|
CheckKind::DocumentAllArguments(vec!["x".to_string(), "y".to_string()])
|
||||||
|
}
|
||||||
// Meta
|
// Meta
|
||||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||||
}
|
}
|
||||||
|
@ -586,6 +597,7 @@ impl CheckKind {
|
||||||
CheckKind::BlankLineBeforeSection(_) => &CheckCode::D411,
|
CheckKind::BlankLineBeforeSection(_) => &CheckCode::D411,
|
||||||
CheckKind::CapitalizeSectionName(_) => &CheckCode::D405,
|
CheckKind::CapitalizeSectionName(_) => &CheckCode::D405,
|
||||||
CheckKind::DashedUnderlineAfterSection(_) => &CheckCode::D407,
|
CheckKind::DashedUnderlineAfterSection(_) => &CheckCode::D407,
|
||||||
|
CheckKind::DocumentAllArguments(_) => &CheckCode::D417,
|
||||||
CheckKind::EndsInPeriod => &CheckCode::D400,
|
CheckKind::EndsInPeriod => &CheckCode::D400,
|
||||||
CheckKind::EndsInPunctuation => &CheckCode::D415,
|
CheckKind::EndsInPunctuation => &CheckCode::D415,
|
||||||
CheckKind::FirstLineCapitalized => &CheckCode::D403,
|
CheckKind::FirstLineCapitalized => &CheckCode::D403,
|
||||||
|
@ -614,8 +626,10 @@ impl CheckKind {
|
||||||
CheckKind::PublicModule => &CheckCode::D100,
|
CheckKind::PublicModule => &CheckCode::D100,
|
||||||
CheckKind::PublicNestedClass => &CheckCode::D106,
|
CheckKind::PublicNestedClass => &CheckCode::D106,
|
||||||
CheckKind::PublicPackage => &CheckCode::D104,
|
CheckKind::PublicPackage => &CheckCode::D104,
|
||||||
|
CheckKind::SectionNotOverIndented(_) => &CheckCode::D214,
|
||||||
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
|
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
|
||||||
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
|
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
|
||||||
|
CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
|
||||||
CheckKind::SkipDocstring => &CheckCode::D418,
|
CheckKind::SkipDocstring => &CheckCode::D418,
|
||||||
CheckKind::UsesTripleQuotes => &CheckCode::D300,
|
CheckKind::UsesTripleQuotes => &CheckCode::D300,
|
||||||
// Meta
|
// Meta
|
||||||
|
@ -962,6 +976,21 @@ impl CheckKind {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CheckKind::NonEmptySection(name) => format!("Section has no content (\"{name}\")"),
|
CheckKind::NonEmptySection(name) => format!("Section has no content (\"{name}\")"),
|
||||||
|
CheckKind::SectionNotOverIndented(name) => {
|
||||||
|
format!("Section is over-indented (\"{name}\")")
|
||||||
|
}
|
||||||
|
CheckKind::SectionUnderlineNotOverIndented(name) => {
|
||||||
|
format!("Section underline is over-indented (\"{name}\")")
|
||||||
|
}
|
||||||
|
CheckKind::DocumentAllArguments(names) => {
|
||||||
|
if names.len() == 1 {
|
||||||
|
let name = &names[0];
|
||||||
|
format!("Missing argument description in the docstring: `{name}`")
|
||||||
|
} else {
|
||||||
|
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||||
|
format!("Missing argument descriptions in the docstring: {names}")
|
||||||
|
}
|
||||||
|
}
|
||||||
// Meta
|
// Meta
|
||||||
CheckKind::UnusedNOQA(codes) => match codes {
|
CheckKind::UnusedNOQA(codes) => match codes {
|
||||||
None => "Unused `noqa` directive".to_string(),
|
None => "Unused `noqa` directive".to_string(),
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
use itertools::Itertools;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use rustpython_ast::{Arg, Expr, Location, StmtKind};
|
||||||
use titlecase::titlecase;
|
use titlecase::titlecase;
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
use crate::check_ast::Checker;
|
use crate::check_ast::Checker;
|
||||||
use crate::checks::{Check, CheckCode, CheckKind};
|
use crate::checks::{Check, CheckCode, CheckKind};
|
||||||
use crate::docstrings::docstring_checks::range_for;
|
use crate::docstrings::docstring_checks::range_for;
|
||||||
use crate::docstrings::types::Definition;
|
use crate::docstrings::types::{Definition, DefinitionKind};
|
||||||
|
use crate::visibility::is_static;
|
||||||
|
|
||||||
static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||||
BTreeSet::from([
|
BTreeSet::from([
|
||||||
|
@ -78,7 +82,21 @@ static NUMPY_SECTION_NAMES_LOWERCASE: Lazy<BTreeSet<&'static str>> = Lazy::new(|
|
||||||
// ])
|
// ])
|
||||||
// });
|
// });
|
||||||
|
|
||||||
fn get_leading_words(line: &str) -> String {
|
fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
|
||||||
|
let range = range_for(docstring);
|
||||||
|
checker.locator.slice_source_code_range(&Range {
|
||||||
|
location: Location::new(range.location.row(), 1),
|
||||||
|
end_location: Location::new(range.location.row(), range.location.column()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leading_space(line: &str) -> String {
|
||||||
|
line.chars()
|
||||||
|
.take_while(|char| char.is_whitespace())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leading_words(line: &str) -> String {
|
||||||
line.trim()
|
line.trim()
|
||||||
.chars()
|
.chars()
|
||||||
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
|
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
|
||||||
|
@ -86,7 +104,7 @@ fn get_leading_words(line: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suspected_as_section(line: &str) -> bool {
|
fn suspected_as_section(line: &str) -> bool {
|
||||||
NUMPY_SECTION_NAMES_LOWERCASE.contains(&get_leading_words(line).to_lowercase().as_str())
|
NUMPY_SECTION_NAMES_LOWERCASE.contains(&leading_words(line).to_lowercase().as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -143,7 +161,7 @@ pub fn section_contexts<'a>(lines: &'a [&'a str]) -> Vec<SectionContext<'a>> {
|
||||||
let mut contexts = vec![];
|
let mut contexts = vec![];
|
||||||
for lineno in suspected_section_indices {
|
for lineno in suspected_section_indices {
|
||||||
let context = SectionContext {
|
let context = SectionContext {
|
||||||
section_name: get_leading_words(lines[lineno]),
|
section_name: leading_words(lines[lineno]),
|
||||||
previous_line: lines[lineno - 1],
|
previous_line: lines[lineno - 1],
|
||||||
line: lines[lineno],
|
line: lines[lineno],
|
||||||
following_lines: &lines[lineno + 1..],
|
following_lines: &lines[lineno + 1..],
|
||||||
|
@ -196,14 +214,12 @@ fn check_blanks_and_section_underline(
|
||||||
|
|
||||||
// Nothing but blank lines after the section header.
|
// Nothing but blank lines after the section header.
|
||||||
if blank_lines_after_header == context.following_lines.len() {
|
if blank_lines_after_header == context.following_lines.len() {
|
||||||
// D407
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D407) {
|
if checker.settings.enabled.contains(&CheckCode::D407) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
||||||
range_for(docstring),
|
range_for(docstring),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// D414
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||||
|
@ -219,7 +235,6 @@ fn check_blanks_and_section_underline(
|
||||||
.all(|char| char.is_whitespace() || char == '-');
|
.all(|char| char.is_whitespace() || char == '-');
|
||||||
|
|
||||||
if !dash_line_found {
|
if !dash_line_found {
|
||||||
// D407
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D407) {
|
if checker.settings.enabled.contains(&CheckCode::D407) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
||||||
|
@ -227,7 +242,6 @@ fn check_blanks_and_section_underline(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if blank_lines_after_header > 0 {
|
if blank_lines_after_header > 0 {
|
||||||
// D212
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D212) {
|
if checker.settings.enabled.contains(&CheckCode::D212) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
||||||
|
@ -239,7 +253,6 @@ fn check_blanks_and_section_underline(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if blank_lines_after_header > 0 {
|
if blank_lines_after_header > 0 {
|
||||||
// D408
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D408) {
|
if checker.settings.enabled.contains(&CheckCode::D408) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
|
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
|
||||||
|
@ -255,7 +268,6 @@ fn check_blanks_and_section_underline(
|
||||||
.count()
|
.count()
|
||||||
!= context.section_name.len()
|
!= context.section_name.len()
|
||||||
{
|
{
|
||||||
// D409
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D409) {
|
if checker.settings.enabled.contains(&CheckCode::D409) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::SectionUnderlineMatchesSectionLength(
|
CheckKind::SectionUnderlineMatchesSectionLength(
|
||||||
|
@ -266,16 +278,22 @@ fn check_blanks_and_section_underline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(charlie): Implement D215, which requires indentation and leading space tracking.
|
if checker.settings.enabled.contains(&CheckCode::D215) {
|
||||||
|
if leading_space(non_empty_line).len() > indentation(checker, docstring).len() {
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
|
||||||
|
range_for(docstring),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let line_after_dashes_index = blank_lines_after_header + 1;
|
let line_after_dashes_index = blank_lines_after_header + 1;
|
||||||
|
|
||||||
if line_after_dashes_index < context.following_lines.len() {
|
if line_after_dashes_index < context.following_lines.len() {
|
||||||
let line_after_dashes = context.following_lines[line_after_dashes_index];
|
let line_after_dashes = context.following_lines[line_after_dashes_index];
|
||||||
|
|
||||||
if line_after_dashes.trim().is_empty() {
|
if line_after_dashes.trim().is_empty() {
|
||||||
let rest_of_lines = &context.following_lines[line_after_dashes_index..];
|
let rest_of_lines = &context.following_lines[line_after_dashes_index..];
|
||||||
if rest_of_lines.iter().all(|line| line.trim().is_empty()) {
|
if rest_of_lines.iter().all(|line| line.trim().is_empty()) {
|
||||||
// D414
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||||
|
@ -283,7 +301,6 @@ fn check_blanks_and_section_underline(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 412
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D412) {
|
if checker.settings.enabled.contains(&CheckCode::D412) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
||||||
|
@ -295,7 +312,6 @@ fn check_blanks_and_section_underline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// D414
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||||
|
@ -307,7 +323,6 @@ fn check_blanks_and_section_underline(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_common_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
|
fn check_common_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
|
||||||
// TODO(charlie): Implement D214, which requires indentation and leading space tracking.
|
|
||||||
let docstring = definition
|
let docstring = definition
|
||||||
.docstring
|
.docstring
|
||||||
.expect("Sections are only available for docstrings.");
|
.expect("Sections are only available for docstrings.");
|
||||||
|
@ -323,6 +338,15 @@ fn check_common_section(checker: &mut Checker, definition: &Definition, context:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::D214) {
|
||||||
|
if leading_space(context.line).len() > indentation(checker, docstring).len() {
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
|
||||||
|
range_for(docstring),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if context
|
if context
|
||||||
.following_lines
|
.following_lines
|
||||||
.last()
|
.last()
|
||||||
|
@ -356,12 +380,110 @@ fn check_common_section(checker: &mut Checker, definition: &Definition, context:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_missing_args(
|
||||||
|
checker: &mut Checker,
|
||||||
|
definition: &Definition,
|
||||||
|
docstrings_args: BTreeSet<&str>,
|
||||||
|
) {
|
||||||
|
if let DefinitionKind::Function(parent)
|
||||||
|
| DefinitionKind::NestedFunction(parent)
|
||||||
|
| DefinitionKind::Method(parent) = definition.kind
|
||||||
|
{
|
||||||
|
if let StmtKind::FunctionDef {
|
||||||
|
args: arguments, ..
|
||||||
|
}
|
||||||
|
| StmtKind::AsyncFunctionDef {
|
||||||
|
args: arguments, ..
|
||||||
|
} = &parent.node
|
||||||
|
{
|
||||||
|
// Collect all the arguments into a single vector.
|
||||||
|
let mut all_arguments: Vec<&Arg> = arguments
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.chain(arguments.posonlyargs.iter())
|
||||||
|
.chain(arguments.kwonlyargs.iter())
|
||||||
|
.skip(
|
||||||
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
|
if matches!(definition.kind, DefinitionKind::Method(_)) && !is_static(parent) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
if let Some(arg) = &arguments.vararg {
|
||||||
|
all_arguments.push(arg);
|
||||||
|
}
|
||||||
|
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> = Default::default();
|
||||||
|
for arg in all_arguments {
|
||||||
|
let arg_name = arg.node.arg.as_str();
|
||||||
|
if arg_name.starts_with('_') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::DocumentAllArguments(names),
|
||||||
|
Range::from_located(parent),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_parameters_section(
|
||||||
|
checker: &mut Checker,
|
||||||
|
definition: &Definition,
|
||||||
|
context: &SectionContext,
|
||||||
|
) {
|
||||||
|
// Collect the list of arguments documented in the docstring.
|
||||||
|
let mut docstring_args: BTreeSet<&str> = Default::default();
|
||||||
|
let section_level_indent = leading_space(context.line);
|
||||||
|
for i in 1..context.following_lines.len() {
|
||||||
|
let current_line = context.following_lines[i - 1];
|
||||||
|
let current_leading_space = leading_space(current_line);
|
||||||
|
let next_line = context.following_lines[i];
|
||||||
|
if current_leading_space == section_level_indent
|
||||||
|
&& (leading_space(next_line).len() > current_leading_space.len())
|
||||||
|
&& !next_line.trim().is_empty()
|
||||||
|
{
|
||||||
|
let parameters = if let Some(semi_index) = current_line.find(':') {
|
||||||
|
// If the parameter has a type annotation, exclude it.
|
||||||
|
¤t_line[..semi_index]
|
||||||
|
} else {
|
||||||
|
// Otherwise, it's just a list of parameters on the current line.
|
||||||
|
current_line.trim()
|
||||||
|
};
|
||||||
|
// Notably, NumPy lets you put multiple parameters of the same type on the same line.
|
||||||
|
for parameter in parameters.split(',') {
|
||||||
|
docstring_args.insert(parameter.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Validate that all arguments were documented.
|
||||||
|
check_missing_args(checker, definition, docstring_args);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_numpy_section(
|
pub fn check_numpy_section(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
definition: &Definition,
|
definition: &Definition,
|
||||||
context: &SectionContext,
|
context: &SectionContext,
|
||||||
) {
|
) {
|
||||||
// TODO(charlie): Implement `_check_parameters_section`.
|
|
||||||
check_common_section(checker, definition, context);
|
check_common_section(checker, definition, context);
|
||||||
check_blanks_and_section_underline(checker, definition, context);
|
check_blanks_and_section_underline(checker, definition, context);
|
||||||
|
|
||||||
|
@ -381,4 +503,10 @@ pub fn check_numpy_section(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::D417) {
|
||||||
|
if titlecase(&context.section_name) == "Parameters" {
|
||||||
|
check_parameters_section(checker, definition, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,6 +277,8 @@ mod tests {
|
||||||
#[test_case(CheckCode::D211, Path::new("D.py"); "D211")]
|
#[test_case(CheckCode::D211, Path::new("D.py"); "D211")]
|
||||||
#[test_case(CheckCode::D212, Path::new("D.py"); "D212")]
|
#[test_case(CheckCode::D212, Path::new("D.py"); "D212")]
|
||||||
#[test_case(CheckCode::D213, Path::new("D.py"); "D213")]
|
#[test_case(CheckCode::D213, Path::new("D.py"); "D213")]
|
||||||
|
#[test_case(CheckCode::D214, Path::new("sections.py"); "D214")]
|
||||||
|
#[test_case(CheckCode::D215, Path::new("sections.py"); "D215")]
|
||||||
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
|
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
|
||||||
#[test_case(CheckCode::D400, Path::new("D.py"); "D400")]
|
#[test_case(CheckCode::D400, Path::new("D.py"); "D400")]
|
||||||
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]
|
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]
|
||||||
|
@ -293,6 +295,8 @@ mod tests {
|
||||||
#[test_case(CheckCode::D413, Path::new("sections.py"); "D413")]
|
#[test_case(CheckCode::D413, Path::new("sections.py"); "D413")]
|
||||||
#[test_case(CheckCode::D414, Path::new("sections.py"); "D414")]
|
#[test_case(CheckCode::D414, Path::new("sections.py"); "D414")]
|
||||||
#[test_case(CheckCode::D415, Path::new("D.py"); "D415")]
|
#[test_case(CheckCode::D415, Path::new("D.py"); "D415")]
|
||||||
|
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
|
||||||
|
#[test_case(CheckCode::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
|
||||||
#[test_case(CheckCode::D418, Path::new("D.py"); "D418")]
|
#[test_case(CheckCode::D418, Path::new("D.py"); "D418")]
|
||||||
#[test_case(CheckCode::D419, Path::new("D.py"); "D419")]
|
#[test_case(CheckCode::D419, Path::new("D.py"); "D419")]
|
||||||
#[test_case(CheckCode::E402, Path::new("E402.py"); "E402")]
|
#[test_case(CheckCode::E402, Path::new("E402.py"); "E402")]
|
||||||
|
|
14
src/snapshots/ruff__linter__tests__D214_sections.py.snap
Normal file
14
src/snapshots/ruff__linter__tests__D214_sections.py.snap
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
SectionNotOverIndented: Returns
|
||||||
|
location:
|
||||||
|
row: 135
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 141
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
|
23
src/snapshots/ruff__linter__tests__D215_sections.py.snap
Normal file
23
src/snapshots/ruff__linter__tests__D215_sections.py.snap
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
SectionUnderlineNotOverIndented: Returns
|
||||||
|
location:
|
||||||
|
row: 147
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 153
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
SectionUnderlineNotOverIndented: Returns
|
||||||
|
location:
|
||||||
|
row: 161
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 165
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
50
src/snapshots/ruff__linter__tests__D417_sections.py.snap
Normal file
50
src/snapshots/ruff__linter__tests__D417_sections.py.snap
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- y
|
||||||
|
location:
|
||||||
|
row: 389
|
||||||
|
column: 1
|
||||||
|
end_location:
|
||||||
|
row: 401
|
||||||
|
column: 1
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- test
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 425
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 436
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- test
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 440
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 455
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- a
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 459
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 471
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
|
|
@ -26,6 +26,17 @@ pub struct VisibleScope {
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a function is a "static method".
|
||||||
|
pub fn is_static(stmt: &Stmt) -> bool {
|
||||||
|
match &stmt.node {
|
||||||
|
StmtKind::FunctionDef { decorator_list, .. }
|
||||||
|
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list
|
||||||
|
.iter()
|
||||||
|
.any(|expr| match_name_or_attr(expr, "staticmethod")),
|
||||||
|
_ => panic!("Found non-FunctionDef in is_overload"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if a function definition is an `@overload`.
|
/// Returns `true` if a function definition is an `@overload`.
|
||||||
pub fn is_overload(stmt: &Stmt) -> bool {
|
pub fn is_overload(stmt: &Stmt) -> bool {
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue