Rename ruff crate to ruff_linter (#7529)

This commit is contained in:
Charlie Marsh 2023-09-20 02:38:27 -04:00 committed by GitHub
parent dcbd8eacd8
commit 5849a75223
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4397 changed files with 93921 additions and 93915 deletions

View file

@ -0,0 +1,73 @@
use std::collections::BTreeSet;
use ruff_python_ast::call_path::from_qualified_name;
use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::Expr;
use ruff_python_semantic::{Definition, SemanticModel};
use ruff_source_file::UniversalNewlines;
/// 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.
let mut logical_line = None;
for (i, line) in content.universal_newlines().enumerate() {
if line.trim().is_empty() {
// Empty line. If this is the line _after_ the first logical line, stop.
if logical_line.is_some() {
break;
}
} else {
// Non-empty line. Store the index.
logical_line = Some(i);
}
}
logical_line
}
/// Normalize a word by removing all non-alphanumeric characters
/// and converting it to lowercase.
pub(super) fn normalize_word(first_word: &str) -> String {
first_word
.replace(|c: char| !c.is_alphanumeric(), "")
.to_lowercase()
}
/// Return true if a line ends with an odd number of backslashes (i.e., ends with an escape).
pub(super) fn ends_with_backslash(line: &str) -> bool {
line.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
}
/// Check decorator list to see if function should be ignored.
pub(crate) fn should_ignore_definition(
definition: &Definition,
ignore_decorators: &BTreeSet<String>,
semantic: &SemanticModel,
) -> bool {
if ignore_decorators.is_empty() {
return false;
}
let Some(function) = definition.as_function_def() else {
return false;
};
function.decorator_list.iter().any(|decorator| {
semantic
.resolve_call_path(map_callable(&decorator.expression))
.is_some_and(|call_path| {
ignore_decorators
.iter()
.any(|decorator| from_qualified_name(decorator) == call_path)
})
})
}
/// Check if a docstring should be ignored.
pub(crate) fn should_ignore_docstring(docstring: &Expr) -> bool {
// Avoid analyzing docstrings that contain implicit string concatenations.
// Python does consider these docstrings, but they're almost certainly a
// user error, and supporting them "properly" is extremely difficult.
docstring
.as_constant_expr()
.is_some_and(|constant| constant.value.is_implicit_concatenated())
}

View file

@ -0,0 +1,201 @@
//! Rules from [pydocstyle](https://pypi.org/project/pydocstyle/).
pub(crate) mod helpers;
pub(crate) mod rules;
pub mod settings;
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};
use super::settings::{Convention, Settings};
#[test_case(Rule::BlankLineAfterLastSection, Path::new("sections.py"))]
#[test_case(Rule::NoBlankLineAfterSection, Path::new("sections.py"))]
#[test_case(Rule::BlankLineAfterSummary, Path::new("D.py"))]
#[test_case(Rule::NoBlankLineBeforeSection, Path::new("sections.py"))]
#[test_case(Rule::CapitalizeSectionName, Path::new("sections.py"))]
#[test_case(Rule::DashedUnderlineAfterSection, Path::new("sections.py"))]
#[test_case(Rule::UndocumentedParam, Path::new("canonical_google_examples.py"))]
#[test_case(Rule::UndocumentedParam, Path::new("canonical_numpy_examples.py"))]
#[test_case(Rule::UndocumentedParam, Path::new("sections.py"))]
#[test_case(Rule::EndsInPeriod, Path::new("D.py"))]
#[test_case(Rule::EndsInPeriod, Path::new("D400.py"))]
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"))]
#[test_case(Rule::FirstLineCapitalized, Path::new("D403.py"))]
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
#[test_case(Rule::MultiLineSummaryFirstLine, Path::new("D.py"))]
#[test_case(Rule::MultiLineSummarySecondLine, Path::new("D.py"))]
#[test_case(Rule::NewLineAfterLastParagraph, Path::new("D.py"))]
#[test_case(Rule::NewLineAfterSectionName, Path::new("sections.py"))]
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D.py"))]
#[test_case(Rule::FitsOnOneLine, Path::new("D200.py"))]
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D202.py"))]
#[test_case(Rule::BlankLineBeforeClass, Path::new("D.py"))]
#[test_case(Rule::NoBlankLineBeforeFunction, Path::new("D.py"))]
#[test_case(Rule::BlankLinesBetweenHeaderAndContent, Path::new("sections.py"))]
#[test_case(Rule::OverIndentation, Path::new("D.py"))]
#[test_case(Rule::NoSignature, Path::new("D.py"))]
#[test_case(Rule::SurroundingWhitespace, Path::new("D.py"))]
#[test_case(Rule::DocstringStartsWithThis, Path::new("D.py"))]
#[test_case(Rule::UnderIndentation, Path::new("D.py"))]
#[test_case(Rule::EmptyDocstring, Path::new("D.py"))]
#[test_case(Rule::EmptyDocstringSection, Path::new("sections.py"))]
#[test_case(Rule::NonImperativeMood, Path::new("D401.py"))]
#[test_case(Rule::NoBlankLineAfterSection, Path::new("D410.py"))]
#[test_case(Rule::OneBlankLineAfterClass, Path::new("D.py"))]
#[test_case(Rule::OneBlankLineBeforeClass, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicClass, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicFunction, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicInit, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicMethod, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicMethod, Path::new("setter.py"))]
#[test_case(Rule::UndocumentedPublicModule, Path::new("D.py"))]
#[test_case(
Rule::UndocumentedPublicModule,
Path::new("_unrelated/pkg/D100_pub.py")
)]
#[test_case(
Rule::UndocumentedPublicModule,
Path::new("_unrelated/pkg/_priv/no_D100_priv.py")
)]
#[test_case(
Rule::UndocumentedPublicModule,
Path::new("_unrelated/_no_pkg_priv.py")
)]
#[test_case(Rule::UndocumentedPublicNestedClass, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicPackage, Path::new("D.py"))]
#[test_case(Rule::UndocumentedPublicPackage, Path::new("D104/__init__.py"))]
#[test_case(Rule::SectionNameEndsInColon, Path::new("D.py"))]
#[test_case(Rule::SectionNotOverIndented, Path::new("sections.py"))]
#[test_case(Rule::SectionNotOverIndented, Path::new("D214_module.py"))]
#[test_case(Rule::SectionUnderlineAfterName, Path::new("sections.py"))]
#[test_case(Rule::SectionUnderlineMatchesSectionLength, Path::new("sections.py"))]
#[test_case(Rule::SectionUnderlineNotOverIndented, Path::new("sections.py"))]
#[test_case(Rule::OverloadWithDocstring, Path::new("D.py"))]
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D.py"))]
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D301.py"))]
#[test_case(Rule::TripleSingleQuotes, Path::new("D.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("pydocstyle").join(path).as_path(),
&settings::Settings {
pydocstyle: Settings {
convention: None,
ignore_decorators: BTreeSet::from_iter(["functools.wraps".to_string()]),
property_decorators: BTreeSet::from_iter([
"gi.repository.GObject.Property".to_string()
]),
},
..settings::Settings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn bom() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/bom.py"),
&settings::Settings::for_rule(Rule::TripleSingleQuotes),
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn d417_unspecified() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::Settings {
// When inferring the convention, we'll see a few false negatives.
// See: https://github.com/PyCQA/pydocstyle/issues/459.
pydocstyle: Settings {
convention: None,
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
..settings::Settings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn d417_google() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::Settings {
// With explicit Google convention, we should flag every function.
pydocstyle: Settings {
convention: Some(Convention::Google),
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
..settings::Settings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn d417_numpy() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D417.py"),
&settings::Settings {
// With explicit Google convention, we shouldn't flag anything.
pydocstyle: Settings {
convention: Some(Convention::Numpy),
ignore_decorators: BTreeSet::new(),
property_decorators: BTreeSet::new(),
},
..settings::Settings::for_rule(Rule::UndocumentedParam)
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn d209_d400() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/D209_D400.py"),
&settings::Settings::for_rules([Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod]),
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test]
fn all() -> Result<()> {
let diagnostics = test_path(
Path::new("pydocstyle/all.py"),
&settings::Settings::for_rules([
Rule::UndocumentedPublicModule,
Rule::UndocumentedPublicClass,
Rule::UndocumentedPublicMethod,
Rule::UndocumentedPublicFunction,
Rule::UndocumentedPublicPackage,
Rule::UndocumentedMagicMethod,
Rule::UndocumentedPublicNestedClass,
Rule::UndocumentedPublicInit,
]),
)?;
assert_messages!(diagnostics);
Ok(())
}
}

View file

@ -0,0 +1,77 @@
use memchr::memchr_iter;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
/// ## What it does
/// Checks for docstrings that include backslashes, but are not defined as
/// raw string literals.
///
/// ## Why is this bad?
/// In Python, backslashes are typically used to escape characters in strings.
/// In raw strings (those prefixed with an `r`), however, backslashes are
/// treated as literal characters.
///
/// [PEP 257](https://peps.python.org/pep-0257/#what-is-a-docstring) recommends
/// the use of raw strings (i.e., `r"""raw triple double quotes"""`) for
/// docstrings that include backslashes. The use of a raw string ensures that
/// any backslashes are treated as literal characters, and not as escape
/// sequences, which avoids confusion.
///
/// ## Example
/// ```python
/// def foobar():
/// """Docstring for foo\bar."""
///
///
/// foobar.__doc__ # "Docstring for foar."
/// ```
///
/// Use instead:
/// ```python
/// def foobar():
/// r"""Docstring for foo\bar."""
///
///
/// foobar.__doc__ # "Docstring for foo\bar."
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [Python documentation: String and Bytes literals](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals)
#[violation]
pub struct EscapeSequenceInDocstring;
impl Violation for EscapeSequenceInDocstring {
#[derive_message_formats]
fn message(&self) -> String {
format!(r#"Use `r"""` if any backslashes in a docstring"#)
}
}
/// D301
pub(crate) fn backslashes(checker: &mut Checker, docstring: &Docstring) {
// Docstring is already raw.
let contents = docstring.contents;
if contents.starts_with('r') || contents.starts_with("ur") {
return;
}
// Docstring contains at least one backslash.
let body = docstring.body();
let bytes = body.as_bytes();
if memchr_iter(b'\\', bytes).any(|position| {
let escaped_char = bytes.get(position.saturating_add(1));
// Allow continuations (backslashes followed by newlines) and Unicode escapes.
!matches!(escaped_char, Some(b'\r' | b'\n' | b'u' | b'N'))
}) {
checker.diagnostics.push(Diagnostic::new(
EscapeSequenceInDocstring,
docstring.range(),
));
}
}

View file

@ -0,0 +1,126 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
/// ## What it does
/// Checks for docstring summary lines that are not separated from the docstring
/// description by one blank line.
///
/// ## Why is this bad?
/// [PEP 257] recommends that multi-line docstrings consist of "a summary line
/// just like a one-line docstring, followed by a blank line, followed by a
/// more elaborate description."
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
/// Sort the list in ascending order and return a copy of the
/// result using the bubble sort algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the
/// result using the bubble sort algorithm.
/// """
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct BlankLineAfterSummary {
num_lines: usize,
}
impl Violation for BlankLineAfterSummary {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let BlankLineAfterSummary { num_lines } = self;
if *num_lines == 0 {
format!("1 blank line required between summary line and description")
} else {
format!(
"1 blank line required between summary line and description (found {num_lines})"
)
}
}
fn autofix_title(&self) -> Option<String> {
Some("Insert single blank line".to_string())
}
}
/// D205
pub(crate) fn blank_after_summary(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
if !docstring.triple_quoted() {
return;
}
let mut lines_count: usize = 1;
let mut blanks_count = 0;
for line in body.trim().universal_newlines().skip(1) {
lines_count += 1;
if line.trim().is_empty() {
blanks_count += 1;
} else {
break;
}
}
if lines_count > 1 && blanks_count != 1 {
let mut diagnostic = Diagnostic::new(
BlankLineAfterSummary {
num_lines: blanks_count,
},
docstring.range(),
);
if checker.patch(diagnostic.kind.rule()) {
if blanks_count > 1 {
let mut lines = UniversalNewlineIterator::with_offset(&body, body.start());
let mut summary_end = body.start();
// Find the "summary" line (defined as the first non-blank line).
for line in lines.by_ref() {
if !line.trim().is_empty() {
summary_end = line.full_end();
break;
}
}
// Find the last blank line
let mut blank_end = summary_end;
for line in lines {
if !line.trim().is_empty() {
blank_end = line.start();
break;
}
}
// Insert one blank line after the summary (replacing any existing lines).
diagnostic.set_fix(Fix::automatic(Edit::replacement(
checker.stylist().line_ending().to_string(),
summary_end,
blank_end,
)));
}
}
checker.diagnostics.push(diagnostic);
}
}

View file

@ -0,0 +1,294 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_trivia::{indentation_at_offset, PythonWhitespace};
use ruff_source_file::{Line, UniversalNewlineIterator};
use ruff_text_size::Ranged;
use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for docstrings on class definitions that are not preceded by a
/// blank line.
///
/// ## Why is this bad?
/// Use a blank line to separate the docstring from the class definition, for
/// consistency.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is disabled when using the `google`,
/// `numpy`, and `pep257` conventions.
///
/// For an alternative, see [D211].
///
/// ## Example
/// ```python
/// class PhotoMetadata:
/// """Metadata about a photo."""
/// ```
///
/// Use instead:
/// ```python
/// class PhotoMetadata:
///
/// """Metadata about a photo."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// [D211]: https://docs.astral.sh/ruff/rules/blank-line-before-class
#[violation]
pub struct OneBlankLineBeforeClass;
impl AlwaysAutofixableViolation for OneBlankLineBeforeClass {
#[derive_message_formats]
fn message(&self) -> String {
format!("1 blank line required before class docstring")
}
fn autofix_title(&self) -> String {
"Insert 1 blank line before class docstring".to_string()
}
}
/// ## What it does
/// Checks for class methods that are not separated from the class's docstring
/// by a blank line.
///
/// ## Why is this bad?
/// [PEP 257] recommends the use of a blank line to separate a class's
/// docstring its methods.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `google`
/// convention, and disabled when using the `numpy` and `pep257` conventions.
///
/// ## Example
/// ```python
/// class PhotoMetadata:
/// """Metadata about a photo."""
/// def __init__(self, file: Path):
/// ...
/// ```
///
/// Use instead:
/// ```python
/// class PhotoMetadata:
/// """Metadata about a photo."""
///
/// def __init__(self, file: Path):
/// ...
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct OneBlankLineAfterClass;
impl AlwaysAutofixableViolation for OneBlankLineAfterClass {
#[derive_message_formats]
fn message(&self) -> String {
format!("1 blank line required after class docstring")
}
fn autofix_title(&self) -> String {
"Insert 1 blank line after class docstring".to_string()
}
}
/// ## What it does
/// Checks for docstrings on class definitions that are preceded by a blank
/// line.
///
/// ## Why is this bad?
/// Avoid introducing any blank lines between a class definition and its
/// docstring, for consistency.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `google`,
/// `numpy`, and `pep257` conventions.
///
/// For an alternative, see [D203].
///
/// ## Example
/// ```python
/// class PhotoMetadata:
///
/// """Metadata about a photo."""
/// ```
///
/// Use instead:
/// ```python
/// class PhotoMetadata:
/// """Metadata about a photo."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// [D203]: https://docs.astral.sh/ruff/rules/one-blank-line-before-class
#[violation]
pub struct BlankLineBeforeClass;
impl AlwaysAutofixableViolation for BlankLineBeforeClass {
#[derive_message_formats]
fn message(&self) -> String {
format!("No blank lines allowed before class docstring")
}
fn autofix_title(&self) -> String {
"Remove blank line(s) before class docstring".to_string()
}
}
/// D203, D204, D211
pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
let Some(class) = docstring.definition.as_class_def() else {
return;
};
// Special-case: the docstring is on the same line as the class. For example:
// ```python
// class PhotoMetadata: """Metadata about a photo."""
// ```
let between_range = TextRange::new(class.start(), docstring.start());
if !checker.locator().contains_line_break(between_range) {
return;
}
if checker.enabled(Rule::OneBlankLineBeforeClass) || checker.enabled(Rule::BlankLineBeforeClass)
{
let mut lines = UniversalNewlineIterator::with_offset(
checker.locator().slice(between_range),
between_range.start(),
)
.rev();
let mut blank_lines_before = 0usize;
let mut blank_lines_start = lines.next().map(|line| line.start()).unwrap_or_default();
for line in lines {
if line.trim().is_empty() {
blank_lines_before += 1;
blank_lines_start = line.start();
} else {
break;
}
}
if checker.enabled(Rule::BlankLineBeforeClass) {
if blank_lines_before != 0 {
let mut diagnostic = Diagnostic::new(BlankLineBeforeClass, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
// Delete the blank line before the class.
diagnostic.set_fix(Fix::automatic(Edit::deletion(
blank_lines_start,
docstring.start() - docstring.indentation.text_len(),
)));
}
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::OneBlankLineBeforeClass) {
if blank_lines_before != 1 {
let mut diagnostic = Diagnostic::new(OneBlankLineBeforeClass, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
// Insert one blank line before the class.
diagnostic.set_fix(Fix::automatic(Edit::replacement(
checker.stylist().line_ending().to_string(),
blank_lines_start,
docstring.start() - docstring.indentation.text_len(),
)));
}
checker.diagnostics.push(diagnostic);
}
}
}
if checker.enabled(Rule::OneBlankLineAfterClass) {
let class_after_docstring_range = TextRange::new(docstring.end(), class.end());
let class_after_docstring = checker.locator().slice(class_after_docstring_range);
let mut lines = UniversalNewlineIterator::with_offset(
class_after_docstring,
class_after_docstring_range.start(),
);
// If the class is empty except for comments, we don't need to insert a newline between
// docstring and no content
let all_blank_after = lines.clone().all(|line| {
line.trim_whitespace().is_empty() || line.trim_whitespace_start().starts_with('#')
});
if all_blank_after {
return;
}
let first_line = lines.next();
let mut replacement_start = first_line.as_ref().map(Line::start).unwrap_or_default();
// Edge case: There is trailing end-of-line content after the docstring, either a statement
// separated by a semicolon or a comment.
if let Some(first_line) = &first_line {
let trailing = first_line.as_str().trim_whitespace_start();
if let Some(next_statement) = trailing.strip_prefix(';') {
let indentation = indentation_at_offset(docstring.start(), checker.locator())
.expect("Own line docstring must have indentation");
let mut diagnostic = Diagnostic::new(OneBlankLineAfterClass, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
let line_ending = checker.stylist().line_ending().as_str();
// We have to trim the whitespace twice, once before the semicolon above and
// once after the semicolon here, or we get invalid indents:
// ```rust
// class Priority:
// """Has priorities""" ; priorities=1
// ```
let next_statement = next_statement.trim_whitespace_start();
diagnostic.set_fix(Fix::automatic(Edit::replacement(
line_ending.to_string() + line_ending + indentation + next_statement,
replacement_start,
first_line.end(),
)));
}
checker.diagnostics.push(diagnostic);
return;
} else if trailing.starts_with('#') {
// Keep the end-of-line comment, start counting empty lines after it
replacement_start = first_line.end();
}
}
let mut blank_lines_after = 0usize;
let mut blank_lines_end = first_line.as_ref().map_or(docstring.end(), Line::end);
for line in lines {
if line.trim_whitespace().is_empty() {
blank_lines_end = line.end();
blank_lines_after += 1;
} else {
break;
}
}
if blank_lines_after != 1 {
let mut diagnostic = Diagnostic::new(OneBlankLineAfterClass, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
// Insert a blank line before the class (replacing any existing lines).
diagnostic.set_fix(Fix::automatic(Edit::replacement(
checker.stylist().line_ending().to_string(),
replacement_start,
blank_lines_end,
)));
}
checker.diagnostics.push(diagnostic);
}
}
}

View file

@ -0,0 +1,201 @@
use once_cell::sync::Lazy;
use regex::Regex;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_trivia::PythonWhitespace;
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
use ruff_text_size::Ranged;
use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for docstrings on functions that are separated by one or more blank
/// lines from the function definition.
///
/// ## Why is this bad?
/// Remove any blank lines between the function definition and its docstring,
/// for consistency.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
///
/// """Return the mean of the given values."""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct NoBlankLineBeforeFunction {
num_lines: usize,
}
impl AlwaysAutofixableViolation for NoBlankLineBeforeFunction {
#[derive_message_formats]
fn message(&self) -> String {
let NoBlankLineBeforeFunction { num_lines } = self;
format!("No blank lines allowed before function docstring (found {num_lines})")
}
fn autofix_title(&self) -> String {
"Remove blank line(s) before function docstring".to_string()
}
}
/// ## What it does
/// Checks for docstrings on functions that are separated by one or more blank
/// lines from the function body.
///
/// ## Why is this bad?
/// Remove any blank lines between the function body and the function
/// docstring, for consistency.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
///
/// return sum(values) / len(values)
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// return sum(values) / len(values)
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct NoBlankLineAfterFunction {
num_lines: usize,
}
impl AlwaysAutofixableViolation for NoBlankLineAfterFunction {
#[derive_message_formats]
fn message(&self) -> String {
let NoBlankLineAfterFunction { num_lines } = self;
format!("No blank lines allowed after function docstring (found {num_lines})")
}
fn autofix_title(&self) -> String {
"Remove blank line(s) after function docstring".to_string()
}
}
static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s+(?:(?:class|def|async def)\s|@)").unwrap());
/// D201, D202
pub(crate) fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
let Some(function) = docstring.definition.as_function_def() else {
return;
};
if checker.enabled(Rule::NoBlankLineBeforeFunction) {
let before = checker
.locator()
.slice(TextRange::new(function.start(), docstring.start()));
let mut lines = UniversalNewlineIterator::with_offset(before, function.start()).rev();
let mut blank_lines_before = 0usize;
let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default();
for line in lines {
if line.trim().is_empty() {
blank_lines_before += 1;
blank_lines_start = line.start();
} else {
break;
}
}
if blank_lines_before != 0 {
let mut diagnostic = Diagnostic::new(
NoBlankLineBeforeFunction {
num_lines: blank_lines_before,
},
docstring.range(),
);
if checker.patch(diagnostic.kind.rule()) {
// Delete the blank line before the docstring.
diagnostic.set_fix(Fix::automatic(Edit::deletion(
blank_lines_start,
docstring.start() - docstring.indentation.text_len(),
)));
}
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::NoBlankLineAfterFunction) {
let after = checker
.locator()
.slice(TextRange::new(docstring.end(), function.end()));
// If the docstring is only followed by blank and commented lines, abort.
let all_blank_after = after.universal_newlines().skip(1).all(|line| {
line.trim_whitespace().is_empty() || line.trim_whitespace_start().starts_with('#')
});
if all_blank_after {
return;
}
// Count the number of blank lines after the docstring.
let mut blank_lines_after = 0usize;
let mut lines = UniversalNewlineIterator::with_offset(after, docstring.end()).peekable();
let first_line_end = lines.next().map(|l| l.end()).unwrap_or_default();
let mut blank_lines_end = first_line_end;
while let Some(line) = lines.peek() {
if line.trim().is_empty() {
blank_lines_after += 1;
blank_lines_end = line.end();
lines.next();
} else {
break;
}
}
// Avoid violations for blank lines followed by inner functions or classes.
if blank_lines_after == 1
&& lines
.find(|line| !line.trim_whitespace_start().starts_with('#'))
.is_some_and(|line| INNER_FUNCTION_OR_CLASS_REGEX.is_match(&line))
{
return;
}
if blank_lines_after != 0 {
let mut diagnostic = Diagnostic::new(
NoBlankLineAfterFunction {
num_lines: blank_lines_after,
},
docstring.range(),
);
if checker.patch(diagnostic.kind.rule()) {
// Delete the blank line after the docstring.
diagnostic.set_fix(Fix::automatic(Edit::deletion(
first_line_end,
blank_lines_end,
)));
}
checker.diagnostics.push(diagnostic);
}
}
}

View file

@ -0,0 +1,101 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
/// ## What it does
/// Checks for docstrings that do not start with a capital letter.
///
/// ## Why is this bad?
/// The first character in a docstring should be capitalized for, grammatical
/// correctness and consistency.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """return the mean of the given values."""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct FirstLineCapitalized {
first_word: String,
capitalized_word: String,
}
impl AlwaysAutofixableViolation for FirstLineCapitalized {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"First word of the first line should be capitalized: `{}` -> `{}`",
self.first_word, self.capitalized_word
)
}
fn autofix_title(&self) -> String {
format!(
"Capitalize `{}` to `{}`",
self.first_word, self.capitalized_word
)
}
}
/// D403
pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
if docstring.definition.as_function_def().is_none() {
return;
}
let body = docstring.body();
let Some(first_word) = body.split(' ').next() else {
return;
};
// Like pydocstyle, we only support ASCII for now.
for char in first_word.chars() {
if !char.is_ascii_alphabetic() && char != '\'' {
return;
}
}
let mut first_word_chars = first_word.chars();
let Some(first_char) = first_word_chars.next() else {
return;
};
let uppercase_first_char = first_char.to_ascii_uppercase();
if first_char == uppercase_first_char {
return;
}
let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str();
let mut diagnostic = Diagnostic::new(
FirstLineCapitalized {
first_word: first_word.to_string(),
capitalized_word: capitalized_word.to_string(),
},
docstring.expr.range(),
);
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
capitalized_word,
TextRange::at(body.start(), first_word.text_len()),
)));
}
checker.diagnostics.push(diagnostic);
}

