diff --git a/README.md b/README.md index 38ae3fc8efbf8..fcffe125b3fc7 100644 --- a/README.md +++ b/README.md @@ -2217,7 +2217,8 @@ target-version = "py37" A list of task tags to recognize (e.g., "TODO", "FIXME", "XXX"). Comments starting with these tags will be ignored by commented-out code -detection (`ERA`). +detection (`ERA`), and skipped by line-length checks (`E501`) if +`ignore-overlong-task-comments` is set to `true`. **Default value**: `["TODO", "FIXME", "XXX"]` @@ -2980,6 +2981,25 @@ staticmethod-decorators = ["staticmethod", "stcmthd"] ### `pycodestyle` +#### [`ignore-overlong-task-comments`](#ignore-overlong-task-comments) + +Whether or not line-length checks (`E501`) should be triggered for +comments starting with `task-tags` (by default: ["TODO", "FIXME", +and "XXX"]). + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff.pycodestyle] +ignore-overlong-task-comments = true +``` + +--- + ### `pydocstyle` #### [`convention`](#convention) diff --git a/resources/test/fixtures/pycodestyle/E501_1.py b/resources/test/fixtures/pycodestyle/E501_1.py new file mode 100644 index 0000000000000..6c9e62fa81a0d --- /dev/null +++ b/resources/test/fixtures/pycodestyle/E501_1.py @@ -0,0 +1,6 @@ +# TODO: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` +# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` +# TODO comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` +# FIXME: comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` +# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` +# FIXME comments starting with one of the configured task-tags sometimes are longer than line-length so that you can easily find them with `git grep` diff --git a/ruff.schema.json b/ruff.schema.json index 6e1604100831e..9c8990bd6ea9b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -378,7 +378,7 @@ ] }, "task-tags": { - "description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code detection (`ERA`).", + "description": "A list of task tags to recognize (e.g., \"TODO\", \"FIXME\", \"XXX\").\n\nComments starting with these tags will be ignored by commented-out code detection (`ERA`), and skipped by line-length checks (`E501`) if `ignore-overlong-task-comments` is set to `true`.", "type": [ "array", "null" @@ -1461,6 +1461,15 @@ }, "Pycodestyle": { "type": "object", + "properties": { + "ignore-overlong-task-comments": { + "description": "Whether or not line-length checks (`E501`) should be triggered for comments starting with `task-tags` (by default: [\"TODO\", \"FIXME\", and \"XXX\"]).", + "type": [ + "boolean", + "null" + ] + } + }, "additionalProperties": false }, "Pydocstyle": { diff --git a/src/checkers/lines.rs b/src/checkers/lines.rs index 2d244ef932795..6cd8d5397fa35 100644 --- a/src/checkers/lines.rs +++ b/src/checkers/lines.rs @@ -57,7 +57,7 @@ pub fn check_lines( } if enforce_line_too_long { - if let Some(check) = line_too_long(index, line, settings.line_length) { + if let Some(check) = line_too_long(index, line, settings) { checks.push(check); } } diff --git a/src/pycodestyle/checks.rs b/src/pycodestyle/checks.rs index c4f5f893367be..d62dc13879b28 100644 --- a/src/pycodestyle/checks.rs +++ b/src/pycodestyle/checks.rs @@ -8,34 +8,44 @@ use crate::ast::helpers::except_range; use crate::ast::types::Range; use crate::autofix::Fix; use crate::registry::{Check, CheckKind}; +use crate::settings::Settings; use crate::source_code_locator::SourceCodeLocator; static URL_REGEX: Lazy = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap()); /// E501 -pub fn line_too_long(lineno: usize, line: &str, max_line_length: usize) -> Option { +pub fn line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option { let line_length = line.chars().count(); - if line_length <= max_line_length { + if line_length <= settings.line_length { return None; } let mut chunks = line.split_whitespace(); - let (Some(first), Some(_)) = (chunks.next(), chunks.next()) else { + let (Some(first), Some(second)) = (chunks.next(), chunks.next()) else { // Single word / no printable chars - no way to make the line shorter return None; }; - // Do not enforce the line length for commented lines that end with a URL - // or contain only a single word. - if first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)) { - return None; + if first == "#" { + if settings.pycodestyle.ignore_overlong_task_comments { + let second = second.trim_end_matches(':'); + if settings.task_tags.iter().any(|tag| tag == second) { + return None; + } + } + + // Do not enforce the line length for commented lines that end with a URL + // or contain only a single word. + if chunks.last().map_or(true, |c| URL_REGEX.is_match(c)) { + return None; + } } Some(Check::new( - CheckKind::LineTooLong(line_length, max_line_length), + CheckKind::LineTooLong(line_length, settings.line_length), Range::new( - Location::new(lineno + 1, max_line_length), + Location::new(lineno + 1, settings.line_length), Location::new(lineno + 1, line_length), ), )) diff --git a/src/pycodestyle/mod.rs b/src/pycodestyle/mod.rs index 36faa8b942690..49ee8db613090 100644 --- a/src/pycodestyle/mod.rs +++ b/src/pycodestyle/mod.rs @@ -10,6 +10,7 @@ mod tests { use anyhow::Result; use test_case::test_case; + use super::settings::Settings; use crate::linter::test_path; use crate::registry::CheckCode; use crate::settings; @@ -57,4 +58,21 @@ mod tests { insta::assert_yaml_snapshot!(checks); Ok(()) } + + #[test_case(false)] + #[test_case(true)] + fn task_tags(ignore_overlong_task_comments: bool) -> Result<()> { + let snapshot = format!("task_tags_{ignore_overlong_task_comments}"); + let checks = test_path( + Path::new("./resources/test/fixtures/pycodestyle/E501_1.py"), + &settings::Settings { + pycodestyle: Settings { + ignore_overlong_task_comments, + }, + ..settings::Settings::for_rule(CheckCode::E501) + }, + )?; + insta::assert_yaml_snapshot!(snapshot, checks); + Ok(()) + } } diff --git a/src/pycodestyle/settings.rs b/src/pycodestyle/settings.rs index ed37731d8814d..a64e1e4426c8b 100644 --- a/src/pycodestyle/settings.rs +++ b/src/pycodestyle/settings.rs @@ -8,19 +8,39 @@ use serde::{Deserialize, Serialize}; Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema, )] #[serde(deny_unknown_fields, rename_all = "kebab-case", rename = "Pycodestyle")] -pub struct Options {} +pub struct Options { + #[option( + default = "false", + value_type = "bool", + example = r#" + ignore-overlong-task-comments = true + "# + )] + /// Whether or not line-length checks (`E501`) should be triggered for + /// comments starting with `task-tags` (by default: ["TODO", "FIXME", + /// and "XXX"]). + pub ignore_overlong_task_comments: Option, +} #[derive(Debug, Default, Hash)] -pub struct Settings {} +pub struct Settings { + pub ignore_overlong_task_comments: bool, +} impl From for Settings { fn from(options: Options) -> Self { - Self {} + Self { + ignore_overlong_task_comments: options + .ignore_overlong_task_comments + .unwrap_or_default(), + } } } impl From for Options { fn from(settings: Settings) -> Self { - Self {} + Self { + ignore_overlong_task_comments: Some(settings.ignore_overlong_task_comments), + } } } diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_false.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_false.snap new file mode 100644 index 0000000000000..050ba2dede7ce --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_false.snap @@ -0,0 +1,77 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +- kind: + LineTooLong: + - 149 + - 88 + location: + row: 1 + column: 88 + end_location: + row: 1 + column: 149 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 148 + - 88 + location: + row: 2 + column: 88 + end_location: + row: 2 + column: 148 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 155 + - 88 + location: + row: 3 + column: 88 + end_location: + row: 3 + column: 155 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 150 + - 88 + location: + row: 4 + column: 88 + end_location: + row: 4 + column: 150 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 149 + - 88 + location: + row: 5 + column: 88 + end_location: + row: 5 + column: 149 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 156 + - 88 + location: + row: 6 + column: 88 + end_location: + row: 6 + column: 156 + fix: ~ + parent: ~ + diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_true.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_true.snap new file mode 100644 index 0000000000000..803588cfbe0fc --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__task_tags_true.snap @@ -0,0 +1,6 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +[] + diff --git a/src/settings/options.rs b/src/settings/options.rs index b4d31ebdfb071..094cfe9dcc79a 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -347,7 +347,8 @@ pub struct Options { /// A list of task tags to recognize (e.g., "TODO", "FIXME", "XXX"). /// /// Comments starting with these tags will be ignored by commented-out code - /// detection (`ERA`). + /// detection (`ERA`), and skipped by line-length checks (`E501`) if + /// `ignore-overlong-task-comments` is set to `true`. pub task_tags: Option>, #[option( default = "true",