mirror of https://github.com/astral-sh/ruff
Implement checks for Google-style docstrings (#427)
This commit is contained in:
parent
3e28d6de04
commit
952a0eb4e3
|
|
@ -981,6 +981,9 @@ name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
|
|
@ -1999,6 +2002,7 @@ dependencies = [
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"test-case",
|
"test-case",
|
||||||
|
"textwrap",
|
||||||
"titlecase",
|
"titlecase",
|
||||||
"toml",
|
"toml",
|
||||||
"update-informer",
|
"update-informer",
|
||||||
|
|
@ -2261,6 +2265,12 @@ version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smawk"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
|
|
@ -2435,6 +2445,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
|
||||||
|
dependencies = [
|
||||||
|
"smawk",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.37"
|
version = "1.0.37"
|
||||||
|
|
@ -2602,6 +2623,16 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.22"
|
version = "0.1.22"
|
||||||
|
|
@ -2611,6 +2642,12 @@ dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ serde = { version = "1.0.143", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.83" }
|
serde_json = { version = "1.0.83" }
|
||||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||||
strum_macros = { version = "0.24.3" }
|
strum_macros = { version = "0.24.3" }
|
||||||
|
textwrap = { version = "0.15.1" }
|
||||||
titlecase = { version = "2.2.1" }
|
titlecase = { version = "2.2.1" }
|
||||||
toml = { version = "0.5.9" }
|
toml = { version = "0.5.9" }
|
||||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ ruff also implements some of the most popular Flake8 plugins natively, including
|
||||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (15/16)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (15/16)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
||||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (37/48)
|
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (41/48)
|
||||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||||
|
|
||||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||||
|
|
@ -349,6 +349,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||||
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("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 | | |
|
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
|
||||||
|
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | | |
|
||||||
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | | |
|
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | | |
|
||||||
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
|
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
|
||||||
| D419 | NonEmpty | Docstring is empty | | |
|
| D419 | NonEmpty | Docstring is empty | | |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
"""A one line summary of the module or program, terminated by a period.
|
||||||
|
|
||||||
|
Leave one blank line. The rest of this docstring should contain an
|
||||||
|
overall description of the module or program. Optionally, it may also
|
||||||
|
contain a brief description of exported classes and functions and/or usage
|
||||||
|
examples.
|
||||||
|
|
||||||
|
Typical usage example:
|
||||||
|
|
||||||
|
foo = ClassFoo()
|
||||||
|
bar = foo.FunctionBar()
|
||||||
|
"""
|
||||||
|
# above: "2.8.2 Modules" section example
|
||||||
|
# https://google.github.io/styleguide/pyguide.html#382-modules
|
||||||
|
|
||||||
|
# Examples from the official "Google Python Style Guide" documentation:
|
||||||
|
# * As HTML: https://google.github.io/styleguide/pyguide.html
|
||||||
|
# * Source Markdown:
|
||||||
|
# https://github.com/google/styleguide/blob/gh-pages/pyguide.md
|
||||||
|
|
||||||
|
import os
|
||||||
|
from .expected import Expectation
|
||||||
|
|
||||||
|
expectation = Expectation()
|
||||||
|
expect = expectation.expect
|
||||||
|
|
||||||
|
# module docstring expected violations:
|
||||||
|
expectation.expected.add((
|
||||||
|
os.path.normcase(__file__),
|
||||||
|
"D213: Multi-line docstring summary should start at the second line"))
|
||||||
|
|
||||||
|
|
||||||
|
# "3.8.3 Functions and Methods" section example
|
||||||
|
# https://google.github.io/styleguide/pyguide.html#383-functions-and-methods
|
||||||
|
@expect("D213: Multi-line docstring summary should start at the second line",
|
||||||
|
arg_count=3)
|
||||||
|
@expect("D401: First line should be in imperative mood "
|
||||||
|
"(perhaps 'Fetch', not 'Fetches')", arg_count=3)
|
||||||
|
@expect("D406: Section name should end with a newline "
|
||||||
|
"('Raises', not 'Raises:')", arg_count=3)
|
||||||
|
@expect("D406: Section name should end with a newline "
|
||||||
|
"('Returns', not 'Returns:')", arg_count=3)
|
||||||
|
@expect("D407: Missing dashed underline after section ('Raises')", arg_count=3)
|
||||||
|
@expect("D407: Missing dashed underline after section ('Returns')",
|
||||||
|
arg_count=3)
|
||||||
|
@expect("D413: Missing blank line after last section ('Raises')", arg_count=3)
|
||||||
|
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
|
||||||
|
"""Fetches rows from a Bigtable.
|
||||||
|
|
||||||
|
Retrieves rows pertaining to the given keys from the Table instance
|
||||||
|
represented by big_table. Silly things may happen if
|
||||||
|
other_silly_variable is not None.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
big_table: An open Bigtable Table instance.
|
||||||
|
keys: A sequence of strings representing the key of each table row
|
||||||
|
to fetch.
|
||||||
|
other_silly_variable: Another optional variable, that has a much
|
||||||
|
longer name than the other args, and which does nothing.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dict mapping keys to the corresponding table row data
|
||||||
|
fetched. Each row is represented as a tuple of strings. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
{'Serak': ('Rigel VII', 'Preparer'),
|
||||||
|
'Zim': ('Irk', 'Invader'),
|
||||||
|
'Lrrr': ('Omicron Persei 8', 'Emperor')}
|
||||||
|
|
||||||
|
If a key from the keys argument is missing from the dictionary,
|
||||||
|
then that row was not found in the table.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
IOError: An error occurred accessing the bigtable.Table object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# "3.8.4 Classes" section example
|
||||||
|
# https://google.github.io/styleguide/pyguide.html#384-classes
|
||||||
|
@expect("D203: 1 blank line required before class docstring (found 0)")
|
||||||
|
@expect("D213: Multi-line docstring summary should start at the second line")
|
||||||
|
@expect("D406: Section name should end with a newline "
|
||||||
|
"('Attributes', not 'Attributes:')")
|
||||||
|
@expect("D407: Missing dashed underline after section ('Attributes')")
|
||||||
|
@expect("D413: Missing blank line after last section ('Attributes')")
|
||||||
|
class SampleClass:
|
||||||
|
"""Summary of class here.
|
||||||
|
|
||||||
|
Longer class information....
|
||||||
|
Longer class information....
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
likes_spam: A boolean indicating if we like SPAM or not.
|
||||||
|
eggs: An integer count of the eggs we have laid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@expect("D401: First line should be in imperative mood "
|
||||||
|
"(perhaps 'Init', not 'Inits')", arg_count=2)
|
||||||
|
def __init__(self, likes_spam=False):
|
||||||
|
"""Inits SampleClass with blah."""
|
||||||
|
if self: # added to avoid NameError when run via @expect decorator
|
||||||
|
self.likes_spam = likes_spam
|
||||||
|
self.eggs = 0
|
||||||
|
|
||||||
|
@expect("D401: First line should be in imperative mood "
|
||||||
|
"(perhaps 'Perform', not 'Performs')", arg_count=1)
|
||||||
|
def public_method(self):
|
||||||
|
"""Performs operation blah."""
|
||||||
|
|
@ -192,6 +192,7 @@ pub enum CheckCode {
|
||||||
D413,
|
D413,
|
||||||
D414,
|
D414,
|
||||||
D415,
|
D415,
|
||||||
|
D416,
|
||||||
D417,
|
D417,
|
||||||
D418,
|
D418,
|
||||||
D419,
|
D419,
|
||||||
|
|
@ -330,6 +331,7 @@ pub enum CheckKind {
|
||||||
PublicModule,
|
PublicModule,
|
||||||
PublicNestedClass,
|
PublicNestedClass,
|
||||||
PublicPackage,
|
PublicPackage,
|
||||||
|
SectionNameEndsInColon(String),
|
||||||
SectionNotOverIndented(String),
|
SectionNotOverIndented(String),
|
||||||
SectionUnderlineAfterName(String),
|
SectionUnderlineAfterName(String),
|
||||||
SectionUnderlineMatchesSectionLength(String),
|
SectionUnderlineMatchesSectionLength(String),
|
||||||
|
|
@ -473,6 +475,8 @@ impl CheckCode {
|
||||||
CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1),
|
CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1),
|
||||||
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
|
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
|
||||||
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
|
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
|
||||||
|
CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
|
||||||
|
CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
|
||||||
CheckCode::D300 => CheckKind::UsesTripleQuotes,
|
CheckCode::D300 => CheckKind::UsesTripleQuotes,
|
||||||
CheckCode::D400 => CheckKind::EndsInPeriod,
|
CheckCode::D400 => CheckKind::EndsInPeriod,
|
||||||
CheckCode::D402 => CheckKind::NoSignature,
|
CheckCode::D402 => CheckKind::NoSignature,
|
||||||
|
|
@ -493,13 +497,12 @@ impl CheckCode {
|
||||||
CheckCode::D413 => CheckKind::BlankLineAfterLastSection("Returns".to_string()),
|
CheckCode::D413 => CheckKind::BlankLineAfterLastSection("Returns".to_string()),
|
||||||
CheckCode::D414 => CheckKind::NonEmptySection("Returns".to_string()),
|
CheckCode::D414 => CheckKind::NonEmptySection("Returns".to_string()),
|
||||||
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
||||||
CheckCode::D418 => CheckKind::SkipDocstring,
|
CheckCode::D416 => CheckKind::SectionNameEndsInColon("Returns".to_string()),
|
||||||
CheckCode::D419 => CheckKind::NonEmpty,
|
|
||||||
CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
|
|
||||||
CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
|
|
||||||
CheckCode::D417 => {
|
CheckCode::D417 => {
|
||||||
CheckKind::DocumentAllArguments(vec!["x".to_string(), "y".to_string()])
|
CheckKind::DocumentAllArguments(vec!["x".to_string(), "y".to_string()])
|
||||||
}
|
}
|
||||||
|
CheckCode::D418 => CheckKind::SkipDocstring,
|
||||||
|
CheckCode::D419 => CheckKind::NonEmpty,
|
||||||
// Meta
|
// Meta
|
||||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||||
}
|
}
|
||||||
|
|
@ -626,6 +629,7 @@ 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::SectionNameEndsInColon(_) => &CheckCode::D416,
|
||||||
CheckKind::SectionNotOverIndented(_) => &CheckCode::D214,
|
CheckKind::SectionNotOverIndented(_) => &CheckCode::D214,
|
||||||
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
|
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
|
||||||
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
|
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
|
||||||
|
|
@ -982,6 +986,9 @@ impl CheckKind {
|
||||||
CheckKind::SectionUnderlineNotOverIndented(name) => {
|
CheckKind::SectionUnderlineNotOverIndented(name) => {
|
||||||
format!("Section underline is over-indented (\"{name}\")")
|
format!("Section underline is over-indented (\"{name}\")")
|
||||||
}
|
}
|
||||||
|
CheckKind::SectionNameEndsInColon(name) => {
|
||||||
|
format!("Section name should end with a colon (\"{name}\")")
|
||||||
|
}
|
||||||
CheckKind::DocumentAllArguments(names) => {
|
CheckKind::DocumentAllArguments(names) => {
|
||||||
if names.len() == 1 {
|
if names.len() == 1 {
|
||||||
let name = &names[0];
|
let name = &names[0];
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use rustpython_ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||||
use crate::ast::types::Range;
|
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::sections::{check_numpy_section, section_contexts};
|
use crate::docstrings::sections::{
|
||||||
|
check_google_section, check_numpy_section, section_contexts, SectionStyle,
|
||||||
|
};
|
||||||
use crate::docstrings::types::{Definition, DefinitionKind, Documentable};
|
use crate::docstrings::types::{Definition, DefinitionKind, Documentable};
|
||||||
use crate::visibility::{is_init, is_magic, is_overload, Modifier, Visibility, VisibleScope};
|
use crate::visibility::{is_init, is_magic, is_overload, Modifier, Visibility, VisibleScope};
|
||||||
|
|
||||||
|
|
@ -668,9 +670,20 @@ pub fn check_sections(checker: &mut Checker, definition: &Definition) {
|
||||||
if lines.len() < 2 {
|
if lines.len() < 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for context in §ion_contexts(&lines) {
|
|
||||||
|
// First, try to interpret as NumPy-style sections.
|
||||||
|
let mut found_numpy_section = false;
|
||||||
|
for context in §ion_contexts(&lines, &SectionStyle::NumPy) {
|
||||||
|
found_numpy_section = true;
|
||||||
check_numpy_section(checker, definition, context);
|
check_numpy_section(checker, definition, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no such sections were identified, interpret as Google-style sections.
|
||||||
|
if !found_numpy_section {
|
||||||
|
for context in §ion_contexts(&lines, &SectionStyle::Google) {
|
||||||
|
check_google_section(checker, definition, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use itertools::Itertools;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
use rustpython_ast::{Arg, Expr, Location, StmtKind};
|
use rustpython_ast::{Arg, Expr, Location, StmtKind};
|
||||||
use titlecase::titlecase;
|
use titlecase::titlecase;
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
static NUMPY_SECTION_NAMES_LOWERCASE: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||||
BTreeSet::from([
|
BTreeSet::from([
|
||||||
"short summary",
|
"short summary",
|
||||||
"extended summary",
|
"extended summary",
|
||||||
|
|
@ -48,39 +49,92 @@ static NUMPY_SECTION_NAMES_LOWERCASE: Lazy<BTreeSet<&'static str>> = Lazy::new(|
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(charlie): Include Google section names.
|
static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||||
// static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
BTreeSet::from([
|
||||||
// BTreeSet::from([
|
"Args",
|
||||||
// "Args",
|
"Arguments",
|
||||||
// "Arguments",
|
"Attention",
|
||||||
// "Attention",
|
"Attributes",
|
||||||
// "Attributes",
|
"Caution",
|
||||||
// "Caution",
|
"Danger",
|
||||||
// "Danger",
|
"Error",
|
||||||
// "Error",
|
"Example",
|
||||||
// "Example",
|
"Examples",
|
||||||
// "Examples",
|
"Hint",
|
||||||
// "Hint",
|
"Important",
|
||||||
// "Important",
|
"Keyword Args",
|
||||||
// "Keyword Args",
|
"Keyword Arguments",
|
||||||
// "Keyword Arguments",
|
"Methods",
|
||||||
// "Methods",
|
"Note",
|
||||||
// "Note",
|
"Notes",
|
||||||
// "Notes",
|
"Return",
|
||||||
// "Return",
|
"Returns",
|
||||||
// "Returns",
|
"Raises",
|
||||||
// "Raises",
|
"References",
|
||||||
// "References",
|
"See Also",
|
||||||
// "See Also",
|
"Tip",
|
||||||
// "Tip",
|
"Todo",
|
||||||
// "Todo",
|
"Warning",
|
||||||
// "Warning",
|
"Warnings",
|
||||||
// "Warnings",
|
"Warns",
|
||||||
// "Warns",
|
"Yield",
|
||||||
// "Yield",
|
"Yields",
|
||||||
// "Yields",
|
])
|
||||||
// ])
|
});
|
||||||
// });
|
|
||||||
|
static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||||
|
BTreeSet::from([
|
||||||
|
"args",
|
||||||
|
"arguments",
|
||||||
|
"attention",
|
||||||
|
"attributes",
|
||||||
|
"caution",
|
||||||
|
"danger",
|
||||||
|
"error",
|
||||||
|
"example",
|
||||||
|
"examples",
|
||||||
|
"hint",
|
||||||
|
"important",
|
||||||
|
"keyword args",
|
||||||
|
"keyword arguments",
|
||||||
|
"methods",
|
||||||
|
"note",
|
||||||
|
"notes",
|
||||||
|
"return",
|
||||||
|
"returns",
|
||||||
|
"raises",
|
||||||
|
"references",
|
||||||
|
"see also",
|
||||||
|
"tip",
|
||||||
|
"todo",
|
||||||
|
"warning",
|
||||||
|
"warnings",
|
||||||
|
"warns",
|
||||||
|
"yield",
|
||||||
|
"yields",
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
pub enum SectionStyle {
|
||||||
|
NumPy,
|
||||||
|
Google,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectionStyle {
|
||||||
|
fn section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||||
|
match self {
|
||||||
|
SectionStyle::NumPy => &NUMPY_SECTION_NAMES,
|
||||||
|
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lowercase_section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||||
|
match self {
|
||||||
|
SectionStyle::NumPy => &LOWERCASE_NUMPY_SECTION_NAMES,
|
||||||
|
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
|
fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
|
||||||
let range = range_for(docstring);
|
let range = range_for(docstring);
|
||||||
|
|
@ -103,8 +157,10 @@ fn leading_words(line: &str) -> String {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suspected_as_section(line: &str) -> bool {
|
fn suspected_as_section(line: &str, style: &SectionStyle) -> bool {
|
||||||
NUMPY_SECTION_NAMES_LOWERCASE.contains(&leading_words(line).to_lowercase().as_str())
|
style
|
||||||
|
.lowercase_section_names()
|
||||||
|
.contains(&leading_words(line).to_lowercase().as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -145,12 +201,12 @@ fn is_docstring_section(context: &SectionContext) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract all `SectionContext` values from a docstring.
|
/// Extract all `SectionContext` values from a docstring.
|
||||||
pub fn section_contexts<'a>(lines: &'a [&'a str]) -> Vec<SectionContext<'a>> {
|
pub fn section_contexts<'a>(lines: &'a [&'a str], style: &SectionStyle) -> Vec<SectionContext<'a>> {
|
||||||
let suspected_section_indices: Vec<usize> = lines
|
let suspected_section_indices: Vec<usize> = lines
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(lineno, line)| {
|
.filter_map(|(lineno, line)| {
|
||||||
if lineno > 0 && suspected_as_section(line) {
|
if lineno > 0 && suspected_as_section(line, style) {
|
||||||
Some(lineno)
|
Some(lineno)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -322,14 +378,23 @@ 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,
|
||||||
|
style: &SectionStyle,
|
||||||
|
) {
|
||||||
let docstring = definition
|
let docstring = definition
|
||||||
.docstring
|
.docstring
|
||||||
.expect("Sections are only available for docstrings.");
|
.expect("Sections are only available for docstrings.");
|
||||||
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D405) {
|
if checker.settings.enabled.contains(&CheckCode::D405) {
|
||||||
if !NUMPY_SECTION_NAMES.contains(&context.section_name.as_str())
|
if !style
|
||||||
&& NUMPY_SECTION_NAMES.contains(titlecase(&context.section_name).as_str())
|
.section_names()
|
||||||
|
.contains(&context.section_name.as_str())
|
||||||
|
&& style
|
||||||
|
.section_names()
|
||||||
|
.contains(titlecase(&context.section_name).as_str())
|
||||||
{
|
{
|
||||||
checker.add_check(Check::new(
|
checker.add_check(Check::new(
|
||||||
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
|
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
|
||||||
|
|
@ -383,7 +448,7 @@ fn check_common_section(checker: &mut Checker, definition: &Definition, context:
|
||||||
fn check_missing_args(
|
fn check_missing_args(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
definition: &Definition,
|
definition: &Definition,
|
||||||
docstrings_args: BTreeSet<&str>,
|
docstrings_args: &BTreeSet<&str>,
|
||||||
) {
|
) {
|
||||||
if let DefinitionKind::Function(parent)
|
if let DefinitionKind::Function(parent)
|
||||||
| DefinitionKind::NestedFunction(parent)
|
| DefinitionKind::NestedFunction(parent)
|
||||||
|
|
@ -476,7 +541,7 @@ fn check_parameters_section(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Validate that all arguments were documented.
|
// Validate that all arguments were documented.
|
||||||
check_missing_args(checker, definition, docstring_args);
|
check_missing_args(checker, definition, &docstring_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_numpy_section(
|
pub fn check_numpy_section(
|
||||||
|
|
@ -484,7 +549,7 @@ pub fn check_numpy_section(
|
||||||
definition: &Definition,
|
definition: &Definition,
|
||||||
context: &SectionContext,
|
context: &SectionContext,
|
||||||
) {
|
) {
|
||||||
check_common_section(checker, definition, context);
|
check_common_section(checker, definition, context, &SectionStyle::NumPy);
|
||||||
check_blanks_and_section_underline(checker, definition, context);
|
check_blanks_and_section_underline(checker, definition, context);
|
||||||
|
|
||||||
if checker.settings.enabled.contains(&CheckCode::D406) {
|
if checker.settings.enabled.contains(&CheckCode::D406) {
|
||||||
|
|
@ -510,3 +575,74 @@ pub fn check_numpy_section(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
|
||||||
|
static GOOGLE_ARGS_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").expect("Invalid regex"));
|
||||||
|
|
||||||
|
fn check_args_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
|
||||||
|
let mut args_sections: Vec<String> = vec![];
|
||||||
|
for line in textwrap::dedent(&context.following_lines.join("\n")).lines() {
|
||||||
|
if line
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.map(|char| char.is_whitespace())
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
// This is a continuation of documentation for the last
|
||||||
|
// parameter because it does start with whitespace.
|
||||||
|
if let Some(current) = args_sections.last_mut() {
|
||||||
|
current.push_str(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This line is the start of documentation for the next
|
||||||
|
// parameter because it doesn't start with any whitespace.
|
||||||
|
args_sections.push(line.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_missing_args(
|
||||||
|
checker,
|
||||||
|
definition,
|
||||||
|
// Collect the list of arguments documented in the docstring.
|
||||||
|
&BTreeSet::from_iter(args_sections.iter().filter_map(|section| {
|
||||||
|
match GOOGLE_ARGS_REGEX.captures(section.as_str()) {
|
||||||
|
Some(caps) => caps.get(1).map(|arg_name| arg_name.as_str()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_google_section(
|
||||||
|
checker: &mut Checker,
|
||||||
|
definition: &Definition,
|
||||||
|
context: &SectionContext,
|
||||||
|
) {
|
||||||
|
check_common_section(checker, definition, context, &SectionStyle::Google);
|
||||||
|
check_blanks_and_section_underline(checker, definition, context);
|
||||||
|
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::D416) {
|
||||||
|
let suffix = context
|
||||||
|
.line
|
||||||
|
.trim()
|
||||||
|
.strip_prefix(&context.section_name)
|
||||||
|
.unwrap();
|
||||||
|
if suffix != ":" {
|
||||||
|
let docstring = definition
|
||||||
|
.docstring
|
||||||
|
.expect("Sections are only available for docstrings.");
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
|
||||||
|
range_for(docstring),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker.settings.enabled.contains(&CheckCode::D417) {
|
||||||
|
let capitalized_section_name = titlecase(&context.section_name);
|
||||||
|
if capitalized_section_name == "Args" || capitalized_section_name == "Arguments" {
|
||||||
|
check_args_section(checker, definition, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -295,8 +295,10 @@ 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::D416, Path::new("D.py"); "D416")]
|
||||||
#[test_case(CheckCode::D417, Path::new("sections.py"); "D417_0")]
|
#[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::D417, Path::new("canonical_numpy_examples.py"); "D417_1")]
|
||||||
|
#[test_case(CheckCode::D417, Path::new("canonical_google_examples.py"); "D417_2")]
|
||||||
#[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")]
|
||||||
|
|
|
||||||
|
|
@ -47,4 +47,94 @@ expression: checks
|
||||||
row: 262
|
row: 262
|
||||||
column: 8
|
column: 8
|
||||||
fix: ~
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 269
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 274
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 284
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 292
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 301
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 306
|
||||||
|
column: 8
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 313
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 319
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 325
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 330
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 337
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 343
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 350
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 355
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 362
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 367
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 371
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 382
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DashedUnderlineAfterSection: Args
|
||||||
|
location:
|
||||||
|
row: 490
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 497
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
||||||
|
|
@ -2,6 +2,73 @@
|
||||||
source: src/linter.rs
|
source: src/linter.rs
|
||||||
expression: checks
|
expression: checks
|
||||||
---
|
---
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- y
|
||||||
|
location:
|
||||||
|
row: 283
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 296
|
||||||
|
column: 1
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- y
|
||||||
|
location:
|
||||||
|
row: 300
|
||||||
|
column: 1
|
||||||
|
end_location:
|
||||||
|
row: 309
|
||||||
|
column: 1
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- test
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 324
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 332
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- test
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 336
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 345
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- a
|
||||||
|
- y
|
||||||
|
- z
|
||||||
|
location:
|
||||||
|
row: 349
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 357
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
location:
|
||||||
|
row: 361
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 369
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
- kind:
|
- kind:
|
||||||
DocumentAllArguments:
|
DocumentAllArguments:
|
||||||
- y
|
- y
|
||||||
|
|
@ -47,4 +114,14 @@ expression: checks
|
||||||
row: 471
|
row: 471
|
||||||
column: 5
|
column: 5
|
||||||
fix: ~
|
fix: ~
|
||||||
|
- kind:
|
||||||
|
DocumentAllArguments:
|
||||||
|
- y
|
||||||
|
location:
|
||||||
|
row: 489
|
||||||
|
column: 5
|
||||||
|
end_location:
|
||||||
|
row: 498
|
||||||
|
column: 1
|
||||||
|
fix: ~
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue