diff --git a/crates/ruff/src/rules/pycodestyle/helpers.rs b/crates/ruff/src/rules/pycodestyle/helpers.rs index b21b4ea0baf17..da1a831a87c58 100644 --- a/crates/ruff/src/rules/pycodestyle/helpers.rs +++ b/crates/ruff/src/rules/pycodestyle/helpers.rs @@ -1,7 +1,8 @@ use ruff_python_ast::helpers::{create_expr, unparse_expr}; use ruff_python_ast::source_code::Stylist; -use rustpython_parser::ast::{Cmpop, Expr, ExprKind}; -use unicode_width::UnicodeWidthStr; +use ruff_python_ast::types::Range; +use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Location}; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; pub fn is_ambiguous_name(name: &str) -> bool { name == "l" || name == "I" || name == "O" @@ -18,28 +19,40 @@ pub fn compare(left: &Expr, ops: &[Cmpop], comparators: &[Expr], stylist: &Styli ) } -pub fn is_overlong( +pub(super) fn is_overlong( line: &str, - line_width: usize, limit: usize, ignore_overlong_task_comments: bool, task_tags: &[String], -) -> bool { - if line_width <= limit { - return false; +) -> Option { + let mut start_column = 0; + let mut width = 0; + let mut end = 0; + + for c in line.chars() { + if width < limit { + start_column += 1; + } + + width += c.width().unwrap_or(0); + end += 1; + } + + if width <= limit { + return None; } let mut chunks = line.split_whitespace(); let (Some(first_chunk), Some(second_chunk)) = (chunks.next(), chunks.next()) else { // Single word / no printable chars - no way to make the line shorter - return false; + return None; }; if first_chunk == "#" { if ignore_overlong_task_comments { let second = second_chunk.trim_end_matches(':'); if task_tags.iter().any(|task_tag| task_tag == second) { - return false; + return None; } } } @@ -48,10 +61,33 @@ pub fn is_overlong( // begins before the limit. let last_chunk = chunks.last().unwrap_or(second_chunk); if last_chunk.contains("://") { - if line_width - last_chunk.width() <= limit { - return false; + if width - last_chunk.width() <= limit { + return None; } } - true + Some(Overlong { + column: start_column, + end_column: end, + width, + }) +} + +pub(super) struct Overlong { + column: usize, + end_column: usize, + width: usize, +} + +impl Overlong { + pub(super) fn range(&self, line_no: usize) -> Range { + Range::new( + Location::new(line_no + 1, self.column), + Location::new(line_no + 1, self.end_column), + ) + } + + pub(super) const fn width(&self) -> usize { + self.width + } } diff --git a/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs b/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs index f6e27bd845d93..c3526c063cce6 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/doc_line_too_long.rs @@ -1,9 +1,5 @@ -use rustpython_parser::ast::Location; -use unicode_width::UnicodeWidthStr; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::Range; use crate::rules::pycodestyle::helpers::is_overlong; use crate::settings::Settings; @@ -46,22 +42,16 @@ pub fn doc_line_too_long(lineno: usize, line: &str, settings: &Settings) -> Opti return None; }; - let line_width = line.width(); - if is_overlong( + is_overlong( line, - line_width, limit, settings.pycodestyle.ignore_overlong_task_comments, &settings.task_tags, - ) { - Some(Diagnostic::new( - DocLineTooLong(line_width, limit), - Range::new( - Location::new(lineno + 1, limit), - Location::new(lineno + 1, line.chars().count()), - ), - )) - } else { - None - } + ) + .map(|overlong| { + Diagnostic::new( + DocLineTooLong(overlong.width(), limit), + overlong.range(lineno), + ) + }) } diff --git a/crates/ruff/src/rules/pycodestyle/rules/line_too_long.rs b/crates/ruff/src/rules/pycodestyle/rules/line_too_long.rs index 431e6939006b8..c5eafbb423585 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/line_too_long.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/line_too_long.rs @@ -1,9 +1,5 @@ -use rustpython_parser::ast::Location; -use unicode_width::UnicodeWidthStr; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::types::Range; use crate::rules::pycodestyle::helpers::is_overlong; use crate::settings::Settings; @@ -39,23 +35,13 @@ impl Violation for LineTooLong { /// E501 pub fn line_too_long(lineno: usize, line: &str, settings: &Settings) -> Option { - let line_width = line.width(); let limit = settings.line_length; - if is_overlong( + + is_overlong( line, - line_width, limit, settings.pycodestyle.ignore_overlong_task_comments, &settings.task_tags, - ) { - Some(Diagnostic::new( - LineTooLong(line_width, limit), - Range::new( - Location::new(lineno + 1, limit), - Location::new(lineno + 1, line.chars().count()), - ), - )) - } else { - None - } + ) + .map(|overlong| Diagnostic::new(LineTooLong(overlong.width(), limit), overlong.range(lineno))) } diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E501_E501.py.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E501_E501.py.snap index b90b05b2e9876..c7e156569d460 100644 --- a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E501_E501.py.snap +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__E501_E501.py.snap @@ -10,11 +10,11 @@ E501.py:5:89: E501 Line too long (123 > 88 characters) 8 | """ | -E501.py:16:89: E501 Line too long (95 > 88 characters) +E501.py:16:85: E501 Line too long (95 > 88 characters) | 16 | _ = "---------------------------------------------------------------------------AAAAAAA" 17 | _ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜" - | E501 + | ^^^^^^^ E501 | E501.py:25:89: E501 Line too long (127 > 88 characters)