mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:37 +00:00
Disambiguate argument descriptors from section headers (#9427)
## Summary Given a docstring like: ```python def func(x: int, args: tuple[int]): """Toggle the gizmo. Args: x: Some argument. args: Some other arguments. """ ``` We were considering the `args:` descriptor to be an indented docstring section header (since `Args:`) is a valid header name. This led to very confusing diagnostics. This PR makes the parsing a bit more lax in this case, such that if we see a nested header that's more deeply indented than the preceding header, and the preceding section allows sub-items (like `Args:`), we avoid treating the nested item as a section header. Closes https://github.com/astral-sh/ruff/issues/9426.
This commit is contained in:
parent
d5a439cbd3
commit
04afdf177b
8 changed files with 200 additions and 8 deletions
|
@ -150,3 +150,21 @@ class Test:
|
||||||
Args:
|
Args:
|
||||||
arg1: some description of arg
|
arg1: some description of arg
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def select_data(
|
||||||
|
query: str,
|
||||||
|
args: tuple,
|
||||||
|
database: str,
|
||||||
|
auto_save: bool,
|
||||||
|
) -> None:
|
||||||
|
"""This function has an argument `args`, which shouldn't be mistaken for a section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query:
|
||||||
|
Query template.
|
||||||
|
args:
|
||||||
|
A list of arguments.
|
||||||
|
database:
|
||||||
|
Which database to connect to ("origin" or "destination").
|
||||||
|
"""
|
||||||
|
|
|
@ -536,9 +536,29 @@ def non_empty_blank_line_before_section(): # noqa: D416
|
||||||
"""Toggle the gizmo.
|
"""Toggle the gizmo.
|
||||||
|
|
||||||
The function's description.
|
The function's description.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
A value of some sort.
|
A value of some sort.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def lowercase_sub_section_header():
|
||||||
|
"""Below, `returns:` should _not_ be considered a section header.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
Here's a note.
|
||||||
|
|
||||||
|
returns:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def titlecase_sub_section_header():
|
||||||
|
"""Below, `Returns:` should be considered a section header.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
Here's a note.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"""
|
||||||
|
|
|
@ -153,13 +153,17 @@ impl<'a> SectionContexts<'a> {
|
||||||
while let Some(line) = lines.next() {
|
while let Some(line) = lines.next() {
|
||||||
if let Some(section_kind) = suspected_as_section(&line, style) {
|
if let Some(section_kind) = suspected_as_section(&line, style) {
|
||||||
let indent = leading_space(&line);
|
let indent = leading_space(&line);
|
||||||
let section_name = leading_words(&line);
|
let indent_size = indent.text_len();
|
||||||
|
|
||||||
let section_name_range = TextRange::at(indent.text_len(), section_name.text_len());
|
let section_name = leading_words(&line);
|
||||||
|
let section_name_size = section_name.text_len();
|
||||||
|
|
||||||
if is_docstring_section(
|
if is_docstring_section(
|
||||||
&line,
|
&line,
|
||||||
section_name_range,
|
indent_size,
|
||||||
|
section_name_size,
|
||||||
|
section_kind,
|
||||||
|
last.as_ref(),
|
||||||
previous_line.as_ref(),
|
previous_line.as_ref(),
|
||||||
lines.peek(),
|
lines.peek(),
|
||||||
) {
|
) {
|
||||||
|
@ -170,7 +174,8 @@ impl<'a> SectionContexts<'a> {
|
||||||
|
|
||||||
last = Some(SectionContextData {
|
last = Some(SectionContextData {
|
||||||
kind: section_kind,
|
kind: section_kind,
|
||||||
name_range: section_name_range + line.start(),
|
indent_size: indent.text_len(),
|
||||||
|
name_range: TextRange::at(line.start() + indent_size, section_name_size),
|
||||||
range: TextRange::empty(line.start()),
|
range: TextRange::empty(line.start()),
|
||||||
summary_full_end: line.full_end(),
|
summary_full_end: line.full_end(),
|
||||||
});
|
});
|
||||||
|
@ -204,8 +209,8 @@ impl<'a> SectionContexts<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a SectionContexts<'a> {
|
impl<'a> IntoIterator for &'a SectionContexts<'a> {
|
||||||
type IntoIter = SectionContextsIter<'a>;
|
|
||||||
type Item = SectionContext<'a>;
|
type Item = SectionContext<'a>;
|
||||||
|
type IntoIter = SectionContextsIter<'a>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.iter()
|
self.iter()
|
||||||
|
@ -257,6 +262,9 @@ impl ExactSizeIterator for SectionContextsIter<'_> {}
|
||||||
struct SectionContextData {
|
struct SectionContextData {
|
||||||
kind: SectionKind,
|
kind: SectionKind,
|
||||||
|
|
||||||
|
/// The size of the indentation of the section name.
|
||||||
|
indent_size: TextSize,
|
||||||
|
|
||||||
/// Range of the section name, relative to the [`Docstring::body`]
|
/// Range of the section name, relative to the [`Docstring::body`]
|
||||||
name_range: TextRange,
|
name_range: TextRange,
|
||||||
|
|
||||||
|
@ -401,12 +409,15 @@ fn suspected_as_section(line: &str, style: SectionStyle) -> Option<SectionKind>
|
||||||
/// Check if the suspected context is really a section header.
|
/// Check if the suspected context is really a section header.
|
||||||
fn is_docstring_section(
|
fn is_docstring_section(
|
||||||
line: &Line,
|
line: &Line,
|
||||||
section_name_range: TextRange,
|
indent_size: TextSize,
|
||||||
|
section_name_size: TextSize,
|
||||||
|
section_kind: SectionKind,
|
||||||
|
previous_section: Option<&SectionContextData>,
|
||||||
previous_line: Option<&Line>,
|
previous_line: Option<&Line>,
|
||||||
next_line: Option<&Line>,
|
next_line: Option<&Line>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// Determine whether the current line looks like a section header, e.g., "Args:".
|
// Determine whether the current line looks like a section header, e.g., "Args:".
|
||||||
let section_name_suffix = line[usize::from(section_name_range.end())..].trim();
|
let section_name_suffix = line[usize::from(indent_size + section_name_size)..].trim();
|
||||||
let this_looks_like_a_section_name =
|
let this_looks_like_a_section_name =
|
||||||
section_name_suffix == ":" || section_name_suffix.is_empty();
|
section_name_suffix == ":" || section_name_suffix.is_empty();
|
||||||
if !this_looks_like_a_section_name {
|
if !this_looks_like_a_section_name {
|
||||||
|
@ -439,5 +450,25 @@ fn is_docstring_section(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if this is a sub-section within another section, like `args` in:
|
||||||
|
// ```python
|
||||||
|
// def func(args: tuple[int]):
|
||||||
|
// """Toggle the gizmo.
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// args: The arguments to the function.
|
||||||
|
// """
|
||||||
|
// ```
|
||||||
|
// However, if the header is an _exact_ match (like `Returns:`, as opposed to `returns:`), then
|
||||||
|
// continue to treat it as a section header.
|
||||||
|
if let Some(previous_section) = previous_section {
|
||||||
|
if previous_section.indent_size < indent_size {
|
||||||
|
let verbatim = &line[TextRange::at(indent_size, section_name_size)];
|
||||||
|
if section_kind.as_str() != verbatim {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,4 +27,27 @@ sections.py:144:5: D214 [*] Section is over-indented ("Returns")
|
||||||
148 148 | A value of some sort.
|
148 148 | A value of some sort.
|
||||||
149 149 |
|
149 149 |
|
||||||
|
|
||||||
|
sections.py:558:5: D214 [*] Section is over-indented ("Returns")
|
||||||
|
|
|
||||||
|
557 | def titlecase_sub_section_header():
|
||||||
|
558 | """Below, `Returns:` should be considered a section header.
|
||||||
|
| _____^
|
||||||
|
559 | |
|
||||||
|
560 | | Args:
|
||||||
|
561 | | Here's a note.
|
||||||
|
562 | |
|
||||||
|
563 | | Returns:
|
||||||
|
564 | | """
|
||||||
|
| |_______^ D214
|
||||||
|
|
|
||||||
|
= help: Remove over-indentation from "Returns"
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
560 560 | Args:
|
||||||
|
561 561 | Here's a note.
|
||||||
|
562 562 |
|
||||||
|
563 |- Returns:
|
||||||
|
563 |+ Returns:
|
||||||
|
564 564 | """
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -498,4 +498,74 @@ sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters"
|
||||||
531 532 | """
|
531 532 | """
|
||||||
532 533 |
|
532 533 |
|
||||||
|
|
||||||
|
sections.py:548:5: D407 [*] Missing dashed underline after section ("Args")
|
||||||
|
|
|
||||||
|
547 | def lowercase_sub_section_header():
|
||||||
|
548 | """Below, `returns:` should _not_ be considered a section header.
|
||||||
|
| _____^
|
||||||
|
549 | |
|
||||||
|
550 | | Args:
|
||||||
|
551 | | Here's a note.
|
||||||
|
552 | |
|
||||||
|
553 | | returns:
|
||||||
|
554 | | """
|
||||||
|
| |_______^ D407
|
||||||
|
|
|
||||||
|
= help: Add dashed line under "Args"
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
548 548 | """Below, `returns:` should _not_ be considered a section header.
|
||||||
|
549 549 |
|
||||||
|
550 550 | Args:
|
||||||
|
551 |+ ----
|
||||||
|
551 552 | Here's a note.
|
||||||
|
552 553 |
|
||||||
|
553 554 | returns:
|
||||||
|
|
||||||
|
sections.py:558:5: D407 [*] Missing dashed underline after section ("Args")
|
||||||
|
|
|
||||||
|
557 | def titlecase_sub_section_header():
|
||||||
|
558 | """Below, `Returns:` should be considered a section header.
|
||||||
|
| _____^
|
||||||
|
559 | |
|
||||||
|
560 | | Args:
|
||||||
|
561 | | Here's a note.
|
||||||
|
562 | |
|
||||||
|
563 | | Returns:
|
||||||
|
564 | | """
|
||||||
|
| |_______^ D407
|
||||||
|
|
|
||||||
|
= help: Add dashed line under "Args"
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
558 558 | """Below, `Returns:` should be considered a section header.
|
||||||
|
559 559 |
|
||||||
|
560 560 | Args:
|
||||||
|
561 |+ ----
|
||||||
|
561 562 | Here's a note.
|
||||||
|
562 563 |
|
||||||
|
563 564 | Returns:
|
||||||
|
|
||||||
|
sections.py:558:5: D407 [*] Missing dashed underline after section ("Returns")
|
||||||
|
|
|
||||||
|
557 | def titlecase_sub_section_header():
|
||||||
|
558 | """Below, `Returns:` should be considered a section header.
|
||||||
|
| _____^
|
||||||
|
559 | |
|
||||||
|
560 | | Args:
|
||||||
|
561 | | Here's a note.
|
||||||
|
562 | |
|
||||||
|
563 | | Returns:
|
||||||
|
564 | | """
|
||||||
|
| |_______^ D407
|
||||||
|
|
|
||||||
|
= help: Add dashed line under "Returns"
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
561 561 | Here's a note.
|
||||||
|
562 562 |
|
||||||
|
563 563 | Returns:
|
||||||
|
564 |+ -------
|
||||||
|
564 565 | """
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -97,4 +97,18 @@ sections.py:261:5: D414 Section has no content ("Returns")
|
||||||
| |_______^ D414
|
| |_______^ D414
|
||||||
|
|
|
|
||||||
|
|
||||||
|
sections.py:558:5: D414 Section has no content ("Returns")
|
||||||
|
|
|
||||||
|
557 | def titlecase_sub_section_header():
|
||||||
|
558 | """Below, `Returns:` should be considered a section header.
|
||||||
|
| _____^
|
||||||
|
559 | |
|
||||||
|
560 | | Args:
|
||||||
|
561 | | Here's a note.
|
||||||
|
562 | |
|
||||||
|
563 | | Returns:
|
||||||
|
564 | | """
|
||||||
|
| |_______^ D414
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,4 +64,12 @@ D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*arg
|
||||||
109 | """Do something.
|
109 | """Do something.
|
||||||
|
|
|
|
||||||
|
|
||||||
|
D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save`
|
||||||
|
|
|
||||||
|
155 | def select_data(
|
||||||
|
| ^^^^^^^^^^^ D417
|
||||||
|
156 | query: str,
|
||||||
|
157 | args: tuple,
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,4 +64,12 @@ D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*arg
|
||||||
109 | """Do something.
|
109 | """Do something.
|
||||||
|
|
|
|
||||||
|
|
||||||
|
D417.py:155:5: D417 Missing argument description in the docstring for `select_data`: `auto_save`
|
||||||
|
|
|
||||||
|
155 | def select_data(
|
||||||
|
| ^^^^^^^^^^^ D417
|
||||||
|
156 | query: str,
|
||||||
|
157 | args: tuple,
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue