-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ignore overlong pragma comments when enforcing linter line length
- Loading branch information
1 parent
695dbbc
commit 0bf409b
Showing
11 changed files
with
273 additions
and
134 deletions.
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
crates/ruff_linter/resources/test/fixtures/pycodestyle/E501_1.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
# 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(charlie): 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` | ||
# FIXME(charlie): 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: 11 additions & 0 deletions
11
crates/ruff_linter/resources/test/fixtures/pycodestyle/E501_3.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# OK (88 characters) | ||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa" # type: ignore | ||
|
||
# OK (88 characters) | ||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa"# type: ignore | ||
|
||
# OK (88 characters) | ||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaa" # type: ignore | ||
|
||
# Error (89 characters) | ||
"shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:" + "shape:aaaa" # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
use std::borrow::Cow; | ||
|
||
use unicode_width::UnicodeWidthStr; | ||
|
||
use ruff_python_index::Indexer; | ||
use ruff_python_trivia::is_pragma_comment; | ||
use ruff_source_file::Line; | ||
use ruff_text_size::{TextLen, TextRange}; | ||
|
||
use crate::line_width::{LineLength, LineWidthBuilder, TabSize}; | ||
|
||
#[derive(Debug)] | ||
pub(super) struct Overlong { | ||
range: TextRange, | ||
width: usize, | ||
} | ||
|
||
impl Overlong { | ||
/// Returns an [`Overlong`] if the measured line exceeds the configured line length, or `None` | ||
/// otherwise. | ||
pub(super) fn try_from_line( | ||
line: &Line, | ||
indexer: &Indexer, | ||
limit: LineLength, | ||
task_tags: &[String], | ||
tab_size: TabSize, | ||
) -> Option<Self> { | ||
// The maximum width of the line is the number of bytes multiplied by the tab size (the | ||
// worst-case scenario is that the line is all tabs). If the maximum width is less than the | ||
// limit, then the line is not overlong. | ||
let max_width = line.len() * tab_size.as_usize(); | ||
if max_width < limit.value() as usize { | ||
return None; | ||
} | ||
|
||
// Measure the line. If it's already below the limit, exit early. | ||
let width = Overlong::measure(line.as_str(), tab_size); | ||
if width <= limit { | ||
return None; | ||
} | ||
|
||
// Strip trailing pragma comments and, if necessary, re-measure the line. | ||
let (line, width) = if let Some(line) = Overlong::strip_trailing(line, indexer, task_tags) { | ||
// Re-measure the line. | ||
let width = Overlong::measure(line.as_str(), tab_size); | ||
if width <= limit { | ||
return None; | ||
} | ||
|
||
(Cow::Owned(line), width) | ||
} else { | ||
(Cow::Borrowed(line), width) | ||
}; | ||
|
||
let mut chunks = line.split_whitespace(); | ||
let (Some(_), Some(second_chunk)) = (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 lines that end with a URL, as long as the URL | ||
// begins before the limit. | ||
let last_chunk = chunks.last().unwrap_or(second_chunk); | ||
if last_chunk.contains("://") { | ||
if width.get() - last_chunk.width() <= limit.value() as usize { | ||
return None; | ||
} | ||
} | ||
|
||
// Obtain the start offset of the part of the line that exceeds the limit. | ||
let mut start_offset = line.start(); | ||
let mut start_width = LineWidthBuilder::new(tab_size); | ||
for c in line.chars() { | ||
if start_width < limit { | ||
start_offset += c.text_len(); | ||
start_width = start_width.add_char(c); | ||
} else { | ||
break; | ||
} | ||
} | ||
|
||
Some(Self { | ||
range: TextRange::new(start_offset, line.end()), | ||
width: width.get(), | ||
}) | ||
} | ||
|
||
/// Return the range of the overlong portion of the line. | ||
pub(super) const fn range(&self) -> TextRange { | ||
self.range | ||
} | ||
|
||
/// Return the measured width of the line. | ||
pub(super) const fn width(&self) -> usize { | ||
self.width | ||
} | ||
|
||
/// Returns the width of a given string, accounting for the tab size. | ||
fn measure(s: &str, tab_size: TabSize) -> LineWidthBuilder { | ||
let mut width = LineWidthBuilder::new(tab_size); | ||
width = width.add_str(s); | ||
width | ||
} | ||
|
||
/// Strip trailing comments from a [`Line`], if the line ends with a pragma comment (like | ||
/// `# type: ignore`) or, if necessary, a task comment (like `# TODO`). | ||
fn strip_trailing<'a>( | ||
line: &Line<'a>, | ||
indexer: &Indexer, | ||
task_tags: &[String], | ||
) -> Option<Line<'a>> { | ||
let [comment_range] = indexer.comment_ranges().comments_in_range(line.range()) else { | ||
return None; | ||
}; | ||
|
||
// Convert from absolute to relative range. | ||
let comment_range = comment_range - line.start(); | ||
let comment = &line.as_str()[comment_range]; | ||
|
||
// Ex) `# type: ignore` | ||
if is_pragma_comment(comment) { | ||
// Remove the pragma from the line. | ||
let prefix = &line.as_str()[..usize::from(comment_range.start())].trim_end(); | ||
return Some(Line::new(prefix, line.start())); | ||
} | ||
|
||
// Ex) `# TODO(charlie): ...` | ||
if !task_tags.is_empty() { | ||
let trimmed = comment.strip_prefix('#')?.trim_start(); | ||
if task_tags | ||
.iter() | ||
.any(|task_tag| trimmed.starts_with(task_tag)) | ||
{ | ||
// Remove the task tag from the line. | ||
let prefix = &line.as_str()[..usize::from(comment_range.start())].trim_end(); | ||
return Some(Line::new(prefix, line.start())); | ||
} | ||
} | ||
|
||
None | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.