Skip to content

Commit

Permalink
Add ignore-overlong-task-comments setting
Browse files Browse the repository at this point in the history
Imagine a .py file containing the following comment:

    # TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
    # do eiusmod tempor incididunt ut labore et dolore magna aliqua.

Since `git grep` only matches individual lines `git grep TODO` would
only output the first line of the comment, cutting off potentially
important information. (git grep currently doesn't support multiline
grepping). Projects using such a workflow therefore probably format
the comment in a single line instead:

    # TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

This commit introduces a setting to accomdate this workflow by making
the line-length checks (`E501`) optionally ignore overlong lines
if they start with a recognized task tag.

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
  • Loading branch information
not-my-profile and charliermarsh committed Jan 5, 2023
1 parent 78c9056 commit 53e3dd8
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 17 deletions.
22 changes: 21 additions & 1 deletion README.md
Expand Up @@ -2237,7 +2237,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"]`
Expand Down Expand Up @@ -3037,6 +3038,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)
Expand Down
6 changes: 6 additions & 0 deletions 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`
11 changes: 10 additions & 1 deletion ruff.schema.json
Expand Up @@ -389,7 +389,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"
Expand Down Expand Up @@ -1510,6 +1510,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": {
Expand Down
2 changes: 1 addition & 1 deletion src/checkers/lines.rs
Expand Up @@ -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);
}
}
Expand Down
28 changes: 19 additions & 9 deletions src/pycodestyle/checks.rs
Expand Up @@ -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<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap());

/// E501
pub fn line_too_long(lineno: usize, line: &str, max_line_length: usize) -> Option<Check> {
pub fn line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option<Check> {
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),
),
))
Expand Down
18 changes: 18 additions & 0 deletions src/pycodestyle/mod.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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(())
}
}
28 changes: 24 additions & 4 deletions src/pycodestyle/settings.rs
Expand Up @@ -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<bool>,
}

#[derive(Debug, Default, Hash)]
pub struct Settings {}
pub struct Settings {
pub ignore_overlong_task_comments: bool,
}

impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {}
Self {
ignore_overlong_task_comments: options
.ignore_overlong_task_comments
.unwrap_or_default(),
}
}
}

impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {}
Self {
ignore_overlong_task_comments: Some(settings.ignore_overlong_task_comments),
}
}
}
@@ -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: ~

@@ -0,0 +1,6 @@
---
source: src/pycodestyle/mod.rs
expression: checks
---
[]

3 changes: 2 additions & 1 deletion src/settings/options.rs
Expand Up @@ -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<Vec<String>>,
#[option(
default = "true",
Expand Down

0 comments on commit 53e3dd8

Please sign in to comment.