View file

@ -0,0 +1,116 @@
use ruff_text_size::TextLen;
use strum::IntoEnumIterator;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::sections::SectionKind;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
use crate::rules::pydocstyle::helpers::logical_line;
/// ## What it does
/// Checks for docstrings in which the first line does not end in a period.
///
/// ## Why is this bad?
/// [PEP 257] recommends that the first line of a docstring is written in the
/// form of a command, ending in a period.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `numpy` and
/// `pep257` conventions, and disabled when using the `google` convention.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values"""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct EndsInPeriod;
impl AlwaysAutofixableViolation for EndsInPeriod {
#[derive_message_formats]
fn message(&self) -> String {
format!("First line should end with a period")
}
fn autofix_title(&self) -> String {
"Add period".to_string()
}
}
/// D400
pub(crate) fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
if let Some(first_line) = body.trim().universal_newlines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
for prefix in [":param", ":type", ":raises", ":return", ":rtype"] {
if trimmed.starts_with(prefix) {
return;
}
}
// Avoid false-positives: `Args:`, etc.
for section_kind in SectionKind::iter() {
if let Some(suffix) = trimmed.strip_suffix(section_kind.as_str()) {
if suffix.is_empty() {
return;
}
if suffix == ":" {
return;
}
}
}
}
if let Some(index) = logical_line(body.as_str()) {
let mut lines = UniversalNewlineIterator::with_offset(&body, body.start());
let line = lines.nth(index).unwrap();
let trimmed = line.trim_end();
if trimmed.ends_with('\\') {
// Ignore the edge case whether a single quoted string is multiple lines through an
// escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are
// flagged by D300.
// ```python
// "\
// "
// ```
return;
}
if !trimmed.ends_with('.') {
let mut diagnostic = Diagnostic::new(EndsInPeriod, docstring.range());
// Best-effort autofix: avoid adding a period after other punctuation marks.
if checker.patch(diagnostic.kind.rule()) && !trimmed.ends_with([':', ';']) {
diagnostic.set_fix(Fix::suggested(Edit::insertion(
".".to_string(),
line.start() + trimmed.text_len(),
)));
}
checker.diagnostics.push(diagnostic);
};
}
}

View file

@ -0,0 +1,115 @@
use ruff_text_size::TextLen;
use strum::IntoEnumIterator;
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::sections::SectionKind;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
use crate::rules::pydocstyle::helpers::logical_line;
/// ## What it does
/// Checks for docstrings in which the first line does not end in a punctuation
/// mark, such as a period, question mark, or exclamation point.
///
/// ## Why is this bad?
/// The first line of a docstring should end with a period, question mark, or
/// exclamation point, for grammatical correctness and consistency.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `google`
/// convention, and disabled when using the `numpy` and `pep257` conventions.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values"""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct EndsInPunctuation;
impl AlwaysAutofixableViolation for EndsInPunctuation {
#[derive_message_formats]
fn message(&self) -> String {
format!("First line should end with a period, question mark, or exclamation point")
}
fn autofix_title(&self) -> String {
"Add closing punctuation".to_string()
}
}
/// D415
pub(crate) fn ends_with_punctuation(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
if let Some(first_line) = body.trim().universal_newlines().next() {
let trimmed = first_line.trim();
// Avoid false-positives: `:param`, etc.
for prefix in [":param", ":type", ":raises", ":return", ":rtype"] {
if trimmed.starts_with(prefix) {
return;
}
}
// Avoid false-positives: `Args:`, etc.
for section_kind in SectionKind::iter() {
if let Some(suffix) = trimmed.strip_suffix(section_kind.as_str()) {
if suffix.is_empty() {
return;
}
if suffix == ":" {
return;
}
}
}
}
if let Some(index) = logical_line(body.as_str()) {
let mut lines = UniversalNewlineIterator::with_offset(&body, body.start()).skip(index);
let line = lines.next().unwrap();
let trimmed = line.trim_end();
if trimmed.ends_with('\\') {
// Ignore the edge case whether a single quoted string is multiple lines through an
// escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are
// flagged by D300.
// ```python
// "\
// "
// ```
return;
}
if !trimmed.ends_with(['.', '!', '?']) {
let mut diagnostic = Diagnostic::new(EndsInPunctuation, docstring.range());
// Best-effort autofix: avoid adding a period after other punctuation marks.
if checker.patch(diagnostic.kind.rule()) && !trimmed.ends_with([':', ';']) {
diagnostic.set_fix(Fix::suggested(Edit::insertion(
".".to_string(),
line.start() + trimmed.text_len(),
)));
}
checker.diagnostics.push(diagnostic);
};
}
}

View file

@ -0,0 +1,90 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze::visibility::is_overload;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
/// ## What it does
/// Checks for `@overload` function definitions that contain a docstring.
///
/// ## Why is this bad?
/// The `@overload` decorator is used to define multiple compatible signatures
/// for a given function, to support type-checking. A series of `@overload`
/// definitions should be followed by a single non-decorated definition that
/// contains the implementation of the function.
///
/// `@overload` function definitions should not contain a docstring; instead,
/// the docstring should be placed on the non-decorated definition that contains
/// the implementation.
///
/// ## Example
/// ```python
/// from typing import overload
///
///
/// @overload
/// def factorial(n: int) -> int:
/// """Return the factorial of n."""
///
///
/// @overload
/// def factorial(n: float) -> float:
/// """Return the factorial of n."""
///
///
/// def factorial(n):
/// """Return the factorial of n."""
///
///
/// factorial.__doc__ # "Return the factorial of n."
/// ```
///
/// Use instead:
/// ```python
/// from typing import overload
///
///
/// @overload
/// def factorial(n: int) -> int:
/// ...
///
///
/// @overload
/// def factorial(n: float) -> float:
/// ...
///
///
/// def factorial(n):
/// """Return the factorial of n."""
///
///
/// factorial.__doc__ # "Return the factorial of n."
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [Python documentation: `typing.overload`](https://docs.python.org/3/library/typing.html#typing.overload)
#[violation]
pub struct OverloadWithDocstring;
impl Violation for OverloadWithDocstring {
#[derive_message_formats]
fn message(&self) -> String {
format!("Function decorated with `@overload` shouldn't contain a docstring")
}
}
/// D418
pub(crate) fn if_needed(checker: &mut Checker, docstring: &Docstring) {
let Some(function) = docstring.definition.as_function_def() else {
return;
};
if is_overload(&function.decorator_list, checker.semantic()) {
checker.diagnostics.push(Diagnostic::new(
OverloadWithDocstring,
function.identifier(),
));
}
}

View file

@ -0,0 +1,265 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::docstrings::{clean_space, leading_space};
use ruff_source_file::NewlineWithTrailingNewline;
use ruff_text_size::Ranged;
use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for docstrings that are indented with tabs.
///
/// ## Why is this bad?
/// [PEP 8](https://peps.python.org/pep-0008/#tabs-or-spaces) recommends using
/// spaces over tabs for indentation.
///
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct IndentWithSpaces;
impl Violation for IndentWithSpaces {
#[derive_message_formats]
fn message(&self) -> String {
format!("Docstring should be indented with spaces, not tabs")
}
}
/// ## What it does
/// Checks for under-indented docstrings.
///
/// ## Why is this bad?
/// [PEP 257] recommends that docstrings be indented to the same level as their
/// opening quotes. Avoid under-indenting docstrings, for consistency.
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble sort
/// algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct UnderIndentation;
impl AlwaysAutofixableViolation for UnderIndentation {
#[derive_message_formats]
fn message(&self) -> String {
format!("Docstring is under-indented")
}
fn autofix_title(&self) -> String {
"Increase indentation".to_string()
}
}
/// ## What it does
/// Checks for over-indented docstrings.
///
/// ## Why is this bad?
/// [PEP 257] recommends that docstrings be indented to the same level as their
/// opening quotes. Avoid over-indenting docstrings, for consistency.
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the
/// bubble sort algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct OverIndentation;
impl AlwaysAutofixableViolation for OverIndentation {
#[derive_message_formats]
fn message(&self) -> String {
format!("Docstring is over-indented")
}
fn autofix_title(&self) -> String {
"Remove over-indentation".to_string()
}
}
/// D206, D207, D208
pub(crate) fn indent(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
// Split the docstring into lines.
let lines: Vec<_> = NewlineWithTrailingNewline::with_offset(&body, body.start()).collect();
if lines.len() <= 1 {
return;
}
let mut has_seen_tab = docstring.indentation.contains('\t');
let mut is_over_indented = true;
let mut over_indented_lines = vec![];
for i in 0..lines.len() {
// First lines and continuations doesn't need any indentation.
if i == 0 || lines[i - 1].ends_with('\\') {
continue;
}
let line = &lines[i];
// Omit empty lines, except for the last line, which is non-empty by way of
// containing the closing quotation marks.
let is_blank = line.trim().is_empty();
if i < lines.len() - 1 && is_blank {
continue;
}
let line_indent = leading_space(line);
// We only report tab indentation once, so only check if we haven't seen a tab
// yet.
has_seen_tab = has_seen_tab || line_indent.contains('\t');
if checker.enabled(Rule::UnderIndentation) {
// We report under-indentation on every line. This isn't great, but enables
// autofix.
if (i == lines.len() - 1 || !is_blank)
&& line_indent.len() < docstring.indentation.len()
{
let mut diagnostic =
Diagnostic::new(UnderIndentation, TextRange::empty(line.start()));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
clean_space(docstring.indentation),
TextRange::at(line.start(), line_indent.text_len()),
)));
}
checker.diagnostics.push(diagnostic);
}
}
// Like pydocstyle, we only report over-indentation if either: (1) every line
// (except, optionally, the last line) is over-indented, or (2) the last line
// (which contains the closing quotation marks) is
// over-indented. We can't know if we've achieved that condition
// until we've viewed all the lines, so for now, just track
// the over-indentation status of every line.
if i < lines.len() - 1 {
if line_indent.len() > docstring.indentation.len() {
over_indented_lines.push(TextRange::at(line.start(), line_indent.text_len()));
} else {
is_over_indented = false;
}
}
}
if checker.enabled(Rule::IndentWithSpaces) {
if has_seen_tab {
checker
.diagnostics
.push(Diagnostic::new(IndentWithSpaces, docstring.range()));
}
}
if checker.enabled(Rule::OverIndentation) {
// If every line (except the last) is over-indented...
if is_over_indented {
for over_indented in over_indented_lines {
// We report over-indentation on every line. This isn't great, but
// enables autofix.
let mut diagnostic =
Diagnostic::new(OverIndentation, TextRange::empty(over_indented.start()));
if checker.patch(diagnostic.kind.rule()) {
let indent = clean_space(docstring.indentation);
let edit = if indent.is_empty() {
Edit::range_deletion(over_indented)
} else {
Edit::range_replacement(indent, over_indented)
};
diagnostic.set_fix(Fix::automatic(edit));
}
checker.diagnostics.push(diagnostic);
}
}
// If the last line is over-indented...
if let Some(last) = lines.last() {
let line_indent = leading_space(last);
if line_indent.len() > docstring.indentation.len() {
let mut diagnostic =
Diagnostic::new(OverIndentation, TextRange::empty(last.start()));
if checker.patch(diagnostic.kind.rule()) {
let indent = clean_space(docstring.indentation);
let range = TextRange::at(last.start(), line_indent.text_len());
let edit = if indent.is_empty() {
Edit::range_deletion(range)
} else {
Edit::range_replacement(indent, range)
};
diagnostic.set_fix(Fix::automatic(edit));
}
checker.diagnostics.push(diagnostic);
}
}
}
}

View file

@ -0,0 +1,41 @@
pub(crate) use backslashes::*;
pub(crate) use blank_after_summary::*;
pub(crate) use blank_before_after_class::*;
pub(crate) use blank_before_after_function::*;
pub(crate) use capitalized::*;
pub(crate) use ends_with_period::*;
pub(crate) use ends_with_punctuation::*;
pub(crate) use if_needed::*;
pub(crate) use indent::*;
pub(crate) use multi_line_summary_start::*;
pub(crate) use newline_after_last_paragraph::*;
pub(crate) use no_signature::*;
pub(crate) use no_surrounding_whitespace::*;
pub(crate) use non_imperative_mood::*;
pub(crate) use not_empty::*;
pub(crate) use not_missing::*;
pub(crate) use one_liner::*;
pub(crate) use sections::*;
pub(crate) use starts_with_this::*;
pub(crate) use triple_quotes::*;
mod backslashes;
mod blank_after_summary;
mod blank_before_after_class;
mod blank_before_after_function;
mod capitalized;
mod ends_with_period;
mod ends_with_punctuation;
mod if_needed;
mod indent;
mod multi_line_summary_start;
mod newline_after_last_paragraph;
mod no_signature;
mod no_surrounding_whitespace;
mod non_imperative_mood;
mod not_empty;
mod not_missing;
mod one_liner;
mod sections;
mod starts_with_this;
mod triple_quotes;

View file

@ -0,0 +1,210 @@
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::str::{is_triple_quote, leading_quote};
use ruff_python_semantic::Definition;
use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlineIterator};
use ruff_text_size::{Ranged, TextRange, TextSize};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::{AsRule, Rule};
/// ## What it does
/// Checks for docstring summary lines that are not positioned on the first
/// physical line of the docstring.
///
/// ## Why is this bad?
/// [PEP 257] recommends that multi-line docstrings consist of "a summary line
/// just like a one-line docstring, followed by a blank line, followed by a
/// more elaborate description."
///
/// The summary line should be located on the first physical line of the
/// docstring, immediately after the opening quotes.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `google`
/// convention, and disabled when using the `numpy` and `pep257` conventions.
///
/// For an alternative, see [D213].
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """
/// Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the
/// bubble sort algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// [D213]: https://docs.astral.sh/ruff/rules/multi-line-summary-second-line
#[violation]
pub struct MultiLineSummaryFirstLine;
impl AlwaysAutofixableViolation for MultiLineSummaryFirstLine {
#[derive_message_formats]
fn message(&self) -> String {
format!("Multi-line docstring summary should start at the first line")
}
fn autofix_title(&self) -> String {
"Remove whitespace after opening quotes".to_string()
}
}
/// ## What it does
/// Checks for docstring summary lines that are not positioned on the second
/// physical line of the docstring.
///
/// ## Why is this bad?
/// [PEP 257] recommends that multi-line docstrings consist of "a summary line
/// just like a one-line docstring, followed by a blank line, followed by a
/// more elaborate description."
///
/// The summary line should be located on the second physical line of the
/// docstring, immediately after the opening quotes and the blank line.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is disabled when using the `google`,
/// `numpy`, and `pep257` conventions.
///
/// For an alternative, see [D212].
///
/// ## Example
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the
/// bubble sort algorithm.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: list[int]) -> list[int]:
/// """
/// Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// [D212]: https://docs.astral.sh/ruff/rules/multi-line-summary-first-line
#[violation]
pub struct MultiLineSummarySecondLine;
impl AlwaysAutofixableViolation for MultiLineSummarySecondLine {
#[derive_message_formats]
fn message(&self) -> String {
format!("Multi-line docstring summary should start at the second line")
}
fn autofix_title(&self) -> String {
"Insert line break and indentation after opening quotes".to_string()
}
}
/// D212, D213
pub(crate) fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body();
if NewlineWithTrailingNewline::from(body.as_str())
.nth(1)
.is_none()
{
return;
};
let mut content_lines = UniversalNewlineIterator::with_offset(contents, docstring.start());
let Some(first_line) = content_lines.next() else {
return;
};
if is_triple_quote(&first_line) {
if checker.enabled(Rule::MultiLineSummaryFirstLine) {
let mut diagnostic = Diagnostic::new(MultiLineSummaryFirstLine, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
// Delete until first non-whitespace char.
for line in content_lines {
if let Some(end_column) = line.find(|c: char| !c.is_whitespace()) {
diagnostic.set_fix(Fix::automatic(Edit::deletion(
first_line.end(),
line.start() + TextSize::try_from(end_column).unwrap(),
)));
break;
}
}
}
checker.diagnostics.push(diagnostic);
}
} else if first_line.as_str().ends_with('\\') {
// Ignore the edge case whether a single quoted string is multiple lines through an
// escape (https://github.com/astral-sh/ruff/issues/7139). Single quote docstrings are
// flagged by D300.
// ```python
// "\
// "
// ```
return;
} else {
if checker.enabled(Rule::MultiLineSummarySecondLine) {
let mut diagnostic = Diagnostic::new(MultiLineSummarySecondLine, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
let mut indentation = String::from(docstring.indentation);
let mut fixable = true;
if !indentation.chars().all(char::is_whitespace) {
fixable = false;
// If the docstring isn't on its own line, look at the statement indentation,
// and add the default indentation to get the "right" level.
if let Definition::Member(member) = &docstring.definition {
let stmt_line_start = checker.locator().line_start(member.start());
let stmt_indentation = checker
.locator()
.slice(TextRange::new(stmt_line_start, member.start()));
if stmt_indentation.chars().all(char::is_whitespace) {
indentation.clear();
indentation.push_str(stmt_indentation);
indentation.push_str(checker.stylist().indentation());
fixable = true;
}
};
}
if fixable {
let prefix = leading_quote(contents).unwrap();
// Use replacement instead of insert to trim possible whitespace between leading
// quote and text.
let repl = format!(
"{}{}{}",
checker.stylist().line_ending().as_str(),
indentation,
first_line.strip_prefix(prefix).unwrap().trim_start()
);
diagnostic.set_fix(Fix::automatic(Edit::replacement(
repl,
body.start(),
first_line.end(),
)));
}
}
checker.diagnostics.push(diagnostic);
}
}
}

