mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:49:50 +00:00
Allow blank line before sticky-comment functions in docstrings (#2597)
This commit is contained in:
parent
7fa5ce8b63
commit
79776c12e2
8 changed files with 227 additions and 70 deletions
87
crates/ruff/resources/test/fixtures/pydocstyle/D202.py
vendored
Normal file
87
crates/ruff/resources/test/fixtures/pydocstyle/D202.py
vendored
Normal file
|
@ -0,0 +1,87 @@
|
|||
"""Test: no-blank-line-after-function special-cases around comment handling."""
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
# This is a comment.
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
# This is a comment.
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
# This is a comment.
|
||||
# Followed by another comment.
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# D202
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# D202
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
|
||||
# This is a comment.
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# D202
|
||||
def outer():
|
||||
"""This is a docstring."""
|
||||
|
||||
# This is a comment.
|
||||
|
||||
def inner():
|
||||
return
|
||||
|
||||
return inner
|
|
@ -15,57 +15,58 @@ mod tests {
|
|||
use crate::test::test_path;
|
||||
use crate::{assert_yaml_snapshot, settings};
|
||||
|
||||
#[test_case(Rule::PublicModule, Path::new("D.py"); "D100")]
|
||||
#[test_case(Rule::PublicClass, Path::new("D.py"); "D101")]
|
||||
#[test_case(Rule::PublicMethod, Path::new("D.py"); "D102_0")]
|
||||
#[test_case(Rule::PublicMethod, Path::new("setter.py"); "D102_1")]
|
||||
#[test_case(Rule::PublicFunction, Path::new("D.py"); "D103")]
|
||||
#[test_case(Rule::PublicPackage, Path::new("D.py"); "D104")]
|
||||
#[test_case(Rule::MagicMethod, Path::new("D.py"); "D105")]
|
||||
#[test_case(Rule::PublicNestedClass, Path::new("D.py"); "D106")]
|
||||
#[test_case(Rule::PublicInit, Path::new("D.py"); "D107")]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"); "D200")]
|
||||
#[test_case(Rule::NoBlankLineBeforeFunction, Path::new("D.py"); "D201")]
|
||||
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D.py"); "D202")]
|
||||
#[test_case(Rule::OneBlankLineBeforeClass, Path::new("D.py"); "D203")]
|
||||
#[test_case(Rule::OneBlankLineAfterClass, Path::new("D.py"); "D204")]
|
||||
#[test_case(Rule::BlankLineAfterSummary, Path::new("D.py"); "D205")]
|
||||
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"); "D206")]
|
||||
#[test_case(Rule::NoUnderIndentation, Path::new("D.py"); "D207")]
|
||||
#[test_case(Rule::NoOverIndentation, Path::new("D.py"); "D208")]
|
||||
#[test_case(Rule::NewLineAfterLastParagraph, Path::new("D.py"); "D209")]
|
||||
#[test_case(Rule::NoSurroundingWhitespace, Path::new("D.py"); "D210")]
|
||||
#[test_case(Rule::NoBlankLineBeforeClass, Path::new("D.py"); "D211")]
|
||||
#[test_case(Rule::MultiLineSummaryFirstLine, Path::new("D.py"); "D212")]
|
||||
#[test_case(Rule::MultiLineSummarySecondLine, Path::new("D.py"); "D213")]
|
||||
#[test_case(Rule::SectionNotOverIndented, Path::new("sections.py"); "D214")]
|
||||
#[test_case(Rule::SectionUnderlineNotOverIndented, Path::new("sections.py"); "D215")]
|
||||
#[test_case(Rule::UsesTripleQuotes, Path::new("D.py"); "D300")]
|
||||
#[test_case(Rule::UsesRPrefixForBackslashedContent, Path::new("D.py"); "D301")]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D.py"); "D400_0")]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400.py"); "D400_1")]
|
||||
#[test_case(Rule::NonImperativeMood, Path::new("D401.py"); "D401")]
|
||||
#[test_case(Rule::NoSignature, Path::new("D.py"); "D402")]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"); "D403")]
|
||||
#[test_case(Rule::NoThisPrefix, Path::new("D.py"); "D404")]
|
||||
#[test_case(Rule::CapitalizeSectionName, Path::new("sections.py"); "D405")]
|
||||
#[test_case(Rule::NewLineAfterSectionName, Path::new("sections.py"); "D406")]
|
||||
#[test_case(Rule::DashedUnderlineAfterSection, Path::new("sections.py"); "D407")]
|
||||
#[test_case(Rule::SectionUnderlineAfterName, Path::new("sections.py"); "D408")]
|
||||
#[test_case(Rule::SectionUnderlineMatchesSectionLength, Path::new("sections.py"); "D409")]
|
||||
#[test_case(Rule::BlankLineAfterSection, Path::new("sections.py"); "D410")]
|
||||
#[test_case(Rule::BlankLineBeforeSection, Path::new("sections.py"); "D411")]
|
||||
#[test_case(Rule::NoBlankLinesBetweenHeaderAndContent, Path::new("sections.py"); "D412")]
|
||||
#[test_case(Rule::BlankLineAfterLastSection, Path::new("sections.py"); "D413")]
|
||||
#[test_case(Rule::NonEmptySection, Path::new("sections.py"); "D414")]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"); "D415")]
|
||||
#[test_case(Rule::SectionNameEndsInColon, Path::new("D.py"); "D416")]
|
||||
#[test_case(Rule::BlankLineAfterSection, Path::new("sections.py"); "D410")]
|
||||
#[test_case(Rule::BlankLineAfterSummary, Path::new("D.py"); "D205")]
|
||||
#[test_case(Rule::BlankLineBeforeSection, Path::new("sections.py"); "D411")]
|
||||
#[test_case(Rule::CapitalizeSectionName, Path::new("sections.py"); "D405")]
|
||||
#[test_case(Rule::DashedUnderlineAfterSection, Path::new("sections.py"); "D407")]
|
||||
#[test_case(Rule::DocumentAllArguments, Path::new("canonical_google_examples.py"); "D417_2")]
|
||||
#[test_case(Rule::DocumentAllArguments, Path::new("canonical_numpy_examples.py"); "D417_1")]
|
||||
#[test_case(Rule::DocumentAllArguments, Path::new("sections.py"); "D417_0")]
|
||||
#[test_case(Rule::SkipDocstring, Path::new("D.py"); "D418")]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D.py"); "D400_0")]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400.py"); "D400_1")]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"); "D415")]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"); "D403")]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"); "D200")]
|
||||
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"); "D206")]
|
||||
#[test_case(Rule::MagicMethod, Path::new("D.py"); "D105")]
|
||||
#[test_case(Rule::MultiLineSummaryFirstLine, Path::new("D.py"); "D212")]
|
||||
#[test_case(Rule::MultiLineSummarySecondLine, Path::new("D.py"); "D213")]
|
||||
#[test_case(Rule::NewLineAfterLastParagraph, Path::new("D.py"); "D209")]
|
||||
#[test_case(Rule::NewLineAfterSectionName, Path::new("sections.py"); "D406")]
|
||||
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D.py"); "D202_0")]
|
||||
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D202.py"); "D202_1")]
|
||||
#[test_case(Rule::NoBlankLineBeforeClass, Path::new("D.py"); "D211")]
|
||||
#[test_case(Rule::NoBlankLineBeforeFunction, Path::new("D.py"); "D201")]
|
||||
#[test_case(Rule::NoBlankLinesBetweenHeaderAndContent, Path::new("sections.py"); "D412")]
|
||||
#[test_case(Rule::NoOverIndentation, Path::new("D.py"); "D208")]
|
||||
#[test_case(Rule::NoSignature, Path::new("D.py"); "D402")]
|
||||
#[test_case(Rule::NoSurroundingWhitespace, Path::new("D.py"); "D210")]
|
||||
#[test_case(Rule::NoThisPrefix, Path::new("D.py"); "D404")]
|
||||
#[test_case(Rule::NoUnderIndentation, Path::new("D.py"); "D207")]
|
||||
#[test_case(Rule::NonEmpty, Path::new("D.py"); "D419")]
|
||||
#[test_case(Rule::NonEmptySection, Path::new("sections.py"); "D414")]
|
||||
#[test_case(Rule::NonImperativeMood, Path::new("D401.py"); "D401")]
|
||||
#[test_case(Rule::OneBlankLineAfterClass, Path::new("D.py"); "D204")]
|
||||
#[test_case(Rule::OneBlankLineBeforeClass, Path::new("D.py"); "D203")]
|
||||
#[test_case(Rule::PublicClass, Path::new("D.py"); "D101")]
|
||||
#[test_case(Rule::PublicFunction, Path::new("D.py"); "D103")]
|
||||
#[test_case(Rule::PublicInit, Path::new("D.py"); "D107")]
|
||||
#[test_case(Rule::PublicMethod, Path::new("D.py"); "D102_0")]
|
||||
#[test_case(Rule::PublicMethod, Path::new("setter.py"); "D102_1")]
|
||||
#[test_case(Rule::PublicModule, Path::new("D.py"); "D100")]
|
||||
#[test_case(Rule::PublicNestedClass, Path::new("D.py"); "D106")]
|
||||
#[test_case(Rule::PublicPackage, Path::new("D.py"); "D104")]
|
||||
#[test_case(Rule::PublicPackage, Path::new("D104/__init__.py"); "D104_1")]
|
||||
#[test_case(Rule::SectionNameEndsInColon, Path::new("D.py"); "D416")]
|
||||
#[test_case(Rule::SectionNotOverIndented, Path::new("sections.py"); "D214")]
|
||||
#[test_case(Rule::SectionUnderlineAfterName, Path::new("sections.py"); "D408")]
|
||||
#[test_case(Rule::SectionUnderlineMatchesSectionLength, Path::new("sections.py"); "D409")]
|
||||
#[test_case(Rule::SectionUnderlineNotOverIndented, Path::new("sections.py"); "D215")]
|
||||
#[test_case(Rule::SkipDocstring, Path::new("D.py"); "D418")]
|
||||
#[test_case(Rule::UsesRPrefixForBackslashedContent, Path::new("D.py"); "D301")]
|
||||
#[test_case(Rule::UsesTripleQuotes, Path::new("D.py"); "D300")]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::define_violation;
|
||||
use crate::docstrings::definition::Docstring;
|
||||
use crate::registry::Diagnostic;
|
||||
use crate::rules::pydocstyle::rules::regexes::BACKSLASH_REGEX;
|
||||
use crate::violation::Violation;
|
||||
|
||||
use crate::define_violation;
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
define_violation!(
|
||||
pub struct UsesRPrefixForBackslashedContent;
|
||||
);
|
||||
|
@ -18,6 +20,8 @@ impl Violation for UsesRPrefixForBackslashedContent {
|
|||
}
|
||||
}
|
||||
|
||||
static BACKSLASH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\\[^(\r\n|\n)uN]").unwrap());
|
||||
|
||||
/// D301
|
||||
pub fn backslashes(checker: &mut Checker, docstring: &Docstring) {
|
||||
let contents = docstring.contents;
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use ruff_macros::derive_message_formats;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::define_violation;
|
||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
||||
use crate::fix::Fix;
|
||||
use crate::message::Location;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::pydocstyle::rules::regexes::COMMENT_REGEX;
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
use crate::define_violation;
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
define_violation!(
|
||||
pub struct OneBlankLineBeforeClass {
|
||||
pub lines: usize,
|
||||
|
@ -144,7 +143,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
|||
let all_blank_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
|
||||
.all(|line| line.trim().is_empty() || line.trim_start().starts_with('#'));
|
||||
if all_blank_after {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::define_violation;
|
||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
||||
use crate::fix::Fix;
|
||||
use crate::message::Location;
|
||||
use crate::registry::{Diagnostic, Rule};
|
||||
use crate::rules::pydocstyle::rules::regexes::{COMMENT_REGEX, INNER_FUNCTION_OR_CLASS_REGEX};
|
||||
use crate::violation::AlwaysAutofixableViolation;
|
||||
|
||||
use crate::define_violation;
|
||||
use ruff_macros::derive_message_formats;
|
||||
|
||||
define_violation!(
|
||||
pub struct NoBlankLineBeforeFunction {
|
||||
pub num_lines: usize,
|
||||
|
@ -44,6 +46,9 @@ impl AlwaysAutofixableViolation for NoBlankLineAfterFunction {
|
|||
}
|
||||
}
|
||||
|
||||
static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\s+(?:(?:class|def|async def)\s|@)").unwrap());
|
||||
|
||||
/// D201, D202
|
||||
pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
|
||||
let (
|
||||
|
@ -98,22 +103,30 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
|
|||
&Range::from_located(docstring.expr),
|
||||
);
|
||||
|
||||
// If the docstring is only followed by blank and commented lines, abort.
|
||||
let all_blank_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
|
||||
.all(|line| line.trim().is_empty() || line.trim_start().starts_with('#'));
|
||||
if all_blank_after {
|
||||
return;
|
||||
}
|
||||
|
||||
// Count the number of blank lines after the docstring.
|
||||
let blank_lines_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
|
||||
// Avoid D202 violations for blank lines followed by inner functions or classes.
|
||||
if blank_lines_after == 1 && INNER_FUNCTION_OR_CLASS_REGEX.is_match(after) {
|
||||
// Avoid violations for blank lines followed by inner functions or classes.
|
||||
if blank_lines_after == 1
|
||||
&& after
|
||||
.lines()
|
||||
.skip(1 + blank_lines_after)
|
||||
.find(|line| !line.trim_start().starts_with('#'))
|
||||
.map_or(false, |line| INNER_FUNCTION_OR_CLASS_REGEX.is_match(line))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,6 @@ mod non_imperative_mood;
|
|||
mod not_empty;
|
||||
mod not_missing;
|
||||
mod one_liner;
|
||||
mod regexes;
|
||||
mod sections;
|
||||
mod starts_with_this;
|
||||
mod triple_quotes;
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
pub static BACKSLASH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\\[^(\r\n|\n)uN]").unwrap());
|
||||
|
||||
pub static COMMENT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\s*#").unwrap());
|
||||
pub static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\s+(?:(?:class|def|async def)\s|@)").unwrap());
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
source: crates/ruff/src/rules/pydocstyle/mod.rs
|
||||
expression: diagnostics
|
||||
---
|
||||
- kind:
|
||||
NoBlankLineAfterFunction:
|
||||
num_lines: 2
|
||||
location:
|
||||
row: 57
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
column: 30
|
||||
fix:
|
||||
content:
|
||||
- ""
|
||||
location:
|
||||
row: 58
|
||||
column: 0
|
||||
end_location:
|
||||
row: 60
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoBlankLineAfterFunction:
|
||||
num_lines: 2
|
||||
location:
|
||||
row: 68
|
||||
column: 4
|
||||
end_location:
|
||||
row: 68
|
||||
column: 30
|
||||
fix:
|
||||
content:
|
||||
- ""
|
||||
location:
|
||||
row: 69
|
||||
column: 0
|
||||
end_location:
|
||||
row: 71
|
||||
column: 0
|
||||
parent: ~
|
||||
- kind:
|
||||
NoBlankLineAfterFunction:
|
||||
num_lines: 1
|
||||
location:
|
||||
row: 80
|
||||
column: 4
|
||||
end_location:
|
||||
row: 80
|
||||
column: 30
|
||||
fix:
|
||||
content:
|
||||
- ""
|
||||
location:
|
||||
row: 81
|
||||
column: 0
|
||||
end_location:
|
||||
row: 82
|
||||
column: 0
|
||||
parent: ~
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue