diff --git a/crates/ruff/resources/test/fixtures/pycodestyle/W505.py b/crates/ruff/resources/test/fixtures/pycodestyle/W505.py index d8085da9892a8..c297b8c1e9e89 100644 --- a/crates/ruff/resources/test/fixtures/pycodestyle/W505.py +++ b/crates/ruff/resources/test/fixtures/pycodestyle/W505.py @@ -2,7 +2,7 @@ """Here's a top-level docstring that's over the limit.""" -def f(): +def f1(): """Here's a docstring that's also over the limit.""" x = 1 # Here's a comment that's over the limit, but it's not standalone. @@ -16,3 +16,16 @@ def f(): "This is also considered a docstring, and is over the limit." + + +def f2(): + """Here's a multi-line docstring. + + It's over the limit on this line, which isn't the first line in the docstring. + """ + + +def f3(): + """Here's a multi-line docstring. + + It's over the limit on this line, which isn't the first line in the docstring.""" diff --git a/crates/ruff/src/checkers/physical_lines.rs b/crates/ruff/src/checkers/physical_lines.rs index 565d41578a83a..a7e87043a8ba2 100644 --- a/crates/ruff/src/checkers/physical_lines.rs +++ b/crates/ruff/src/checkers/physical_lines.rs @@ -118,7 +118,7 @@ pub fn check_physical_lines( } while doc_lines_iter - .next_if(|doc_line_start| line.range().contains(**doc_line_start)) + .next_if(|doc_line_start| line.range().contains_inclusive(**doc_line_start)) .is_some() { if enforce_doc_line_too_long { diff --git a/crates/ruff/src/doc_lines.rs b/crates/ruff/src/doc_lines.rs index 7cb8de1ff6972..ab6b0cb752cd7 100644 --- a/crates/ruff/src/doc_lines.rs +++ b/crates/ruff/src/doc_lines.rs @@ -1,14 +1,15 @@ //! Doc line extraction. In this context, a doc line is a line consisting of a //! standalone comment or a constant string statement. -use ruff_text_size::{TextRange, TextSize}; use std::iter::FusedIterator; -use ruff_python_ast::source_code::Locator; +use ruff_text_size::{TextRange, TextSize}; use rustpython_parser::ast::{Constant, ExprKind, Stmt, StmtKind, Suite}; use rustpython_parser::lexer::LexResult; use rustpython_parser::Tok; +use ruff_python_ast::newlines::UniversalNewlineIterator; +use ruff_python_ast::source_code::Locator; use ruff_python_ast::visitor; use ruff_python_ast::visitor::Visitor; @@ -69,12 +70,12 @@ impl Iterator for DocLines<'_> { impl FusedIterator for DocLines<'_> {} -#[derive(Default)] -struct StringLinesVisitor { +struct StringLinesVisitor<'a> { string_lines: Vec, + locator: &'a Locator<'a>, } -impl Visitor<'_> for StringLinesVisitor { +impl Visitor<'_> for StringLinesVisitor<'_> { fn visit_stmt(&mut self, stmt: &Stmt) { if let StmtKind::Expr { value } = &stmt.node { if let ExprKind::Constant { @@ -82,16 +83,30 @@ impl Visitor<'_> for StringLinesVisitor { .. } = &value.node { - self.string_lines.push(value.start()); + for line in UniversalNewlineIterator::with_offset( + self.locator.slice(value.range()), + value.start(), + ) { + self.string_lines.push(line.start()); + } } } visitor::walk_stmt(self, stmt); } } +impl<'a> StringLinesVisitor<'a> { + fn new(locator: &'a Locator<'a>) -> Self { + Self { + string_lines: Vec::new(), + locator, + } + } +} + /// Extract doc lines (standalone strings) start positions from an AST. -pub fn doc_lines_from_ast(python_ast: &Suite) -> Vec { - let mut visitor = StringLinesVisitor::default(); +pub fn doc_lines_from_ast(python_ast: &Suite, locator: &Locator) -> Vec { + let mut visitor = StringLinesVisitor::new(locator); visitor.visit_body(python_ast); visitor.string_lines } diff --git a/crates/ruff/src/linter.rs b/crates/ruff/src/linter.rs index 8ed70e159691a..bb4bbcc5ce55f 100644 --- a/crates/ruff/src/linter.rs +++ b/crates/ruff/src/linter.rs @@ -163,7 +163,7 @@ pub fn check_path( diagnostics.extend(import_diagnostics); } if use_doc_lines { - doc_lines.extend(doc_lines_from_ast(&python_ast)); + doc_lines.extend(doc_lines_from_ast(&python_ast, locator)); } } Err(parse_error) => { diff --git a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length.snap b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length.snap index a1fc3e95e1d68..ddd89346dc584 100644 --- a/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length.snap +++ b/crates/ruff/src/rules/pycodestyle/snapshots/ruff__rules__pycodestyle__tests__max_doc_length.snap @@ -10,7 +10,7 @@ W505.py:2:51: W505 Doc line too long (57 > 50 characters) W505.py:6:51: W505 Doc line too long (56 > 50 characters) | -6 | def f(): +6 | def f1(): 7 | """Here's a docstring that's also over the limit.""" | ^^^^^^ W505 8 | @@ -42,4 +42,21 @@ W505.py:18:51: W505 Doc line too long (61 > 50 characters) | ^^^^^^^^^^^ W505 | +W505.py:24:51: W505 Doc line too long (82 > 50 characters) + | +24 | """Here's a multi-line docstring. +25 | +26 | It's over the limit on this line, which isn't the first line in the docstring. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505 +27 | """ + | + +W505.py:31:51: W505 Doc line too long (85 > 50 characters) + | +31 | """Here's a multi-line docstring. +32 | +33 | It's over the limit on this line, which isn't the first line in the docstring.""" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ W505 + | +