View file

@ -0,0 +1,111 @@
use ruff_text_size::{TextLen, TextSize};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::docstrings::clean_space;
use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlines};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
/// ## What it does
/// Checks for multi-line docstrings whose closing quotes are not on their
/// own line.
///
/// ## Why is this bad?
/// [PEP 257] recommends that the closing quotes of a multi-line docstring be
/// on their own line, for consistency and compatibility with documentation
/// tools that may need to parse the docstring.
///
/// ## Example
/// ```python
/// def sort_list(l: List[int]) -> List[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the
/// bubble sort algorithm."""
/// ```
///
/// Use instead:
/// ```python
/// def sort_list(l: List[int]) -> List[int]:
/// """Return a sorted copy of the list.
///
/// Sort the list in ascending order and return a copy of the result using the bubble
/// sort algorithm.
/// """
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct NewLineAfterLastParagraph;
impl AlwaysAutofixableViolation for NewLineAfterLastParagraph {
#[derive_message_formats]
fn message(&self) -> String {
format!("Multi-line docstring closing quotes should be on a separate line")
}
fn autofix_title(&self) -> String {
"Move closing quotes to new line".to_string()
}
}
/// D209
pub(crate) fn newline_after_last_paragraph(checker: &mut Checker, docstring: &Docstring) {
let contents = docstring.contents;
let body = docstring.body();
if !docstring.triple_quoted() {
return;
}
let mut line_count = 0;
for line in NewlineWithTrailingNewline::from(body.as_str()) {
if !line.trim().is_empty() {
line_count += 1;
}
if line_count > 1 {
if let Some(last_line) = contents
.universal_newlines()
.last()
.map(|l| l.as_str().trim())
{
if last_line != "\"\"\"" && last_line != "'''" {
let mut diagnostic =
Diagnostic::new(NewLineAfterLastParagraph, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
// Insert a newline just before the end-quote(s).
let num_trailing_quotes = "'''".text_len();
let num_trailing_spaces: TextSize = last_line
.chars()
.rev()
.skip(usize::from(num_trailing_quotes))
.take_while(|c| c.is_whitespace())
.map(TextLen::text_len)
.sum();
let content = format!(
"{}{}",
checker.stylist().line_ending().as_str(),
clean_space(docstring.indentation)
);
diagnostic.set_fix(Fix::automatic(Edit::replacement(
content,
docstring.expr.end() - num_trailing_quotes - num_trailing_spaces,
docstring.expr.end() - num_trailing_quotes,
)));
}
checker.diagnostics.push(diagnostic);
}
}
return;
}
}
}

View file

@ -0,0 +1,75 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::UniversalNewlines;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
/// ## What it does
/// Checks for function docstrings that include the function's signature in
/// the summary line.
///
/// ## Why is this bad?
/// [PEP 257] recommends against including a function's signature in its
/// docstring. Instead, consider using type annotations as a form of
/// documentation for the function's parameters and return value.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `google` and
/// `pep257` conventions, and disabled when using the `numpy` convention.
///
/// ## Example
/// ```python
/// def foo(a, b):
/// """foo(a: int, b: int) -> list[int]"""
/// ```
///
/// Use instead:
/// ```python
/// def foo(a: int, b: int) -> list[int]:
/// """Return a list of a and b."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct NoSignature;
impl Violation for NoSignature {
#[derive_message_formats]
fn message(&self) -> String {
format!("First line should not be the function's signature")
}
}
/// D402
pub(crate) fn no_signature(checker: &mut Checker, docstring: &Docstring) {
let Some(function) = docstring.definition.as_function_def() else {
return;
};
let body = docstring.body();
let Some(first_line) = body.trim().universal_newlines().next() else {
return;
};
// Search for occurrences of the function name followed by an open parenthesis (e.g., `foo(` for
// a function named `foo`).
if first_line
.match_indices(function.name.as_str())
.any(|(index, _)| first_line[index + function.name.len()..].starts_with('('))
{
checker
.diagnostics
.push(Diagnostic::new(NoSignature, docstring.range()));
}
}

View file

@ -0,0 +1,79 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_source_file::NewlineWithTrailingNewline;
use ruff_text_size::Ranged;
use ruff_text_size::{TextLen, TextRange};
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
use crate::rules::pydocstyle::helpers::ends_with_backslash;
/// ## What it does
/// Checks for surrounding whitespace in docstrings.
///
/// ## Why is this bad?
/// Remove surrounding whitespace from the docstring, for consistency.
///
/// ## Example
/// ```python
/// def factorial(n: int) -> int:
/// """ Return the factorial of n. """
/// ```
///
/// Use instead:
/// ```python
/// def factorial(n: int) -> int:
/// """Return the factorial of n."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct SurroundingWhitespace;
impl Violation for SurroundingWhitespace {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("No whitespaces allowed surrounding docstring text")
}
fn autofix_title(&self) -> Option<String> {
Some("Trim surrounding whitespace".to_string())
}
}
/// D210
pub(crate) fn no_surrounding_whitespace(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
let mut lines = NewlineWithTrailingNewline::from(body.as_str());
let Some(line) = lines.next() else {
return;
};
let trimmed = line.trim();
if trimmed.is_empty() {
return;
}
if line == trimmed {
return;
}
let mut diagnostic = Diagnostic::new(SurroundingWhitespace, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
let quote = docstring.contents.chars().last().unwrap();
// If removing whitespace would lead to an invalid string of quote
// characters, avoid applying the fix.
if !trimmed.ends_with(quote) && !trimmed.starts_with(quote) && !ends_with_backslash(trimmed)
{
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
trimmed.to_string(),
TextRange::at(body.start(), line.text_len()),
)));
}
}
checker.diagnostics.push(diagnostic);
}

View file

@ -0,0 +1,115 @@
use std::collections::BTreeSet;
use imperative::Mood;
use once_cell::sync::Lazy;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::{from_qualified_name, CallPath};
use ruff_python_semantic::analyze::visibility::{is_property, is_test};
use ruff_source_file::UniversalNewlines;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::rules::pydocstyle::helpers::normalize_word;
static MOOD: Lazy<Mood> = Lazy::new(Mood::new);
/// ## What it does
/// Checks for docstring first lines that are not in an imperative mood.
///
/// ## Why is this bad?
/// [PEP 257] recommends that the first line of a docstring be written in the
/// imperative mood, for consistency.
///
/// Hint: to rewrite the docstring in the imperative, phrase the first line as
/// if it were a command.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `numpy` and
/// `pep257` conventions, and disabled when using the `google` conventions.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """Returns the mean of the given values."""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct NonImperativeMood {
first_line: String,
}
impl Violation for NonImperativeMood {
#[derive_message_formats]
fn message(&self) -> String {
let NonImperativeMood { first_line } = self;
format!("First line of docstring should be in imperative mood: \"{first_line}\"")
}
}
/// D401
pub(crate) fn non_imperative_mood(
checker: &mut Checker,
docstring: &Docstring,
property_decorators: &BTreeSet<String>,
) {
let Some(function) = docstring.definition.as_function_def() else {
return;
};
let property_decorators = property_decorators
.iter()
.map(|decorator| from_qualified_name(decorator))
.collect::<Vec<CallPath>>();
if is_test(&function.name)
|| is_property(
&function.decorator_list,
&property_decorators,
checker.semantic(),
)
{
return;
}
let body = docstring.body();
// Find first line, disregarding whitespace.
let first_line = match body.trim().universal_newlines().next() {
Some(line) => line.as_str().trim(),
None => return,
};
// Find the first word on that line and normalize it to lower-case.
let first_word_norm = match first_line.split_whitespace().next() {
Some(word) => normalize_word(word),
None => return,
};
if first_word_norm.is_empty() {
return;
}
if matches!(MOOD.is_imperative(&first_word_norm), Some(false)) {
checker.diagnostics.push(Diagnostic::new(
NonImperativeMood {
first_line: first_line.to_string(),
},
docstring.range(),
));
}
}

View file

@ -0,0 +1,54 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::Rule;
/// ## What it does
/// Checks for empty docstrings.
///
/// ## Why is this bad?
/// An empty docstring is indicative of incomplete documentation. It should either
/// be removed or replaced with a meaningful docstring.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """"""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct EmptyDocstring;
impl Violation for EmptyDocstring {
#[derive_message_formats]
fn message(&self) -> String {
format!("Docstring is empty")
}
}
/// D419
pub(crate) fn not_empty(checker: &mut Checker, docstring: &Docstring) -> bool {
if !docstring.body().trim().is_empty() {
return true;
}
if checker.enabled(Rule::EmptyDocstring) {
checker
.diagnostics
.push(Diagnostic::new(EmptyDocstring, docstring.range()));
}
false
}

View file

@ -0,0 +1,639 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze::visibility::{
is_call, is_init, is_magic, is_new, is_overload, is_override, Visibility,
};
use ruff_python_semantic::{Definition, Member, MemberKind, Module, ModuleKind};
use ruff_text_size::TextRange;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does
/// Checks for undocumented public module definitions.
///
/// ## Why is this bad?
/// Public modules should be documented via docstrings to outline their purpose
/// and contents.
///
/// Generally, module docstrings should describe the purpose of the module and
/// list the classes, exceptions, functions, and other objects that are exported
/// by the module, alongside a one-line summary of each.
///
/// If the module is a script, the docstring should be usable as its "usage"
/// message.
///
/// If the codebase adheres to a standard format for module docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// class FasterThanLightError(ZeroDivisionError):
/// ...
///
///
/// def calculate_speed(distance: float, time: float) -> float:
/// ...
/// ```
///
/// Use instead:
/// ```python
/// """Utility functions and classes for calculating speed.
///
/// This module provides:
/// - FasterThanLightError: exception when FTL speed is calculated;
/// - calculate_speed: calculate speed given distance and time.
/// """
///
///
/// class FasterThanLightError(ZeroDivisionError):
/// ...
///
///
/// def calculate_speed(distance: float, time: float) -> float:
/// ...
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicModule;
impl Violation for UndocumentedPublicModule {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public module")
}
}
/// ## What it does
/// Checks for undocumented public class definitions.
///
/// ## Why is this bad?
/// Public classes should be documented via docstrings to outline their purpose
/// and behavior.
///
/// Generally, a class docstring should describe the class's purpose and list
/// its public attributes and methods.
///
/// If the codebase adheres to a standard format for class docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// class Player:
/// def __init__(self, name: str, points: int = 0) -> None:
/// self.name: str = name
/// self.points: int = points
///
/// def add_points(self, points: int) -> None:
/// self.points += points
/// ```
///
/// Use instead (in the NumPy docstring format):
/// ```python
/// class Player:
/// """A player in the game.
///
/// Attributes
/// ----------
/// name : str
/// The name of the player.
/// points : int
/// The number of points the player has.
///
/// Methods
/// -------
/// add_points(points: int) -> None
/// Add points to the player's score.
/// """
///
/// def __init__(self, name: str, points: int = 0) -> None:
/// self.name: str = name
/// self.points: int = points
///
/// def add_points(self, points: int) -> None:
/// self.points += points
/// ```
///
/// Or (in the Google docstring format):
/// ```python
/// class Player:
/// """A player in the game.
///
/// Attributes:
/// name: The name of the player.
/// points: The number of points the player has.
/// """
///
/// def __init__(self, name: str, points: int = 0) -> None:
/// self.name: str = name
/// self.points: int = points
///
/// def add_points(self, points: int) -> None:
/// self.points += points
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicClass;
impl Violation for UndocumentedPublicClass {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public class")
}
}
/// ## What it does
/// Checks for undocumented public method definitions.
///
/// ## Why is this bad?
/// Public methods should be documented via docstrings to outline their purpose
/// and behavior.
///
/// Generally, a method docstring should describe the method's behavior,
/// arguments, side effects, exceptions, return values, and any other
/// information that may be relevant to the user.
///
/// If the codebase adheres to a standard format for method docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// class Cat(Animal):
/// def greet(self, happy: bool = True):
/// if happy:
/// print("Meow!")
/// else:
/// raise ValueError("Tried to greet an unhappy cat.")
/// ```
///
/// Use instead (in the NumPy docstring format):
/// ```python
/// class Cat(Animal):
/// def greet(self, happy: bool = True):
/// """Print a greeting from the cat.
///
/// Parameters
/// ----------
/// happy : bool, optional
/// Whether the cat is happy, is True by default.
///
/// Raises
/// ------
/// ValueError
/// If the cat is not happy.
/// """
/// if happy:
/// print("Meow!")
/// else:
/// raise ValueError("Tried to greet an unhappy cat.")
/// ```
///
/// Or (in the Google docstring format):
/// ```python
/// class Cat(Animal):
/// def greet(self, happy: bool = True):
/// """Print a greeting from the cat.
///
/// Args:
/// happy: Whether the cat is happy, is True by default.
///
/// Raises:
/// ValueError: If the cat is not happy.
/// """
/// if happy:
/// print("Meow!")
/// else:
/// raise ValueError("Tried to greet an unhappy cat.")
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicMethod;
impl Violation for UndocumentedPublicMethod {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public method")
}
}
/// ## What it does
/// Checks for undocumented public function definitions.
///
/// ## Why is this bad?
/// Public functions should be documented via docstrings to outline their
/// purpose and behavior.
///
/// Generally, a function docstring should describe the function's behavior,
/// arguments, side effects, exceptions, return values, and any other
/// information that may be relevant to the user.
///
/// If the codebase adheres to a standard format for function docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// def calculate_speed(distance: float, time: float) -> float:
/// try:
/// return distance / time
/// except ZeroDivisionError as exc:
/// raise FasterThanLightError from exc
/// ```
///
/// Use instead (using the NumPy docstring format):
/// ```python
/// def calculate_speed(distance: float, time: float) -> float:
/// """Calculate speed as distance divided by time.
///
/// Parameters
/// ----------
/// distance : float
/// Distance traveled.
/// time : float
/// Time spent traveling.
///
/// Returns
/// -------
/// float
/// Speed as distance divided by time.
///
/// Raises
/// ------
/// FasterThanLightError
/// If speed is greater than the speed of light.
/// """
/// try:
/// return distance / time
/// except ZeroDivisionError as exc:
/// raise FasterThanLightError from exc
/// ```
///
/// Or, using the Google docstring format:
/// ```python
/// def calculate_speed(distance: float, time: float) -> float:
/// """Calculate speed as distance divided by time.
///
/// Args:
/// distance: Distance traveled.
/// time: Time spent traveling.
///
/// Returns:
/// Speed as distance divided by time.
///
/// Raises:
/// FasterThanLightError: If speed is greater than the speed of light.
/// """
/// try:
/// return distance / time
/// except ZeroDivisionError as exc:
/// raise FasterThanLightError from exc
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicFunction;
impl Violation for UndocumentedPublicFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public function")
}
}
/// ## What it does
/// Checks for undocumented public package definitions.
///
/// ## Why is this bad?
/// Public packages should be documented via docstrings to outline their
/// purpose and contents.
///
/// Generally, package docstrings should list the modules and subpackages that
/// are exported by the package.
///
/// If the codebase adheres to a standard format for package docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// __all__ = ["Player", "Game"]
/// ```
///
/// Use instead:
/// ```python
/// """Game and player management package.
///
/// This package provides classes for managing players and games.
/// """
///
/// __all__ = ["player", "game"]
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicPackage;
impl Violation for UndocumentedPublicPackage {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public package")
}
}
/// ## What it does
/// Checks for undocumented magic method definitions.
///
/// ## Why is this bad?
/// Magic methods (methods with names that start and end with double
/// underscores) are used to implement operator overloading and other special
/// behavior. Such methods should should be documented via docstrings to
/// outline their behavior.
///
/// Generally, magic method docstrings should describe the method's behavior,
/// arguments, side effects, exceptions, return values, and any other
/// information that may be relevant to the user.
///
/// If the codebase adheres to a standard format for method docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// class Cat(Animal):
/// def __str__(self) -> str:
/// return f"Cat: {self.name}"
///
///
/// cat = Cat("Dusty")
/// print(cat) # "Cat: Dusty"
/// ```
///
/// Use instead:
/// ```python
/// class Cat(Animal):
/// def __str__(self) -> str:
/// """Return a string representation of the cat."""
/// return f"Cat: {self.name}"
///
///
/// cat = Cat("Dusty")
/// print(cat) # "Cat: Dusty"
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
#[violation]
pub struct UndocumentedMagicMethod;
impl Violation for UndocumentedMagicMethod {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in magic method")
}
}
/// ## What it does
/// Checks for undocumented public class definitions, for nested classes.
///
/// ## Why is this bad?
/// Public classes should be documented via docstrings to outline their
/// purpose and behavior.
///
/// Nested classes do not inherit the docstring of their enclosing class, so
/// they should have their own docstrings.
///
/// If the codebase adheres to a standard format for class docstrings, follow
/// that format for consistency.
///
/// ## Example
/// ```python
/// class Foo:
/// """Class Foo."""
///
/// class Bar:
/// ...
///
///
/// bar = Foo.Bar()
/// bar.__doc__ # None
/// ```
///
/// Use instead:
/// ```python
/// class Foo:
/// """Class Foo."""
///
/// class Bar:
/// """Class Bar."""
///
///
/// bar = Foo.Bar()
/// bar.__doc__ # "Class Bar."
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicNestedClass;
impl Violation for UndocumentedPublicNestedClass {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in public nested class")
}
}
/// ## What it does
/// Checks for public `__init__` method definitions that are missing
/// docstrings.
///
/// ## Why is this bad?
/// Public `__init__` methods are used to initialize objects. `__init__`
/// methods should be documented via docstrings to describe the method's
/// behavior, arguments, side effects, exceptions, and any other information
/// that may be relevant to the user.
///
/// If the codebase adheres to a standard format for `__init__` method docstrings,
/// follow that format for consistency.
///
/// ## Example
/// ```python
/// class City:
/// def __init__(self, name: str, population: int) -> None:
/// self.name: str = name
/// self.population: int = population
/// ```
///
/// Use instead:
/// ```python
/// class City:
/// def __init__(self, name: str, population: int) -> None:
/// """Initialize a city with a name and population."""
/// self.name: str = name
/// self.population: int = population
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Style Python Docstrings](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
#[violation]
pub struct UndocumentedPublicInit;
impl Violation for UndocumentedPublicInit {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing docstring in `__init__`")
}
}
/// D100, D101, D102, D103, D104, D105, D106, D107
pub(crate) fn not_missing(
checker: &mut Checker,
definition: &Definition,
visibility: Visibility,
) -> bool {
if checker.source_type.is_stub() {
return true;
}
if visibility.is_private() {
return true;
}
match definition {
Definition::Module(Module {
kind: ModuleKind::Module,
..
}) => {
if checker.enabled(Rule::UndocumentedPublicModule) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicModule,
TextRange::default(),
));
}
false
}
Definition::Module(Module {
kind: ModuleKind::Package,
..
}) => {
if checker.enabled(Rule::UndocumentedPublicPackage) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicPackage,
TextRange::default(),
));
}
false
}
Definition::Member(Member {
kind: MemberKind::Class(class),
..
}) => {
if checker.enabled(Rule::UndocumentedPublicClass) {
checker
.diagnostics
.push(Diagnostic::new(UndocumentedPublicClass, class.identifier()));
}
false
}
Definition::Member(Member {
kind: MemberKind::NestedClass(function),
..
}) => {
if checker.enabled(Rule::UndocumentedPublicNestedClass) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicNestedClass,
function.identifier(),
));
}
false
}
Definition::Member(Member {
kind: MemberKind::Function(function) | MemberKind::NestedFunction(function),
..
}) => {
if is_overload(&function.decorator_list, checker.semantic()) {
true
} else {
if checker.enabled(Rule::UndocumentedPublicFunction) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicFunction,
function.identifier(),
));
}
false
}
}
Definition::Member(Member {
kind: MemberKind::Method(function),
..
}) => {
if is_overload(&function.decorator_list, checker.semantic())
|| is_override(&function.decorator_list, checker.semantic())
{
true
} else if is_init(&function.name) {
if checker.enabled(Rule::UndocumentedPublicInit) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicInit,
function.identifier(),
));
}
true
} else if is_new(&function.name) || is_call(&function.name) {
if checker.enabled(Rule::UndocumentedPublicMethod) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicMethod,
function.identifier(),
));
}
true
} else if is_magic(&function.name) {
if checker.enabled(Rule::UndocumentedMagicMethod) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedMagicMethod,
function.identifier(),
));
}
true
} else {
if checker.enabled(Rule::UndocumentedPublicMethod) {
checker.diagnostics.push(Diagnostic::new(
UndocumentedPublicMethod,
function.identifier(),
));
}
true
}
}
}
}

