diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D400_415.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D400_415.py new file mode 100644 index 0000000000..9134866026 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D400_415.py @@ -0,0 +1,20 @@ +def f(): + "Here's a line ending in a question mark?" + ... + + +def f(): + """Here's a line ending in an exclamation mark!""" + ... + +def f(): + """Here's a line ending in a colon:""" + ... + +def f(): + """Here's a line ending in a semi colon;""" + ... + +def f(): + """Here's a line ending with a whitespace """ + ... \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/pydocstyle/mod.rs b/crates/ruff_linter/src/rules/pydocstyle/mod.rs index 4d129b1430..9f387fdebb 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/mod.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/mod.rs @@ -29,7 +29,9 @@ mod tests { #[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::EndsInPeriod, Path::new("D400_415.py"))] #[test_case(Rule::EndsInPunctuation, Path::new("D.py"))] + #[test_case(Rule::EndsInPunctuation, Path::new("D400_415.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"))] diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs index 73c64b3850..7bf3583cf3 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_period.rs @@ -1,7 +1,7 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -47,14 +47,18 @@ use crate::rules::pydocstyle::helpers::logical_line; #[violation] pub struct EndsInPeriod; -impl AlwaysFixableViolation for EndsInPeriod { +impl Violation for EndsInPeriod { + /// `None` in the case a fix is never available or otherwise Some + /// [`FixAvailability`] describing the available fix. + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { format!("First line should end with a period") } - fn fix_title(&self) -> String { - "Add period".to_string() + fn fix_title(&self) -> Option { + Some("Add period".to_string()) } } @@ -104,7 +108,7 @@ pub(crate) fn ends_with_period(checker: &mut Checker, docstring: &Docstring) { if !trimmed.ends_with('.') { let mut diagnostic = Diagnostic::new(EndsInPeriod, docstring.range()); // Best-effort fix: avoid adding a period after other punctuation marks. - if !trimmed.ends_with([':', ';']) { + if !trimmed.ends_with([':', ';', '?', '!']) { diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion( ".".to_string(), line.start() + trimmed.text_len(), diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs index 6f8cf9ef2f..4b7ae2633e 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/ends_with_punctuation.rs @@ -1,7 +1,7 @@ use ruff_text_size::TextLen; use strum::IntoEnumIterator; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines}; use ruff_text_size::Ranged; @@ -46,14 +46,18 @@ use crate::rules::pydocstyle::helpers::logical_line; #[violation] pub struct EndsInPunctuation; -impl AlwaysFixableViolation for EndsInPunctuation { +impl Violation for EndsInPunctuation { + /// `None` in the case a fix is never available or otherwise Some + /// [`FixAvailability`] describing the available fix. + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { format!("First line should end with a period, question mark, or exclamation point") } - fn fix_title(&self) -> String { - "Add closing punctuation".to_string() + fn fix_title(&self) -> Option { + Some("Add closing punctuation".to_string()) } } diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap index aacdb3584b..9953a0c2ef 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D.py.snap @@ -194,7 +194,7 @@ D.py:487:5: D400 [*] First line should end with a period 489 489 | 490 490 | -D.py:514:5: D400 [*] First line should end with a period +D.py:514:5: D400 First line should end with a period | 513 | def valid_google_string(): # noqa: D400 514 | """Test a valid something!""" @@ -202,16 +202,6 @@ D.py:514:5: D400 [*] First line should end with a period | = help: Add period -ℹ Unsafe 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')") @@ -328,6 +318,4 @@ D.py:664:5: D400 [*] First line should end with a period 665 |+ but continuations shouldn't be considered multi-line." 666 666 | 667 667 | -668 668 | - - +668 668 | diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D400_415.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D400_415.py.snap new file mode 100644 index 0000000000..d4dd5303ed --- /dev/null +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D400_D400_415.py.snap @@ -0,0 +1,55 @@ +--- +source: crates/ruff_linter/src/rules/pydocstyle/mod.rs +--- +D400_415.py:2:5: D400 First line should end with a period + | +1 | def f(): +2 | "Here's a line ending in a question mark?" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400 +3 | ... + | + = help: Add period + +D400_415.py:7:5: D400 First line should end with a period + | +6 | def f(): +7 | """Here's a line ending in an exclamation mark!""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400 +8 | ... + | + = help: Add period + +D400_415.py:11:5: D400 First line should end with a period + | +10 | def f(): +11 | """Here's a line ending in a colon:""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400 +12 | ... + | + = help: Add period + +D400_415.py:15:5: D400 First line should end with a period + | +14 | def f(): +15 | """Here's a line ending in a semi colon;""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400 +16 | ... + | + = help: Add period + +D400_415.py:19:5: D400 [*] First line should end with a period + | +18 | def f(): +19 | """Here's a line ending with a whitespace """ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D400 +20 | ... + | + = help: Add period + +ℹ Unsafe fix +16 16 | ... +17 17 | +18 18 | def f(): +19 |- """Here's a line ending with a whitespace """ + 19 |+ """Here's a line ending with a whitespace. """ +20 20 | ... diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D400_415.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D400_415.py.snap new file mode 100644 index 0000000000..e59a2755af --- /dev/null +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D400_415.py.snap @@ -0,0 +1,37 @@ +--- +source: crates/ruff_linter/src/rules/pydocstyle/mod.rs +--- +D400_415.py:11:5: D415 First line should end with a period, question mark, or exclamation point + | +10 | def f(): +11 | """Here's a line ending in a colon:""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +12 | ... + | + = help: Add closing punctuation + +D400_415.py:15:5: D415 First line should end with a period, question mark, or exclamation point + | +14 | def f(): +15 | """Here's a line ending in a semi colon;""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +16 | ... + | + = help: Add closing punctuation + +D400_415.py:19:5: D415 [*] First line should end with a period, question mark, or exclamation point + | +18 | def f(): +19 | """Here's a line ending with a whitespace """ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +20 | ... + | + = help: Add closing punctuation + +ℹ Unsafe fix +16 16 | ... +17 17 | +18 18 | def f(): +19 |- """Here's a line ending with a whitespace """ + 19 |+ """Here's a line ending with a whitespace. """ +20 20 | ... diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D415.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D415.py.snap new file mode 100644 index 0000000000..1a644bdd36 --- /dev/null +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D415_D415.py.snap @@ -0,0 +1,37 @@ +--- +source: crates/ruff_linter/src/rules/pydocstyle/mod.rs +--- +D415.py:11:5: D415 First line should end with a period, question mark, or exclamation point + | +10 | def f(): +11 | """Here's a line ending in a colon:""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +12 | ... + | + = help: Add closing punctuation + +D415.py:15:5: D415 First line should end with a period, question mark, or exclamation point + | +14 | def f(): +15 | """Here's a line ending in a semi colon;""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +16 | ... + | + = help: Add closing punctuation + +D415.py:19:5: D415 [*] First line should end with a period, question mark, or exclamation point + | +18 | def f(): +19 | """Here's a line ending with a whitespace """ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D415 +20 | ... + | + = help: Add closing punctuation + +ℹ Unsafe fix +16 16 | ... +17 17 | +18 18 | def f(): +19 |- """Here's a line ending with a whitespace """ + 19 |+ """Here's a line ending with a whitespace. """ +20 20 | ...