diff --git a/README.md b/README.md index f3ec7dbeeb6ce5..6ff48b2a121744 100644 --- a/README.md +++ b/README.md @@ -1741,6 +1741,24 @@ cache-dir = "~/.cache/ruff" --- +#### [`comment-tags`](#comment-tags) + +A list of comment tags to recognize (e.g. TODO, FIXME, XXX). +Comments starting with these tags will be skipped by E501 and ERA001. + +**Default value**: `["TODO", "FIXME", "XXX"]` + +**Type**: `Vec` + +**Example usage**: + +```toml +[tool.ruff] +comment-tags = ["TODO", "FIXME", "HACK"] +``` + +--- + #### [`dummy-variable-rgx`](#dummy-variable-rgx) A regular expression used to identify "dummy" variables, or those which @@ -2953,6 +2971,26 @@ staticmethod-decorators = ["staticmethod", "stcmthd"] --- +### `pycodestyle` + +#### [`allow-overlong-lines-for-comment-tags`](#allow-overlong-lines-for-comment-tags) + +Whether or not E501 (LineTooLong) should be triggered for comments +starting with one of the `comment-tags`. + +**Default value**: `false` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff.pycodestyle] +allow-overlong-lines-for-comment-tags = true +``` + +--- + ### `pydocstyle` #### [`convention`](#convention) diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index 53498c7234cf34..9cfb27af784323 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -397,6 +397,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -409,6 +410,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); @@ -457,6 +459,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -469,6 +472,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); @@ -517,6 +521,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -529,6 +534,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); @@ -577,6 +583,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -589,6 +596,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); @@ -637,6 +645,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -654,6 +663,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); @@ -705,6 +715,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -717,6 +728,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: Some(pydocstyle::settings::Options { convention: Some(Convention::Numpy), }), @@ -768,6 +780,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -785,6 +798,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }); diff --git a/resources/test/fixtures/pycodestyle/E501_1.py b/resources/test/fixtures/pycodestyle/E501_1.py new file mode 100644 index 00000000000000..9298ce38daebd5 --- /dev/null +++ b/resources/test/fixtures/pycodestyle/E501_1.py @@ -0,0 +1,6 @@ +# TODO: comments starting with one of the configured comment-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 comment-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 comment-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 comment-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 comment-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 comment-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 0e7d1321808164..4a12d94b6b8a4d 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -22,6 +22,16 @@ "null" ] }, + "comment-tags": { + "description": "A list of comment tags to recognize (e.g. TODO, FIXME, XXX). Comments starting with these tags will be skipped by E501 and ERA001.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, "dummy-variable-rgx": { "description": "A regular expression used to identify \"dummy\" variables, or those which should be ignored when evaluating (e.g.) unused-variable checks. The default expression matches `_`, `__`, and `_var`, but not `_var_`.", "type": [ @@ -288,6 +298,17 @@ } } }, + "pycodestyle": { + "description": "Options for the `pycodestyle` plugin.", + "anyOf": [ + { + "$ref": "#/definitions/Pycodestyle" + }, + { + "type": "null" + } + ] + }, "pydocstyle": { "description": "Options for the `pydocstyle` plugin.", "anyOf": [ @@ -1418,6 +1439,19 @@ }, "additionalProperties": false }, + "Pycodestyle": { + "type": "object", + "properties": { + "allow-overlong-lines-for-comment-tags": { + "description": "Whether or not E501 (LineTooLong) should be triggered for comments starting with one of the `comment-tags`.", + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": false + }, "Pydocstyle": { "type": "object", "properties": { diff --git a/src/checkers/lines.rs b/src/checkers/lines.rs index 2d244ef9327953..6cd8d5397fa35c 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/eradicate/checks.rs b/src/eradicate/checks.rs index 5cefb29b3a6e8b..cbfda5873c9120 100644 --- a/src/eradicate/checks.rs +++ b/src/eradicate/checks.rs @@ -31,7 +31,7 @@ pub fn commented_out_code( let line = locator.slice_source_code_range(&Range::new(location, end_location)); // Verify that the comment is on its own line, and that it contains code. - if is_standalone_comment(&line) && comment_contains_code(&line) { + if is_standalone_comment(&line) && comment_contains_code(&line, &settings.comment_tags[..]) { let mut check = Check::new(CheckKind::CommentedOutCode, Range::new(start, end)); if matches!(autofix, flags::Autofix::Enabled) && settings.fixable.contains(&CheckCode::ERA001) diff --git a/src/eradicate/detection.rs b/src/eradicate/detection.rs index 4d368f9800cad5..1c39bf960cc8ce 100644 --- a/src/eradicate/detection.rs +++ b/src/eradicate/detection.rs @@ -4,7 +4,7 @@ use regex::Regex; static ALLOWLIST_REGEX: Lazy = Lazy::new(|| { Regex::new( - r"^(?i)(?:pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX)" + r"^(?i)(?:pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?))" ).unwrap() }); static BRACKET_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap()); @@ -30,7 +30,7 @@ static PARTIAL_DICTIONARY_REGEX: Lazy = static PRINT_RETURN_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap()); /// Returns `true` if a comment contains Python code. -pub fn comment_contains_code(line: &str) -> bool { +pub fn comment_contains_code(line: &str, comment_tags: &[String]) -> bool { let line = if let Some(line) = line.trim().strip_prefix('#') { line.trim() } else { @@ -47,6 +47,12 @@ pub fn comment_contains_code(line: &str) -> bool { return false; } + if let Some(first) = line.split(&[' ', ':']).next() { + if comment_tags.iter().any(|tag| tag == first) { + return false; + } + } + if CODING_COMMENT_REGEX.is_match(line) { return false; } @@ -97,142 +103,166 @@ mod tests { #[test] fn comment_contains_code_basic() { - assert!(comment_contains_code("# x = 1")); - assert!(comment_contains_code("#from foo import eradicate")); - assert!(comment_contains_code("#import eradicate")); - assert!(comment_contains_code(r#"#"key": value,"#)); - assert!(comment_contains_code(r#"#"key": "value","#)); - assert!(comment_contains_code(r#"#"key": 1 + 1,"#)); - assert!(comment_contains_code("#'key': 1 + 1,")); - assert!(comment_contains_code(r#"#"key": {"#)); - assert!(comment_contains_code("#}")); - assert!(comment_contains_code("#} )]")); - - assert!(!comment_contains_code("#")); - assert!(!comment_contains_code("# This is a (real) comment.")); - assert!(!comment_contains_code("# 123")); - assert!(!comment_contains_code("# 123.1")); - assert!(!comment_contains_code("# 1, 2, 3")); - assert!(!comment_contains_code("x = 1 # x = 1")); + assert!(comment_contains_code("# x = 1", &[])); + assert!(comment_contains_code("#from foo import eradicate", &[])); + assert!(comment_contains_code("#import eradicate", &[])); + assert!(comment_contains_code(r#"#"key": value,"#, &[])); + assert!(comment_contains_code(r#"#"key": "value","#, &[])); + assert!(comment_contains_code(r#"#"key": 1 + 1,"#, &[])); + assert!(comment_contains_code("#'key': 1 + 1,", &[])); + assert!(comment_contains_code(r#"#"key": {"#, &[])); + assert!(comment_contains_code("#}", &[])); + assert!(comment_contains_code("#} )]", &[])); + + assert!(!comment_contains_code("#", &[])); + assert!(!comment_contains_code("# This is a (real) comment.", &[])); + assert!(!comment_contains_code("# 123", &[])); + assert!(!comment_contains_code("# 123.1", &[])); + assert!(!comment_contains_code("# 1, 2, 3", &[])); + assert!(!comment_contains_code("x = 1 # x = 1", &[])); + assert!(!comment_contains_code( + "# pylint: disable=redefined-outer-name", + &[] + ),); assert!(!comment_contains_code( - "# pylint: disable=redefined-outer-name" + "# Issue #999: This is not code", + &[] )); - assert!(!comment_contains_code("# Issue #999: This is not code")); // TODO(charlie): This should be `true` under aggressive mode. - assert!(!comment_contains_code("#},")); + assert!(!comment_contains_code("#},", &[])); } #[test] fn comment_contains_code_with_print() { - assert!(comment_contains_code("#print")); - assert!(comment_contains_code("#print(1)")); - assert!(comment_contains_code("#print 1")); + assert!(comment_contains_code("#print", &[])); + assert!(comment_contains_code("#print(1)", &[])); + assert!(comment_contains_code("#print 1", &[])); - assert!(!comment_contains_code("#to print")); + assert!(!comment_contains_code("#to print", &[])); } #[test] fn comment_contains_code_with_return() { - assert!(comment_contains_code("#return x")); + assert!(comment_contains_code("#return x", &[])); - assert!(!comment_contains_code("#to print")); + assert!(!comment_contains_code("#to print", &[])); } #[test] fn comment_contains_code_with_multiline() { - assert!(comment_contains_code("#else:")); - assert!(comment_contains_code("# else : ")); - assert!(comment_contains_code(r#"# "foo %d" % \\"#)); - assert!(comment_contains_code("#elif True:")); - assert!(comment_contains_code("#x = foo(")); - assert!(comment_contains_code("#except Exception:")); - - assert!(!comment_contains_code("# this is = to that :(")); - assert!(!comment_contains_code("#else")); - assert!(!comment_contains_code("#or else:")); - assert!(!comment_contains_code("#else True:")); + assert!(comment_contains_code("#else:", &[])); + assert!(comment_contains_code("# else : ", &[])); + assert!(comment_contains_code(r#"# "foo %d" % \\"#, &[])); + assert!(comment_contains_code("#elif True:", &[])); + assert!(comment_contains_code("#x = foo(", &[])); + assert!(comment_contains_code("#except Exception:", &[])); + + assert!(!comment_contains_code("# this is = to that :(", &[])); + assert!(!comment_contains_code("#else", &[])); + assert!(!comment_contains_code("#or else:", &[])); + assert!(!comment_contains_code("#else True:", &[])); // Unpacking assignments assert!(comment_contains_code( - "# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create(" - )); + "# user_content_type, _ = TimelineEvent.objects.using(db_alias).get_or_create(", + &[] + ),); assert!(comment_contains_code( - "# (user_content_type, _) = TimelineEvent.objects.using(db_alias).get_or_create(" - )); + "# (user_content_type, _) = TimelineEvent.objects.using(db_alias).get_or_create(", + &[] + ),); assert!(comment_contains_code( - "# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(" + "# ( user_content_type , _ )= TimelineEvent.objects.using(db_alias).get_or_create(", + &[] )); assert!(comment_contains_code( - "# app_label=\"core\", model=\"user\"" + "# app_label=\"core\", model=\"user\"", + &[] )); - assert!(comment_contains_code("# )")); + assert!(comment_contains_code("# )", &[])); // TODO(charlie): This should be `true` under aggressive mode. - assert!(!comment_contains_code("#def foo():")); + assert!(!comment_contains_code("#def foo():", &[])); } #[test] fn comment_contains_code_with_sentences() { - assert!(!comment_contains_code("#code is good")); + assert!(!comment_contains_code("#code is good", &[])); } #[test] fn comment_contains_code_with_encoding() { - assert!(comment_contains_code("# codings=utf-8")); + assert!(comment_contains_code("# codings=utf-8", &[])); - assert!(!comment_contains_code("# coding=utf-8")); - assert!(!comment_contains_code("#coding= utf-8")); - assert!(!comment_contains_code("# coding: utf-8")); - assert!(!comment_contains_code("# encoding: utf8")); + assert!(!comment_contains_code("# coding=utf-8", &[])); + assert!(!comment_contains_code("#coding= utf-8", &[])); + assert!(!comment_contains_code("# coding: utf-8", &[])); + assert!(!comment_contains_code("# encoding: utf8", &[])); } #[test] fn comment_contains_code_with_default_allowlist() { - assert!(!comment_contains_code("# pylint: disable=A0123")); - assert!(!comment_contains_code("# pylint:disable=A0123")); - assert!(!comment_contains_code("# pylint: disable = A0123")); - assert!(!comment_contains_code("# pylint:disable = A0123")); - assert!(!comment_contains_code("# pyright: reportErrorName=true")); - assert!(!comment_contains_code("# noqa")); - assert!(!comment_contains_code("# NOQA")); - assert!(!comment_contains_code("# noqa: A123")); - assert!(!comment_contains_code("# noqa:A123")); - assert!(!comment_contains_code("# nosec")); - assert!(!comment_contains_code("# fmt: on")); - assert!(!comment_contains_code("# fmt: off")); - assert!(!comment_contains_code("# fmt:on")); - assert!(!comment_contains_code("# fmt:off")); - assert!(!comment_contains_code("# isort: on")); - assert!(!comment_contains_code("# isort:on")); - assert!(!comment_contains_code("# isort: off")); - assert!(!comment_contains_code("# isort:off")); - assert!(!comment_contains_code("# isort: skip")); - assert!(!comment_contains_code("# isort:skip")); - assert!(!comment_contains_code("# isort: skip_file")); - assert!(!comment_contains_code("# isort:skip_file")); - assert!(!comment_contains_code("# isort: split")); - assert!(!comment_contains_code("# isort:split")); - assert!(!comment_contains_code("# isort: dont-add-imports")); - assert!(!comment_contains_code("# isort:dont-add-imports")); + assert!(!comment_contains_code("# pylint: disable=A0123", &[])); + assert!(!comment_contains_code("# pylint:disable=A0123", &[])); + assert!(!comment_contains_code("# pylint: disable = A0123", &[])); + assert!(!comment_contains_code("# pylint:disable = A0123", &[])); + assert!(!comment_contains_code( + "# pyright: reportErrorName=true", + &[] + )); + assert!(!comment_contains_code("# noqa", &[])); + assert!(!comment_contains_code("# NOQA", &[])); + assert!(!comment_contains_code("# noqa: A123", &[])); + assert!(!comment_contains_code("# noqa:A123", &[])); + assert!(!comment_contains_code("# nosec", &[])); + assert!(!comment_contains_code("# fmt: on", &[])); + assert!(!comment_contains_code("# fmt: off", &[])); + assert!(!comment_contains_code("# fmt:on", &[])); + assert!(!comment_contains_code("# fmt:off", &[])); + assert!(!comment_contains_code("# isort: on", &[])); + assert!(!comment_contains_code("# isort:on", &[])); + assert!(!comment_contains_code("# isort: off", &[])); + assert!(!comment_contains_code("# isort:off", &[])); + assert!(!comment_contains_code("# isort: skip", &[])); + assert!(!comment_contains_code("# isort:skip", &[])); + assert!(!comment_contains_code("# isort: skip_file", &[])); + assert!(!comment_contains_code("# isort:skip_file", &[])); + assert!(!comment_contains_code("# isort: split", &[])); + assert!(!comment_contains_code("# isort:split", &[])); + assert!(!comment_contains_code("# isort: dont-add-imports", &[])); + assert!(!comment_contains_code("# isort:dont-add-imports", &[])); + assert!(!comment_contains_code( + "# isort: dont-add-imports: [\"import os\"]", + &[] + )); + assert!(!comment_contains_code( + "# isort:dont-add-imports: [\"import os\"]", + &[] + )); + assert!(!comment_contains_code( + "# isort: dont-add-imports:[\"import os\"]", + &[] + )); assert!(!comment_contains_code( - "# isort: dont-add-imports: [\"import os\"]" + "# isort:dont-add-imports:[\"import os\"]", + &[] )); + assert!(!comment_contains_code("# type: ignore", &[])); + assert!(!comment_contains_code("# type:ignore", &[])); + assert!(!comment_contains_code("# type: ignore[import]", &[])); + assert!(!comment_contains_code("# type:ignore[import]", &[])); assert!(!comment_contains_code( - "# isort:dont-add-imports: [\"import os\"]" + "# TODO: Do that", + &["TODO".to_string()] )); assert!(!comment_contains_code( - "# isort: dont-add-imports:[\"import os\"]" + "# FIXME: Fix that", + &["FIXME".to_string()] )); assert!(!comment_contains_code( - "# isort:dont-add-imports:[\"import os\"]" + "# XXX: What ever", + &["XXX".to_string()] )); - assert!(!comment_contains_code("# type: ignore")); - assert!(!comment_contains_code("# type:ignore")); - assert!(!comment_contains_code("# type: ignore[import]")); - assert!(!comment_contains_code("# type:ignore[import]")); - assert!(!comment_contains_code("# TODO: Do that")); - assert!(!comment_contains_code("# FIXME: Fix that")); - assert!(!comment_contains_code("# XXX: What ever")); } } diff --git a/src/lib_wasm.rs b/src/lib_wasm.rs index 6a949dbbab3292..708bb50375104d 100644 --- a/src/lib_wasm.rs +++ b/src/lib_wasm.rs @@ -112,6 +112,7 @@ pub fn defaultSettings() -> Result { show_source: None, src: None, unfixable: None, + comment_tags: None, update_check: None, // Use default options for all plugins. flake8_annotations: Some(flake8_annotations::settings::Settings::default().into()), diff --git a/src/pycodestyle/checks.rs b/src/pycodestyle/checks.rs index c4f5f893367be2..d4df80b281bb4d 100644 --- a/src/pycodestyle/checks.rs +++ b/src/pycodestyle/checks.rs @@ -8,34 +8,41 @@ 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; }; + let second = second.trim_end_matches(':'); + // 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)) { + if first == "#" + && ((settings.pycodestyle.allow_overlong_lines_for_comment_tags + && settings.comment_tags.iter().any(|tag| tag == second)) + || 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 8027c14f3950e0..dbb2f59f339d2e 100644 --- a/src/pycodestyle/mod.rs +++ b/src/pycodestyle/mod.rs @@ -1,5 +1,6 @@ pub mod checks; pub mod plugins; +pub mod settings; #[cfg(test)] mod tests { @@ -9,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; @@ -56,4 +58,20 @@ mod tests { insta::assert_yaml_snapshot!(checks); Ok(()) } + + #[test_case(false)] + #[test_case(true)] + fn e501_comment_tags(allow_overlong_lines_for_comment_tags: bool) -> Result<()> { + let checks = test_path( + Path::new("./resources/test/fixtures/pycodestyle/E501_1.py"), + &settings::Settings { + pycodestyle: Settings { + allow_overlong_lines_for_comment_tags, + }, + ..settings::Settings::for_rule(CheckCode::E501) + }, + )?; + insta::assert_yaml_snapshot!(checks); + Ok(()) + } } diff --git a/src/pycodestyle/settings.rs b/src/pycodestyle/settings.rs new file mode 100644 index 00000000000000..66d4071742527d --- /dev/null +++ b/src/pycodestyle/settings.rs @@ -0,0 +1,47 @@ +//! Settings for the `pycodestyle` plugin. + +use ruff_macros::ConfigurationOptions; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, JsonSchema, +)] +#[serde(deny_unknown_fields, rename_all = "kebab-case", rename = "Pycodestyle")] +pub struct Options { + #[option( + default = "false", + value_type = "bool", + example = r#" + allow-overlong-lines-for-comment-tags = true + "# + )] + /// Whether or not E501 (LineTooLong) should be triggered for comments + /// starting with one of the `comment-tags`. + pub allow_overlong_lines_for_comment_tags: Option, +} + +#[derive(Debug, Default, Hash)] +pub struct Settings { + pub allow_overlong_lines_for_comment_tags: bool, +} + +impl From for Settings { + fn from(options: Options) -> Self { + Self { + allow_overlong_lines_for_comment_tags: options + .allow_overlong_lines_for_comment_tags + .unwrap_or_default(), + } + } +} + +impl From for Options { + fn from(settings: Settings) -> Self { + Self { + allow_overlong_lines_for_comment_tags: Some( + settings.allow_overlong_lines_for_comment_tags, + ), + } + } +} diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags-2.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags-2.snap new file mode 100644 index 00000000000000..803588cfbe0fc0 --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags-2.snap @@ -0,0 +1,6 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +[] + diff --git a/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags.snap b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags.snap new file mode 100644 index 00000000000000..d39c77667c2736 --- /dev/null +++ b/src/pycodestyle/snapshots/ruff__pycodestyle__tests__e501_comment_tags.snap @@ -0,0 +1,77 @@ +--- +source: src/pycodestyle/mod.rs +expression: checks +--- +- kind: + LineTooLong: + - 152 + - 88 + location: + row: 1 + column: 88 + end_location: + row: 1 + column: 152 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 151 + - 88 + location: + row: 2 + column: 88 + end_location: + row: 2 + column: 151 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 158 + - 88 + location: + row: 3 + column: 88 + end_location: + row: 3 + column: 158 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 153 + - 88 + location: + row: 4 + column: 88 + end_location: + row: 4 + column: 153 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 152 + - 88 + location: + row: 5 + column: 88 + end_location: + row: 5 + column: 152 + fix: ~ + parent: ~ +- kind: + LineTooLong: + - 159 + - 88 + location: + row: 6 + column: 88 + end_location: + row: 6 + column: 159 + fix: ~ + parent: ~ + diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index 370d813113cf9e..3a49e1a786ee08 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -22,7 +22,7 @@ use crate::settings::types::{ use crate::{ flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, fs, isort, - mccabe, pep8_naming, pydocstyle, pyupgrade, + mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, }; #[derive(Debug, Default)] @@ -53,6 +53,7 @@ pub struct Configuration { pub target_version: Option, pub unfixable: Option>, pub update_check: Option, + pub comment_tags: Option>, // Plugins pub flake8_annotations: Option, pub flake8_bugbear: Option, @@ -65,6 +66,7 @@ pub struct Configuration { pub isort: Option, pub mccabe: Option, pub pep8_naming: Option, + pub pycodestyle: Option, pub pydocstyle: Option, pub pyupgrade: Option, } @@ -149,6 +151,7 @@ impl Configuration { .transpose()?, target_version: options.target_version, unfixable: options.unfixable, + comment_tags: options.comment_tags, update_check: options.update_check, // Plugins flake8_annotations: options.flake8_annotations, @@ -162,6 +165,7 @@ impl Configuration { isort: options.isort, mccabe: options.mccabe, pep8_naming: options.pep8_naming, + pycodestyle: options.pycodestyle, pydocstyle: options.pydocstyle, pyupgrade: options.pyupgrade, }) @@ -209,6 +213,7 @@ impl Configuration { src: self.src.or(config.src), target_version: self.target_version.or(config.target_version), unfixable: self.unfixable.or(config.unfixable), + comment_tags: self.comment_tags.or(config.comment_tags), update_check: self.update_check.or(config.update_check), // Plugins flake8_annotations: self.flake8_annotations.or(config.flake8_annotations), @@ -226,6 +231,7 @@ impl Configuration { isort: self.isort.or(config.isort), mccabe: self.mccabe.or(config.mccabe), pep8_naming: self.pep8_naming.or(config.pep8_naming), + pycodestyle: self.pycodestyle.or(config.pycodestyle), pydocstyle: self.pydocstyle.or(config.pydocstyle), pyupgrade: self.pyupgrade.or(config.pyupgrade), } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 612059daa23fa5..17989a56553528 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -24,7 +24,7 @@ use crate::settings::types::{ use crate::{ flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, - mccabe, one_time_warning, pep8_naming, pydocstyle, pyupgrade, + mccabe, one_time_warning, pep8_naming, pycodestyle, pydocstyle, pyupgrade, }; pub mod configuration; @@ -58,6 +58,7 @@ pub struct Settings { pub show_source: bool, pub src: Vec, pub target_version: PythonVersion, + pub comment_tags: Vec, pub update_check: bool, // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, @@ -71,6 +72,7 @@ pub struct Settings { pub isort: isort::settings::Settings, pub mccabe: mccabe::settings::Settings, pub pep8_naming: pep8_naming::settings::Settings, + pub pycodestyle: pycodestyle::settings::Settings, pub pydocstyle: pydocstyle::settings::Settings, pub pyupgrade: pyupgrade::settings::Settings, } @@ -162,6 +164,9 @@ impl Settings { .src .unwrap_or_else(|| vec![project_root.to_path_buf()]), target_version: config.target_version.unwrap_or_default(), + comment_tags: config.comment_tags.unwrap_or_else(|| { + vec!["TODO".to_string(), "FIXME".to_string(), "XXX".to_string()] + }), update_check: config.update_check.unwrap_or(true), // Plugins flake8_annotations: config @@ -208,6 +213,10 @@ impl Settings { .pep8_naming .map(std::convert::Into::into) .unwrap_or_default(), + pycodestyle: config + .pycodestyle + .map(std::convert::Into::into) + .unwrap_or_default(), pydocstyle: config .pydocstyle .map(std::convert::Into::into) @@ -241,6 +250,7 @@ impl Settings { show_source: false, src: vec![path_dedot::CWD.clone()], target_version: PythonVersion::Py310, + comment_tags: vec!["TODO".to_string(), "FIXME".to_string()], update_check: false, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), @@ -253,6 +263,7 @@ impl Settings { isort: isort::settings::Settings::default(), mccabe: mccabe::settings::Settings::default(), pep8_naming: pep8_naming::settings::Settings::default(), + pycodestyle: pycodestyle::settings::Settings::default(), pydocstyle: pydocstyle::settings::Settings::default(), pyupgrade: pyupgrade::settings::Settings::default(), } @@ -280,6 +291,7 @@ impl Settings { show_source: false, src: vec![path_dedot::CWD.clone()], target_version: PythonVersion::Py310, + comment_tags: vec!["TODO".to_string()], update_check: false, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), @@ -292,6 +304,7 @@ impl Settings { isort: isort::settings::Settings::default(), mccabe: mccabe::settings::Settings::default(), pep8_naming: pep8_naming::settings::Settings::default(), + pycodestyle: pycodestyle::settings::Settings::default(), pydocstyle: pydocstyle::settings::Settings::default(), pyupgrade: pyupgrade::settings::Settings::default(), } diff --git a/src/settings/options.rs b/src/settings/options.rs index 8d23c6e0286446..46f5d39873e5b6 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -10,7 +10,7 @@ use crate::settings::types::{PythonVersion, SerializationFormat, Version}; use crate::{ flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort, - mccabe, pep8_naming, pydocstyle, pyupgrade, + mccabe, pep8_naming, pycodestyle, pydocstyle, pyupgrade, }; #[derive( @@ -339,6 +339,14 @@ pub struct Options { )] /// A list of check code prefixes to consider un-autofix-able. pub unfixable: Option>, + #[option( + default = r#"["TODO", "FIXME", "XXX"]"#, + value_type = "Vec", + example = r#"comment-tags = ["TODO", "FIXME", "HACK"]"# + )] + /// A list of comment tags to recognize (e.g. TODO, FIXME, XXX). + /// Comments starting with these tags will be skipped by E501 and ERA001. + pub comment_tags: Option>, #[option( default = "true", value_type = "bool", @@ -381,6 +389,9 @@ pub struct Options { /// Options for the `pep8-naming` plugin. pub pep8_naming: Option, #[option_group] + /// Options for the `pycodestyle` plugin. + pub pycodestyle: Option, + #[option_group] /// Options for the `pydocstyle` plugin. pub pydocstyle: Option, #[option_group] diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index 8c02c31248dc27..d1c03fa5923b6b 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -188,6 +188,7 @@ mod tests { src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -200,6 +201,7 @@ mod tests { isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }) @@ -241,6 +243,7 @@ line-length = 79 src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, cache_dir: None, flake8_annotations: None, @@ -254,6 +257,7 @@ line-length = 79 isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }) @@ -296,6 +300,7 @@ exclude = ["foo.py"] src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_errmsg: None, @@ -308,6 +313,7 @@ exclude = ["foo.py"] isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }) @@ -350,6 +356,7 @@ select = ["E501"] src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -362,6 +369,7 @@ select = ["E501"] isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }) @@ -405,6 +413,7 @@ ignore = ["E501"] src: None, target_version: None, unfixable: None, + comment_tags: None, update_check: None, flake8_annotations: None, flake8_bugbear: None, @@ -417,6 +426,7 @@ ignore = ["E501"] isort: None, mccabe: None, pep8_naming: None, + pycodestyle: None, pydocstyle: None, pyupgrade: None, }) @@ -490,6 +500,7 @@ other-attribute = 1 format: None, force_exclude: None, unfixable: None, + comment_tags: None, update_check: None, cache_dir: None, per_file_ignores: Some(FxHashMap::from_iter([( @@ -592,6 +603,7 @@ other-attribute = 1 ]), staticmethod_decorators: Some(vec!["staticmethod".to_string()]), }), + pycodestyle: None, pydocstyle: None, pyupgrade: None, }