View file

@ -0,0 +1,90 @@
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::str::{leading_quote, trailing_quote};
use ruff_source_file::NewlineWithTrailingNewline;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::registry::AsRule;
/// ## What it does
/// Checks for single-line docstrings that are broken across multiple lines.
///
/// ## Why is this bad?
/// [PEP 257] recommends that docstrings that _can_ fit on one line should be
/// formatted on a single line, for consistency and readability.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """
/// Return the mean of the given values.
/// """
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct FitsOnOneLine;
impl Violation for FitsOnOneLine {
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("One-line docstring should fit on one line")
}
fn autofix_title(&self) -> Option<String> {
Some("Reformat to one line".to_string())
}
}
/// D200
pub(crate) fn one_liner(checker: &mut Checker, docstring: &Docstring) {
let mut line_count = 0;
let mut non_empty_line_count = 0;
for line in NewlineWithTrailingNewline::from(docstring.body().as_str()) {
line_count += 1;
if !line.trim().is_empty() {
non_empty_line_count += 1;
}
if non_empty_line_count > 1 {
return;
}
}
if non_empty_line_count == 1 && line_count > 1 {
let mut diagnostic = Diagnostic::new(FitsOnOneLine, docstring.range());
if checker.patch(diagnostic.kind.rule()) {
if let (Some(leading), Some(trailing)) = (
leading_quote(docstring.contents),
trailing_quote(docstring.contents),
) {
// If removing whitespace would lead to an invalid string of quote
// characters, avoid applying the fix.
let body = docstring.body();
let trimmed = body.trim();
if trimmed.chars().rev().take_while(|c| *c == '\\').count() % 2 == 0
&& !trimmed.ends_with(trailing.chars().last().unwrap())
&& !trimmed.starts_with(leading.chars().last().unwrap())
{
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
format!("{leading}{trimmed}{trailing}"),
docstring.range(),
)));
}
}
}
checker.diagnostics.push(diagnostic);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,70 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
use crate::rules::pydocstyle::helpers::normalize_word;
/// ## What it does
/// Checks for docstrings that start with `This`.
///
/// ## Why is this bad?
/// [PEP 257] recommends that the first line of a docstring be written in the
/// imperative mood, for consistency.
///
/// Hint: to rewrite the docstring in the imperative, phrase the first line as
/// if it were a command.
///
/// This rule may not apply to all projects; its applicability is a matter of
/// convention. By default, this rule is enabled when using the `numpy`
/// convention,, and disabled when using the `google` and `pep257` conventions.
///
/// ## Example
/// ```python
/// def average(values: list[float]) -> float:
/// """This function returns the mean of the given values."""
/// ```
///
/// Use instead:
/// ```python
/// def average(values: list[float]) -> float:
/// """Return the mean of the given values."""
/// ```
///
/// ## Options
/// - `pydocstyle.convention`
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
///
/// [PEP 257]: https://peps.python.org/pep-0257/
#[violation]
pub struct DocstringStartsWithThis;
impl Violation for DocstringStartsWithThis {
#[derive_message_formats]
fn message(&self) -> String {
format!(r#"First word of the docstring should not be "This""#)
}
}
/// D404
pub(crate) fn starts_with_this(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body();
let trimmed = body.trim();
if trimmed.is_empty() {
return;
}
let Some(first_word) = trimmed.split(' ').next() else {
return;
};
if normalize_word(first_word) != "this" {
return;
}
checker
.diagnostics
.push(Diagnostic::new(DocstringStartsWithThis, docstring.range()));
}

View file

@ -0,0 +1,78 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_codegen::Quote;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::docstrings::Docstring;
/// ## What it does
/// Checks for docstrings that use `'''triple single quotes'''` instead of
/// `"""triple double quotes"""`.
///
/// ## Why is this bad?
/// [PEP 257](https://peps.python.org/pep-0257/#what-is-a-docstring) recommends
/// the use of `"""triple double quotes"""` for docstrings, to ensure
/// consistency.
///
/// ## Example
/// ```python
/// def kos_root():
/// '''Return the pathname of the KOS root directory.'''
/// ```
///
/// Use instead:
/// ```python
/// def kos_root():
/// """Return the pathname of the KOS root directory."""
/// ```
///
/// ## References
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[violation]
pub struct TripleSingleQuotes {
expected_quote: Quote,
}
impl Violation for TripleSingleQuotes {
#[derive_message_formats]
fn message(&self) -> String {
let TripleSingleQuotes { expected_quote } = self;
match expected_quote {
Quote::Double => format!(r#"Use triple double quotes `"""`"#),
Quote::Single => format!(r#"Use triple single quotes `'''`"#),
}
}
}
/// D300
pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let leading_quote = docstring.leading_quote();
let expected_quote = if docstring.body().contains("\"\"\"") {
Quote::Single
} else {
Quote::Double
};
match expected_quote {
Quote::Single => {
if !leading_quote.ends_with("'''") {
checker.diagnostics.push(Diagnostic::new(
TripleSingleQuotes { expected_quote },
docstring.range(),
));
}
}
Quote::Double => {
if !leading_quote.ends_with("\"\"\"") {
checker.diagnostics.push(Diagnostic::new(
TripleSingleQuotes { expected_quote },
docstring.range(),
));
}
}
}
}

View file

@ -0,0 +1,79 @@
//! Settings for the `pydocstyle` plugin.
use std::collections::BTreeSet;
use serde::{Deserialize, Serialize};
use ruff_macros::CacheKey;
use crate::registry::Rule;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Convention {
/// Use Google-style docstrings.
Google,
/// Use NumPy-style docstrings.
Numpy,
/// Use PEP257-style docstrings.
Pep257,
}
impl Convention {
pub const fn rules_to_be_ignored(self) -> &'static [Rule] {
match self {
Convention::Google => &[
Rule::OneBlankLineBeforeClass,
Rule::OneBlankLineAfterClass,
Rule::MultiLineSummarySecondLine,
Rule::SectionUnderlineNotOverIndented,
Rule::EndsInPeriod,
Rule::NonImperativeMood,
Rule::DocstringStartsWithThis,
Rule::NewLineAfterSectionName,
Rule::DashedUnderlineAfterSection,
Rule::SectionUnderlineAfterName,
Rule::SectionUnderlineMatchesSectionLength,
Rule::BlankLineAfterLastSection,
],
Convention::Numpy => &[
Rule::UndocumentedPublicInit,
Rule::OneBlankLineBeforeClass,
Rule::MultiLineSummaryFirstLine,
Rule::MultiLineSummarySecondLine,
Rule::NoSignature,
Rule::BlankLineAfterLastSection,
Rule::EndsInPunctuation,
Rule::SectionNameEndsInColon,
Rule::UndocumentedParam,
],
Convention::Pep257 => &[
Rule::OneBlankLineBeforeClass,
Rule::MultiLineSummaryFirstLine,
Rule::MultiLineSummarySecondLine,
Rule::SectionNotOverIndented,
Rule::SectionUnderlineNotOverIndented,
Rule::DocstringStartsWithThis,
Rule::CapitalizeSectionName,
Rule::NewLineAfterSectionName,
Rule::DashedUnderlineAfterSection,
Rule::SectionUnderlineAfterName,
Rule::SectionUnderlineMatchesSectionLength,
Rule::NoBlankLineAfterSection,
Rule::NoBlankLineBeforeSection,
Rule::BlankLineAfterLastSection,
Rule::EndsInPunctuation,
Rule::SectionNameEndsInColon,
Rule::UndocumentedParam,
],
}
}
}
#[derive(Debug, Default, CacheKey)]
pub struct Settings {
pub convention: Option<Convention>,
pub ignore_decorators: BTreeSet<String>,
pub property_decorators: BTreeSet<String>,
}

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:1:1: D100 Missing docstring in public module
|
1 | # No docstring, so we can test D100
| D100
2 | from functools import wraps
3 | import os
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,8 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D100_pub.py:1:1: D100 Missing docstring in public module
|
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:15:7: D101 Missing docstring in public class
|
15 | class class_:
| ^^^^^^ D101
16 |
17 | expect('meta', 'D419: Docstring is empty')
|

View file

@ -0,0 +1,46 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:23:9: D102 Missing docstring in public method
|
22 | @expect('D102: Missing docstring in public method')
23 | def method(self=None):
| ^^^^^^ D102
24 | pass
|
D.py:56:9: D102 Missing docstring in public method
|
55 | @expect('D102: Missing docstring in public method')
56 | def __new__(self=None):
| ^^^^^^^ D102
57 | pass
|
D.py:68:9: D102 Missing docstring in public method
|
67 | @expect('D102: Missing docstring in public method')
68 | def __call__(self=None, x=None, y=None, z=None):
| ^^^^^^^^ D102
69 | pass
|
D.py:650:9: D102 Missing docstring in public method
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
650 | def sort_services(self):
| ^^^^^^^^^^^^^ D102
651 | pass
|
D.py:659:9: D102 Missing docstring in public method
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
659 | def sort_services(self):
| ^^^^^^^^^^^^^ D102
660 | pass
|

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
setter.py:16:9: D102 Missing docstring in public method
|
15 | @foo
16 | def foo(self, value: str) -> None:
| ^^^ D102
17 | pass
|

View file

@ -0,0 +1,11 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:400:5: D103 Missing docstring in public function
|
399 | @expect("D103: Missing docstring in public function")
400 | def oneliner_d102(): return
| ^^^^^^^^^^^^^ D103
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,8 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
__init__.py:1:1: D104 Missing docstring in public package
|
|

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:64:9: D105 Missing docstring in magic method
|
63 | @expect('D105: Missing docstring in magic method')
64 | def __str__(self=None):
| ^^^^^^^ D105
65 | pass
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,21 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:60:9: D107 Missing docstring in `__init__`
|
59 | @expect('D107: Missing docstring in __init__')
60 | def __init__(self=None):
| ^^^^^^^^ D107
61 | pass
|
D.py:534:9: D107 Missing docstring in `__init__`
|
532 | """
533 |
534 | def __init__(self, x):
| ^^^^^^^^ D107
535 | pass
|

View file

@ -0,0 +1,112 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:129:5: D200 [*] One-line docstring should fit on one line
|
127 | @expect('D212: Multi-line docstring summary should start at the first line')
128 | def asdlkfasd():
129 | """
| _____^
130 | | Wrong.
131 | | """
| |_______^ D200
|
= help: Reformat to one line
Suggested fix
126 126 | '(found 3)')
127 127 | @expect('D212: Multi-line docstring summary should start at the first line')
128 128 | def asdlkfasd():
129 |- """
130 |- Wrong.
131 |- """
129 |+ """Wrong."""
132 130 |
133 131 |
134 132 | @expect('D201: No blank lines allowed before function docstring (found 1)')
D.py:597:5: D200 [*] One-line docstring should fit on one line
|
595 | @expect('D212: Multi-line docstring summary should start at the first line')
596 | def one_liner():
597 | """
| _____^
598 | |
599 | | Wrong."""
| |_____________^ D200
|
= help: Reformat to one line
Suggested fix
594 594 | '(found 3)')
595 595 | @expect('D212: Multi-line docstring summary should start at the first line')
596 596 | def one_liner():
597 |- """
598 |-
599 |- Wrong."""
597 |+ """Wrong."""
600 598 |
601 599 |
602 600 | @expect('D200: One-line docstring should fit on one line with quotes '
D.py:606:5: D200 [*] One-line docstring should fit on one line
|
604 | @expect('D212: Multi-line docstring summary should start at the first line')
605 | def one_liner():
606 | r"""Wrong.
| _____^
607 | |
608 | | """
| |_______^ D200
|
= help: Reformat to one line
Suggested fix
603 603 | '(found 3)')
604 604 | @expect('D212: Multi-line docstring summary should start at the first line')
605 605 | def one_liner():
606 |- r"""Wrong.
607 |-
608 |- """
606 |+ r"""Wrong."""
609 607 |
610 608 |
611 609 | @expect('D200: One-line docstring should fit on one line with quotes '
D.py:615:5: D200 One-line docstring should fit on one line
|
613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 | def one_liner():
615 | """Wrong."
| _____^
616 | |
617 | | """
| |_______^ D200
|
= help: Reformat to one line
D.py:624:5: D200 One-line docstring should fit on one line
|
622 | @expect('D212: Multi-line docstring summary should start at the first line')
623 | def one_liner():
624 | """
| _____^
625 | |
626 | | "Wrong."""
| |______________^ D200
|
= help: Reformat to one line
D.py:645:5: D200 One-line docstring should fit on one line
|
644 | def single_line_docstring_with_an_escaped_backslash():
645 | "\
| _____^
646 | | "
| |_____^ D200
647 |
648 | class StatementOnSameLineAsDocstring:
|
= help: Reformat to one line

View file

@ -0,0 +1,45 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D200.py:2:5: D200 One-line docstring should fit on one line
|
1 | def func():
2 | """\
| _____^
3 | | """
| |_______^ D200
|
= help: Reformat to one line
D200.py:7:5: D200 [*] One-line docstring should fit on one line
|
6 | def func():
7 | """\\
| _____^
8 | | """
| |_______^ D200
|
= help: Reformat to one line
Suggested fix
4 4 |
5 5 |
6 6 | def func():
7 |- """\\
8 |- """
7 |+ """\\"""
9 8 |
10 9 |
11 10 | def func():
D200.py:12:5: D200 One-line docstring should fit on one line
|
11 | def func():
12 | """\ \
| _____^
13 | | """
| |_______^ D200
|
= help: Reformat to one line

View file

@ -0,0 +1,88 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:137:5: D201 [*] No blank lines allowed before function docstring (found 1)
|
135 | def leading_space():
136 |
137 | """Leading space."""
| ^^^^^^^^^^^^^^^^^^^^ D201
|
= help: Remove blank line(s) before function docstring
Fix
133 133 |
134 134 | @expect('D201: No blank lines allowed before function docstring (found 1)')
135 135 | def leading_space():
136 |-
137 136 | """Leading space."""
138 137 |
139 138 |
D.py:151:5: D201 [*] No blank lines allowed before function docstring (found 1)
|
149 | def trailing_and_leading_space():
150 |
151 | """Trailing and leading space."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D201
152 |
153 | pass
|
= help: Remove blank line(s) before function docstring
Fix
147 147 | @expect('D201: No blank lines allowed before function docstring (found 1)')
148 148 | @expect('D202: No blank lines allowed after function docstring (found 1)')
149 149 | def trailing_and_leading_space():
150 |-
151 150 | """Trailing and leading space."""
152 151 |
153 152 | pass
D.py:546:5: D201 [*] No blank lines allowed before function docstring (found 1)
|
544 | def multiline_leading_space():
545 |
546 | """Leading space.
| _____^
547 | |
548 | | More content.
549 | | """
| |_______^ D201
|
= help: Remove blank line(s) before function docstring
Fix
542 542 | @expect('D201: No blank lines allowed before function docstring (found 1)')
543 543 | @expect('D213: Multi-line docstring summary should start at the second line')
544 544 | def multiline_leading_space():
545 |-
546 545 | """Leading space.
547 546 |
548 547 | More content.
D.py:568:5: D201 [*] No blank lines allowed before function docstring (found 1)
|
566 | def multiline_trailing_and_leading_space():
567 |
568 | """Trailing and leading space.
| _____^
569 | |
570 | | More content.
571 | | """
| |_______^ D201
572 |
573 | pass
|
= help: Remove blank line(s) before function docstring
Fix
564 564 | @expect('D202: No blank lines allowed after function docstring (found 1)')
565 565 | @expect('D213: Multi-line docstring summary should start at the second line')
566 566 | def multiline_trailing_and_leading_space():
567 |-
568 567 | """Trailing and leading space.
569 568 |
570 569 | More content.

View file

@ -0,0 +1,92 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:142:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
140 | @expect('D202: No blank lines allowed after function docstring (found 1)')
141 | def trailing_space():
142 | """Leading space."""
| ^^^^^^^^^^^^^^^^^^^^ D202
143 |
144 | pass
|
= help: Remove blank line(s) after function docstring
Fix
140 140 | @expect('D202: No blank lines allowed after function docstring (found 1)')
141 141 | def trailing_space():
142 142 | """Leading space."""
143 |-
144 143 | pass
145 144 |
146 145 |
D.py:151:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
149 | def trailing_and_leading_space():
150 |
151 | """Trailing and leading space."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
152 |
153 | pass
|
= help: Remove blank line(s) after function docstring
Fix
149 149 | def trailing_and_leading_space():
150 150 |
151 151 | """Trailing and leading space."""
152 |-
153 152 | pass
154 153 |
155 154 |
D.py:555:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
553 | @expect('D213: Multi-line docstring summary should start at the second line')
554 | def multiline_trailing_space():
555 | """Leading space.
| _____^
556 | |
557 | | More content.
558 | | """
| |_______^ D202
559 |
560 | pass
|
= help: Remove blank line(s) after function docstring
Fix
556 556 |
557 557 | More content.
558 558 | """
559 |-
560 559 | pass
561 560 |
562 561 |
D.py:568:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
566 | def multiline_trailing_and_leading_space():
567 |
568 | """Trailing and leading space.
| _____^
569 | |
570 | | More content.
571 | | """
| |_______^ D202
572 |
573 | pass
|
= help: Remove blank line(s) after function docstring
Fix
569 569 |
570 570 | More content.
571 571 | """
572 |-
573 572 | pass
574 573 |
575 574 |

View file

@ -0,0 +1,62 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D202.py:57:5: D202 [*] No blank lines allowed after function docstring (found 2)
|
55 | # D202
56 | def outer():
57 | """This is a docstring."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
|
= help: Remove blank line(s) after function docstring
Fix
55 55 | # D202
56 56 | def outer():
57 57 | """This is a docstring."""
58 |-
59 |-
60 58 | def inner():
61 59 | return
62 60 |
D202.py:68:5: D202 [*] No blank lines allowed after function docstring (found 2)
|
66 | # D202
67 | def outer():
68 | """This is a docstring."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
|
= help: Remove blank line(s) after function docstring
Fix
66 66 | # D202
67 67 | def outer():
68 68 | """This is a docstring."""
69 |-
70 |-
71 69 | # This is a comment.
72 70 | def inner():
73 71 | return
D202.py:80:5: D202 [*] No blank lines allowed after function docstring (found 1)
|
78 | # D202
79 | def outer():
80 | """This is a docstring."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ D202
81 |
82 | # This is a comment.
|
= help: Remove blank line(s) after function docstring
Fix
78 78 | # D202
79 79 | def outer():
80 80 | """This is a docstring."""
81 |-
82 81 | # This is a comment.
83 82 |
84 83 | def inner():

View file

@ -0,0 +1,121 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:161:5: D203 [*] 1 blank line required before class docstring
|
160 | class LeadingSpaceMissing:
161 | """Leading space missing."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D203
|
= help: Insert 1 blank line before class docstring
Fix
158 158 |
159 159 |
160 160 | class LeadingSpaceMissing:
161 |+
161 162 | """Leading space missing."""
162 163 |
163 164 |
D.py:192:5: D203 [*] 1 blank line required before class docstring
|
191 | class LeadingAndTrailingSpaceMissing:
192 | """Leading and trailing space missing."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D203
193 | pass
|
= help: Insert 1 blank line before class docstring
Fix
189 189 |
190 190 |
191 191 | class LeadingAndTrailingSpaceMissing:
192 |+
192 193 | """Leading and trailing space missing."""
193 194 | pass
194 195 |
D.py:526:5: D203 [*] 1 blank line required before class docstring
|
524 | # parameters as functions for Google / Numpy conventions.
525 | class Blah: # noqa: D203,D213
526 | """A Blah.
| _____^
527 | |
528 | | Parameters
529 | | ----------
530 | | x : int
531 | |
532 | | """
| |_______^ D203
533 |
534 | def __init__(self, x):
|
= help: Insert 1 blank line before class docstring
Fix
523 523 | # This is reproducing a bug where AttributeError is raised when parsing class
524 524 | # parameters as functions for Google / Numpy conventions.
525 525 | class Blah: # noqa: D203,D213
526 |+
526 527 | """A Blah.
527 528 |
528 529 | Parameters
D.py:649:5: D203 [*] 1 blank line required before class docstring
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D203
650 | def sort_services(self):
651 | pass
|
= help: Insert 1 blank line before class docstring
Fix
646 646 | "
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 |+
649 650 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
650 651 | def sort_services(self):
651 652 | pass
D.py:654:5: D203 [*] 1 blank line required before class docstring
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D203
|
= help: Insert 1 blank line before class docstring
Fix
651 651 | pass
652 652 |
653 653 | class StatementOnSameLineAsDocstring:
654 |+
654 655 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
655 656 |
656 657 |
D.py:658:5: D203 [*] 1 blank line required before class docstring
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D203
659 | def sort_services(self):
660 | pass
|
= help: Insert 1 blank line before class docstring
Fix
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
658 |+
658 659 | "After this docstring there's a comment." # priorities=1
659 660 | def sort_services(self):
660 661 | pass

View file

@ -0,0 +1,102 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:181:5: D204 [*] 1 blank line required after class docstring
|
179 | class TrailingSpace:
180 |
181 | """TrailingSpace."""
| ^^^^^^^^^^^^^^^^^^^^ D204
182 | pass
|
= help: Insert 1 blank line after class docstring
Fix
179 179 | class TrailingSpace:
180 180 |
181 181 | """TrailingSpace."""
182 |+
182 183 | pass
183 184 |
184 185 |
D.py:192:5: D204 [*] 1 blank line required after class docstring
|
191 | class LeadingAndTrailingSpaceMissing:
192 | """Leading and trailing space missing."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D204
193 | pass
|
= help: Insert 1 blank line after class docstring
Fix
190 190 |
191 191 | class LeadingAndTrailingSpaceMissing:
192 192 | """Leading and trailing space missing."""
193 |+
193 194 | pass
194 195 |
195 196 |
D.py:649:5: D204 [*] 1 blank line required after class docstring
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D204
650 | def sort_services(self):
651 | pass
|
= help: Insert 1 blank line after class docstring
Fix
646 646 | "
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
649 |+ "After this docstring there's another statement on the same line separated by a semicolon."
650 |+
651 |+ priorities=1
650 652 | def sort_services(self):
651 653 | pass
652 654 |
D.py:654:5: D204 [*] 1 blank line required after class docstring
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D204
|
= help: Insert 1 blank line after class docstring
Fix
651 651 | pass
652 652 |
653 653 | class StatementOnSameLineAsDocstring:
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
654 |+ "After this docstring there's another statement on the same line separated by a semicolon."
655 |+
656 |+ priorities=1
655 657 |
656 658 |
657 659 | class CommentAfterDocstring:
D.py:658:5: D204 [*] 1 blank line required after class docstring
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D204
659 | def sort_services(self):
660 | pass
|
= help: Insert 1 blank line after class docstring
Fix
656 656 |
657 657 | class CommentAfterDocstring:
658 658 | "After this docstring there's a comment." # priorities=1
659 |+
659 660 | def sort_services(self):
660 661 | pass
661 662 |

View file

@ -0,0 +1,41 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:200:5: D205 1 blank line required between summary line and description
|
198 | @expect('D213: Multi-line docstring summary should start at the second line')
199 | def multi_line_zero_separating_blanks():
200 | """Summary.
| _____^
201 | | Description.
202 | |
203 | | """
| |_______^ D205
|
= help: Insert single blank line
D.py:210:5: D205 [*] 1 blank line required between summary line and description (found 2)
|
208 | @expect('D213: Multi-line docstring summary should start at the second line')
209 | def multi_line_two_separating_blanks():
210 | """Summary.
| _____^
211 | |
212 | |
213 | | Description.
214 | |
215 | | """
| |_______^ D205
|
= help: Insert single blank line
Fix
209 209 | def multi_line_two_separating_blanks():
210 210 | """Summary.
211 211 |
212 |-
213 212 | Description.
214 213 |
215 214 | """

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,82 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:232:1: D207 [*] Docstring is under-indented
|
230 | """Summary.
231 |
232 | Description.
| D207
233 |
234 | """
|
= help: Increase indentation
Fix
229 229 | def asdfsdf():
230 230 | """Summary.
231 231 |
232 |-Description.
232 |+ Description.
233 233 |
234 234 | """
235 235 |
D.py:244:1: D207 [*] Docstring is under-indented
|
242 | Description.
243 |
244 | """
| D207
|
= help: Increase indentation
Fix
241 241 |
242 242 | Description.
243 243 |
244 |-"""
244 |+ """
245 245 |
246 246 |
247 247 | @expect('D208: Docstring is over-indented')
D.py:440:1: D207 [*] Docstring is under-indented
|
438 | def docstring_start_in_same_line(): """First Line.
439 |
440 | Second Line
| D207
441 | """
|
= help: Increase indentation
Fix
437 437 | @expect('D213: Multi-line docstring summary should start at the second line')
438 438 | def docstring_start_in_same_line(): """First Line.
439 439 |
440 |- Second Line
440 |+ Second Line
441 441 | """
442 442 |
443 443 |
D.py:441:1: D207 [*] Docstring is under-indented
|
440 | Second Line
441 | """
| D207
|
= help: Increase indentation
Fix
438 438 | def docstring_start_in_same_line(): """First Line.
439 439 |
440 440 | Second Line
441 |- """
441 |+ """
442 442 |
443 443 |
444 444 | def function_with_lambda_arg(x=lambda y: y):

View file

@ -0,0 +1,65 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:252:1: D208 [*] Docstring is over-indented
|
250 | """Summary.
251 |
252 | Description.
| D208
253 |
254 | """
|
= help: Remove over-indentation
Fix
249 249 | def asdfsdsdf24():
250 250 | """Summary.
251 251 |
252 |- Description.
252 |+ Description.
253 253 |
254 254 | """
255 255 |
D.py:264:1: D208 [*] Docstring is over-indented
|
262 | Description.
263 |
264 | """
| D208
|
= help: Remove over-indentation
Fix
261 261 |
262 262 | Description.
263 263 |
264 |- """
264 |+ """
265 265 |
266 266 |
267 267 | @expect('D208: Docstring is over-indented')
D.py:272:1: D208 [*] Docstring is over-indented
|
270 | """Summary.
271 |
272 | Description.
| D208
273 |
274 | """
|
= help: Remove over-indentation
Fix
269 269 | def asdfsdfsdsdsdfsdf24():
270 270 | """Summary.
271 271 |
272 |- Description.
272 |+ Description.
273 273 |
274 274 | """
275 275 |

View file

@ -0,0 +1,50 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:281:5: D209 [*] Multi-line docstring closing quotes should be on a separate line
|
279 | @expect('D213: Multi-line docstring summary should start at the second line')
280 | def asdfljdf24():
281 | """Summary.
| _____^
282 | |
283 | | Description."""
| |___________________^ D209
|
= help: Move closing quotes to new line
Fix
280 280 | def asdfljdf24():
281 281 | """Summary.
282 282 |
283 |- Description."""
283 |+ Description.
284 |+ """
284 285 |
285 286 |
286 287 | @expect('D210: No whitespaces allowed surrounding docstring text')
D.py:588:5: D209 [*] Multi-line docstring closing quotes should be on a separate line
|
586 | @expect('D213: Multi-line docstring summary should start at the second line')
587 | def asdfljdjgf24():
588 | """Summary.
| _____^
589 | |
590 | | Description. """
| |_____________________^ D209
|
= help: Move closing quotes to new line
Fix
587 587 | def asdfljdjgf24():
588 588 | """Summary.
589 589 |
590 |- Description. """
590 |+ Description.
591 |+ """
591 592 |
592 593 |
593 594 | @expect('D200: One-line docstring should fit on one line with quotes '

View file

@ -0,0 +1,74 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:288:5: D210 [*] No whitespaces allowed surrounding docstring text
|
286 | @expect('D210: No whitespaces allowed surrounding docstring text')
287 | def endswith():
288 | """Whitespace at the end. """
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D210
|
= help: Trim surrounding whitespace
Fix
285 285 |
286 286 | @expect('D210: No whitespaces allowed surrounding docstring text')
287 287 | def endswith():
288 |- """Whitespace at the end. """
288 |+ """Whitespace at the end."""
289 289 |
290 290 |
291 291 | @expect('D210: No whitespaces allowed surrounding docstring text')
D.py:293:5: D210 [*] No whitespaces allowed surrounding docstring text
|
291 | @expect('D210: No whitespaces allowed surrounding docstring text')
292 | def around():
293 | """ Whitespace at everywhere. """
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D210
|
= help: Trim surrounding whitespace
Fix
290 290 |
291 291 | @expect('D210: No whitespaces allowed surrounding docstring text')
292 292 | def around():
293 |- """ Whitespace at everywhere. """
293 |+ """Whitespace at everywhere."""
294 294 |
295 295 |
296 296 | @expect('D210: No whitespaces allowed surrounding docstring text')
D.py:299:5: D210 [*] No whitespaces allowed surrounding docstring text
|
297 | @expect('D213: Multi-line docstring summary should start at the second line')
298 | def multiline():
299 | """ Whitespace at the beginning.
| _____^
300 | |
301 | | This is the end.
302 | | """
| |_______^ D210
|
= help: Trim surrounding whitespace
Fix
296 296 | @expect('D210: No whitespaces allowed surrounding docstring text')
297 297 | @expect('D213: Multi-line docstring summary should start at the second line')
298 298 | def multiline():
299 |- """ Whitespace at the beginning.
299 |+ """Whitespace at the beginning.
300 300 |
301 301 | This is the end.
302 302 | """
D.py:581:5: D210 No whitespaces allowed surrounding docstring text
|
579 | "or exclamation point (not '\"')")
580 | def endswith_quote():
581 | """Whitespace at the end, but also a quote" """
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D210
|
= help: Trim surrounding whitespace

