diff --git a/crates/ruff/src/checkers/physical_lines.rs b/crates/ruff/src/checkers/physical_lines.rs index 856b43b8d24da..adb2963e40494 100644 --- a/crates/ruff/src/checkers/physical_lines.rs +++ b/crates/ruff/src/checkers/physical_lines.rs @@ -4,7 +4,7 @@ use std::path::Path; use ruff_diagnostics::Diagnostic; use ruff_python_ast::newlines::StrExt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::{Locator, Stylist}; use crate::registry::Rule; use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective}; @@ -22,8 +22,8 @@ use crate::settings::{flags, Settings}; pub fn check_physical_lines( path: &Path, + locator: &Locator, stylist: &Stylist, - contents: &str, commented_lines: &[usize], doc_lines: &[usize], settings: &Settings, @@ -57,7 +57,7 @@ pub fn check_physical_lines( let mut commented_lines_iter = commented_lines.iter().peekable(); let mut doc_lines_iter = doc_lines.iter().peekable(); - for (index, line) in contents.universal_newlines().enumerate() { + for (index, line) in locator.contents().universal_newlines().enumerate() { while commented_lines_iter .next_if(|lineno| &(index + 1) == *lineno) .is_some() @@ -163,8 +163,8 @@ pub fn check_physical_lines( if enforce_no_newline_at_end_of_file { if let Some(diagnostic) = no_newline_at_end_of_file( + locator, stylist, - contents, autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile), ) { diagnostics.push(diagnostic); @@ -200,8 +200,8 @@ mod tests { let check_with_max_line_length = |line_length: usize| { check_physical_lines( Path::new("foo.py"), + &locator, &stylist, - line, &[], &[], &Settings { diff --git a/crates/ruff/src/linter.rs b/crates/ruff/src/linter.rs index 7c634b409012c..1ad51a0893602 100644 --- a/crates/ruff/src/linter.rs +++ b/crates/ruff/src/linter.rs @@ -191,8 +191,8 @@ pub fn check_path( { diagnostics.extend(check_physical_lines( path, + locator, stylist, - contents, indexer.commented_lines(), &doc_lines, settings, diff --git a/crates/ruff/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs b/crates/ruff/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs index 5a3b3de96ee94..9609c37895112 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/no_newline_at_end_of_file.rs @@ -3,7 +3,7 @@ use rustpython_parser::ast::Location; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::newlines::StrExt; -use ruff_python_ast::source_code::Stylist; +use ruff_python_ast::source_code::{Locator, Stylist}; use ruff_python_ast::types::Range; /// ## What it does @@ -38,16 +38,16 @@ impl AlwaysAutofixableViolation for NoNewLineAtEndOfFile { /// W292 pub fn no_newline_at_end_of_file( + locator: &Locator, stylist: &Stylist, - contents: &str, autofix: bool, ) -> Option { - if !contents.ends_with(['\n', '\r']) { + if !locator.contents().ends_with(['\n', '\r']) { // Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't // want to raise W292 anyway). - if let Some(line) = contents.universal_newlines().last() { + if let Some(line) = locator.contents().universal_newlines().last() { // Both locations are at the end of the file (and thus the same). - let location = Location::new(contents.universal_newlines().count(), line.len()); + let location = Location::new(locator.count_lines(), line.len()); let mut diagnostic = Diagnostic::new(NoNewLineAtEndOfFile, Range::new(location, location)); if autofix { diff --git a/crates/ruff_python_ast/Cargo.toml b/crates/ruff_python_ast/Cargo.toml index 635a315c80ef7..44df6c5fb9f26 100644 --- a/crates/ruff_python_ast/Cargo.toml +++ b/crates/ruff_python_ast/Cargo.toml @@ -24,5 +24,6 @@ regex = { workspace = true } rustc-hash = { workspace = true } rustpython-common = { workspace = true } rustpython-parser = { workspace = true } +# TODO(charlie): See https://github.com/RustPython/RustPython/pull/4684. serde = { workspace = true } smallvec = { version = "1.10.0" } diff --git a/crates/ruff_python_ast/src/source_code/locator.rs b/crates/ruff_python_ast/src/source_code/locator.rs index 035110f78ed67..9acf3a95db894 100644 --- a/crates/ruff_python_ast/src/source_code/locator.rs +++ b/crates/ruff_python_ast/src/source_code/locator.rs @@ -56,10 +56,18 @@ impl<'a> Locator<'a> { self.contents } + /// Return the number of lines in the source code. + pub fn count_lines(&self) -> usize { + let index = self.get_or_init_index(); + index.count_lines() + } + + /// Return the number of bytes in the source code. pub const fn len(&self) -> usize { self.contents.len() } + /// Return `true` if the source code is empty. pub const fn is_empty(&self) -> bool { self.contents.is_empty() } @@ -83,6 +91,14 @@ impl Index { Index::Utf8(utf8) => utf8.byte_offset(location, contents), } } + + /// Return the number of lines in the source code. + fn count_lines(&self) -> usize { + match self { + Index::Ascii(ascii) => ascii.line_start_byte_offsets.len(), + Index::Utf8(utf8) => utf8.line_start_byte_offsets.len(), + } + } } impl From<&str> for Index {