[pydoclint] Implement docstring-missing-exception and docstring-extraneous-exception (DOC501, DOC502) (#11471)

## Summary

These are the first rules implemented as part of #458, but I plan to
implement more.

Specifically, this implements `docstring-missing-exception` which checks
for raised exceptions not documented in the docstring, and
`docstring-extraneous-exception` which checks for exceptions in the
docstring not present in the body.

## Test Plan

Test fixtures added for both google and numpy style.
This commit is contained in:
Auguste Lalande 2024-07-20 15:41:51 -04:00 committed by GitHub
parent 53b84ab054
commit 4bc73dd87e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1161 additions and 67 deletions

View file

@ -5,6 +5,11 @@ use ruff_python_ast::name::QualifiedName;
use ruff_python_semantic::{Definition, SemanticModel};
use ruff_source_file::UniversalNewlines;
use crate::docstrings::sections::{SectionContexts, SectionKind};
use crate::docstrings::styles::SectionStyle;
use crate::docstrings::Docstring;
use crate::rules::pydocstyle::settings::Convention;
/// Return the index of the first logical line in a string.
pub(super) fn logical_line(content: &str) -> Option<usize> {
// Find the first logical line.
@ -61,3 +66,59 @@ pub(crate) fn should_ignore_definition(
})
})
}
pub(crate) fn get_section_contexts<'a>(
docstring: &'a Docstring<'a>,
convention: Option<&'a Convention>,
) -> SectionContexts<'a> {
match convention {
Some(Convention::Google) => {
return SectionContexts::from_docstring(docstring, SectionStyle::Google);
}
Some(Convention::Numpy) => {
return SectionContexts::from_docstring(docstring, SectionStyle::Numpy);
}
Some(Convention::Pep257) | None => {
// There are some overlapping section names, between the Google and NumPy conventions
// (e.g., "Returns", "Raises"). Break ties by checking for the presence of some of the
// section names that are unique to each convention.
// If the docstring contains `Parameters:` or `Other Parameters:`, use the NumPy
// convention.
let numpy_sections = SectionContexts::from_docstring(docstring, SectionStyle::Numpy);
if numpy_sections.iter().any(|context| {
matches!(
context.kind(),
SectionKind::Parameters
| SectionKind::OtherParams
| SectionKind::OtherParameters
)
}) {
return numpy_sections;
}
// If the docstring contains any argument specifier, use the Google convention.
let google_sections = SectionContexts::from_docstring(docstring, SectionStyle::Google);
if google_sections.iter().any(|context| {
matches!(
context.kind(),
SectionKind::Args
| SectionKind::Arguments
| SectionKind::KeywordArgs
| SectionKind::KeywordArguments
| SectionKind::OtherArgs
| SectionKind::OtherArguments
)
}) {
return google_sections;
}
// Otherwise, use whichever convention matched more sections.
if google_sections.len() > numpy_sections.len() {
google_sections
} else {
numpy_sections
}
}
}
}

View file

@ -1324,67 +1324,16 @@ impl AlwaysFixableViolation for BlankLinesBetweenHeaderAndContent {
pub(crate) fn sections(
checker: &mut Checker,
docstring: &Docstring,
section_contexts: &SectionContexts,
convention: Option<&Convention>,
) {
match convention {
Some(Convention::Google) => {
parse_google_sections(
checker,
docstring,
&SectionContexts::from_docstring(docstring, SectionStyle::Google),
);
}
Some(Convention::Numpy) => {
parse_numpy_sections(
checker,
docstring,
&SectionContexts::from_docstring(docstring, SectionStyle::Numpy),
);
}
Some(Convention::Pep257) | None => {
// There are some overlapping section names, between the Google and NumPy conventions
// (e.g., "Returns", "Raises"). Break ties by checking for the presence of some of the
// section names that are unique to each convention.
// If the docstring contains `Parameters:` or `Other Parameters:`, use the NumPy
// convention.
let numpy_sections = SectionContexts::from_docstring(docstring, SectionStyle::Numpy);
if numpy_sections.iter().any(|context| {
matches!(
context.kind(),
SectionKind::Parameters
| SectionKind::OtherParams
| SectionKind::OtherParameters
)
}) {
parse_numpy_sections(checker, docstring, &numpy_sections);
return;
}
// If the docstring contains any argument specifier, use the Google convention.
let google_sections = SectionContexts::from_docstring(docstring, SectionStyle::Google);
if google_sections.iter().any(|context| {
matches!(
context.kind(),
SectionKind::Args
| SectionKind::Arguments
| SectionKind::KeywordArgs
| SectionKind::KeywordArguments
| SectionKind::OtherArgs
| SectionKind::OtherArguments
)
}) {
parse_google_sections(checker, docstring, &google_sections);
return;
}
// Otherwise, use whichever convention matched more sections.
if google_sections.len() > numpy_sections.len() {
parse_google_sections(checker, docstring, &google_sections);
} else {
parse_numpy_sections(checker, docstring, &numpy_sections);
}
}
Some(Convention::Google) => parse_google_sections(checker, docstring, section_contexts),
Some(Convention::Numpy) => parse_numpy_sections(checker, docstring, section_contexts),
Some(Convention::Pep257) | None => match section_contexts.style() {
SectionStyle::Google => parse_google_sections(checker, docstring, section_contexts),
SectionStyle::Numpy => parse_numpy_sections(checker, docstring, section_contexts),
},
}
}