View file

@ -0,0 +1,41 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:170:5: D211 [*] No blank lines allowed before class docstring
|
168 | class WithLeadingSpace:
169 |
170 | """With leading space."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^ D211
|
= help: Remove blank line(s) before class docstring
Fix
166 166 |
167 167 |
168 168 | class WithLeadingSpace:
169 |-
170 169 | """With leading space."""
171 170 |
172 171 |
D.py:181:5: D211 [*] No blank lines allowed before class docstring
|
179 | class TrailingSpace:
180 |
181 | """TrailingSpace."""
| ^^^^^^^^^^^^^^^^^^^^ D211
182 | pass
|
= help: Remove blank line(s) before class docstring
Fix
177 177 |
178 178 |
179 179 | class TrailingSpace:
180 |-
181 180 | """TrailingSpace."""
182 181 | pass
183 182 |

View file

@ -0,0 +1,75 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:129:5: D212 [*] Multi-line docstring summary should start at the first line
|
127 | @expect('D212: Multi-line docstring summary should start at the first line')
128 | def asdlkfasd():
129 | """
| _____^
130 | | Wrong.
131 | | """
| |_______^ D212
|
= help: Remove whitespace after opening quotes
Fix
126 126 | '(found 3)')
127 127 | @expect('D212: Multi-line docstring summary should start at the first line')
128 128 | def asdlkfasd():
129 |- """
130 |- Wrong.
129 |+ """Wrong.
131 130 | """
132 131 |
133 132 |
D.py:597:5: D212 [*] Multi-line docstring summary should start at the first line
|
595 | @expect('D212: Multi-line docstring summary should start at the first line')
596 | def one_liner():
597 | """
| _____^
598 | |
599 | | Wrong."""
| |_____________^ D212
|
= help: Remove whitespace after opening quotes
Fix
594 594 | '(found 3)')
595 595 | @expect('D212: Multi-line docstring summary should start at the first line')
596 596 | def one_liner():
597 |- """
598 |-
599 |- Wrong."""
597 |+ """Wrong."""
600 598 |
601 599 |
602 600 | @expect('D200: One-line docstring should fit on one line with quotes '
D.py:624:5: D212 [*] Multi-line docstring summary should start at the first line
|
622 | @expect('D212: Multi-line docstring summary should start at the first line')
623 | def one_liner():
624 | """
| _____^
625 | |
626 | | "Wrong."""
| |______________^ D212
|
= help: Remove whitespace after opening quotes
Fix
621 621 | '(found 3)')
622 622 | @expect('D212: Multi-line docstring summary should start at the first line')
623 623 | def one_liner():
624 |- """
625 |-
626 |- "Wrong."""
624 |+ """"Wrong."""
627 625 |
628 626 |
629 627 | @expect('D404: First word of the docstring should not be "This"')

View file

@ -0,0 +1,550 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:200:5: D213 [*] Multi-line docstring summary should start at the second line
|
198 | @expect('D213: Multi-line docstring summary should start at the second line')
199 | def multi_line_zero_separating_blanks():
200 | """Summary.
| _____^
201 | | Description.
202 | |
203 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
197 197 | '(found 0)')
198 198 | @expect('D213: Multi-line docstring summary should start at the second line')
199 199 | def multi_line_zero_separating_blanks():
200 |- """Summary.
200 |+ """
201 |+ Summary.
201 202 | Description.
202 203 |
203 204 | """
D.py:210:5: D213 [*] Multi-line docstring summary should start at the second line
|
208 | @expect('D213: Multi-line docstring summary should start at the second line')
209 | def multi_line_two_separating_blanks():
210 | """Summary.
| _____^
211 | |
212 | |
213 | | Description.
214 | |
215 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
207 207 | '(found 2)')
208 208 | @expect('D213: Multi-line docstring summary should start at the second line')
209 209 | def multi_line_two_separating_blanks():
210 |- """Summary.
210 |+ """
211 |+ Summary.
211 212 |
212 213 |
213 214 | Description.
D.py:220:5: D213 [*] Multi-line docstring summary should start at the second line
|
218 | @expect('D213: Multi-line docstring summary should start at the second line')
219 | def multi_line_one_separating_blanks():
220 | """Summary.
| _____^
221 | |
222 | | Description.
223 | |
224 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
217 217 |
218 218 | @expect('D213: Multi-line docstring summary should start at the second line')
219 219 | def multi_line_one_separating_blanks():
220 |- """Summary.
220 |+ """
221 |+ Summary.
221 222 |
222 223 | Description.
223 224 |
D.py:230:5: D213 [*] Multi-line docstring summary should start at the second line
|
228 | @expect('D213: Multi-line docstring summary should start at the second line')
229 | def asdfsdf():
230 | """Summary.
| _____^
231 | |
232 | | Description.
233 | |
234 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
227 227 | @expect('D207: Docstring is under-indented')
228 228 | @expect('D213: Multi-line docstring summary should start at the second line')
229 229 | def asdfsdf():
230 |- """Summary.
230 |+ """
231 |+ Summary.
231 232 |
232 233 | Description.
233 234 |
D.py:240:5: D213 [*] Multi-line docstring summary should start at the second line
|
238 | @expect('D213: Multi-line docstring summary should start at the second line')
239 | def asdsdfsdffsdf():
240 | """Summary.
| _____^
241 | |
242 | | Description.
243 | |
244 | | """
| |___^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
237 237 | @expect('D207: Docstring is under-indented')
238 238 | @expect('D213: Multi-line docstring summary should start at the second line')
239 239 | def asdsdfsdffsdf():
240 |- """Summary.
240 |+ """
241 |+ Summary.
241 242 |
242 243 | Description.
243 244 |
D.py:250:5: D213 [*] Multi-line docstring summary should start at the second line
|
248 | @expect('D213: Multi-line docstring summary should start at the second line')
249 | def asdfsdsdf24():
250 | """Summary.
| _____^
251 | |
252 | | Description.
253 | |
254 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
247 247 | @expect('D208: Docstring is over-indented')
248 248 | @expect('D213: Multi-line docstring summary should start at the second line')
249 249 | def asdfsdsdf24():
250 |- """Summary.
250 |+ """
251 |+ Summary.
251 252 |
252 253 | Description.
253 254 |
D.py:260:5: D213 [*] Multi-line docstring summary should start at the second line
|
258 | @expect('D213: Multi-line docstring summary should start at the second line')
259 | def asdfsdsdfsdf24():
260 | """Summary.
| _____^
261 | |
262 | | Description.
263 | |
264 | | """
| |___________^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
257 257 | @expect('D208: Docstring is over-indented')
258 258 | @expect('D213: Multi-line docstring summary should start at the second line')
259 259 | def asdfsdsdfsdf24():
260 |- """Summary.
260 |+ """
261 |+ Summary.
261 262 |
262 263 | Description.
263 264 |
D.py:270:5: D213 [*] Multi-line docstring summary should start at the second line
|
268 | @expect('D213: Multi-line docstring summary should start at the second line')
269 | def asdfsdfsdsdsdfsdf24():
270 | """Summary.
| _____^
271 | |
272 | | Description.
273 | |
274 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
267 267 | @expect('D208: Docstring is over-indented')
268 268 | @expect('D213: Multi-line docstring summary should start at the second line')
269 269 | def asdfsdfsdsdsdfsdf24():
270 |- """Summary.
270 |+ """
271 |+ Summary.
271 272 |
272 273 | Description.
273 274 |
D.py:281:5: D213 [*] Multi-line docstring summary should start at the second line
|
279 | @expect('D213: Multi-line docstring summary should start at the second line')
280 | def asdfljdf24():
281 | """Summary.
| _____^
282 | |
283 | | Description."""
| |___________________^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
278 278 | 'line')
279 279 | @expect('D213: Multi-line docstring summary should start at the second line')
280 280 | def asdfljdf24():
281 |- """Summary.
281 |+ """
282 |+ Summary.
282 283 |
283 284 | Description."""
284 285 |
D.py:299:5: D213 [*] Multi-line docstring summary should start at the second line
|
297 | @expect('D213: Multi-line docstring summary should start at the second line')
298 | def multiline():
299 | """ Whitespace at the beginning.
| _____^
300 | |
301 | | This is the end.
302 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
296 296 | @expect('D210: No whitespaces allowed surrounding docstring text')
297 297 | @expect('D213: Multi-line docstring summary should start at the second line')
298 298 | def multiline():
299 |- """ Whitespace at the beginning.
299 |+ """
300 |+ Whitespace at the beginning.
300 301 |
301 302 | This is the end.
302 303 | """
D.py:343:5: D213 [*] Multi-line docstring summary should start at the second line
|
341 | @expect('D213: Multi-line docstring summary should start at the second line')
342 | def exceptions_of_D301():
343 | """Exclude some backslashes from D301.
| _____^
344 | |
345 | | In particular, line continuations \
346 | | and unicode literals \u0394 and \N{GREEK CAPITAL LETTER DELTA}.
347 | | They are considered to be intentionally unescaped.
348 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
340 340 |
341 341 | @expect('D213: Multi-line docstring summary should start at the second line')
342 342 | def exceptions_of_D301():
343 |- """Exclude some backslashes from D301.
343 |+ """
344 |+ Exclude some backslashes from D301.
344 345 |
345 346 | In particular, line continuations \
346 347 | and unicode literals \u0394 and \N{GREEK CAPITAL LETTER DELTA}.
D.py:383:5: D213 [*] Multi-line docstring summary should start at the second line
|
381 | @expect('D213: Multi-line docstring summary should start at the second line')
382 | def new_209():
383 | """First line.
| _____^
384 | |
385 | | More lines.
386 | | """
| |_______^ D213
387 | pass
|
= help: Insert line break and indentation after opening quotes
Fix
380 380 |
381 381 | @expect('D213: Multi-line docstring summary should start at the second line')
382 382 | def new_209():
383 |- """First line.
383 |+ """
384 |+ First line.
384 385 |
385 386 | More lines.
386 387 | """
D.py:392:5: D213 [*] Multi-line docstring summary should start at the second line
|
390 | @expect('D213: Multi-line docstring summary should start at the second line')
391 | def old_209():
392 | """One liner.
| _____^
393 | |
394 | | Multi-line comments. OK to have extra blank line
395 | |
396 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
389 389 |
390 390 | @expect('D213: Multi-line docstring summary should start at the second line')
391 391 | def old_209():
392 |- """One liner.
392 |+ """
393 |+ One liner.
393 394 |
394 395 | Multi-line comments. OK to have extra blank line
395 396 |
D.py:438:37: D213 [*] Multi-line docstring summary should start at the second line
|
436 | @expect("D207: Docstring is under-indented")
437 | @expect('D213: Multi-line docstring summary should start at the second line')
438 | def docstring_start_in_same_line(): """First Line.
| _____________________________________^
439 | |
440 | | Second Line
441 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
435 435 |
436 436 | @expect("D207: Docstring is under-indented")
437 437 | @expect('D213: Multi-line docstring summary should start at the second line')
438 |-def docstring_start_in_same_line(): """First Line.
438 |+def docstring_start_in_same_line(): """
439 |+ First Line.
439 440 |
440 441 | Second Line
441 442 | """
D.py:450:5: D213 [*] Multi-line docstring summary should start at the second line
|
448 | @expect('D213: Multi-line docstring summary should start at the second line')
449 | def a_following_valid_function(x=None):
450 | """Check for a bug where the previous function caused an assertion.
| _____^
451 | |
452 | | The assertion was caused in the next function, so this one is necessary.
453 | |
454 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
447 447 |
448 448 | @expect('D213: Multi-line docstring summary should start at the second line')
449 449 | def a_following_valid_function(x=None):
450 |- """Check for a bug where the previous function caused an assertion.
450 |+ """
451 |+ Check for a bug where the previous function caused an assertion.
451 452 |
452 453 | The assertion was caused in the next function, so this one is necessary.
453 454 |
D.py:526:5: D213 [*] Multi-line docstring summary should start at the second line
|
524 | # parameters as functions for Google / Numpy conventions.
525 | class Blah: # noqa: D203,D213
526 | """A Blah.
| _____^
527 | |
528 | | Parameters
529 | | ----------
530 | | x : int
531 | |
532 | | """
| |_______^ D213
533 |
534 | def __init__(self, x):
|
= help: Insert line break and indentation after opening quotes
Fix
523 523 | # This is reproducing a bug where AttributeError is raised when parsing class
524 524 | # parameters as functions for Google / Numpy conventions.
525 525 | class Blah: # noqa: D203,D213
526 |- """A Blah.
526 |+ """
527 |+ A Blah.
527 528 |
528 529 | Parameters
529 530 | ----------
D.py:546:5: D213 [*] Multi-line docstring summary should start at the second line
|
544 | def multiline_leading_space():
545 |
546 | """Leading space.
| _____^
547 | |
548 | | More content.
549 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
543 543 | @expect('D213: Multi-line docstring summary should start at the second line')
544 544 | def multiline_leading_space():
545 545 |
546 |- """Leading space.
546 |+ """
547 |+ Leading space.
547 548 |
548 549 | More content.
549 550 | """
D.py:555:5: D213 [*] Multi-line docstring summary should start at the second line
|
553 | @expect('D213: Multi-line docstring summary should start at the second line')
554 | def multiline_trailing_space():
555 | """Leading space.
| _____^
556 | |
557 | | More content.
558 | | """
| |_______^ D213
559 |
560 | pass
|
= help: Insert line break and indentation after opening quotes
Fix
552 552 | @expect('D202: No blank lines allowed after function docstring (found 1)')
553 553 | @expect('D213: Multi-line docstring summary should start at the second line')
554 554 | def multiline_trailing_space():
555 |- """Leading space.
555 |+ """
556 |+ Leading space.
556 557 |
557 558 | More content.
558 559 | """
D.py:568:5: D213 [*] Multi-line docstring summary should start at the second line
|
566 | def multiline_trailing_and_leading_space():
567 |
568 | """Trailing and leading space.
| _____^
569 | |
570 | | More content.
571 | | """
| |_______^ D213
572 |
573 | pass
|
= help: Insert line break and indentation after opening quotes
Fix
565 565 | @expect('D213: Multi-line docstring summary should start at the second line')
566 566 | def multiline_trailing_and_leading_space():
567 567 |
568 |- """Trailing and leading space.
568 |+ """
569 |+ Trailing and leading space.
569 570 |
570 571 | More content.
571 572 | """
D.py:588:5: D213 [*] Multi-line docstring summary should start at the second line
|
586 | @expect('D213: Multi-line docstring summary should start at the second line')
587 | def asdfljdjgf24():
588 | """Summary.
| _____^
589 | |
590 | | Description. """
| |_____________________^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
585 585 | 'line')
586 586 | @expect('D213: Multi-line docstring summary should start at the second line')
587 587 | def asdfljdjgf24():
588 |- """Summary.
588 |+ """
589 |+ Summary.
589 590 |
590 591 | Description. """
591 592 |
D.py:606:5: D213 [*] Multi-line docstring summary should start at the second line
|
604 | @expect('D212: Multi-line docstring summary should start at the first line')
605 | def one_liner():
606 | r"""Wrong.
| _____^
607 | |
608 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
603 603 | '(found 3)')
604 604 | @expect('D212: Multi-line docstring summary should start at the first line')
605 605 | def one_liner():
606 |- r"""Wrong.
606 |+ r"""
607 |+ Wrong.
607 608 |
608 609 | """
609 610 |
D.py:615:5: D213 [*] Multi-line docstring summary should start at the second line
|
613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 | def one_liner():
615 | """Wrong."
| _____^
616 | |
617 | | """
| |_______^ D213
|
= help: Insert line break and indentation after opening quotes
Fix
612 612 | '(found 3)')
613 613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 614 | def one_liner():
615 |- """Wrong."
615 |+ """
616 |+ Wrong."
616 617 |
617 618 | """
618 619 |

View file

@ -0,0 +1,59 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D214_module.py:1:1: D214 [*] Section is over-indented ("Returns")
|
1 | / """A module docstring with D214 violations
2 | |
3 | | Returns
4 | | -----
5 | | valid returns
6 | |
7 | | Args
8 | | -----
9 | | valid args
10 | | """
| |___^ D214
11 |
12 | import os
|
= help: Remove over-indentation from "Returns"
Fix
1 1 | """A module docstring with D214 violations
2 2 |
3 |- Returns
3 |+Returns
4 4 | -----
5 5 | valid returns
6 6 |
D214_module.py:1:1: D214 [*] Section is over-indented ("Args")
|
1 | / """A module docstring with D214 violations
2 | |
3 | | Returns
4 | | -----
5 | | valid returns
6 | |
7 | | Args
8 | | -----
9 | | valid args
10 | | """
| |___^ D214
11 |
12 | import os
|
= help: Remove over-indentation from "Args"
Fix
4 4 | -----
5 5 | valid returns
6 6 |
7 |- Args
7 |+Args
8 8 | -----
9 9 | valid args
10 10 | """

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:144:5: D214 [*] Section is over-indented ("Returns")
|
142 | @expect("D214: Section is over-indented ('Returns')")
143 | def section_overindented(): # noqa: D416
144 | """Toggle the gizmo.
| _____^
145 | |
146 | | Returns
147 | | -------
148 | | A value of some sort.
149 | |
150 | | """
| |_______^ D214
|
= help: Remove over-indentation from "Returns"
Fix
143 143 | def section_overindented(): # noqa: D416
144 144 | """Toggle the gizmo.
145 145 |
146 |- Returns
146 |+ Returns
147 147 | -------
148 148 | A value of some sort.
149 149 |

View file

@ -0,0 +1,54 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:156:5: D215 [*] Section underline is over-indented ("Returns")
|
154 | @expect("D215: Section underline is over-indented (in section 'Returns')")
155 | def section_underline_overindented(): # noqa: D416
156 | """Toggle the gizmo.
| _____^
157 | |
158 | | Returns
159 | | -------
160 | | A value of some sort.
161 | |
162 | | """
| |_______^ D215
|
= help: Remove over-indentation from "Returns" underline
Fix
156 156 | """Toggle the gizmo.
157 157 |
158 158 | Returns
159 |- -------
159 |+ ------
160 160 | A value of some sort.
161 161 |
162 162 | """
sections.py:170:5: D215 [*] Section underline is over-indented ("Returns")
|
168 | @expect("D414: Section has no content ('Returns')")
169 | def section_underline_overindented_and_contentless(): # noqa: D416
170 | """Toggle the gizmo.
| _____^
171 | |
172 | | Returns
173 | | -------
174 | | """
| |_______^ D215
|
= help: Remove over-indentation from "Returns" underline
Fix
170 170 | """Toggle the gizmo.
171 171 |
172 172 | Returns
173 |- -------
173 |+ ------
174 174 | """
175 175 |
176 176 |

View file

@ -0,0 +1,89 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:307:5: D300 Use triple double quotes `"""`
|
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 | def triple_single_quotes_raw():
307 | r'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
D.py:312:5: D300 Use triple double quotes `"""`
|
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 | def triple_single_quotes_raw_uppercase():
312 | R'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
D.py:317:5: D300 Use triple double quotes `"""`
|
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 | def single_quotes_raw():
317 | r'Summary.'
| ^^^^^^^^^^^ D300
|
D.py:322:5: D300 Use triple double quotes `"""`
|
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 | def single_quotes_raw_uppercase():
322 | R'Summary.'
| ^^^^^^^^^^^ D300
|
D.py:328:5: D300 Use triple double quotes `"""`
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
328 | R'Sum\mary.'
| ^^^^^^^^^^^^ D300
|
D.py:645:5: D300 Use triple double quotes `"""`
|
644 | def single_line_docstring_with_an_escaped_backslash():
645 | "\
| _____^
646 | | "
| |_____^ D300
647 |
648 | class StatementOnSameLineAsDocstring:
|
D.py:649:5: D300 Use triple double quotes `"""`
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
650 | def sort_services(self):
651 | pass
|
D.py:654:5: D300 Use triple double quotes `"""`
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
D.py:658:5: D300 Use triple double quotes `"""`
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
659 | def sort_services(self):
660 | pass
|
D.py:664:5: D300 Use triple double quotes `"""`
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D300
|

View file

@ -0,0 +1,28 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:328:5: D301 Use `r"""` if any backslashes in a docstring
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
328 | R'Sum\mary.'
| ^^^^^^^^^^^^ D301
|
D.py:333:5: D301 Use `r"""` if any backslashes in a docstring
|
331 | @expect('D301: Use r""" if any backslashes in a docstring')
332 | def double_quotes_backslash():
333 | """Sum\\mary."""
| ^^^^^^^^^^^^^^^^ D301
|
D.py:338:5: D301 Use `r"""` if any backslashes in a docstring
|
336 | @expect('D301: Use r""" if any backslashes in a docstring')
337 | def double_quotes_backslash_uppercase():
338 | R"""Sum\\mary."""
| ^^^^^^^^^^^^^^^^^ D301
|

View file

@ -0,0 +1,18 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D301.py:2:5: D301 Use `r"""` if any backslashes in a docstring
|
1 | def double_quotes_backslash():
2 | """Sum\\mary."""
| ^^^^^^^^^^^^^^^^ D301
|
D301.py:10:5: D301 Use `r"""` if any backslashes in a docstring
|
9 | def double_quotes_backslash_uppercase():
10 | R"""Sum\\mary."""
| ^^^^^^^^^^^^^^^^^ D301
|

View file

@ -0,0 +1,330 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:355:5: D400 [*] First line should end with a period
|
353 | "or exclamation point (not 'y')")
354 | def lwnlkjl():
355 | """Summary"""
| ^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
352 352 | @expect("D415: First line should end with a period, question mark, "
353 353 | "or exclamation point (not 'y')")
354 354 | def lwnlkjl():
355 |- """Summary"""
355 |+ """Summary."""
356 356 |
357 357 |
358 358 | @expect("D401: First line should be in imperative mood "
D.py:406:25: D400 [*] First line should end with a period
|
404 | @expect("D415: First line should end with a period, question mark,"
405 | " or exclamation point (not 'r')")
406 | def oneliner_withdoc(): """One liner"""
| ^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
403 403 | @expect("D400: First line should end with a period (not 'r')")
404 404 | @expect("D415: First line should end with a period, question mark,"
405 405 | " or exclamation point (not 'r')")
406 |-def oneliner_withdoc(): """One liner"""
406 |+def oneliner_withdoc(): """One liner."""
407 407 |
408 408 |
409 409 | def ignored_decorator(func): # noqa: D400,D401,D415
D.py:410:5: D400 [*] First line should end with a period
|
409 | def ignored_decorator(func): # noqa: D400,D401,D415
410 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
411 | func()
412 | pass
|
= help: Add period
Suggested fix
407 407 |
408 408 |
409 409 | def ignored_decorator(func): # noqa: D400,D401,D415
410 |- """Runs something"""
410 |+ """Runs something."""
411 411 | func()
412 412 | pass
413 413 |
D.py:416:5: D400 [*] First line should end with a period
|
415 | def decorator_for_test(func): # noqa: D400,D401,D415
416 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
417 | func()
418 | pass
|
= help: Add period
Suggested fix
413 413 |
414 414 |
415 415 | def decorator_for_test(func): # noqa: D400,D401,D415
416 |- """Runs something"""
416 |+ """Runs something."""
417 417 | func()
418 418 | pass
419 419 |
D.py:422:35: D400 [*] First line should end with a period
|
421 | @ignored_decorator
422 | def oneliner_ignored_decorator(): """One liner"""
| ^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
419 419 |
420 420 |
421 421 | @ignored_decorator
422 |-def oneliner_ignored_decorator(): """One liner"""
422 |+def oneliner_ignored_decorator(): """One liner."""
423 423 |
424 424 |
425 425 | @decorator_for_test
D.py:429:49: D400 [*] First line should end with a period
|
427 | @expect("D415: First line should end with a period, question mark,"
428 | " or exclamation point (not 'r')")
429 | def oneliner_with_decorator_expecting_errors(): """One liner"""
| ^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
426 426 | @expect("D400: First line should end with a period (not 'r')")
427 427 | @expect("D415: First line should end with a period, question mark,"
428 428 | " or exclamation point (not 'r')")
429 |-def oneliner_with_decorator_expecting_errors(): """One liner"""
429 |+def oneliner_with_decorator_expecting_errors(): """One liner."""
430 430 |
431 431 |
432 432 | @decorator_for_test
D.py:470:5: D400 [*] First line should end with a period
|
468 | "or exclamation point (not 'g')")
469 | def docstring_bad():
470 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
471 | pass
|
= help: Add period
Suggested fix
467 467 | @expect("D415: First line should end with a period, question mark, "
468 468 | "or exclamation point (not 'g')")
469 469 | def docstring_bad():
470 |- """Runs something"""
470 |+ """Runs something."""
471 471 | pass
472 472 |
473 473 |
D.py:475:5: D400 [*] First line should end with a period
|
474 | def docstring_bad_ignore_all(): # noqa
475 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
476 | pass
|
= help: Add period
Suggested fix
472 472 |
473 473 |
474 474 | def docstring_bad_ignore_all(): # noqa
475 |- """Runs something"""
475 |+ """Runs something."""
476 476 | pass
477 477 |
478 478 |
D.py:480:5: D400 [*] First line should end with a period
|
479 | def docstring_bad_ignore_one(): # noqa: D400,D401,D415
480 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
481 | pass
|
= help: Add period
Suggested fix
477 477 |
478 478 |
479 479 | def docstring_bad_ignore_one(): # noqa: D400,D401,D415
480 |- """Runs something"""
480 |+ """Runs something."""
481 481 | pass
482 482 |
483 483 |
D.py:487:5: D400 [*] First line should end with a period
|
485 | "(perhaps 'Run', not 'Runs')")
486 | def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
487 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D400
488 | pass
|
= help: Add period
Suggested fix
484 484 | @expect("D401: First line should be in imperative mood "
485 485 | "(perhaps 'Run', not 'Runs')")
486 486 | def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
487 |- """Runs something"""
487 |+ """Runs something."""
488 488 | pass
489 489 |
490 490 |
D.py:514:5: D400 [*] First line should end with a period
|
513 | def valid_google_string(): # noqa: D400
514 | """Test a valid something!"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
511 511 |
512 512 |
513 513 | def valid_google_string(): # noqa: D400
514 |- """Test a valid something!"""
514 |+ """Test a valid something!."""
515 515 |
516 516 |
517 517 | @expect("D415: First line should end with a period, question mark, "
D.py:520:5: D400 [*] First line should end with a period
|
518 | "or exclamation point (not 'g')")
519 | def bad_google_string(): # noqa: D400
520 | """Test a valid something"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
517 517 | @expect("D415: First line should end with a period, question mark, "
518 518 | "or exclamation point (not 'g')")
519 519 | def bad_google_string(): # noqa: D400
520 |- """Test a valid something"""
520 |+ """Test a valid something."""
521 521 |
522 522 |
523 523 | # This is reproducing a bug where AttributeError is raised when parsing class
D.py:581:5: D400 [*] First line should end with a period
|
579 | "or exclamation point (not '\"')")
580 | def endswith_quote():
581 | """Whitespace at the end, but also a quote" """
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
578 578 | @expect("D415: First line should end with a period, question mark, "
579 579 | "or exclamation point (not '\"')")
580 580 | def endswith_quote():
581 |- """Whitespace at the end, but also a quote" """
581 |+ """Whitespace at the end, but also a quote". """
582 582 |
583 583 |
584 584 | @expect('D209: Multi-line docstring closing quotes should be on a separate '
D.py:615:5: D400 [*] First line should end with a period
|
613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 | def one_liner():
615 | """Wrong."
| _____^
616 | |
617 | | """
| |_______^ D400
|
= help: Add period
Suggested fix
612 612 | '(found 3)')
613 613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 614 | def one_liner():
615 |- """Wrong."
615 |+ """Wrong.".
616 616 |
617 617 | """
618 618 |
D.py:639:17: D400 [*] First line should end with a period
|
639 | class SameLine: """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
640 |
641 | def same_line(): """This is a docstring on the same line"""
|
= help: Add period
Suggested fix
636 636 | """ This is a docstring that starts with a space.""" # noqa: D210
637 637 |
638 638 |
639 |-class SameLine: """This is a docstring on the same line"""
639 |+class SameLine: """This is a docstring on the same line."""
640 640 |
641 641 | def same_line(): """This is a docstring on the same line"""
642 642 |
D.py:641:18: D400 [*] First line should end with a period
|
639 | class SameLine: """This is a docstring on the same line"""
640 |
641 | def same_line(): """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
|
= help: Add period
Suggested fix
638 638 |
639 639 | class SameLine: """This is a docstring on the same line"""
640 640 |
641 |-def same_line(): """This is a docstring on the same line"""
641 |+def same_line(): """This is a docstring on the same line."""
642 642 |
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
D.py:664:5: D400 [*] First line should end with a period
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D400
|
= help: Add period
Suggested fix
662 662 |
663 663 | def newline_after_closing_quote(self):
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
665 |+ but continuations shouldn't be considered multi-line."

View file

@ -0,0 +1,250 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D400.py:2:5: D400 [*] First line should end with a period
|
1 | def f():
2 | "Here's a line without a period"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
3 | ...
|
= help: Add period
Suggested fix
1 1 | def f():
2 |- "Here's a line without a period"
2 |+ "Here's a line without a period."
3 3 | ...
4 4 |
5 5 |
D400.py:7:5: D400 [*] First line should end with a period
|
6 | def f():
7 | """Here's a line without a period"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
8 | ...
|
= help: Add period
Suggested fix
4 4 |
5 5 |
6 6 | def f():
7 |- """Here's a line without a period"""
7 |+ """Here's a line without a period."""
8 8 | ...
9 9 |
10 10 |
D400.py:12:5: D400 [*] First line should end with a period
|
11 | def f():
12 | """
| _____^
13 | | Here's a line without a period,
14 | | but here's the next line
15 | | """
| |_______^ D400
16 | ...
|
= help: Add period
Suggested fix
11 11 | def f():
12 12 | """
13 13 | Here's a line without a period,
14 |- but here's the next line
14 |+ but here's the next line.
15 15 | """
16 16 | ...
17 17 |
D400.py:20:5: D400 [*] First line should end with a period
|
19 | def f():
20 | """Here's a line without a period"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
21 | ...
|
= help: Add period
Suggested fix
17 17 |
18 18 |
19 19 | def f():
20 |- """Here's a line without a period"""
20 |+ """Here's a line without a period."""
21 21 | ...
22 22 |
23 23 |
D400.py:25:5: D400 [*] First line should end with a period
|
24 | def f():
25 | """
| _____^
26 | | Here's a line without a period,
27 | | but here's the next line"""
| |_______________________________^ D400
28 | ...
|
= help: Add period
Suggested fix
24 24 | def f():
25 25 | """
26 26 | Here's a line without a period,
27 |- but here's the next line"""
27 |+ but here's the next line."""
28 28 | ...
29 29 |
30 30 |
D400.py:32:5: D400 [*] First line should end with a period
|
31 | def f():
32 | """
| _____^
33 | | Here's a line without a period,
34 | | but here's the next line with trailing space """
| |____________________________________________________^ D400
35 | ...
|
= help: Add period
Suggested fix
31 31 | def f():
32 32 | """
33 33 | Here's a line without a period,
34 |- but here's the next line with trailing space """
34 |+ but here's the next line with trailing space. """
35 35 | ...
36 36 |
37 37 |
D400.py:39:5: D400 [*] First line should end with a period
|
38 | def f():
39 | r"Here's a line without a period"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
40 | ...
|
= help: Add period
Suggested fix
36 36 |
37 37 |
38 38 | def f():
39 |- r"Here's a line without a period"
39 |+ r"Here's a line without a period."
40 40 | ...
41 41 |
42 42 |
D400.py:44:5: D400 [*] First line should end with a period
|
43 | def f():
44 | r"""Here's a line without a period"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
45 | ...
|
= help: Add period
Suggested fix
41 41 |
42 42 |
43 43 | def f():
44 |- r"""Here's a line without a period"""
44 |+ r"""Here's a line without a period."""
45 45 | ...
46 46 |
47 47 |
D400.py:49:5: D400 [*] First line should end with a period
|
48 | def f():
49 | r"""
| _____^
50 | | Here's a line without a period,
51 | | but here's the next line
52 | | """
| |_______^ D400
53 | ...
|
= help: Add period
Suggested fix
48 48 | def f():
49 49 | r"""
50 50 | Here's a line without a period,
51 |- but here's the next line
51 |+ but here's the next line.
52 52 | """
53 53 | ...
54 54 |
D400.py:57:5: D400 [*] First line should end with a period
|
56 | def f():
57 | r"""Here's a line without a period"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400
58 | ...
|
= help: Add period
Suggested fix
54 54 |
55 55 |
56 56 | def f():
57 |- r"""Here's a line without a period"""
57 |+ r"""Here's a line without a period."""
58 58 | ...
59 59 |
60 60 |
D400.py:62:5: D400 [*] First line should end with a period
|
61 | def f():
62 | r"""
| _____^
63 | | Here's a line without a period,
64 | | but here's the next line"""
| |_______________________________^ D400
65 | ...
|
= help: Add period
Suggested fix
61 61 | def f():
62 62 | r"""
63 63 | Here's a line without a period,
64 |- but here's the next line"""
64 |+ but here's the next line."""
65 65 | ...
66 66 |
67 67 |
D400.py:69:5: D400 [*] First line should end with a period
|
68 | def f():
69 | r"""
| _____^
70 | | Here's a line without a period,
71 | | but here's the next line with trailing space """
| |____________________________________________________^ D400
72 | ...
|
= help: Add period
Suggested fix
68 68 | def f():
69 69 | r"""
70 70 | Here's a line without a period,
71 |- but here's the next line with trailing space """
71 |+ but here's the next line with trailing space. """
72 72 | ...
73 73 |
74 74 |

