diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E26.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E26.py index 9d35553dc521a..052baafcab781 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E26.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E26.py @@ -72,3 +72,15 @@ def oof(): # EF Means test is giving error and Failing #! Means test is segfaulting # 8 Means test runs forever + +#: Colon prefix is okay + +###This is a variable ### + +# We should strip the space, but preserve the hashes. +#: E266:1:3 +## Foo + +a = 1 ## Foo + +a = 1 #:Foo diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs index 235c12f7b6c65..0df27d8fe583b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_parser::TokenKind; use ruff_python_trivia::PythonWhitespace; @@ -66,11 +66,15 @@ impl AlwaysFixableViolation for TooFewSpacesBeforeInlineComment { #[violation] pub struct NoSpaceAfterInlineComment; -impl Violation for NoSpaceAfterInlineComment { +impl AlwaysFixableViolation for NoSpaceAfterInlineComment { #[derive_message_formats] fn message(&self) -> String { format!("Inline comment should start with `# `") } + + fn fix_title(&self) -> String { + format!("Format space") + } } /// ## What it does @@ -98,11 +102,15 @@ impl Violation for NoSpaceAfterInlineComment { #[violation] pub struct NoSpaceAfterBlockComment; -impl Violation for NoSpaceAfterBlockComment { +impl AlwaysFixableViolation for NoSpaceAfterBlockComment { #[derive_message_formats] fn message(&self) -> String { format!("Block comment should start with `# `") } + + fn fix_title(&self) -> String { + format!("Format space") + } } /// ## What it does @@ -130,11 +138,15 @@ impl Violation for NoSpaceAfterBlockComment { #[violation] pub struct MultipleLeadingHashesForBlockComment; -impl Violation for MultipleLeadingHashesForBlockComment { +impl AlwaysFixableViolation for MultipleLeadingHashesForBlockComment { #[derive_message_formats] fn message(&self) -> String { format!("Too many leading `#` before block comment") } + + fn fix_title(&self) -> String { + format!("Remove leading `#`") + } } /// E261, E262, E265, E266 @@ -184,14 +196,30 @@ pub(crate) fn whitespace_before_comment( if is_inline_comment { if bad_prefix.is_some() || comment.chars().next().is_some_and(char::is_whitespace) { - context.push(NoSpaceAfterInlineComment, range); + let mut diagnostic = Diagnostic::new(NoSpaceAfterInlineComment, range); + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + format_leading_space(token_text), + range, + ))); + context.push_diagnostic(diagnostic); } } else if let Some(bad_prefix) = bad_prefix { if bad_prefix != '!' || !line.is_start_of_file() { if bad_prefix != '#' { - context.push(NoSpaceAfterBlockComment, range); + let mut diagnostic = Diagnostic::new(NoSpaceAfterBlockComment, range); + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + format_leading_space(token_text), + range, + ))); + context.push_diagnostic(diagnostic); } else if !comment.is_empty() { - context.push(MultipleLeadingHashesForBlockComment, range); + let mut diagnostic = + Diagnostic::new(MultipleLeadingHashesForBlockComment, range); + diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + format_leading_hashes(token_text), + range, + ))); + context.push_diagnostic(diagnostic); } } } @@ -200,3 +228,17 @@ pub(crate) fn whitespace_before_comment( } } } + +/// Format a comment to have a single space after the `#`. +fn format_leading_space(comment: &str) -> String { + if let Some(rest) = comment.strip_prefix("#:") { + format!("#: {}", rest.trim_start()) + } else { + format!("# {}", comment.trim_start_matches('#').trim_start()) + } +} + +/// Format a comment to strip multiple leading `#` characters. +fn format_leading_hashes(comment: &str) -> String { + format!("# {}", comment.trim_start_matches('#').trim_start()) +} diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E262_E26.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E262_E26.py.snap index a01a1eb2d7cd1..658391acdc36b 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E262_E26.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E262_E26.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E26.py:4:12: E262 Inline comment should start with `# ` +E26.py:4:12: E262 [*] Inline comment should start with `# ` | 2 | pass # an inline comment 3 | #: E262:1:12 @@ -10,8 +10,19 @@ E26.py:4:12: E262 Inline comment should start with `# ` 5 | #: E262:1:12 6 | x = x + 1 # Increment x | + = help: Format space -E26.py:6:12: E262 Inline comment should start with `# ` +ℹ Safe fix +1 1 | #: E261:1:5 +2 2 | pass # an inline comment +3 3 | #: E262:1:12 +4 |-x = x + 1 #Increment x + 4 |+x = x + 1 # Increment x +5 5 | #: E262:1:12 +6 6 | x = x + 1 # Increment x +7 7 | #: E262:1:12 + +E26.py:6:12: E262 [*] Inline comment should start with `# ` | 4 | x = x + 1 #Increment x 5 | #: E262:1:12 @@ -20,8 +31,19 @@ E26.py:6:12: E262 Inline comment should start with `# ` 7 | #: E262:1:12 8 | x = y + 1 #: Increment x | + = help: Format space + +ℹ Safe fix +3 3 | #: E262:1:12 +4 4 | x = x + 1 #Increment x +5 5 | #: E262:1:12 +6 |-x = x + 1 # Increment x + 6 |+x = x + 1 # Increment x +7 7 | #: E262:1:12 +8 8 | x = y + 1 #: Increment x +9 9 | #: E265:1:1 -E26.py:8:12: E262 Inline comment should start with `# ` +E26.py:8:12: E262 [*] Inline comment should start with `# ` | 6 | x = x + 1 # Increment x 7 | #: E262:1:12 @@ -30,8 +52,19 @@ E26.py:8:12: E262 Inline comment should start with `# ` 9 | #: E265:1:1 10 | #Block comment | + = help: Format space -E26.py:63:9: E262 Inline comment should start with `# ` +ℹ Safe fix +5 5 | #: E262:1:12 +6 6 | x = x + 1 # Increment x +7 7 | #: E262:1:12 +8 |-x = y + 1 #: Increment x + 8 |+x = y + 1 #: Increment x +9 9 | #: E265:1:1 +10 10 | #Block comment +11 11 | a = 1 + +E26.py:63:9: E262 [*] Inline comment should start with `# ` | 61 | # -*- coding: utf8 -*- 62 | #  (One space one NBSP) Ok for block comment @@ -40,8 +73,19 @@ E26.py:63:9: E262 Inline comment should start with `# ` 64 | #: E262:2:9 65 | # (Two spaces) Ok for block comment | + = help: Format space + +ℹ Safe fix +60 60 | #: E262:3:9 +61 61 | # -*- coding: utf8 -*- +62 62 | #  (One space one NBSP) Ok for block comment +63 |-a = 42 #  (One space one NBSP) + 63 |+a = 42 # (One space one NBSP) +64 64 | #: E262:2:9 +65 65 | # (Two spaces) Ok for block comment +66 66 | a = 42 # (Two spaces) -E26.py:66:9: E262 Inline comment should start with `# ` +E26.py:66:9: E262 [*] Inline comment should start with `# ` | 64 | #: E262:2:9 65 | # (Two spaces) Ok for block comment @@ -50,5 +94,52 @@ E26.py:66:9: E262 Inline comment should start with `# ` 67 | 68 | #: E265:5:1 | + = help: Format space + +ℹ Safe fix +63 63 | a = 42 #  (One space one NBSP) +64 64 | #: E262:2:9 +65 65 | # (Two spaces) Ok for block comment +66 |-a = 42 # (Two spaces) + 66 |+a = 42 # (Two spaces) +67 67 | +68 68 | #: E265:5:1 +69 69 | ### Means test is not done yet + +E26.py:84:8: E262 [*] Inline comment should start with `# ` + | +82 | ## Foo +83 | +84 | a = 1 ## Foo + | ^^^^^^ E262 +85 | +86 | a = 1 #:Foo + | + = help: Format space + +ℹ Safe fix +81 81 | #: E266:1:3 +82 82 | ## Foo +83 83 | +84 |-a = 1 ## Foo + 84 |+a = 1 # Foo +85 85 | +86 86 | a = 1 #:Foo + +E26.py:86:8: E262 [*] Inline comment should start with `# ` + | +84 | a = 1 ## Foo +85 | +86 | a = 1 #:Foo + | ^^^^^ E262 + | + = help: Format space + +ℹ Safe fix +83 83 | +84 84 | a = 1 ## Foo +85 85 | +86 |-a = 1 #:Foo + 86 |+a = 1 #: Foo diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E265_E26.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E265_E26.py.snap index 6017827141782..92b25ccb212b6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E265_E26.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E265_E26.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E26.py:10:1: E265 Block comment should start with `# ` +E26.py:10:1: E265 [*] Block comment should start with `# ` | 8 | x = y + 1 #: Increment x 9 | #: E265:1:1 @@ -10,8 +10,19 @@ E26.py:10:1: E265 Block comment should start with `# ` 11 | a = 1 12 | #: E265:2:1 | + = help: Format space -E26.py:14:1: E265 Block comment should start with `# ` +ℹ Safe fix +7 7 | #: E262:1:12 +8 8 | x = y + 1 #: Increment x +9 9 | #: E265:1:1 +10 |-#Block comment + 10 |+# Block comment +11 11 | a = 1 +12 12 | #: E265:2:1 +13 13 | m = 42 + +E26.py:14:1: E265 [*] Block comment should start with `# ` | 12 | #: E265:2:1 13 | m = 42 @@ -20,8 +31,19 @@ E26.py:14:1: E265 Block comment should start with `# ` 15 | mx = 42 - 42 16 | #: E266:3:5 E266:6:5 | + = help: Format space + +ℹ Safe fix +11 11 | a = 1 +12 12 | #: E265:2:1 +13 13 | m = 42 +14 |-#! This is important + 14 |+# ! This is important +15 15 | mx = 42 - 42 +16 16 | #: E266:3:5 E266:6:5 +17 17 | def how_it_feel(r): -E26.py:25:1: E265 Block comment should start with `# ` +E26.py:25:1: E265 [*] Block comment should start with `# ` | 23 | return 24 | #: E265:1:1 E266:2:1 @@ -30,8 +52,19 @@ E26.py:25:1: E265 Block comment should start with `# ` 26 | ## logging.error() 27 | #: W291:1:42 | + = help: Format space + +ℹ Safe fix +22 22 | ### Of course it is unused +23 23 | return +24 24 | #: E265:1:1 E266:2:1 +25 |-##if DEBUG: + 25 |+# if DEBUG: +26 26 | ## logging.error() +27 27 | #: W291:1:42 +28 28 | ######################################### -E26.py:32:1: E265 Block comment should start with `# ` +E26.py:32:1: E265 [*] Block comment should start with `# ` | 31 | #: Okay 32 | #!/usr/bin/env python @@ -39,8 +72,19 @@ E26.py:32:1: E265 Block comment should start with `# ` 33 | 34 | pass # an inline comment | + = help: Format space -E26.py:73:1: E265 Block comment should start with `# ` +ℹ Safe fix +29 29 | #: +30 30 | +31 31 | #: Okay +32 |-#!/usr/bin/env python + 32 |+# !/usr/bin/env python +33 33 | +34 34 | pass # an inline comment +35 35 | x = x + 1 # Increment x + +E26.py:73:1: E265 [*] Block comment should start with `# ` | 71 | # F Means test is failing (F) 72 | # EF Means test is giving error and Failing @@ -48,5 +92,37 @@ E26.py:73:1: E265 Block comment should start with `# ` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E265 74 | # 8 Means test runs forever | + = help: Format space + +ℹ Safe fix +70 70 | # E Means test is giving error (E) +71 71 | # F Means test is failing (F) +72 72 | # EF Means test is giving error and Failing +73 |-#! Means test is segfaulting + 73 |+# ! Means test is segfaulting +74 74 | # 8 Means test runs forever +75 75 | +76 76 | #: Colon prefix is okay + +E26.py:78:1: E265 [*] Block comment should start with `# ` + | +76 | #: Colon prefix is okay +77 | +78 | ###This is a variable ### + | ^^^^^^^^^^^^^^^^^^^^^^^^^ E265 +79 | +80 | # We should strip the space, but preserve the hashes. + | + = help: Format space + +ℹ Safe fix +75 75 | +76 76 | #: Colon prefix is okay +77 77 | +78 |-###This is a variable ### + 78 |+# This is a variable ### +79 79 | +80 80 | # We should strip the space, but preserve the hashes. +81 81 | #: E266:1:3 diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E266_E26.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E266_E26.py.snap index 5eb382385cccb..4469e1b225d64 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E266_E26.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E266_E26.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E26.py:19:5: E266 Too many leading `#` before block comment +E26.py:19:5: E266 [*] Too many leading `#` before block comment | 17 | def how_it_feel(r): 18 | @@ -9,8 +9,19 @@ E26.py:19:5: E266 Too many leading `#` before block comment | ^^^^^^^^^^^^^^^^^^^^^^^^^^ E266 20 | a = 42 | + = help: Remove leading `#` -E26.py:22:5: E266 Too many leading `#` before block comment +ℹ Safe fix +16 16 | #: E266:3:5 E266:6:5 +17 17 | def how_it_feel(r): +18 18 | +19 |- ### This is a variable ### + 19 |+ # This is a variable ### +20 20 | a = 42 +21 21 | +22 22 | ### Of course it is unused + +E26.py:22:5: E266 [*] Too many leading `#` before block comment | 20 | a = 42 21 | @@ -19,8 +30,19 @@ E26.py:22:5: E266 Too many leading `#` before block comment 23 | return 24 | #: E265:1:1 E266:2:1 | + = help: Remove leading `#` + +ℹ Safe fix +19 19 | ### This is a variable ### +20 20 | a = 42 +21 21 | +22 |- ### Of course it is unused + 22 |+ # Of course it is unused +23 23 | return +24 24 | #: E265:1:1 E266:2:1 +25 25 | ##if DEBUG: -E26.py:26:1: E266 Too many leading `#` before block comment +E26.py:26:1: E266 [*] Too many leading `#` before block comment | 24 | #: E265:1:1 E266:2:1 25 | ##if DEBUG: @@ -29,8 +51,19 @@ E26.py:26:1: E266 Too many leading `#` before block comment 27 | #: W291:1:42 28 | ######################################### | + = help: Remove leading `#` -E26.py:69:1: E266 Too many leading `#` before block comment +ℹ Safe fix +23 23 | return +24 24 | #: E265:1:1 E266:2:1 +25 25 | ##if DEBUG: +26 |-## logging.error() + 26 |+# logging.error() +27 27 | #: W291:1:42 +28 28 | ######################################### +29 29 | #: + +E26.py:69:1: E266 [*] Too many leading `#` before block comment | 68 | #: E265:5:1 69 | ### Means test is not done yet @@ -38,5 +71,37 @@ E26.py:69:1: E266 Too many leading `#` before block comment 70 | # E Means test is giving error (E) 71 | # F Means test is failing (F) | + = help: Remove leading `#` + +ℹ Safe fix +66 66 | a = 42 # (Two spaces) +67 67 | +68 68 | #: E265:5:1 +69 |-### Means test is not done yet + 69 |+# Means test is not done yet +70 70 | # E Means test is giving error (E) +71 71 | # F Means test is failing (F) +72 72 | # EF Means test is giving error and Failing + +E26.py:82:1: E266 [*] Too many leading `#` before block comment + | +80 | # We should strip the space, but preserve the hashes. +81 | #: E266:1:3 +82 | ## Foo + | ^^^^^^^ E266 +83 | +84 | a = 1 ## Foo + | + = help: Remove leading `#` + +ℹ Safe fix +79 79 | +80 80 | # We should strip the space, but preserve the hashes. +81 81 | #: E266:1:3 +82 |-## Foo + 82 |+# Foo +83 83 | +84 84 | a = 1 ## Foo +85 85 | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__shebang.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__shebang.snap index 63d5b9fefc8c5..fa3897bebc056 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__shebang.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__shebang.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -shebang.py:3:1: E265 Block comment should start with `# ` +shebang.py:3:1: E265 [*] Block comment should start with `# ` | 1 | #!/usr/bin/python 2 | # @@ -9,5 +9,13 @@ shebang.py:3:1: E265 Block comment should start with `# ` | ^^ E265 4 | #: | + = help: Format space + +ℹ Safe fix +1 1 | #!/usr/bin/python +2 2 | # +3 |-#! + 3 |+# ! +4 4 | #: