diff --git a/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py b/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py new file mode 100644 index 0000000000..a7e6732451 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pydocstyle/D209_D400.py @@ -0,0 +1,3 @@ +def lorem(): + """lorem ipsum dolor sit amet consectetur adipiscing elit + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua""" diff --git a/crates/ruff/src/autofix/mod.rs b/crates/ruff/src/autofix/mod.rs index a88a474915..d6e2e4bb84 100644 --- a/crates/ruff/src/autofix/mod.rs +++ b/crates/ruff/src/autofix/mod.rs @@ -9,7 +9,7 @@ use ruff_python_ast::source_code::Locator; use ruff_python_ast::types::Range; use crate::linter::FixTable; -use crate::registry::AsRule; +use crate::registry::{AsRule, Rule}; pub mod helpers; @@ -39,7 +39,7 @@ fn apply_fixes<'a>( .as_ref() .map(|fix| (diagnostic.kind.rule(), fix)) }) - .sorted_by_key(|(.., fix)| fix.location) + .sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2)) { // If we already applied an identical fix as part of another correction, skip // any re-application. @@ -92,6 +92,18 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String { output } +/// Compare two fixes. +fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering { + fix1.location + .cmp(&fix2.location) + .then_with(|| match (&rule1, &rule2) { + // Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes. + (Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less, + (Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater, + _ => std::cmp::Ordering::Equal, + }) +} + #[cfg(test)] mod tests { use rustpython_parser::ast::Location; diff --git a/crates/ruff/src/rules/pydocstyle/mod.rs b/crates/ruff/src/rules/pydocstyle/mod.rs index 139becc9cf..85eec194a4 100644 --- a/crates/ruff/src/rules/pydocstyle/mod.rs +++ b/crates/ruff/src/rules/pydocstyle/mod.rs @@ -153,4 +153,14 @@ mod tests { assert_yaml_snapshot!(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_yaml_snapshot!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap new file mode 100644 index 0000000000..01db548f2a --- /dev/null +++ b/crates/ruff/src/rules/pydocstyle/snapshots/ruff__rules__pydocstyle__tests__d209_d400.snap @@ -0,0 +1,45 @@ +--- +source: crates/ruff/src/rules/pydocstyle/mod.rs +expression: diagnostics +--- +- kind: + name: NewLineAfterLastParagraph + body: Multi-line docstring closing quotes should be on a separate line + suggestion: Move closing quotes to new line + fixable: true + location: + row: 2 + column: 4 + end_location: + row: 3 + column: 72 + fix: + content: "\n " + location: + row: 3 + column: 69 + end_location: + row: 3 + column: 69 + parent: ~ +- kind: + name: EndsInPeriod + body: First line should end with a period + suggestion: Add period + fixable: true + location: + row: 2 + column: 4 + end_location: + row: 3 + column: 72 + fix: + content: "." + location: + row: 3 + column: 69 + end_location: + row: 3 + column: 69 + parent: ~ +