View file

@ -0,0 +1,67 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D401.py:10:5: D401 First line of docstring should be in imperative mood: "Returns foo."
|
9 | def bad_liouiwnlkjl():
10 | """Returns foo."""
| ^^^^^^^^^^^^^^^^^^ D401
|
D401.py:14:5: D401 First line of docstring should be in imperative mood: "Constructor for a foo."
|
13 | def bad_sdgfsdg23245():
14 | """Constructor for a foo."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D401
|
D401.py:18:5: D401 First line of docstring should be in imperative mood: "Constructor for a boa."
|
17 | def bad_sdgfsdg23245777():
18 | """
| _____^
19 | |
20 | | Constructor for a boa.
21 | |
22 | | """
| |_______^ D401
|
D401.py:26:5: D401 First line of docstring should be in imperative mood: "Runs something"
|
25 | def bad_run_something():
26 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D401
27 |
28 | def bad_nested():
|
D401.py:29:9: D401 First line of docstring should be in imperative mood: "Runs other things, nested"
|
28 | def bad_nested():
29 | """Runs other things, nested"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D401
30 |
31 | bad_nested()
|
D401.py:35:5: D401 First line of docstring should be in imperative mood: "Writes a logical line that"
|
34 | def multi_line():
35 | """Writes a logical line that
| _____^
36 | | extends to two physical lines.
37 | | """
| |_______^ D401
|
D401.py:74:9: D401 First line of docstring should be in imperative mood: "This method docstring should be written in imperative mood."
|
73 | def bad_method(self):
74 | """This method docstring should be written in imperative mood."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D401
75 |
76 | @property
|

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:378:5: D402 First line should not be the function's signature
|
376 | @expect('D402: First line should not be the function\'s "signature"')
377 | def foobar():
378 | """Signature: foobar()."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ D402
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,22 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this` -> `This`
|
1 | def bad_function():
2 | """this docstring is not capitalized"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D403
3 |
4 | def good_function():
|
= help: Capitalize `this` to `This`
Fix
1 1 | def bad_function():
2 |- """this docstring is not capitalized"""
2 |+ """This docstring is not capitalized"""
3 3 |
4 4 | def good_function():
5 5 | """This docstring is capitalized."""

View file

@ -0,0 +1,36 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:631:5: D404 First word of the docstring should not be "This"
|
629 | @expect('D404: First word of the docstring should not be "This"')
630 | def starts_with_this():
631 | """This is a docstring."""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|
D.py:636:5: D404 First word of the docstring should not be "This"
|
634 | @expect('D404: First word of the docstring should not be "This"')
635 | def starts_with_space_then_this():
636 | """ This is a docstring that starts with a space.""" # noqa: D210
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|
D.py:639:17: D404 First word of the docstring should not be "This"
|
639 | class SameLine: """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
640 |
641 | def same_line(): """This is a docstring on the same line"""
|
D.py:641:18: D404 First word of the docstring should not be "This"
|
639 | class SameLine: """This is a docstring on the same line"""
640 |
641 | def same_line(): """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D404
|

View file

@ -0,0 +1,64 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:17:5: D405 [*] Section name should be properly capitalized ("returns")
|
15 | "('Returns', not 'returns')")
16 | def not_capitalized(): # noqa: D416
17 | """Toggle the gizmo.
| _____^
18 | |
19 | | returns
20 | | -------
21 | | A value of some sort.
22 | |
23 | | """
| |_______^ D405
|
= help: Capitalize "returns"
Fix
16 16 | def not_capitalized(): # noqa: D416
17 17 | """Toggle the gizmo.
18 18 |
19 |- returns
19 |+ Returns
20 20 | -------
21 21 | A value of some sort.
22 22 |
sections.py:216:5: D405 [*] Section name should be properly capitalized ("Short summary")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D405
|
= help: Capitalize "Short summary"
Fix
215 215 | def multiple_sections(): # noqa: D416
216 216 | """Toggle the gizmo.
217 217 |
218 |- Short summary
218 |+ Short Summary
219 219 | -------------
220 220 |
221 221 | This is the function's description, which will also specify what it

View file

@ -0,0 +1,64 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:30:5: D406 [*] Section name should end with a newline ("Returns")
|
28 | "('Returns', not 'Returns:')")
29 | def superfluous_suffix(): # noqa: D416
30 | """Toggle the gizmo.
| _____^
31 | |
32 | | Returns:
33 | | -------
34 | | A value of some sort.
35 | |
36 | | """
| |_______^ D406
|
= help: Add newline after "Returns"
Fix
29 29 | def superfluous_suffix(): # noqa: D416
30 30 | """Toggle the gizmo.
31 31 |
32 |- Returns:
32 |+ Returns
33 33 | -------
34 34 | A value of some sort.
35 35 |
sections.py:216:5: D406 [*] Section name should end with a newline ("Raises")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D406
|
= help: Add newline after "Raises"
Fix
224 224 | Returns
225 225 | ------
226 226 | Many many wonderful things.
227 |- Raises:
227 |+ Raises
228 228 | My attention.
229 229 |
230 230 | """

View file

@ -0,0 +1,501 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:42:5: D407 [*] Missing dashed underline after section ("Returns")
|
40 | @expect("D407: Missing dashed underline after section ('Returns')")
41 | def no_underline(): # noqa: D416
42 | """Toggle the gizmo.
| _____^
43 | |
44 | | Returns
45 | | A value of some sort.
46 | |
47 | | """
| |_______^ D407
|
= help: Add dashed line under "Returns"
Fix
42 42 | """Toggle the gizmo.
43 43 |
44 44 | Returns
45 |+ -------
45 46 | A value of some sort.
46 47 |
47 48 | """
sections.py:54:5: D407 [*] Missing dashed underline after section ("Returns")
|
52 | @expect("D414: Section has no content ('Returns')")
53 | def no_underline_and_no_description(): # noqa: D416
54 | """Toggle the gizmo.
| _____^
55 | |
56 | | Returns
57 | |
58 | | """
| |_______^ D407
|
= help: Add dashed line under "Returns"
Fix
54 54 | """Toggle the gizmo.
55 55 |
56 56 | Returns
57 |+ -------
57 58 |
58 59 | """
59 60 |
sections.py:65:5: D407 [*] Missing dashed underline after section ("Returns")
|
63 | @expect("D414: Section has no content ('Returns')")
64 | def no_underline_and_no_newline(): # noqa: D416
65 | """Toggle the gizmo.
| _____^
66 | |
67 | | Returns"""
| |______________^ D407
|
= help: Add dashed line under "Returns"
Fix
64 64 | def no_underline_and_no_newline(): # noqa: D416
65 65 | """Toggle the gizmo.
66 66 |
67 |- Returns"""
67 |+ Returns
68 |+ -------"""
68 69 |
69 70 |
70 71 | @expect(_D213)
sections.py:216:5: D407 [*] Missing dashed underline after section ("Raises")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D407
|
= help: Add dashed line under "Raises"
Fix
225 225 | ------
226 226 | Many many wonderful things.
227 227 | Raises:
228 |+ ------
228 229 | My attention.
229 230 |
230 231 | """
sections.py:261:5: D407 [*] Missing dashed underline after section ("Args")
|
259 | @expect("D414: Section has no content ('Returns')")
260 | def valid_google_style_section(): # noqa: D406, D407
261 | """Toggle the gizmo.
| _____^
262 | |
263 | | Args:
264 | | note: A random string.
265 | |
266 | | Returns:
267 | |
268 | | Raises:
269 | | RandomError: A random error that occurs randomly.
270 | |
271 | | """
| |_______^ D407
|
= help: Add dashed line under "Args"
Fix
261 261 | """Toggle the gizmo.
262 262 |
263 263 | Args:
264 |+ ----
264 265 | note: A random string.
265 266 |
266 267 | Returns:
sections.py:261:5: D407 [*] Missing dashed underline after section ("Returns")
|
259 | @expect("D414: Section has no content ('Returns')")
260 | def valid_google_style_section(): # noqa: D406, D407
261 | """Toggle the gizmo.
| _____^
262 | |
263 | | Args:
264 | | note: A random string.
265 | |
266 | | Returns:
267 | |
268 | | Raises:
269 | | RandomError: A random error that occurs randomly.
270 | |
271 | | """
| |_______^ D407
|
= help: Add dashed line under "Returns"
Fix
264 264 | note: A random string.
265 265 |
266 266 | Returns:
267 |+ -------
267 268 |
268 269 | Raises:
269 270 | RandomError: A random error that occurs randomly.
sections.py:261:5: D407 [*] Missing dashed underline after section ("Raises")
|
259 | @expect("D414: Section has no content ('Returns')")
260 | def valid_google_style_section(): # noqa: D406, D407
261 | """Toggle the gizmo.
| _____^
262 | |
263 | | Args:
264 | | note: A random string.
265 | |
266 | | Returns:
267 | |
268 | | Raises:
269 | | RandomError: A random error that occurs randomly.
270 | |
271 | | """
| |_______^ D407
|
= help: Add dashed line under "Raises"
Fix
266 266 | Returns:
267 267 |
268 268 | Raises:
269 |+ ------
269 270 | RandomError: A random error that occurs randomly.
270 271 |
271 272 | """
sections.py:278:5: D407 [*] Missing dashed underline after section ("Args")
|
276 | "('Args:', not 'Args')")
277 | def missing_colon_google_style_section(): # noqa: D406, D407
278 | """Toggle the gizmo.
| _____^
279 | |
280 | | Args
281 | | note: A random string.
282 | |
283 | | """
| |_______^ D407
|
= help: Add dashed line under "Args"
Fix
278 278 | """Toggle the gizmo.
279 279 |
280 280 | Args
281 |+ ----
281 282 | note: A random string.
282 283 |
283 284 | """
sections.py:293:9: D407 [*] Missing dashed underline after section ("Args")
|
292 | def bar(y=2): # noqa: D207, D213, D406, D407
293 | """Nested function test for docstrings.
| _________^
294 | |
295 | | Will this work when referencing x?
296 | |
297 | | Args:
298 | | x: Test something
299 | | that is broken.
300 | |
301 | | """
| |___________^ D407
302 | print(x)
|
= help: Add dashed line under "Args"
Fix
295 295 | Will this work when referencing x?
296 296 |
297 297 | Args:
298 |+ ----
298 299 | x: Test something
299 300 | that is broken.
300 301 |
sections.py:310:5: D407 [*] Missing dashed underline after section ("Args")
|
308 | "'test_missing_google_args' docstring)")
309 | def test_missing_google_args(x=1, y=2, _private=3): # noqa: D406, D407
310 | """Toggle the gizmo.
| _____^
311 | |
312 | | Args:
313 | | x (int): The greatest integer.
314 | |
315 | | """
| |_______^ D407
|
= help: Add dashed line under "Args"
Fix
310 310 | """Toggle the gizmo.
311 311 |
312 312 | Args:
313 |+ ----
313 314 | x (int): The greatest integer.
314 315 |
315 316 | """
sections.py:322:9: D407 [*] Missing dashed underline after section ("Args")
|
321 | def test_method(self, test, another_test, _): # noqa: D213, D407
322 | """Test a valid args section.
| _________^
323 | |
324 | | Args:
325 | | test: A parameter.
326 | | another_test: Another parameter.
327 | |
328 | | """
| |___________^ D407
329 |
330 | @expect("D417: Missing argument descriptions in the docstring "
|
= help: Add dashed line under "Args"
Fix
322 322 | """Test a valid args section.
323 323 |
324 324 | Args:
325 |+ ----
325 326 | test: A parameter.
326 327 | another_test: Another parameter.
327 328 |
sections.py:334:9: D407 [*] Missing dashed underline after section ("Args")
|
332 | "'test_missing_args' docstring)", arg_count=5)
333 | def test_missing_args(self, test, x, y, z=3, _private_arg=3): # noqa: D213, D407
334 | """Test a valid args section.
| _________^
335 | |
336 | | Args:
337 | | x: Another parameter.
338 | |
339 | | """
| |___________^ D407
340 |
341 | @classmethod
|
= help: Add dashed line under "Args"
Fix
334 334 | """Test a valid args section.
335 335 |
336 336 | Args:
337 |+ ----
337 338 | x: Another parameter.
338 339 |
339 340 | """
sections.py:346:9: D407 [*] Missing dashed underline after section ("Args")
|
344 | "'test_missing_args_class_method' docstring)", arg_count=5)
345 | def test_missing_args_class_method(cls, test, x, y, _, z=3): # noqa: D213, D407
346 | """Test a valid args section.
| _________^
347 | |
348 | | Args:
349 | | x: Another parameter. The parameter below is missing description.
350 | | y:
351 | |
352 | | """
| |___________^ D407
353 |
354 | @staticmethod
|
= help: Add dashed line under "Args"
Fix
346 346 | """Test a valid args section.
347 347 |
348 348 | Args:
349 |+ ----
349 350 | x: Another parameter. The parameter below is missing description.
350 351 | y:
351 352 |
sections.py:359:9: D407 [*] Missing dashed underline after section ("Args")
|
357 | "'test_missing_args_static_method' docstring)", arg_count=4)
358 | def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407
359 | """Test a valid args section.
| _________^
360 | |
361 | | Args:
362 | | x: Another parameter.
363 | |
364 | | """
| |___________^ D407
365 |
366 | @staticmethod
|
= help: Add dashed line under "Args"
Fix
359 359 | """Test a valid args section.
360 360 |
361 361 | Args:
362 |+ ----
362 363 | x: Another parameter.
363 364 |
364 365 | """
sections.py:371:9: D407 [*] Missing dashed underline after section ("Args")
|
369 | "'test_missing_docstring' docstring)", arg_count=2)
370 | def test_missing_docstring(a, b): # noqa: D213, D407
371 | """Test a valid args section.
| _________^
372 | |
373 | | Args:
374 | | a:
375 | |
376 | | """
| |___________^ D407
377 |
378 | @staticmethod
|
= help: Add dashed line under "Args"
Fix
371 371 | """Test a valid args section.
372 372 |
373 373 | Args:
374 |+ ----
374 375 | a:
375 376 |
376 377 | """
sections.py:380:9: D407 [*] Missing dashed underline after section ("Args")
|
378 | @staticmethod
379 | def test_hanging_indent(skip, verbose): # noqa: D213, D407
380 | """Do stuff.
| _________^
381 | |
382 | | Args:
383 | | skip (:attr:`.Skip`):
384 | | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
385 | | Etiam at tellus a tellus faucibus maximus. Curabitur tellus
386 | | mauris, semper id vehicula ac, feugiat ut tortor.
387 | | verbose (bool):
388 | | If True, print out as much information as possible.
389 | | If False, print out concise "one-liner" information.
390 | |
391 | | """
| |___________^ D407
|
= help: Add dashed line under "Args"
Fix
380 380 | """Do stuff.
381 381 |
382 382 | Args:
383 |+ ----
383 384 | skip (:attr:`.Skip`):
384 385 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
385 386 | Etiam at tellus a tellus faucibus maximus. Curabitur tellus
sections.py:499:9: D407 [*] Missing dashed underline after section ("Args")
|
497 | "'test_incorrect_indent' docstring)", arg_count=3)
498 | def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407
499 | """Reproducing issue #437.
| _________^
500 | |
501 | | Testing this incorrectly indented docstring.
502 | |
503 | | Args:
504 | | x: Test argument.
505 | |
506 | | """
| |___________^ D407
|
= help: Add dashed line under "Args"
Fix
501 501 | Testing this incorrectly indented docstring.
502 502 |
503 503 | Args:
504 |+ ----
504 505 | x: Test argument.
505 506 |
506 507 | """
sections.py:519:5: D407 [*] Missing dashed underline after section ("Parameters")
|
518 | def replace_equals_with_dash():
519 | """Equal length equals should be replaced with dashes.
| _____^
520 | |
521 | | Parameters
522 | | ==========
523 | | """
| |_______^ D407
|
= help: Add dashed line under "Parameters"
Fix
519 519 | """Equal length equals should be replaced with dashes.
520 520 |
521 521 | Parameters
522 |- ==========
522 |+ ----------
523 523 | """
524 524 |
525 525 |
sections.py:527:5: D407 [*] Missing dashed underline after section ("Parameters")
|
526 | def replace_equals_with_dash2():
527 | """Here, the length of equals is not the same.
| _____^
528 | |
529 | | Parameters
530 | | ===========
531 | | """
| |_______^ D407
|
= help: Add dashed line under "Parameters"
Fix
527 527 | """Here, the length of equals is not the same.
528 528 |
529 529 | Parameters
530 |+ ----------
530 531 | ===========
531 532 | """
532 533 |

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:94:5: D408 [*] Section underline should be in the line following the section's name ("Returns")
|
92 | "section's name ('Returns')")
93 | def blank_line_before_underline(): # noqa: D416
94 | """Toggle the gizmo.
| _____^
95 | |
96 | | Returns
97 | |
98 | | -------
99 | | A value of some sort.
100 | |
101 | | """
| |_______^ D408
|
= help: Add underline to "Returns"
Fix
94 94 | """Toggle the gizmo.
95 95 |
96 96 | Returns
97 |-
98 97 | -------
99 98 | A value of some sort.
100 99 |

View file

@ -0,0 +1,64 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:108:5: D409 [*] Section underline should match the length of its name ("Returns")
|
106 | "(Expected 7 dashes in section 'Returns', got 2)")
107 | def bad_underline_length(): # noqa: D416
108 | """Toggle the gizmo.
| _____^
109 | |
110 | | Returns
111 | | --
112 | | A value of some sort.
113 | |
114 | | """
| |_______^ D409
|
= help: Adjust underline length to match "Returns"
Fix
108 108 | """Toggle the gizmo.
109 109 |
110 110 | Returns
111 |- --
111 |+ -------
112 112 | A value of some sort.
113 113 |
114 114 | """
sections.py:216:5: D409 [*] Section underline should match the length of its name ("Returns")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D409
|
= help: Adjust underline length to match "Returns"
Fix
222 222 | returns.
223 223 |
224 224 | Returns
225 |- ------
225 |+ -------
226 226 | Many many wonderful things.
227 227 | Raises:
228 228 | My attention.

View file

@ -0,0 +1,59 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D410.py:2:5: D410 [*] Missing blank line after section ("Parameters")
|
1 | def f(a: int, b: int) -> int:
2 | """Showcase function.
| _____^
3 | |
4 | | Parameters
5 | | ----------
6 | | a : int
7 | | _description_
8 | | b : int
9 | | _description_
10 | | Returns
11 | | -------
12 | | int
13 | | _description
14 | | """
| |_______^ D410
15 | return b - a
|
= help: Add blank line after "Parameters"
Fix
7 7 | _description_
8 8 | b : int
9 9 | _description_
10 |+
10 11 | Returns
11 12 | -------
12 13 | int
D410.py:19:5: D410 [*] Missing blank line after section ("Parameters")
|
18 | def f() -> int:
19 | """Showcase function.
| _____^
20 | |
21 | | Parameters
22 | | ----------
23 | | Returns
24 | | -------
25 | | """
| |_______^ D410
|
= help: Add blank line after "Parameters"
Fix
20 20 |
21 21 | Parameters
22 22 | ----------
23 |+
23 24 | Returns
24 25 | -------
25 26 | """

View file

@ -0,0 +1,67 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:76:5: D410 [*] Missing blank line after section ("Returns")
|
74 | @expect("D414: Section has no content ('Yields')")
75 | def consecutive_sections(): # noqa: D416
76 | """Toggle the gizmo.
| _____^
77 | |
78 | | Returns
79 | | -------
80 | | Yields
81 | | ------
82 | |
83 | | Raises
84 | | ------
85 | | Questions.
86 | |
87 | | """
| |_______^ D410
|
= help: Add blank line after "Returns"
Fix
77 77 |
78 78 | Returns
79 79 | -------
80 |+
80 81 | Yields
81 82 | ------
82 83 |
sections.py:216:5: D410 [*] Missing blank line after section ("Returns")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D410
|
= help: Add blank line after "Returns"
Fix
224 224 | Returns
225 225 | ------
226 226 | Many many wonderful things.
227 |+
227 228 | Raises:
228 229 | My attention.
229 230 |

View file

@ -0,0 +1,93 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:76:5: D411 [*] Missing blank line before section ("Yields")
|
74 | @expect("D414: Section has no content ('Yields')")
75 | def consecutive_sections(): # noqa: D416
76 | """Toggle the gizmo.
| _____^
77 | |
78 | | Returns
79 | | -------
80 | | Yields
81 | | ------
82 | |
83 | | Raises
84 | | ------
85 | | Questions.
86 | |
87 | | """
| |_______^ D411
|
= help: Add blank line before "Yields"
Fix
77 77 |
78 78 | Returns
79 79 | -------
80 |+
80 81 | Yields
81 82 | ------
82 83 |
sections.py:131:5: D411 [*] Missing blank line before section ("Returns")
|
129 | @expect("D411: Missing blank line before section ('Returns')")
130 | def no_blank_line_before_section(): # noqa: D416
131 | """Toggle the gizmo.
| _____^
132 | |
133 | | The function's description.
134 | | Returns
135 | | -------
136 | | A value of some sort.
137 | |
138 | | """
| |_______^ D411
|
= help: Add blank line before "Returns"
Fix
131 131 | """Toggle the gizmo.
132 132 |
133 133 | The function's description.
134 |+
134 135 | Returns
135 136 | -------
136 137 | A value of some sort.
sections.py:216:5: D411 [*] Missing blank line before section ("Raises")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D411
|
= help: Add blank line before "Raises"
Fix
224 224 | Returns
225 225 | ------
226 226 | Many many wonderful things.
227 |+
227 228 | Raises:
228 229 | My attention.
229 230 |

View file

@ -0,0 +1,37 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:216:5: D412 [*] No blank lines allowed between a section header and its content ("Short summary")
|
214 | @expect("D407: Missing dashed underline after section ('Raises')")
215 | def multiple_sections(): # noqa: D416
216 | """Toggle the gizmo.
| _____^
217 | |
218 | | Short summary
219 | | -------------
220 | |
221 | | This is the function's description, which will also specify what it
222 | | returns.
223 | |
224 | | Returns
225 | | ------
226 | | Many many wonderful things.
227 | | Raises:
228 | | My attention.
229 | |
230 | | """
| |_______^ D412
|
= help: Remove blank line(s)
Fix
217 217 |
218 218 | Short summary
219 219 | -------------
220 |-
221 220 | This is the function's description, which will also specify what it
222 221 | returns.
223 222 |

View file

@ -0,0 +1,27 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:65:5: D413 [*] Missing blank line after last section ("Returns")
|
63 | @expect("D414: Section has no content ('Returns')")
64 | def no_underline_and_no_newline(): # noqa: D416
65 | """Toggle the gizmo.
| _____^
66 | |
67 | | Returns"""
| |______________^ D413
|
= help: Add blank line after "Returns"
Fix
64 64 | def no_underline_and_no_newline(): # noqa: D416
65 65 | """Toggle the gizmo.
66 66 |
67 |- Returns"""
67 |+ Returns
68 |+ """
68 69 |
69 70 |
70 71 | @expect(_D213)

View file

@ -0,0 +1,100 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:54:5: D414 Section has no content ("Returns")
|
52 | @expect("D414: Section has no content ('Returns')")
53 | def no_underline_and_no_description(): # noqa: D416
54 | """Toggle the gizmo.
| _____^
55 | |
56 | | Returns
57 | |
58 | | """
| |_______^ D414
|
sections.py:65:5: D414 Section has no content ("Returns")
|
63 | @expect("D414: Section has no content ('Returns')")
64 | def no_underline_and_no_newline(): # noqa: D416
65 | """Toggle the gizmo.
| _____^
66 | |
67 | | Returns"""
| |______________^ D414
|
sections.py:76:5: D414 Section has no content ("Returns")
|
74 | @expect("D414: Section has no content ('Yields')")
75 | def consecutive_sections(): # noqa: D416
76 | """Toggle the gizmo.
| _____^
77 | |
78 | | Returns
79 | | -------
80 | | Yields
81 | | ------
82 | |
83 | | Raises
84 | | ------
85 | | Questions.
86 | |
87 | | """
| |_______^ D414
|
sections.py:76:5: D414 Section has no content ("Yields")
|
74 | @expect("D414: Section has no content ('Yields')")
75 | def consecutive_sections(): # noqa: D416
76 | """Toggle the gizmo.
| _____^
77 | |
78 | | Returns
79 | | -------
80 | | Yields
81 | | ------
82 | |
83 | | Raises
84 | | ------
85 | | Questions.
86 | |
87 | | """
| |_______^ D414
|
sections.py:170:5: D414 Section has no content ("Returns")
|
168 | @expect("D414: Section has no content ('Returns')")
169 | def section_underline_overindented_and_contentless(): # noqa: D416
170 | """Toggle the gizmo.
| _____^
171 | |
172 | | Returns
173 | | -------
174 | | """
| |_______^ D414
|
sections.py:261:5: D414 Section has no content ("Returns")
|
259 | @expect("D414: Section has no content ('Returns')")
260 | def valid_google_style_section(): # noqa: D406, D407
261 | """Toggle the gizmo.
| _____^
262 | |
263 | | Args:
264 | | note: A random string.
265 | |
266 | | Returns:
267 | |
268 | | Raises:
269 | | RandomError: A random error that occurs randomly.
270 | |
271 | | """
| |_______^ D414
|

View file

@ -0,0 +1,312 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:355:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
353 | "or exclamation point (not 'y')")
354 | def lwnlkjl():
355 | """Summary"""
| ^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
352 352 | @expect("D415: First line should end with a period, question mark, "
353 353 | "or exclamation point (not 'y')")
354 354 | def lwnlkjl():
355 |- """Summary"""
355 |+ """Summary."""
356 356 |
357 357 |
358 358 | @expect("D401: First line should be in imperative mood "
D.py:406:25: D415 [*] First line should end with a period, question mark, or exclamation point
|
404 | @expect("D415: First line should end with a period, question mark,"
405 | " or exclamation point (not 'r')")
406 | def oneliner_withdoc(): """One liner"""
| ^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
403 403 | @expect("D400: First line should end with a period (not 'r')")
404 404 | @expect("D415: First line should end with a period, question mark,"
405 405 | " or exclamation point (not 'r')")
406 |-def oneliner_withdoc(): """One liner"""
406 |+def oneliner_withdoc(): """One liner."""
407 407 |
408 408 |
409 409 | def ignored_decorator(func): # noqa: D400,D401,D415
D.py:410:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
409 | def ignored_decorator(func): # noqa: D400,D401,D415
410 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
411 | func()
412 | pass
|
= help: Add closing punctuation
Suggested fix
407 407 |
408 408 |
409 409 | def ignored_decorator(func): # noqa: D400,D401,D415
410 |- """Runs something"""
410 |+ """Runs something."""
411 411 | func()
412 412 | pass
413 413 |
D.py:416:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
415 | def decorator_for_test(func): # noqa: D400,D401,D415
416 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
417 | func()
418 | pass
|
= help: Add closing punctuation
Suggested fix
413 413 |
414 414 |
415 415 | def decorator_for_test(func): # noqa: D400,D401,D415
416 |- """Runs something"""
416 |+ """Runs something."""
417 417 | func()
418 418 | pass
419 419 |
D.py:422:35: D415 [*] First line should end with a period, question mark, or exclamation point
|
421 | @ignored_decorator
422 | def oneliner_ignored_decorator(): """One liner"""
| ^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
419 419 |
420 420 |
421 421 | @ignored_decorator
422 |-def oneliner_ignored_decorator(): """One liner"""
422 |+def oneliner_ignored_decorator(): """One liner."""
423 423 |
424 424 |
425 425 | @decorator_for_test
D.py:429:49: D415 [*] First line should end with a period, question mark, or exclamation point
|
427 | @expect("D415: First line should end with a period, question mark,"
428 | " or exclamation point (not 'r')")
429 | def oneliner_with_decorator_expecting_errors(): """One liner"""
| ^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
426 426 | @expect("D400: First line should end with a period (not 'r')")
427 427 | @expect("D415: First line should end with a period, question mark,"
428 428 | " or exclamation point (not 'r')")
429 |-def oneliner_with_decorator_expecting_errors(): """One liner"""
429 |+def oneliner_with_decorator_expecting_errors(): """One liner."""
430 430 |
431 431 |
432 432 | @decorator_for_test
D.py:470:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
468 | "or exclamation point (not 'g')")
469 | def docstring_bad():
470 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
471 | pass
|
= help: Add closing punctuation
Suggested fix
467 467 | @expect("D415: First line should end with a period, question mark, "
468 468 | "or exclamation point (not 'g')")
469 469 | def docstring_bad():
470 |- """Runs something"""
470 |+ """Runs something."""
471 471 | pass
472 472 |
473 473 |
D.py:475:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
474 | def docstring_bad_ignore_all(): # noqa
475 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
476 | pass
|
= help: Add closing punctuation
Suggested fix
472 472 |
473 473 |
474 474 | def docstring_bad_ignore_all(): # noqa
475 |- """Runs something"""
475 |+ """Runs something."""
476 476 | pass
477 477 |
478 478 |
D.py:480:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
479 | def docstring_bad_ignore_one(): # noqa: D400,D401,D415
480 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
481 | pass
|
= help: Add closing punctuation
Suggested fix
477 477 |
478 478 |
479 479 | def docstring_bad_ignore_one(): # noqa: D400,D401,D415
480 |- """Runs something"""
480 |+ """Runs something."""
481 481 | pass
482 482 |
483 483 |
D.py:487:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
485 | "(perhaps 'Run', not 'Runs')")
486 | def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
487 | """Runs something"""
| ^^^^^^^^^^^^^^^^^^^^ D415
488 | pass
|
= help: Add closing punctuation
Suggested fix
484 484 | @expect("D401: First line should be in imperative mood "
485 485 | "(perhaps 'Run', not 'Runs')")
486 486 | def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
487 |- """Runs something"""
487 |+ """Runs something."""
488 488 | pass
489 489 |
490 490 |
D.py:520:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
518 | "or exclamation point (not 'g')")
519 | def bad_google_string(): # noqa: D400
520 | """Test a valid something"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
517 517 | @expect("D415: First line should end with a period, question mark, "
518 518 | "or exclamation point (not 'g')")
519 519 | def bad_google_string(): # noqa: D400
520 |- """Test a valid something"""
520 |+ """Test a valid something."""
521 521 |
522 522 |
523 523 | # This is reproducing a bug where AttributeError is raised when parsing class
D.py:581:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
579 | "or exclamation point (not '\"')")
580 | def endswith_quote():
581 | """Whitespace at the end, but also a quote" """
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
578 578 | @expect("D415: First line should end with a period, question mark, "
579 579 | "or exclamation point (not '\"')")
580 580 | def endswith_quote():
581 |- """Whitespace at the end, but also a quote" """
581 |+ """Whitespace at the end, but also a quote". """
582 582 |
583 583 |
584 584 | @expect('D209: Multi-line docstring closing quotes should be on a separate '
D.py:615:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 | def one_liner():
615 | """Wrong."
| _____^
616 | |
617 | | """
| |_______^ D415
|
= help: Add closing punctuation
Suggested fix
612 612 | '(found 3)')
613 613 | @expect('D212: Multi-line docstring summary should start at the first line')
614 614 | def one_liner():
615 |- """Wrong."
615 |+ """Wrong.".
616 616 |
617 617 | """
618 618 |
D.py:639:17: D415 [*] First line should end with a period, question mark, or exclamation point
|
639 | class SameLine: """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
640 |
641 | def same_line(): """This is a docstring on the same line"""
|
= help: Add closing punctuation
Suggested fix
636 636 | """ This is a docstring that starts with a space.""" # noqa: D210
637 637 |
638 638 |
639 |-class SameLine: """This is a docstring on the same line"""
639 |+class SameLine: """This is a docstring on the same line."""
640 640 |
641 641 | def same_line(): """This is a docstring on the same line"""
642 642 |
D.py:641:18: D415 [*] First line should end with a period, question mark, or exclamation point
|
639 | class SameLine: """This is a docstring on the same line"""
640 |
641 | def same_line(): """This is a docstring on the same line"""
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415
|
= help: Add closing punctuation
Suggested fix
638 638 |
639 639 | class SameLine: """This is a docstring on the same line"""
640 640 |
641 |-def same_line(): """This is a docstring on the same line"""
641 |+def same_line(): """This is a docstring on the same line."""
642 642 |
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
D.py:664:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D415
|
= help: Add closing punctuation
Suggested fix
662 662 |
663 663 | def newline_after_closing_quote(self):
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
665 |+ but continuations shouldn't be considered multi-line."

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,103 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
sections.py:292:9: D417 Missing argument description in the docstring for `bar`: `y`
|
290 | x = 1
291 |
292 | def bar(y=2): # noqa: D207, D213, D406, D407
| ^^^ D417
293 | """Nested function test for docstrings.
|
sections.py:309:5: D417 Missing argument description in the docstring for `test_missing_google_args`: `y`
|
307 | "(argument(s) y are missing descriptions in "
308 | "'test_missing_google_args' docstring)")
309 | def test_missing_google_args(x=1, y=2, _private=3): # noqa: D406, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^ D417
310 | """Toggle the gizmo.
|
sections.py:333:9: D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
|
331 | "(argument(s) test, y, z are missing descriptions in "
332 | "'test_missing_args' docstring)", arg_count=5)
333 | def test_missing_args(self, test, x, y, z=3, _private_arg=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^ D417
334 | """Test a valid args section.
|
sections.py:345:9: D417 Missing argument descriptions in the docstring for `test_missing_args_class_method`: `test`, `y`, `z`
|
343 | "(argument(s) test, y, z are missing descriptions in "
344 | "'test_missing_args_class_method' docstring)", arg_count=5)
345 | def test_missing_args_class_method(cls, test, x, y, _, z=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D417
346 | """Test a valid args section.
|
sections.py:358:9: D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `y`, `z`
|
356 | "(argument(s) a, y, z are missing descriptions in "
357 | "'test_missing_args_static_method' docstring)", arg_count=4)
358 | def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D417
359 | """Test a valid args section.
|
sections.py:370:9: D417 Missing argument descriptions in the docstring for `test_missing_docstring`: `a`, `b`
|
368 | "(argument(s) a, b are missing descriptions in "
369 | "'test_missing_docstring' docstring)", arg_count=2)
370 | def test_missing_docstring(a, b): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^ D417
371 | """Test a valid args section.
|
sections.py:398:5: D417 Missing argument description in the docstring for `test_missing_numpy_args`: `y`
|
396 | "(argument(s) y are missing descriptions in "
397 | "'test_missing_numpy_args' docstring)")
398 | def test_missing_numpy_args(_private_arg=0, x=1, y=2): # noqa: D406, D407
| ^^^^^^^^^^^^^^^^^^^^^^^ D417
399 | """Toggle the gizmo.
|
sections.py:434:9: D417 Missing argument descriptions in the docstring for `test_missing_args`: `test`, `y`, `z`
|
432 | "(argument(s) test, y, z are missing descriptions in "
433 | "'test_missing_args' docstring)", arg_count=5)
434 | def test_missing_args(self, test, x, y, z=3, t=1, _private=0): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^ D417
435 | """Test a valid args section.
|
sections.py:449:9: D417 Missing argument descriptions in the docstring for `test_missing_args_class_method`: `test`, `y`, `z`
|
447 | "(argument(s) test, y, z are missing descriptions in "
448 | "'test_missing_args_class_method' docstring)", arg_count=4)
449 | def test_missing_args_class_method(cls, test, x, y, z=3): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D417
450 | """Test a valid args section.
|
sections.py:468:9: D417 Missing argument descriptions in the docstring for `test_missing_args_static_method`: `a`, `z`
|
466 | "(argument(s) a, z are missing descriptions in "
467 | "'test_missing_args_static_method' docstring)", arg_count=3)
468 | def test_missing_args_static_method(a, x, y, z=3, t=1): # noqa: D213, D407
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D417
469 | """Test a valid args section.
|
sections.py:498:9: D417 Missing argument description in the docstring for `test_incorrect_indent`: `y`
|
496 | "(argument(s) y are missing descriptions in "
497 | "'test_incorrect_indent' docstring)", arg_count=3)
498 | def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407
| ^^^^^^^^^^^^^^^^^^^^^ D417
499 | """Reproducing issue #437.
|

View file

@ -0,0 +1,31 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:34:9: D418 Function decorated with `@overload` shouldn't contain a docstring
|
33 | @overload
34 | def overloaded_method(self, a: str) -> str:
| ^^^^^^^^^^^^^^^^^ D418
35 | """Foo bar documentation."""
36 | ...
|
D.py:90:9: D418 Function decorated with `@overload` shouldn't contain a docstring
|
89 | @overload
90 | def nested_overloaded_func(a: str) -> str:
| ^^^^^^^^^^^^^^^^^^^^^^ D418
91 | """Foo bar documentation."""
92 | ...
|
D.py:110:5: D418 Function decorated with `@overload` shouldn't contain a docstring
|
109 | @overload
110 | def overloaded_func(a: str) -> str:
| ^^^^^^^^^^^^^^^ D418
111 | """Foo bar documentation."""
112 | ...
|

View file

@ -0,0 +1,31 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:20:9: D419 Docstring is empty
|
19 | class meta:
20 | """"""
| ^^^^^^ D419
21 |
22 | @expect('D102: Missing docstring in public method')
|
D.py:74:5: D419 Docstring is empty
|
72 | @expect('D419: Docstring is empty')
73 | def function():
74 | """ """
| ^^^^^^^ D419
75 | def ok_since_nested():
76 | pass
|
D.py:80:9: D419 Docstring is empty
|
78 | @expect('D419: Docstring is empty')
79 | def nested():
80 | ''
| ^^ D419
|

View file

@ -0,0 +1,34 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
all.py:1:1: D100 Missing docstring in public module
|
1 | def public_func():
| D100
2 | pass
|
all.py:1:5: D103 Missing docstring in public function
|
1 | def public_func():
| ^^^^^^^^^^^ D103
2 | pass
|
all.py:9:7: D101 Missing docstring in public class
|
9 | class PublicClass:
| ^^^^^^^^^^^ D101
10 | class PublicNestedClass:
11 | pass
|
all.py:10:11: D106 Missing docstring in public nested class
|
9 | class PublicClass:
10 | class PublicNestedClass:
| ^^^^^^^^^^^^^^^^^ D106
11 | pass
|

View file

@ -0,0 +1,10 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
bom.py:1:1: D300 Use triple double quotes `"""`
|
1 | ''' SAM macro definitions '''
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|

View file

@ -0,0 +1,37 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D209_D400.py:2:5: D209 [*] Multi-line docstring closing quotes should be on a separate line
|
1 | def lorem():
2 | """lorem ipsum dolor sit amet consectetur adipiscing elit
| _____^
3 | | sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""
| |________________________________________________________________________^ D209
|
= help: Move closing quotes to new line
Fix
1 1 | def lorem():
2 2 | """lorem ipsum dolor sit amet consectetur adipiscing elit
3 |- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""
3 |+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
4 |+ """
D209_D400.py:2:5: D400 [*] First line should end with a period
|
1 | def lorem():
2 | """lorem ipsum dolor sit amet consectetur adipiscing elit
| _____^
3 | | sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""
| |________________________________________________________________________^ D400
|
= help: Add period
Suggested fix
1 1 | def lorem():
2 2 | """lorem ipsum dolor sit amet consectetur adipiscing elit
3 |- sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""
3 |+ sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."""

View file

@ -0,0 +1,67 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
1 | def f(x, y, z):
| ^ D417
2 | """Do something.
|
D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
14 | def f(x, y, z):
| ^ D417
15 | """Do something.
|
D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
27 | def f(x, y, z):
| ^ D417
28 | """Do something.
|
D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
39 | def f(x, y, z):
| ^ D417
40 | """Do something.
|
D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y`
|
52 | def f(x, y, z):
| ^ D417
53 | """Do something.
|
D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y`
|
65 | def f(x, y, z):
| ^ D417
66 | """Do something.
|
D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y`
|
77 | def f(x, y, z):
| ^ D417
78 | """Do something.
|
D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x`
|
98 | def f(x, *args, **kwargs):
| ^ D417
99 | """Do something.
|
D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*args`
|
108 | def f(x, *args, **kwargs):
| ^ D417
109 | """Do something.
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---

View file

@ -0,0 +1,67 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D417.py:1:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
1 | def f(x, y, z):
| ^ D417
2 | """Do something.
|
D417.py:14:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
14 | def f(x, y, z):
| ^ D417
15 | """Do something.
|
D417.py:27:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
27 | def f(x, y, z):
| ^ D417
28 | """Do something.
|
D417.py:39:5: D417 Missing argument descriptions in the docstring for `f`: `y`, `z`
|
39 | def f(x, y, z):
| ^ D417
40 | """Do something.
|
D417.py:52:5: D417 Missing argument description in the docstring for `f`: `y`
|
52 | def f(x, y, z):
| ^ D417
53 | """Do something.
|
D417.py:65:5: D417 Missing argument description in the docstring for `f`: `y`
|
65 | def f(x, y, z):
| ^ D417
66 | """Do something.
|
D417.py:77:5: D417 Missing argument description in the docstring for `f`: `y`
|
77 | def f(x, y, z):
| ^ D417
78 | """Do something.
|
D417.py:98:5: D417 Missing argument description in the docstring for `f`: `x`
|
98 | def f(x, *args, **kwargs):
| ^ D417
99 | """Do something.
|
D417.py:108:5: D417 Missing argument description in the docstring for `f`: `*args`
|
108 | def f(x, *args, **kwargs):
| ^ D417
109 | """Do something.
|