From 43d7ce8edf4ff9815a3596d1eb306a626eda0c6f Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 26 Oct 2024 17:15:01 +0200 Subject: [PATCH 1/2] Split out LineRanges trait --- Cargo.lock | 2 - crates/red_knot_test/src/assertion.rs | 11 +- crates/ruff_formatter/src/builders.rs | 2 +- .../src/checkers/ast/analyze/bindings.rs | 4 +- .../src/checkers/ast/analyze/definitions.rs | 4 +- .../ast/analyze/unresolved_references.rs | 6 +- crates/ruff_linter/src/checkers/ast/mod.rs | 8 +- crates/ruff_linter/src/checkers/filesystem.rs | 2 +- crates/ruff_linter/src/checkers/imports.rs | 2 +- .../ruff_linter/src/checkers/logical_lines.rs | 5 +- crates/ruff_linter/src/checkers/noqa.rs | 11 +- .../src/checkers/physical_lines.rs | 9 +- crates/ruff_linter/src/checkers/tokens.rs | 5 +- crates/ruff_linter/src/directives.rs | 14 +- crates/ruff_linter/src/doc_lines.rs | 5 +- crates/ruff_linter/src/fix/codemods.rs | 2 +- crates/ruff_linter/src/fix/edits.rs | 13 +- crates/ruff_linter/src/fix/mod.rs | 11 +- crates/ruff_linter/src/importer/insertion.rs | 21 +- crates/ruff_linter/src/importer/mod.rs | 2 +- crates/ruff_linter/src/lib.rs | 2 + crates/ruff_linter/src/linter.rs | 16 +- crates/ruff_linter/src/locator.rs | 224 ++++++++ crates/ruff_linter/src/message/mod.rs | 11 +- crates/ruff_linter/src/noqa.rs | 24 +- .../eradicate/rules/commented_out_code.rs | 15 +- .../rules/hardcoded_sql_expression.rs | 2 +- .../src/rules/flake8_bugbear/helpers.rs | 3 +- .../rules/getattr_with_constant.rs | 3 +- .../rules/mutable_argument_default.rs | 9 +- .../flake8_commas/rules/trailing_commas.rs | 3 +- .../src/rules/flake8_comprehensions/fixes.rs | 2 +- .../rules/missing_copyright_notice.rs | 2 +- .../rules/string_in_exception.rs | 15 +- .../src/rules/flake8_executable/rules/mod.rs | 9 +- .../rules/shebang_leading_whitespace.rs | 2 +- .../rules/shebang_not_first_line.rs | 6 +- .../rules/explicit.rs | 9 +- .../rules/implicit.rs | 3 +- .../rules/unconventional_import_alias.rs | 2 +- .../rules/implicit_namespace_package.rs | 2 +- .../rules/unnecessary_placeholder.rs | 2 +- .../flake8_pie/rules/unnecessary_spread.rs | 5 +- .../rules/redundant_final_literal.rs | 2 +- .../rules/flake8_pyi/rules/simple_defaults.rs | 2 +- .../flake8_pyi/rules/type_comment_in_stub.rs | 8 +- .../unaliased_collections_abc_set_import.rs | 2 +- .../flake8_pytest_style/rules/assertion.rs | 13 +- .../flake8_pytest_style/rules/fixture.rs | 1 + .../rules/avoidable_escaped_quote.rs | 7 +- .../rules/check_string_quotes.rs | 2 +- .../rules/unnecessary_escaped_quote.rs | 2 +- .../src/rules/flake8_return/helpers.rs | 3 +- .../src/rules/flake8_return/rules/function.rs | 9 +- .../flake8_simplify/rules/ast_bool_op.rs | 2 +- .../flake8_simplify/rules/collapsible_if.rs | 7 +- .../rules/flake8_simplify/rules/fix_with.rs | 7 +- .../if_else_block_instead_of_dict_get.rs | 4 +- .../rules/if_else_block_instead_of_if_exp.rs | 2 +- .../rules/if_with_same_arms.rs | 3 +- .../flake8_simplify/rules/needless_bool.rs | 2 +- .../rules/reimplemented_builtin.rs | 1 + .../rules/suppressible_exception.rs | 3 +- .../flake8_simplify/rules/yoda_conditions.rs | 2 +- .../src/rules/flake8_todos/rules/todos.rs | 9 +- .../ruff_linter/src/rules/isort/annotate.rs | 3 +- crates/ruff_linter/src/rules/isort/block.rs | 2 +- .../ruff_linter/src/rules/isort/comments.rs | 3 +- crates/ruff_linter/src/rules/isort/helpers.rs | 3 +- crates/ruff_linter/src/rules/isort/mod.rs | 7 +- .../rules/isort/rules/add_required_imports.rs | 3 +- .../src/rules/isort/rules/organize_imports.rs | 11 +- .../pandas_vet/rules/inplace_argument.rs | 2 +- .../rules/pycodestyle/rules/bare_except.rs | 6 +- .../rules/pycodestyle/rules/blank_lines.rs | 12 +- .../pycodestyle/rules/compound_statements.rs | 12 +- .../rules/invalid_escape_sequence.rs | 2 +- .../pycodestyle/rules/lambda_assignment.rs | 6 +- .../pycodestyle/rules/literal_comparisons.rs | 2 +- .../pycodestyle/rules/logical_lines/mod.rs | 22 +- .../logical_lines/redundant_backslash.rs | 3 +- .../whitespace_before_comment.rs | 3 +- .../rules/missing_newline_at_end_of_file.rs | 6 +- .../rules/multiple_imports_on_one_line.rs | 8 +- .../src/rules/pycodestyle/rules/not_tests.rs | 4 +- .../pycodestyle/rules/tab_indentation.rs | 4 +- .../pycodestyle/rules/trailing_whitespace.rs | 5 +- .../rules/blank_before_after_class.rs | 4 +- .../rules/multi_line_summary_start.rs | 2 +- .../src/rules/pydocstyle/rules/sections.rs | 4 +- .../ruff_linter/src/rules/pyflakes/fixes.rs | 2 +- crates/ruff_linter/src/rules/pyflakes/mod.rs | 8 +- .../pyflakes/rules/default_except_not_last.rs | 6 +- .../rules/f_string_missing_placeholders.rs | 2 +- .../src/rules/pyflakes/rules/unused_import.rs | 2 +- .../rules/pyflakes/rules/unused_variable.rs | 2 +- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 2 +- .../pygrep_hooks/rules/blanket_type_ignore.rs | 6 +- .../rules/pylint/rules/collapsible_else_if.rs | 5 +- .../src/rules/pylint/rules/empty_comment.rs | 6 +- .../pylint/rules/invalid_string_characters.rs | 6 +- .../src/rules/pylint/rules/nested_min_max.rs | 2 +- .../rules/pylint/rules/no_method_decorator.rs | 2 +- .../src/rules/pylint/rules/non_ascii_name.rs | 5 +- .../rules/repeated_equality_comparison.rs | 2 +- .../pylint/rules/useless_else_on_loop.rs | 7 +- .../ruff_linter/src/rules/pyupgrade/fixes.rs | 6 +- ...convert_named_tuple_functional_to_class.rs | 1 + .../convert_typed_dict_functional_to_class.rs | 1 + .../pyupgrade/rules/deprecated_import.rs | 7 +- .../pyupgrade/rules/deprecated_mock_import.rs | 6 +- .../pyupgrade/rules/extraneous_parentheses.rs | 6 +- .../src/rules/pyupgrade/rules/f_strings.rs | 4 +- .../rules/pyupgrade/rules/format_literals.rs | 6 +- .../pyupgrade/rules/outdated_version_block.rs | 9 +- .../rules/printf_string_formatting.rs | 6 +- .../rules/unnecessary_coding_comment.rs | 9 +- .../rules/unnecessary_encode_utf8.rs | 2 +- .../rules/if_exp_instead_of_or_operator.rs | 2 +- .../refurb/rules/reimplemented_operator.rs | 4 +- .../rules/single_item_membership_test.rs | 2 +- .../rules/slice_to_remove_prefix_or_suffix.rs | 5 +- .../ruff/rules/ambiguous_unicode_character.rs | 2 +- .../rules/collection_literal_concatenation.rs | 2 +- .../ruff/rules/decimal_from_float_literal.rs | 2 +- .../rules/ruff/rules/default_factory_kwarg.rs | 2 +- .../explicit_f_string_type_conversion.rs | 2 +- .../invalid_formatter_suppression_comment.rs | 14 +- .../ruff/rules/missing_fstring_syntax.rs | 8 +- .../src/rules/ruff/rules/post_init_default.rs | 3 +- .../src/rules/ruff/rules/sequence_sorting.rs | 10 +- .../src/rules/ruff/rules/sort_dunder_all.rs | 1 + .../src/rules/ruff/rules/sort_dunder_slots.rs | 7 +- .../ruff/rules/suppression_comment_visitor.rs | 9 +- .../src/rules/ruff/rules/test_rules.rs | 2 +- crates/ruff_linter/src/test.rs | 12 +- crates/ruff_python_ast/src/helpers.rs | 29 +- crates/ruff_python_ast/src/whitespace.rs | 18 +- crates/ruff_python_codegen/src/lib.rs | 10 +- crates/ruff_python_codegen/src/stylist.rs | 78 +-- .../ruff_python_formatter/src/comments/mod.rs | 4 +- .../src/comments/placement.rs | 273 +++++----- .../src/comments/visitor.rs | 9 +- crates/ruff_python_formatter/src/context.rs | 17 +- .../src/expression/expr_f_string.rs | 18 +- .../src/expression/expr_number_literal.rs | 14 +- crates/ruff_python_formatter/src/lib.rs | 4 +- .../src/other/f_string.rs | 11 +- .../src/other/f_string_element.rs | 4 +- crates/ruff_python_formatter/src/range.rs | 9 +- .../src/string/docstring.rs | 10 +- .../src/string/implicit.rs | 22 +- .../ruff_python_formatter/src/string/mod.rs | 7 +- .../src/string/normalize.rs | 21 +- crates/ruff_python_formatter/src/verbatim.rs | 6 +- crates/ruff_python_index/src/indexer.rs | 68 +-- crates/ruff_python_semantic/Cargo.toml | 1 - crates/ruff_python_semantic/src/binding.rs | 5 +- crates/ruff_python_semantic/src/reference.rs | 5 +- .../ruff_python_trivia/src/comment_ranges.rs | 44 +- crates/ruff_python_trivia/src/whitespace.rs | 29 +- .../Cargo.toml | 1 - .../tests/block_comments.rs | 22 +- .../tests/whitespace.rs | 16 +- crates/ruff_server/src/lint.rs | 28 +- crates/ruff_source_file/src/lib.rs | 4 +- crates/ruff_source_file/src/line_ranges.rs | 358 +++++++++++++ crates/ruff_source_file/src/locator.rs | 483 ------------------ crates/ruff_text_size/src/lib.rs | 4 +- crates/ruff_text_size/src/traits.rs | 21 + crates/ruff_wasm/src/lib.rs | 9 +- fuzz/fuzz_targets/ruff_parse_simple.rs | 4 +- 172 files changed, 1352 insertions(+), 1273 deletions(-) create mode 100644 crates/ruff_linter/src/locator.rs create mode 100644 crates/ruff_source_file/src/line_ranges.rs delete mode 100644 crates/ruff_source_file/src/locator.rs diff --git a/Cargo.lock b/Cargo.lock index 32a762968ac5a..19110cedc1242 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,7 +2748,6 @@ dependencies = [ "ruff_python_ast", "ruff_python_parser", "ruff_python_stdlib", - "ruff_source_file", "ruff_text_size", "rustc-hash 2.0.0", "schemars", @@ -2779,7 +2778,6 @@ dependencies = [ "insta", "ruff_python_parser", "ruff_python_trivia", - "ruff_source_file", "ruff_text_size", ] diff --git a/crates/red_knot_test/src/assertion.rs b/crates/red_knot_test/src/assertion.rs index 2355b19f2f748..d9e46206e5892 100644 --- a/crates/red_knot_test/src/assertion.rs +++ b/crates/red_knot_test/src/assertion.rs @@ -40,7 +40,7 @@ use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_db::source::{line_index, source_text, SourceText}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::{LineIndex, Locator, OneIndexed}; +use ruff_source_file::{LineIndex, OneIndexed}; use ruff_text_size::{Ranged, TextRange}; use smallvec::SmallVec; use std::ops::Deref; @@ -67,8 +67,8 @@ impl InlineFileAssertions { } } - fn locator(&self) -> Locator { - Locator::with_index(&self.source, self.lines.clone()) + fn range_text(&self, range: impl Ranged) -> &str { + &self.source[range.range()] } fn line_number(&self, range: &impl Ranged) -> OneIndexed { @@ -76,7 +76,7 @@ impl InlineFileAssertions { } fn is_own_line_comment(&self, ranged_assertion: &AssertionWithRange) -> bool { - CommentRanges::is_own_line(ranged_assertion.start(), &self.locator()) + CommentRanges::is_own_line(ranged_assertion.start(), self.source.as_str()) } } @@ -131,10 +131,9 @@ impl<'a> Iterator for AssertionWithRangeIterator<'a> { type Item = AssertionWithRange<'a>; fn next(&mut self) -> Option { - let locator = self.file_assertions.locator(); loop { let inner_next = self.inner.next()?; - let comment = locator.slice(inner_next); + let comment = self.file_assertions.range_text(inner_next); if let Some(assertion) = Assertion::from_comment(comment) { return Some(AssertionWithRange(assertion, inner_next)); }; diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index 21ab988b5e38e..cd97dcc03eb5e 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -372,7 +372,7 @@ impl std::fmt::Debug for Text<'_> { } /// Emits a text as it is written in the source document. Optimized to avoid allocations. -pub const fn source_text_slice(range: TextRange) -> SourceTextSliceBuilder { +pub fn source_text_slice(range: TextRange) -> SourceTextSliceBuilder { SourceTextSliceBuilder { range } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs index fe6faeafa6244..586fa7ada081f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs @@ -26,11 +26,11 @@ pub(crate) fn bindings(checker: &mut Checker) { && !checker .settings .dummy_variable_rgx - .is_match(binding.name(checker.locator)) + .is_match(binding.name(checker.source())) { let mut diagnostic = Diagnostic::new( pyflakes::rules::UnusedVariable { - name: binding.name(checker.locator).to_string(), + name: binding.name(checker.source()).to_string(), }, binding.range(), ); diff --git a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs index ef2434b3e6643..e98214fb3b5e6 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/definitions.rs @@ -1,10 +1,10 @@ use ruff_python_ast::str::raw_contents_range; -use ruff_text_size::{Ranged, TextRange}; - use ruff_python_semantic::all::DunderAllName; use ruff_python_semantic::{ BindingKind, ContextualizedDefinition, Definition, Export, Member, MemberKind, }; +use ruff_source_file::LineRanges; +use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::codes::Rule; diff --git a/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs b/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs index e0d77052047d1..22bfd90568eee 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/unresolved_references.rs @@ -17,7 +17,7 @@ pub(crate) fn unresolved_references(checker: &mut Checker) { if checker.enabled(Rule::UndefinedLocalWithImportStarUsage) { checker.diagnostics.push(Diagnostic::new( pyflakes::rules::UndefinedLocalWithImportStarUsage { - name: reference.name(checker.locator).to_string(), + name: reference.name(checker.source()).to_string(), }, reference.range(), )); @@ -31,12 +31,12 @@ pub(crate) fn unresolved_references(checker: &mut Checker) { // Allow __path__. if checker.path.ends_with("__init__.py") { - if reference.name(checker.locator) == "__path__" { + if reference.name(checker.source()) == "__path__" { continue; } } - let symbol_name = reference.name(checker.locator); + let symbol_name = reference.name(checker.source()); checker.diagnostics.push(Diagnostic::new( pyflakes::rules::UndefinedName { diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 821d74eb4d0b7..1ffe583157835 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -59,7 +59,7 @@ use ruff_python_semantic::{ }; use ruff_python_stdlib::builtins::{python_builtins, MAGIC_GLOBALS}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::{Locator, OneIndexed, SourceRow}; +use ruff_source_file::{OneIndexed, SourceRow}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::annotation::AnnotationContext; @@ -69,7 +69,7 @@ use crate::noqa::NoqaMapping; use crate::registry::Rule; use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; use crate::settings::{flags, LinterSettings}; -use crate::{docstrings, noqa}; +use crate::{docstrings, noqa, Locator}; mod analyze; mod annotation; @@ -352,6 +352,10 @@ impl<'a> Checker<'a> { self.locator } + pub(crate) const fn source(&self) -> &'a str { + self.locator.contents() + } + /// The [`Stylist`] for the current file, which detects the current line ending, quote, and /// indentation style. pub(crate) const fn stylist(&self) -> &'a Stylist<'a> { diff --git a/crates/ruff_linter/src/checkers/filesystem.rs b/crates/ruff_linter/src/checkers/filesystem.rs index 2427409b254de..669ee4877f454 100644 --- a/crates/ruff_linter/src/checkers/filesystem.rs +++ b/crates/ruff_linter/src/checkers/filesystem.rs @@ -2,13 +2,13 @@ use std::path::Path; use ruff_diagnostics::Diagnostic; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use crate::registry::Rule; use crate::rules::flake8_builtins::rules::builtin_module_shadowing; use crate::rules::flake8_no_pep420::rules::implicit_namespace_package; use crate::rules::pep8_naming::rules::invalid_module_name; use crate::settings::LinterSettings; +use crate::Locator; pub(crate) fn check_file_path( path: &Path, diff --git a/crates/ruff_linter/src/checkers/imports.rs b/crates/ruff_linter/src/checkers/imports.rs index 4fbf72626f6b0..d956167553474 100644 --- a/crates/ruff_linter/src/checkers/imports.rs +++ b/crates/ruff_linter/src/checkers/imports.rs @@ -8,13 +8,13 @@ use ruff_python_ast::{ModModule, PySourceType}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Parsed; -use ruff_source_file::Locator; use crate::directives::IsortDirectives; use crate::registry::Rule; use crate::rules::isort; use crate::rules::isort::block::{Block, BlockBuilder}; use crate::settings::LinterSettings; +use crate::Locator; #[allow(clippy::too_many_arguments)] pub(crate) fn check_imports( diff --git a/crates/ruff_linter/src/checkers/logical_lines.rs b/crates/ruff_linter/src/checkers/logical_lines.rs index 4bfb6207f7ce5..1933889387b92 100644 --- a/crates/ruff_linter/src/checkers/logical_lines.rs +++ b/crates/ruff_linter/src/checkers/logical_lines.rs @@ -1,11 +1,11 @@ -use crate::line_width::IndentWidth; use ruff_diagnostics::Diagnostic; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; +use crate::line_width::IndentWidth; use crate::registry::{AsRule, Rule}; use crate::rules::pycodestyle::rules::logical_lines::{ extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword, @@ -14,6 +14,7 @@ use crate::rules::pycodestyle::rules::logical_lines::{ whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags, }; use crate::settings::LinterSettings; +use crate::Locator; /// Return the amount of indentation, expanding tabs to the next multiple of the settings' tab size. pub(crate) fn expand_indent(line: &str, indent_width: IndentWidth) -> usize { diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 947c92547aa8a..a2ecc7a34161f 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -7,7 +7,6 @@ use rustc_hash::FxHashSet; use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::fix::edits::delete_comment; @@ -20,6 +19,7 @@ use crate::rules::pygrep_hooks; use crate::rules::ruff; use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA}; use crate::settings::LinterSettings; +use crate::Locator; #[allow(clippy::too_many_arguments)] pub(crate) fn check_noqa( @@ -33,13 +33,8 @@ pub(crate) fn check_noqa( settings: &LinterSettings, ) -> Vec { // Identify any codes that are globally exempted (within the current file). - let file_noqa_directives = FileNoqaDirectives::extract( - locator.contents(), - comment_ranges, - &settings.external, - path, - locator, - ); + let file_noqa_directives = + FileNoqaDirectives::extract(locator, comment_ranges, &settings.external, path); let exemption = FileExemption::from(&file_noqa_directives); // Extract all `noqa` directives. diff --git a/crates/ruff_linter/src/checkers/physical_lines.rs b/crates/ruff_linter/src/checkers/physical_lines.rs index 7f1940a61b460..9fba92a5fe406 100644 --- a/crates/ruff_linter/src/checkers/physical_lines.rs +++ b/crates/ruff_linter/src/checkers/physical_lines.rs @@ -3,7 +3,7 @@ use ruff_diagnostics::Diagnostic; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; -use ruff_source_file::{Locator, UniversalNewlines}; +use ruff_source_file::UniversalNewlines; use ruff_text_size::TextSize; use crate::registry::Rule; @@ -14,6 +14,7 @@ use crate::rules::pycodestyle::rules::{ }; use crate::rules::pylint; use crate::settings::LinterSettings; +use crate::Locator; pub(crate) fn check_physical_lines( locator: &Locator, @@ -92,12 +93,12 @@ mod tests { use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::parse_module; - use ruff_source_file::Locator; use crate::line_width::LineLength; use crate::registry::Rule; use crate::rules::pycodestyle; use crate::settings::LinterSettings; + use crate::Locator; use super::check_physical_lines; @@ -106,8 +107,8 @@ mod tests { let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8. let locator = Locator::new(line); let parsed = parse_module(line).unwrap(); - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); let check_with_max_line_length = |line_length: LineLength| { check_physical_lines( diff --git a/crates/ruff_linter/src/checkers/tokens.rs b/crates/ruff_linter/src/checkers/tokens.rs index f272b910f8d36..7be23f8bfd035 100644 --- a/crates/ruff_linter/src/checkers/tokens.rs +++ b/crates/ruff_linter/src/checkers/tokens.rs @@ -2,14 +2,12 @@ use std::path::Path; +use ruff_diagnostics::Diagnostic; use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; use ruff_python_codegen::Stylist; - -use ruff_diagnostics::Diagnostic; use ruff_python_index::Indexer; use ruff_python_parser::Tokens; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::directives::TodoComment; @@ -20,6 +18,7 @@ use crate::rules::{ flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff, }; use crate::settings::LinterSettings; +use crate::Locator; #[allow(clippy::too_many_arguments)] pub(crate) fn check_tokens( diff --git a/crates/ruff_linter/src/directives.rs b/crates/ruff_linter/src/directives.rs index 50d23c40bc56f..128042bc8dba8 100644 --- a/crates/ruff_linter/src/directives.rs +++ b/crates/ruff_linter/src/directives.rs @@ -4,15 +4,16 @@ use std::iter::Peekable; use std::str::FromStr; use bitflags::bitflags; + +use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; use ruff_python_trivia::CommentRanges; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use ruff_python_index::Indexer; -use ruff_source_file::Locator; - use crate::noqa::NoqaMapping; use crate::settings::LinterSettings; +use crate::Locator; bitflags! { #[derive(Debug, Copy, Clone)] @@ -360,24 +361,23 @@ impl TodoDirectiveKind { #[cfg(test)] mod tests { + use ruff_python_index::Indexer; use ruff_python_parser::parse_module; use ruff_python_trivia::CommentRanges; use ruff_text_size::{TextLen, TextRange, TextSize}; - use ruff_python_index::Indexer; - use ruff_source_file::Locator; - use crate::directives::{ extract_isort_directives, extract_noqa_line_for, TodoDirective, TodoDirectiveKind, }; use crate::noqa::NoqaMapping; + use crate::Locator; use super::IsortDirectives; fn noqa_mappings(contents: &str) -> NoqaMapping { let parsed = parse_module(contents).unwrap(); let locator = Locator::new(contents); - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); extract_noqa_line_for(parsed.tokens(), &locator, &indexer) } diff --git a/crates/ruff_linter/src/doc_lines.rs b/crates/ruff_linter/src/doc_lines.rs index 17041d023f44b..5996d139ab2ec 100644 --- a/crates/ruff_linter/src/doc_lines.rs +++ b/crates/ruff_linter/src/doc_lines.rs @@ -4,12 +4,13 @@ use std::iter::FusedIterator; use std::slice::Iter; +use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor}; use ruff_python_ast::{self as ast, Stmt, Suite}; use ruff_python_parser::{Token, TokenKind, Tokens}; +use ruff_source_file::UniversalNewlineIterator; use ruff_text_size::{Ranged, TextSize}; -use ruff_python_ast::statement_visitor::{walk_stmt, StatementVisitor}; -use ruff_source_file::{Locator, UniversalNewlineIterator}; +use crate::Locator; /// Extract doc lines (standalone comments) from a token sequence. pub(crate) fn doc_lines_from_tokens(tokens: &Tokens) -> DocLines { diff --git a/crates/ruff_linter/src/fix/codemods.rs b/crates/ruff_linter/src/fix/codemods.rs index 70a928856c3b3..fe9c4074bb47b 100644 --- a/crates/ruff_linter/src/fix/codemods.rs +++ b/crates/ruff_linter/src/fix/codemods.rs @@ -13,9 +13,9 @@ use unicode_normalization::UnicodeNormalization; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::Stmt; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; use crate::cst::matchers::match_statement; +use crate::Locator; /// Glue code to make libcst codegen work with ruff's Stylist pub(crate) trait CodegenStylist<'a>: Codegen<'a> { diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 7b1fa7ebb7cfe..e58e9c43412e1 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -13,13 +13,14 @@ use ruff_python_trivia::{ has_leading_content, is_python_whitespace, CommentRanges, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, }; -use ruff_source_file::{Locator, NewlineWithTrailingNewline, UniversalNewlines}; +use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::cst::matchers::{match_function_def, match_indented_block, match_statement}; use crate::fix::codemods; use crate::fix::codemods::CodegenStylist; use crate::line_width::{IndentWidth, LineLength, LineWidthBuilder}; +use crate::Locator; /// Return the `Fix` to use when deleting a `Stmt`. /// @@ -48,9 +49,11 @@ pub(crate) fn delete_stmt( if let Some(semicolon) = trailing_semicolon(stmt.end(), locator) { let next = next_stmt_break(semicolon, locator); Edit::deletion(stmt.start(), next) - } else if has_leading_content(stmt.start(), locator) { + } else if has_leading_content(stmt.start(), locator.contents()) { Edit::range_deletion(stmt.range()) - } else if let Some(start) = indexer.preceded_by_continuations(stmt.start(), locator) { + } else if let Some(start) = + indexer.preceded_by_continuations(stmt.start(), locator.contents()) + { Edit::deletion(start, stmt.end()) } else { let range = locator.full_lines_range(stmt.range()); @@ -595,13 +598,13 @@ mod tests { use ruff_python_ast::Stmt; use ruff_python_codegen::Stylist; use ruff_python_parser::{parse_expression, parse_module}; - use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::fix::apply_fixes; use crate::fix::edits::{ add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; + use crate::Locator; /// Parse the given source using [`Mode::Module`] and return the first statement. fn parse_first_stmt(source: &str) -> Result { @@ -726,7 +729,7 @@ x = 1 \ let locator = Locator::new(raw); let edits = { let parsed = parse_expression(raw)?; - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); add_to_dunder_all(names.iter().copied(), parsed.expr(), &stylist) }; let diag = { diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index 9848f31bba13b..6a46c7a02e61e 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -1,15 +1,15 @@ -use itertools::Itertools; use std::collections::BTreeSet; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use ruff_diagnostics::{Diagnostic, Edit, Fix, IsolationLevel, SourceMap}; -use ruff_source_file::Locator; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::linter::FixTable; use crate::registry::{AsRule, Rule}; use crate::settings::types::UnsafeFixes; +use crate::Locator; pub(crate) mod codemods; pub(crate) mod edits; @@ -158,13 +158,12 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi #[cfg(test)] mod tests { - use ruff_text_size::{Ranged, TextSize}; - use ruff_diagnostics::{Diagnostic, Edit, Fix, SourceMarker}; - use ruff_source_file::Locator; + use ruff_text_size::{Ranged, TextSize}; use crate::fix::{apply_fixes, FixResult}; use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; + use crate::Locator; #[allow(deprecated)] fn create_diagnostics(edit: impl IntoIterator) -> Vec { diff --git a/crates/ruff_linter/src/importer/insertion.rs b/crates/ruff_linter/src/importer/insertion.rs index 715405e19ece5..5181145e9b357 100644 --- a/crates/ruff_linter/src/importer/insertion.rs +++ b/crates/ruff_linter/src/importer/insertion.rs @@ -1,15 +1,16 @@ //! Insert statements into Python code. use std::ops::Add; -use ruff_python_ast::Stmt; -use ruff_python_parser::{TokenKind, Tokens}; -use ruff_text_size::{Ranged, TextSize}; - use ruff_diagnostics::Edit; use ruff_python_ast::helpers::is_docstring_stmt; +use ruff_python_ast::Stmt; use ruff_python_codegen::Stylist; +use ruff_python_parser::{TokenKind, Tokens}; use ruff_python_trivia::{textwrap::indent, PythonWhitespace}; -use ruff_source_file::{Locator, UniversalNewlineIterator}; +use ruff_source_file::{LineRanges, UniversalNewlineIterator}; +use ruff_text_size::{Ranged, TextSize}; + +use crate::Locator; #[derive(Debug, Clone, PartialEq, Eq)] pub(super) enum Placement<'a> { @@ -64,7 +65,7 @@ impl<'a> Insertion<'a> { // Otherwise, advance to the next row. locator.full_line_end(location) } else { - locator.contents_start() + locator.bom_start_offset() }; // Skip over commented lines, with whitespace separation. @@ -319,9 +320,11 @@ mod tests { use ruff_python_codegen::Stylist; use ruff_python_parser::parse_module; - use ruff_source_file::{LineEnding, Locator}; + use ruff_source_file::LineEnding; use ruff_text_size::TextSize; + use crate::Locator; + use super::Insertion; #[test] @@ -329,7 +332,7 @@ mod tests { fn insert(contents: &str) -> Result { let parsed = parse_module(contents)?; let locator = Locator::new(contents); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); Ok(Insertion::start_of_file(parsed.suite(), &locator, &stylist)) } @@ -440,7 +443,7 @@ x = 1 fn insert(contents: &str, offset: TextSize) -> Insertion { let parsed = parse_module(contents).unwrap(); let locator = Locator::new(contents); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); Insertion::start_of_block(offset, &locator, &stylist, parsed.tokens()) } diff --git a/crates/ruff_linter/src/importer/mod.rs b/crates/ruff_linter/src/importer/mod.rs index bba1b155bdd28..4d97468f11a01 100644 --- a/crates/ruff_linter/src/importer/mod.rs +++ b/crates/ruff_linter/src/importer/mod.rs @@ -16,13 +16,13 @@ use ruff_python_semantic::{ ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel, }; use ruff_python_trivia::textwrap::indent; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextSize}; use crate::cst::matchers::{match_aliases, match_import_from, match_statement}; use crate::fix; use crate::fix::codemods::CodegenStylist; use crate::importer::insertion::Insertion; +use crate::Locator; mod insertion; diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index 20e0638cb30bd..e9641737d8392 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -5,6 +5,7 @@ //! //! [Ruff]: https://github.com/astral-sh/ruff +pub use locator::Locator; pub use noqa::generate_noqa_edits; #[cfg(feature = "clap")] pub use registry::clap_completion::RuleParser; @@ -27,6 +28,7 @@ pub mod fs; mod importer; pub mod line_width; pub mod linter; +mod locator; pub mod logging; pub mod message; mod noqa; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 6958b298cf3a3..375c81715f071 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -14,7 +14,7 @@ use ruff_python_ast::{ModModule, PySourceType}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::{ParseError, Parsed}; -use ruff_source_file::{Locator, SourceFileBuilder}; +use ruff_source_file::SourceFileBuilder; use ruff_text_size::Ranged; use crate::checkers::ast::check_ast; @@ -34,7 +34,7 @@ use crate::rules::ruff::rules::test_rules::{self, TestRule, TEST_RULES}; use crate::settings::types::UnsafeFixes; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; -use crate::{directives, fs}; +use crate::{directives, fs, Locator}; pub struct LinterResult { /// A collection of diagnostic messages generated by the linter. @@ -335,10 +335,10 @@ pub fn add_noqa_to_path( let locator = Locator::new(source_kind.source_code()); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( @@ -393,10 +393,10 @@ pub fn lint_only( let locator = Locator::new(source_kind.source_code()); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( @@ -495,10 +495,10 @@ pub fn lint_fix<'a>( let locator = Locator::new(transformed.source_code()); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( diff --git a/crates/ruff_linter/src/locator.rs b/crates/ruff_linter/src/locator.rs new file mode 100644 index 0000000000000..c75cfd4d48569 --- /dev/null +++ b/crates/ruff_linter/src/locator.rs @@ -0,0 +1,224 @@ +//! Struct used to efficiently slice source code at (row, column) Locations. + +use std::cell::OnceCell; + +use ruff_source_file::{LineIndex, LineRanges, OneIndexed, SourceCode, SourceLocation}; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; + +#[derive(Debug)] +pub struct Locator<'a> { + contents: &'a str, + index: OnceCell, +} + +impl<'a> Locator<'a> { + pub const fn new(contents: &'a str) -> Self { + Self { + contents, + index: OnceCell::new(), + } + } + + pub fn with_index(contents: &'a str, index: LineIndex) -> Self { + Self { + contents, + index: OnceCell::from(index), + } + } + + #[deprecated( + note = "This is expensive, avoid using outside of the diagnostic phase. Prefer the other `Locator` methods instead." + )] + pub fn compute_line_index(&self, offset: TextSize) -> OneIndexed { + self.to_index().line_index(offset) + } + + #[deprecated( + note = "This is expensive, avoid using outside of the diagnostic phase. Prefer the other `Locator` methods instead." + )] + pub fn compute_source_location(&self, offset: TextSize) -> SourceLocation { + self.to_source_code().source_location(offset) + } + + pub fn to_index(&self) -> &LineIndex { + self.index + .get_or_init(|| LineIndex::from_source_text(self.contents)) + } + + pub fn line_index(&self) -> Option<&LineIndex> { + self.index.get() + } + + pub fn to_source_code(&self) -> SourceCode { + SourceCode::new(self.contents, self.to_index()) + } + + /// Take the source code up to the given [`TextSize`]. + #[inline] + pub fn up_to(&self, offset: TextSize) -> &'a str { + &self.contents[TextRange::up_to(offset)] + } + + /// Take the source code after the given [`TextSize`]. + #[inline] + pub fn after(&self, offset: TextSize) -> &'a str { + &self.contents[usize::from(offset)..] + } + + /// Finds the closest [`TextSize`] not exceeding the offset for which `is_char_boundary` is + /// `true`. + /// + /// Can be replaced with `str::floor_char_boundary` once it's stable. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_linter::Locator; + /// + /// let locator = Locator::new("Hello"); + /// + /// assert_eq!( + /// locator.floor_char_boundary(TextSize::from(0)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// locator.floor_char_boundary(TextSize::from(5)), + /// TextSize::from(5) + /// ); + /// + /// let locator = Locator::new("α"); + /// + /// assert_eq!( + /// locator.floor_char_boundary(TextSize::from(0)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// locator.floor_char_boundary(TextSize::from(1)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// locator.floor_char_boundary(TextSize::from(2)), + /// TextSize::from(2) + /// ); + /// ``` + pub fn floor_char_boundary(&self, offset: TextSize) -> TextSize { + if offset >= self.text_len() { + self.text_len() + } else { + // We know that the character boundary is within four bytes. + (0u32..=3u32) + .map(TextSize::from) + .filter_map(|index| offset.checked_sub(index)) + .find(|offset| self.contents.is_char_boundary(offset.to_usize())) + .unwrap_or_default() + } + } + + /// Take the source code between the given [`TextRange`]. + #[inline] + pub fn slice(&self, ranged: T) -> &'a str { + &self.contents[ranged.range()] + } + + /// Return the underlying source code. + pub const fn contents(&self) -> &'a str { + self.contents + } + + /// Return the number of bytes in the source code. + pub const fn len(&self) -> usize { + self.contents.len() + } + + pub fn text_len(&self) -> TextSize { + self.contents.text_len() + } + + /// Return `true` if the source code is empty. + pub const fn is_empty(&self) -> bool { + self.contents.is_empty() + } +} + +// Override the `_str` methods from [`LineRanges`] to extend the lifetime to `'a`. +impl<'a> Locator<'a> { + /// Returns the text of the `offset`'s line. + /// + /// See [`LineRanges::full_lines_str`]. + pub fn full_line_str(&self, offset: TextSize) -> &'a str { + self.contents.full_line_str(offset) + } + + /// Returns the text of the `offset`'s line. + /// + /// See [`LineRanges::line_str`]. + pub fn line_str(&self, offset: TextSize) -> &'a str { + self.contents.line_str(offset) + } + + /// Returns the text of all lines that include `range`. + /// + /// See [`LineRanges::lines_str`]. + pub fn lines_str(&self, range: TextRange) -> &'a str { + self.contents.lines_str(range) + } + + /// Returns the text of all lines that include `range`. + /// + /// See [`LineRanges::full_lines_str`]. + pub fn full_lines_str(&self, range: TextRange) -> &'a str { + self.contents.full_lines_str(range) + } +} + +// Allow calling [`LineRanges`] methods on [`Locator`] directly. +impl LineRanges for Locator<'_> { + #[inline] + fn line_start(&self, offset: TextSize) -> TextSize { + self.contents.line_start(offset) + } + + #[inline] + fn bom_start_offset(&self) -> TextSize { + self.contents.bom_start_offset() + } + + #[inline] + fn full_line_end(&self, offset: TextSize) -> TextSize { + self.contents.full_line_end(offset) + } + + #[inline] + fn line_end(&self, offset: TextSize) -> TextSize { + self.contents.line_end(offset) + } + + #[inline] + fn full_line_str(&self, offset: TextSize) -> &str { + self.contents.full_line_str(offset) + } + + #[inline] + fn line_str(&self, offset: TextSize) -> &str { + self.contents.line_str(offset) + } + + #[inline] + fn contains_line_break(&self, range: TextRange) -> bool { + self.contents.contains_line_break(range) + } + + #[inline] + fn lines_str(&self, range: TextRange) -> &str { + self.contents.lines_str(range) + } + + #[inline] + fn full_lines_str(&self, range: TextRange) -> &str { + self.contents.full_lines_str(range) + } +} diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 4c6068f3761a8..9ce54301175b1 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -14,17 +14,17 @@ pub use json_lines::JsonLinesEmitter; pub use junit::JunitEmitter; pub use pylint::PylintEmitter; pub use rdjson::RdjsonEmitter; -pub use sarif::SarifEmitter; -pub use text::TextEmitter; - use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::ParseError; -use ruff_source_file::{Locator, SourceFile, SourceLocation}; +use ruff_source_file::{SourceFile, SourceLocation}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +pub use sarif::SarifEmitter; +pub use text::TextEmitter; use crate::logging::DisplayParseErrorType; use crate::registry::{AsRule, Rule}; +use crate::Locator; mod azure; mod diff; @@ -310,10 +310,11 @@ mod tests { use ruff_diagnostics::{Diagnostic, DiagnosticKind, Edit, Fix}; use ruff_notebook::NotebookIndex; use ruff_python_parser::{parse_unchecked, Mode}; - use ruff_source_file::{Locator, OneIndexed, SourceFileBuilder}; + use ruff_source_file::{OneIndexed, SourceFileBuilder}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::message::{Emitter, EmitterContext, Message}; + use crate::Locator; pub(super) fn create_syntax_error_messages() -> Vec { let source = r"from os import diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index bfacc88a63e6c..948cd534f95da 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -8,16 +8,17 @@ use std::path::Path; use anyhow::Result; use itertools::Itertools; use log::warn; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use ruff_diagnostics::{Diagnostic, Edit}; use ruff_python_trivia::{indentation_at_offset, CommentRanges}; -use ruff_source_file::{LineEnding, Locator}; +use ruff_source_file::{LineEnding, LineRanges}; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::codes::NoqaCode; use crate::fs::relativize_path; use crate::registry::{AsRule, Rule, RuleSet}; use crate::rule_redirects::get_redirect_target; +use crate::Locator; /// Generates an array of edits that matches the length of `diagnostics`. /// Each potential edit in the array is paired, in order, with the associated diagnostic. @@ -33,8 +34,7 @@ pub fn generate_noqa_edits( noqa_line_for: &NoqaMapping, line_ending: LineEnding, ) -> Vec> { - let file_directives = - FileNoqaDirectives::extract(locator.contents(), comment_ranges, external, path, locator); + let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&file_directives); let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for); @@ -352,16 +352,15 @@ impl<'a> FileNoqaDirectives<'a> { /// Extract the [`FileNoqaDirectives`] for a given Python source file, enumerating any rules /// that are globally ignored within the file. pub(crate) fn extract( - contents: &'a str, + locator: &Locator<'a>, comment_ranges: &CommentRanges, external: &[String], path: &Path, - locator: &Locator, ) -> Self { let mut lines = vec![]; for range in comment_ranges { - match ParsedFileExemption::try_extract(&contents[range]) { + match ParsedFileExemption::try_extract(&locator.contents()[range]) { Err(err) => { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); @@ -369,7 +368,7 @@ impl<'a> FileNoqaDirectives<'a> { warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}"); } Ok(Some(exemption)) => { - if indentation_at_offset(range.start(), locator).is_none() { + if indentation_at_offset(range.start(), locator.contents()).is_none() { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); let path_display = relativize_path(path); @@ -619,8 +618,7 @@ fn add_noqa_inner( let mut count = 0; // Whether the file is exempted from all checks. - let directives = - FileNoqaDirectives::extract(locator.contents(), comment_ranges, external, path, locator); + let directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&directives); let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); @@ -1055,17 +1053,17 @@ mod tests { use std::path::Path; use insta::assert_debug_snapshot; - use ruff_text_size::{TextRange, TextSize}; use ruff_diagnostics::{Diagnostic, Edit}; use ruff_python_trivia::CommentRanges; - use ruff_source_file::{LineEnding, Locator}; + use ruff_source_file::LineEnding; + use ruff_text_size::{TextRange, TextSize}; - use crate::generate_noqa_edits; use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption}; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; + use crate::{generate_noqa_edits, Locator}; #[test] fn noqa_all() { diff --git a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs index 53d87dcf94af7..5810ef58c63bd 100644 --- a/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs +++ b/crates/ruff_linter/src/rules/eradicate/rules/commented_out_code.rs @@ -1,10 +1,12 @@ -use crate::settings::LinterSettings; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::{Locator, UniversalNewlineIterator}; +use ruff_source_file::{LineRanges, UniversalNewlineIterator}; use ruff_text_size::TextRange; +use crate::settings::LinterSettings; +use crate::Locator; + use super::super::detection::comment_contains_code; /// ## What it does @@ -53,7 +55,7 @@ pub(crate) fn commented_out_code( let mut comments = comment_ranges.into_iter().peekable(); // Iterate over all comments in the document. while let Some(range) = comments.next() { - let line = locator.line(range.start()); + let line = locator.line_str(range.start()); if is_script_tag_start(line) { if skip_script_comments(range, &mut comments, locator) { @@ -176,11 +178,14 @@ fn is_script_tag_start(line: &str) -> bool { #[cfg(test)] mod tests { - use crate::rules::eradicate::rules::commented_out_code::skip_script_comments; use ruff_python_parser::parse_module; use ruff_python_trivia::CommentRanges; - use ruff_source_file::Locator; + use ruff_source_file::LineRanges; use ruff_text_size::TextSize; + + use crate::rules::eradicate::rules::commented_out_code::skip_script_comments; + use crate::Locator; + #[test] fn script_comment() { let code = r#" diff --git a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs index e68d0dcce5f07..322f9dae33da9 100644 --- a/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs +++ b/crates/ruff_linter/src/rules/flake8_bandit/rules/hardcoded_sql_expression.rs @@ -6,10 +6,10 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::str::raw_contents; use ruff_python_ast::{self as ast, Expr, Operator}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::Locator; static SQL_REGEX: LazyLock = LazyLock::new(|| { Regex::new(r"(?i)\b(select\s+.*\s+from\s|delete\s+from\s|(insert|replace)\s+.*\s+values\s|update\s+.*\s+set\s)") diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs b/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs index 2745153c86b34..51015eb63af6a 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs @@ -1,9 +1,10 @@ use ruff_notebook::CellOffsets; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; +use crate::Locator; + /// Return `true` if the statement containing the current expression is the last /// top-level expression in the cell. This assumes that the source is a Jupyter /// Notebook. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs index 85ff36723e110..550fed3304682 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/getattr_with_constant.rs @@ -1,11 +1,12 @@ -use crate::fix::edits::pad; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_stdlib::identifiers::{is_identifier, is_mangled_private}; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::fix::edits::pad; /// ## What it does /// Checks for uses of `getattr` that take a constant attribute value as an diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index e15ca868abc32..6d78902f162f5 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -9,10 +9,11 @@ use ruff_python_semantic::analyze::function_type::is_stub; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::{indentation_at_offset, textwrap}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for uses of mutable objects as function argument defaults. @@ -145,7 +146,7 @@ fn move_initialization( // Avoid attempting to fix single-line functions. let statement = body.peek()?; - if indexer.preceded_by_multi_statement_line(statement, locator) { + if indexer.preceded_by_multi_statement_line(statement, locator.contents()) { return None; } @@ -170,7 +171,7 @@ fn move_initialization( content.push_str(stylist.line_ending().as_str()); // Determine the indentation depth of the function body. - let indentation = indentation_at_offset(statement.start(), locator)?; + let indentation = indentation_at_offset(statement.start(), locator.contents())?; // Indent the edit to match the body indentation. let mut content = textwrap::indent(&content, indentation).to_string(); @@ -186,7 +187,7 @@ fn move_initialization( if let Some(next) = body.peek() { // If there's a second statement, insert _before_ it, but ensure this isn't a // multi-statement line. - if indexer.in_multi_statement_line(statement, locator) { + if indexer.in_multi_statement_line(statement, locator.contents()) { continue; } pos = locator.line_start(next.start()); diff --git a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs index fd1041f7d25e4..b4e2c423cea47 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs +++ b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs @@ -3,9 +3,10 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; +use crate::Locator; + /// Simplified token type. #[derive(Copy, Clone, PartialEq, Eq)] enum TokenType { diff --git a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs index df712666618b3..ae4896f8152b6 100644 --- a/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs +++ b/crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs @@ -14,13 +14,13 @@ use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Stylist; use ruff_python_semantic::SemanticModel; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::cst::helpers::{negate, space}; use crate::fix::codemods::CodegenStylist; use crate::fix::edits::pad; use crate::rules::flake8_comprehensions::rules::ObjectType; +use crate::Locator; use crate::{ checkers::ast::Checker, cst::matchers::{ diff --git a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs index 5fd119ff8df0c..e765d88d0a51f 100644 --- a/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs +++ b/crates/ruff_linter/src/rules/flake8_copyright/rules/missing_copyright_notice.rs @@ -1,9 +1,9 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_source_file::Locator; use ruff_text_size::{TextRange, TextSize}; use crate::settings::LinterSettings; +use crate::Locator; /// ## What it does /// Checks for the absence of copyright notices within Python files. diff --git a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 23f2bf5881909..c119d11e74aef 100644 --- a/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs @@ -1,14 +1,14 @@ -use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; -use ruff_source_file::Locator; -use ruff_text_size::Ranged; - use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::whitespace; +use ruff_python_ast::{self as ast, Arguments, Expr, Stmt}; use ruff_python_codegen::Stylist; +use ruff_source_file::LineRanges; +use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::Locator; /// ## What it does /// Checks for the use of string literals in exception constructors. @@ -190,7 +190,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr let mut diagnostic = Diagnostic::new(RawStringInException, first.range()); if let Some(indentation) = - whitespace::indentation(checker.locator(), stmt) + whitespace::indentation(checker.source(), stmt) { diagnostic.set_fix(generate_fix( stmt, @@ -208,8 +208,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr Expr::FString(_) => { if checker.enabled(Rule::FStringInException) { let mut diagnostic = Diagnostic::new(FStringInException, first.range()); - if let Some(indentation) = whitespace::indentation(checker.locator(), stmt) - { + if let Some(indentation) = whitespace::indentation(checker.source(), stmt) { diagnostic.set_fix(generate_fix( stmt, first, @@ -231,7 +230,7 @@ pub(crate) fn string_in_exception(checker: &mut Checker, stmt: &Stmt, exc: &Expr let mut diagnostic = Diagnostic::new(DotFormatInException, first.range()); if let Some(indentation) = - whitespace::indentation(checker.locator(), stmt) + whitespace::indentation(checker.source(), stmt) { diagnostic.set_fix(generate_fix( stmt, diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs index d4b2bd596a96b..e8947b596d1ad 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/mod.rs @@ -1,17 +1,18 @@ use std::path::Path; -use crate::codes::Rule; -use crate::comments::shebang::ShebangDirective; -use crate::settings::LinterSettings; use ruff_diagnostics::Diagnostic; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; pub(crate) use shebang_leading_whitespace::*; pub(crate) use shebang_missing_executable_file::*; pub(crate) use shebang_missing_python::*; pub(crate) use shebang_not_executable::*; pub(crate) use shebang_not_first_line::*; +use crate::codes::Rule; +use crate::comments::shebang::ShebangDirective; +use crate::settings::LinterSettings; +use crate::Locator; + mod shebang_leading_whitespace; mod shebang_missing_executable_file; mod shebang_missing_python; diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs index 0aa88beaf17b4..9b865f8b5f4aa 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_leading_whitespace.rs @@ -1,9 +1,9 @@ use ruff_text_size::{TextRange, TextSize}; +use crate::Locator; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::is_python_whitespace; -use ruff_source_file::Locator; /// ## What it does /// Checks for whitespace before a shebang directive. diff --git a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs index 9f199d9038522..47f1eba6a6641 100644 --- a/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs +++ b/crates/ruff_linter/src/rules/flake8_executable/rules/shebang_not_first_line.rs @@ -1,9 +1,9 @@ -use ruff_text_size::{TextRange, TextSize}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::is_python_whitespace; -use ruff_source_file::Locator; +use ruff_text_size::{TextRange, TextSize}; + +use crate::Locator; /// ## What it does /// Checks for a shebang directive that is not at the beginning of the file. diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs index e726067824b03..fbc169131e7fc 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/explicit.rs @@ -1,11 +1,12 @@ -use ruff_python_ast::{self as ast, Expr, Operator}; - -use crate::settings::LinterSettings; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_source_file::Locator; +use ruff_python_ast::{self as ast, Expr, Operator}; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; +use crate::settings::LinterSettings; +use crate::Locator; + /// ## What it does /// Checks for string literals that are explicitly concatenated (using the /// `+` operator). diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs index 0011d9d5051c0..b3a88e538f661 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/implicit.rs @@ -7,10 +7,11 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::str::{leading_quote, trailing_quote}; use ruff_python_index::Indexer; use ruff_python_parser::{TokenKind, Tokens}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::settings::LinterSettings; +use crate::Locator; /// ## What it does /// Checks for implicitly concatenated strings on a single line. diff --git a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs index 9a16bb8ed325d..2ebe87f48d586 100644 --- a/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs +++ b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs @@ -65,7 +65,7 @@ pub(crate) fn unconventional_import_alias( let qualified_name = import.qualified_name().to_string(); let expected_alias = conventions.get(qualified_name.as_str())?; - let name = binding.name(checker.locator()); + let name = binding.name(checker.source()); if name == expected_alias { return None; } diff --git a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs index 33616de138667..733248c47ad0a 100644 --- a/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs +++ b/crates/ruff_linter/src/rules/flake8_no_pep420/rules/implicit_namespace_package.rs @@ -4,11 +4,11 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::PySourceType; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::{TextRange, TextSize}; use crate::comments::shebang::ShebangDirective; use crate::fs; +use crate::Locator; /// ## What it does /// Checks for packages that are missing an `__init__.py` file. diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs index 09a58552d6357..85fafcac08467 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs @@ -105,7 +105,7 @@ pub(crate) fn unnecessary_placeholder(checker: &mut Checker, body: &[Stmt]) { }; let mut diagnostic = Diagnostic::new(UnnecessaryPlaceholder { kind }, stmt.range()); - let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.locator()) { + let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.source()) { Edit::range_deletion(stmt.range().add_end(index)) } else { fix::edits::delete_stmt(stmt, None, checker.locator(), checker.indexer()) diff --git a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs index e4987dc4b5a6e..8bff1abe68729 100644 --- a/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs +++ b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_spread.rs @@ -1,12 +1,11 @@ -use ruff_python_ast::{self as ast, Expr}; - use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr}; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for unnecessary dictionary unpacking operators (`**`). diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs index b3243ab125561..be77a3a948efe 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/redundant_final_literal.rs @@ -1,11 +1,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, comparable::ComparableExpr}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextSize}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::Locator; /// ## What it does /// Checks for redundant `Final[Literal[...]]` annotations. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs index a16bee59b5264..54b6bc8d7df81 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/simple_defaults.rs @@ -5,13 +5,13 @@ use ruff_python_ast::{ self as ast, Expr, Operator, ParameterWithDefault, Parameters, Stmt, UnaryOp, }; use ruff_python_semantic::{analyze::class::is_enumeration, ScopeKind, SemanticModel}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::rules::flake8_pyi::rules::TypingModule; use crate::settings::types::PythonVersion; +use crate::Locator; /// ## What it does /// Checks for typed function arguments in stubs with complex default values. diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs index 719e4b6212f0b..7207e3ebed190 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/type_comment_in_stub.rs @@ -1,10 +1,12 @@ -use regex::Regex; -use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use std::sync::LazyLock; +use regex::Regex; + use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_trivia::CommentRanges; + +use crate::Locator; /// ## What it does /// Checks for the use of type comments (e.g., `x = 1 # type: int`) in stub diff --git a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs index 24aa32e1ad85f..bb24ed01f0d8f 100644 --- a/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs +++ b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs @@ -73,7 +73,7 @@ pub(crate) fn unaliased_collections_abc_set_import( return None; } - let name = binding.name(checker.locator()); + let name = binding.name(checker.source()); if name == "AbstractSet" { return None; } diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs index dc520f173878d..c5e83049cf9bd 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/assertion.rs @@ -17,7 +17,7 @@ use ruff_python_ast::{ }; use ruff_python_ast::{visitor, whitespace}; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -26,6 +26,7 @@ use crate::cst::matchers::match_indented_block; use crate::cst::matchers::match_module; use crate::fix::codemods::CodegenStylist; use crate::importer::ImportRequest; +use crate::Locator; use super::unittest_assert::UnittestAssert; @@ -386,7 +387,7 @@ pub(crate) fn unittest_raises_assertion( ); if !checker .comment_ranges() - .has_comments(call, checker.locator()) + .has_comments(call, checker.source()) { if let Some(args) = to_pytest_raises_args(checker, attr.as_str(), &call.arguments) { diagnostic.try_set_fix(|| { @@ -622,11 +623,11 @@ fn parenthesize<'a>(expression: &Expression<'a>, parent: &Expression<'a>) -> Exp /// `assert a == "hello"` and `assert b == "world"`. fn fix_composite_condition(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Result { // Infer the indentation of the outer block. - let outer_indent = - whitespace::indentation(locator, stmt).context("Unable to fix multiline statement")?; + let outer_indent = whitespace::indentation(locator.contents(), stmt) + .context("Unable to fix multiline statement")?; // Extract the module text. - let contents = locator.lines(stmt.range()); + let contents = locator.lines_str(stmt.range()); // If the block is indented, "embed" it in a function definition, to preserve // indentation while retaining valid source code. (We'll strip the prefix later @@ -747,7 +748,7 @@ pub(crate) fn composite_condition( && !checker.comment_ranges().intersects(stmt.range()) && !checker .indexer() - .in_multi_statement_line(stmt, checker.locator()) + .in_multi_statement_line(stmt, checker.source()) { diagnostic.try_set_fix(|| { fix_composite_condition(stmt, checker.locator(), checker.stylist()) diff --git a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs index 3af592adce7f5..95c31efbe85e9 100644 --- a/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs +++ b/crates/ruff_linter/src/rules/flake8_pytest_style/rules/fixture.rs @@ -9,6 +9,7 @@ use ruff_python_ast::Decorator; use ruff_python_ast::{self as ast, Expr, Parameters, Stmt}; use ruff_python_semantic::analyze::visibility::is_abstract; use ruff_python_semantic::SemanticModel; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs index a22ef2e042b6c..70725a983574d 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/avoidable_escaped_quote.rs @@ -1,16 +1,15 @@ +use flake8_quotes::helpers::{contains_escaped_quote, raw_contents, unescape_string}; +use flake8_quotes::settings::Quote; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::visitor::{walk_f_string, Visitor}; use ruff_python_ast::{self as ast, AnyStringFlags, StringFlags, StringLike}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::rules::flake8_quotes; use crate::settings::LinterSettings; - -use flake8_quotes::helpers::{contains_escaped_quote, raw_contents, unescape_string}; -use flake8_quotes::settings::Quote; +use crate::Locator; /// ## What it does /// Checks for strings that include escaped quotes, and suggests changing diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs index 3a2db2504e3b6..2ae1e4d575684 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/check_string_quotes.rs @@ -1,11 +1,11 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::StringLike; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::registry::Rule; +use crate::Locator; use super::super::settings::Quote; diff --git a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs index 3a397e1eccaab..9fc68dc0463d7 100644 --- a/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs +++ b/crates/ruff_linter/src/rules/flake8_quotes/rules/unnecessary_escaped_quote.rs @@ -1,10 +1,10 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, AnyStringFlags, StringFlags, StringLike}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::Locator; use super::super::helpers::{contains_escaped_quote, raw_contents, unescape_string}; diff --git a/crates/ruff_linter/src/rules/flake8_return/helpers.rs b/crates/ruff_linter/src/rules/flake8_return/helpers.rs index 4b99eae88d0b0..b8e98eee217a1 100644 --- a/crates/ruff_linter/src/rules/flake8_return/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_return/helpers.rs @@ -1,8 +1,9 @@ use ruff_python_ast as ast; use ruff_python_ast::Stmt; +use ruff_source_file::UniversalNewlines; use ruff_text_size::{Ranged, TextSize}; -use ruff_source_file::{Locator, UniversalNewlines}; +use crate::Locator; /// Return `true` if a function's return statement include at least one /// non-`None` value. diff --git a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 35a4553f91ac2..491de3e7e599d 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -15,7 +15,7 @@ use ruff_python_index::Indexer; use ruff_python_semantic::analyze::visibility::is_property; use ruff_python_semantic::SemanticModel; use ruff_python_trivia::{is_python_whitespace, SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; @@ -23,6 +23,7 @@ use crate::fix::edits; use crate::fix::edits::adjust_indentation; use crate::registry::{AsRule, Rule}; use crate::rules::flake8_return::helpers::end_of_last_statement; +use crate::Locator; use super::super::branch::Branch; use super::super::helpers::result_exists; @@ -453,7 +454,7 @@ fn is_noreturn_func(func: &Expr, semantic: &SemanticModel) -> bool { fn add_return_none(checker: &mut Checker, stmt: &Stmt, range: TextRange) { let mut diagnostic = Diagnostic::new(ImplicitReturn, range); - if let Some(indent) = indentation(checker.locator(), stmt) { + if let Some(indent) = indentation(checker.source(), stmt) { let mut content = String::new(); content.push_str(checker.stylist().line_ending().as_str()); content.push_str(indent); @@ -851,14 +852,14 @@ fn remove_else( }; // get the indentation of the `else`, since that is the indent level we want to end with - let Some(desired_indentation) = indentation(locator, elif_else) else { + let Some(desired_indentation) = indentation(locator.contents(), elif_else) else { return Err(anyhow::anyhow!("Compound statement cannot be inlined")); }; // If the statement is on the same line as the `else`, just remove the `else: `. // Ex) `else: return True` -> `return True` if let Some(first) = elif_else.body.first() { - if indexer.preceded_by_multi_statement_line(first, locator) { + if indexer.preceded_by_multi_statement_line(first, locator.contents()) { return Ok(Fix::safe_edit(Edit::deletion( elif_else.start(), first.start(), diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs index f7d8c3b924d81..d61dd56bedd4d 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs @@ -536,7 +536,7 @@ pub(crate) fn compare_with_tuple(checker: &mut Checker, expr: &Expr) { // Avoid removing comments. if checker .comment_ranges() - .has_comments(expr, checker.locator()) + .has_comments(expr, checker.source()) { continue; } diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs index b4516f7dfa58e..83cb7c2b191c6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs @@ -11,7 +11,7 @@ use ruff_python_ast::{self as ast, whitespace, ElifElseClause, Expr, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_semantic::analyze::typing::{is_sys_version_block, is_type_checking_block}; use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -19,6 +19,7 @@ use crate::cst::helpers::space; use crate::cst::matchers::{match_function_def, match_if, match_indented_block, match_statement}; use crate::fix::codemods::CodegenStylist; use crate::fix::edits::fits; +use crate::Locator; /// ## What it does /// Checks for nested `if` statements that can be collapsed into a single `if` @@ -292,12 +293,12 @@ pub(super) fn collapse_nested_if( nested_if: NestedIf, ) -> Result { // Infer the indentation of the outer block. - let Some(outer_indent) = whitespace::indentation(locator, &nested_if) else { + let Some(outer_indent) = whitespace::indentation(locator.contents(), &nested_if) else { bail!("Unable to fix multiline statement"); }; // Extract the module text. - let contents = locator.lines(nested_if.range()); + let contents = locator.lines_str(nested_if.range()); // If this is an `elif`, we have to remove the `elif` keyword for now. (We'll // restore the `el` later on.) diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs index 9360b5fcfbee8..008e9f1f2df1f 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/fix_with.rs @@ -5,11 +5,12 @@ use ruff_diagnostics::Edit; use ruff_python_ast as ast; use ruff_python_ast::whitespace; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use crate::cst::matchers::{match_function_def, match_indented_block, match_statement, match_with}; use crate::fix::codemods::CodegenStylist; +use crate::Locator; /// (SIM117) Convert `with a: with b:` to `with a, b:`. pub(crate) fn fix_multiple_with_statements( @@ -18,12 +19,12 @@ pub(crate) fn fix_multiple_with_statements( with_stmt: &ast::StmtWith, ) -> Result { // Infer the indentation of the outer block. - let Some(outer_indent) = whitespace::indentation(locator, with_stmt) else { + let Some(outer_indent) = whitespace::indentation(locator.contents(), with_stmt) else { bail!("Unable to fix multiline statement"); }; // Extract the module text. - let contents = locator.lines(with_stmt.range()); + let contents = locator.lines_str(with_stmt.range()); // If the block is indented, "embed" it in a function definition, to preserve // indentation while retaining valid source code. (We'll strip the prefix later diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs index 4a754ac30cd78..f14374dfa99c6 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_dict_get.rs @@ -211,7 +211,7 @@ pub(crate) fn if_else_block_instead_of_dict_get(checker: &mut Checker, stmt_if: ); if !checker .comment_ranges() - .has_comments(stmt_if, checker.locator()) + .has_comments(stmt_if, checker.source()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents, @@ -300,7 +300,7 @@ pub(crate) fn if_exp_instead_of_dict_get( ); if !checker .comment_ranges() - .has_comments(expr, checker.locator()) + .has_comments(expr, checker.source()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents, diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs index 03e0dacec0fa2..d113bd46c9efc 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_else_block_instead_of_if_exp.rs @@ -227,7 +227,7 @@ pub(crate) fn if_else_block_instead_of_if_exp(checker: &mut Checker, stmt_if: &a ); if !checker .comment_ranges() - .has_comments(stmt_if, checker.locator()) + .has_comments(stmt_if, checker.source()) { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( contents, diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs index 699544081361c..698961ab741cb 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/if_with_same_arms.rs @@ -9,10 +9,11 @@ use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::stmt_if::{if_elif_branches, IfElifBranch}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_trivia::{CommentRanges, SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for `if` branches with identical arm bodies. diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 8f7a4745516e1..b6dedfd098db1 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs @@ -200,7 +200,7 @@ pub(crate) fn needless_bool(checker: &mut Checker, stmt: &Stmt) { // Generate the replacement condition. let condition = if checker .comment_ranges() - .has_comments(&range, checker.locator()) + .has_comments(&range, checker.source()) { None } else { diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs index 5b81ed48df7f2..4e10108dac6a3 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/reimplemented_builtin.rs @@ -7,6 +7,7 @@ use ruff_python_ast::{ self as ast, Arguments, CmpOp, Comprehension, Expr, ExprContext, Stmt, UnaryOp, }; use ruff_python_codegen::Generator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs index d8bd58a5d301b..dde37f51358e8 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs @@ -3,6 +3,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers; use ruff_python_ast::name::UnqualifiedName; use ruff_python_ast::{self as ast, ExceptHandler, Stmt}; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; @@ -127,7 +128,7 @@ pub(crate) fn suppressible_exception( ); if !checker .comment_ranges() - .has_comments(stmt, checker.locator()) + .has_comments(stmt, checker.source()) { diagnostic.try_set_fix(|| { // let range = statement_range(stmt, checker.locator(), checker.indexer()); diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs index 0bacecda83469..cade5e6c6e064 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/yoda_conditions.rs @@ -8,7 +8,6 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, CmpOp, Expr, UnaryOp}; use ruff_python_codegen::Stylist; use ruff_python_stdlib::str::{self}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -16,6 +15,7 @@ use crate::cst::helpers::or_space; use crate::cst::matchers::{match_comparison, transform_expression}; use crate::fix::edits::pad; use crate::fix::snippet::SourceCodeSnippet; +use crate::Locator; /// ## What it does /// Checks for conditions that position a constant on the left-hand side of the diff --git a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs index 491d94a2f217c..8a5492f1d3bc6 100644 --- a/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs +++ b/crates/ruff_linter/src/rules/flake8_todos/rules/todos.rs @@ -1,13 +1,14 @@ -use regex::RegexSet; -use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; -use ruff_text_size::{TextLen, TextRange, TextSize}; use std::sync::LazyLock; +use regex::RegexSet; + use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, Violation}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_trivia::CommentRanges; +use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::directives::{TodoComment, TodoDirective, TodoDirectiveKind}; +use crate::Locator; /// ## What it does /// Checks that a TODO comment is labelled with "TODO". diff --git a/crates/ruff_linter/src/rules/isort/annotate.rs b/crates/ruff_linter/src/rules/isort/annotate.rs index 5a7b7bc1d1ba6..f3f1a247b2d4f 100644 --- a/crates/ruff_linter/src/rules/isort/annotate.rs +++ b/crates/ruff_linter/src/rules/isort/annotate.rs @@ -1,8 +1,9 @@ use ruff_python_ast::{self as ast, Stmt}; use ruff_python_parser::Tokens; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; -use ruff_source_file::Locator; +use crate::Locator; use super::comments::Comment; use super::helpers::trailing_comma; diff --git a/crates/ruff_linter/src/rules/isort/block.rs b/crates/ruff_linter/src/rules/isort/block.rs index 2fee72371fde6..c982c01e1195f 100644 --- a/crates/ruff_linter/src/rules/isort/block.rs +++ b/crates/ruff_linter/src/rules/isort/block.rs @@ -4,11 +4,11 @@ use std::slice; use ruff_notebook::CellOffsets; use ruff_python_ast::statement_visitor::StatementVisitor; use ruff_python_ast::{self as ast, ElifElseClause, ExceptHandler, MatchCase, Stmt}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::directives::IsortDirectives; use crate::rules::isort::helpers; +use crate::Locator; /// A block of imports within a Python module. #[derive(Debug, Default)] diff --git a/crates/ruff_linter/src/rules/isort/comments.rs b/crates/ruff_linter/src/rules/isort/comments.rs index d2c88213cc63f..da5848be8754e 100644 --- a/crates/ruff_linter/src/rules/isort/comments.rs +++ b/crates/ruff_linter/src/rules/isort/comments.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; +use crate::Locator; + #[derive(Debug)] pub(crate) struct Comment<'a> { pub(crate) value: Cow<'a, str>, diff --git a/crates/ruff_linter/src/rules/isort/helpers.rs b/crates/ruff_linter/src/rules/isort/helpers.rs index 50b8b7ffca49b..1d2a93a65d882 100644 --- a/crates/ruff_linter/src/rules/isort/helpers.rs +++ b/crates/ruff_linter/src/rules/isort/helpers.rs @@ -1,10 +1,11 @@ use ruff_python_ast::Stmt; use ruff_python_parser::{TokenKind, Tokens}; use ruff_python_trivia::PythonWhitespace; -use ruff_source_file::{Locator, UniversalNewlines}; +use ruff_source_file::UniversalNewlines; use ruff_text_size::Ranged; use crate::rules::isort::types::TrailingComma; +use crate::Locator; /// Return `true` if a `Stmt::ImportFrom` statement ends with a magic /// trailing comma. diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index aaaf886308650..613284b34b8b5 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -13,13 +13,13 @@ use order::order_imports; use ruff_python_ast::PySourceType; use ruff_python_codegen::Stylist; use ruff_python_parser::Tokens; -use ruff_source_file::Locator; use settings::Settings; use types::EitherImport::{Import, ImportFrom}; use types::{AliasData, ImportBlock, TrailingComma}; use crate::line_width::{LineLength, LineWidthBuilder}; use crate::settings::types::PythonVersion; +use crate::Locator; mod annotate; pub(crate) mod block; @@ -282,11 +282,12 @@ mod tests { use std::path::Path; use anyhow::Result; - use ruff_python_semantic::{MemberNameImport, ModuleNameImport, NameImport}; - use ruff_text_size::Ranged; use rustc_hash::{FxHashMap, FxHashSet}; use test_case::test_case; + use ruff_python_semantic::{MemberNameImport, ModuleNameImport, NameImport}; + use ruff_text_size::Ranged; + use crate::assert_messages; use crate::registry::Rule; use crate::rules::isort::categorize::{ImportSection, KnownModules}; diff --git a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs index 7d07c7ff4d162..9e1144c61e1ca 100644 --- a/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs @@ -5,12 +5,11 @@ use ruff_python_ast::{self as ast, ModModule, PySourceType, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_parser::Parsed; use ruff_python_semantic::{FutureImport, NameImport}; -use ruff_source_file::Locator; use ruff_text_size::{TextRange, TextSize}; use crate::importer::Importer; - use crate::settings::LinterSettings; +use crate::Locator; /// ## What it does /// Adds any required imports, as specified by the user, to the top of the diff --git a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index 88af0b9055fbb..8f9aebdc0cfc3 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -10,12 +10,12 @@ use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::Tokens; use ruff_python_trivia::{leading_indentation, textwrap::indent, PythonWhitespace}; -use ruff_source_file::{Locator, UniversalNewlines}; +use ruff_source_file::{LineRanges, UniversalNewlines}; use ruff_text_size::{Ranged, TextRange}; use crate::line_width::LineWidthBuilder; - use crate::settings::LinterSettings; +use crate::Locator; use super::super::block::Block; use super::super::{comments, format_imports}; @@ -98,8 +98,9 @@ pub(crate) fn organize_imports( // Special-cases: there's leading or trailing content in the import block. These // are too hard to get right, and relatively rare, so flag but don't fix. - if indexer.preceded_by_multi_statement_line(block.imports.first().unwrap(), locator) - || indexer.followed_by_multi_statement_line(block.imports.last().unwrap(), locator) + if indexer.preceded_by_multi_statement_line(block.imports.first().unwrap(), locator.contents()) + || indexer + .followed_by_multi_statement_line(block.imports.last().unwrap(), locator.contents()) { return Some(Diagnostic::new(UnsortedImports, range)); } @@ -114,7 +115,7 @@ pub(crate) fn organize_imports( let trailing_line_end = if block.trailer.is_none() { locator.full_line_end(range.end()) } else { - trailing_lines_end(block.imports.last().unwrap(), locator) + trailing_lines_end(block.imports.last().unwrap(), locator.contents()) }; // Generate the sorted import block. diff --git a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs index 743b97fcb6857..2347f15878b3b 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/rules/inplace_argument.rs @@ -4,11 +4,11 @@ use ruff_python_ast::helpers::is_const_true; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::{self as ast, Keyword, Stmt}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{remove_argument, Parentheses}; +use crate::Locator; /// ## What it does /// Checks for `inplace=True` usages in `pandas` function and method diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs index 1cf30bc925b4f..3070ed2058ef5 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/bare_except.rs @@ -1,9 +1,9 @@ -use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::identifier::except; -use ruff_source_file::Locator; +use ruff_python_ast::{self as ast, ExceptHandler, Expr, Stmt}; + +use crate::Locator; /// ## What it does /// Checks for bare `except` catches in `try`-`except` statements. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs index 30492e2fcca8b..f281ff13f8446 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/blank_lines.rs @@ -1,28 +1,30 @@ -use itertools::Itertools; -use ruff_notebook::CellOffsets; -use ruff_python_parser::TokenIterWithContext; -use ruff_python_parser::Tokens; use std::cmp::Ordering; use std::iter::Peekable; use std::num::NonZeroU32; use std::slice::Iter; +use itertools::Itertools; + use ruff_diagnostics::AlwaysFixableViolation; use ruff_diagnostics::Diagnostic; use ruff_diagnostics::Edit; use ruff_diagnostics::Fix; use ruff_macros::{derive_message_formats, violation}; +use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; use ruff_python_codegen::Stylist; +use ruff_python_parser::TokenIterWithContext; use ruff_python_parser::TokenKind; +use ruff_python_parser::Tokens; use ruff_python_trivia::PythonWhitespace; -use ruff_source_file::{Locator, UniversalNewlines}; +use ruff_source_file::{LineRanges, UniversalNewlines}; use ruff_text_size::TextRange; use ruff_text_size::TextSize; use crate::checkers::logical_lines::expand_indent; use crate::line_width::IndentWidth; use crate::rules::pycodestyle::helpers::is_non_logical_token; +use crate::Locator; /// Number of blank lines around top level classes and functions. const BLANK_LINES_TOP_LEVEL: u32 = 2; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs index 98278ae0c4ed0..5c279b1c271a9 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs @@ -1,13 +1,13 @@ +use ruff_diagnostics::{AlwaysFixableViolation, Violation}; +use ruff_diagnostics::{Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; use ruff_notebook::CellOffsets; use ruff_python_ast::PySourceType; +use ruff_python_index::Indexer; use ruff_python_parser::{TokenIterWithContext, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextSize}; -use ruff_diagnostics::{AlwaysFixableViolation, Violation}; -use ruff_diagnostics::{Diagnostic, Edit, Fix}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_python_index::Indexer; -use ruff_source_file::Locator; +use crate::Locator; /// ## What it does /// Checks for compound statements (multiple statements on the same line). @@ -170,7 +170,7 @@ pub(crate) fn compound_statements( let mut diagnostic = Diagnostic::new(UselessSemicolon, range); diagnostic.set_fix(Fix::safe_edit(Edit::deletion( indexer - .preceded_by_continuations(range.start(), locator) + .preceded_by_continuations(range.start(), locator.contents()) .unwrap_or(range.start()), range.end(), ))); diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs index fa38ffb73f461..d6ecc94b994b6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/invalid_escape_sequence.rs @@ -3,11 +3,11 @@ use memchr::memchr_iter; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{AnyStringFlags, FStringElement, StringLike, StringLikePart}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::edits::pad_start; +use crate::Locator; /// ## What it does /// Checks for invalid escape sequences. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 5c41c5c44a002..37ef83a1a698e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -79,10 +79,10 @@ pub(crate) fn lambda_assignment( stmt.range(), ); - if !has_leading_content(stmt.start(), checker.locator()) - && !has_trailing_content(stmt.end(), checker.locator()) + if !has_leading_content(stmt.start(), checker.source()) + && !has_trailing_content(stmt.end(), checker.source()) { - let first_line = checker.locator().line(stmt.start()); + let first_line = checker.locator().line_str(stmt.start()); let indentation = leading_indentation(first_line); let mut indented = String::new(); for (idx, line) in function( diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs index 0a59cdb866299..0ae56cdf44b13 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/literal_comparisons.rs @@ -336,7 +336,7 @@ pub(crate) fn literal_comparisons(checker: &mut Checker, compare: &ast::ExprComp &compare.comparators, compare.into(), checker.comment_ranges(), - checker.locator(), + checker.source(), ); for diagnostic in &mut diagnostics { diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/mod.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/mod.rs index 51139a830bda3..8dc69bf72c09e 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/mod.rs @@ -1,26 +1,25 @@ +use std::fmt::{Debug, Formatter}; +use std::iter::FusedIterator; + +use bitflags::bitflags; + pub(crate) use extraneous_whitespace::*; pub(crate) use indentation::*; pub(crate) use missing_whitespace::*; pub(crate) use missing_whitespace_after_keyword::*; pub(crate) use missing_whitespace_around_operator::*; pub(crate) use redundant_backslash::*; +use ruff_python_parser::{TokenKind, Tokens}; +use ruff_python_trivia::is_python_whitespace; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; pub(crate) use space_around_operator::*; pub(crate) use whitespace_around_keywords::*; pub(crate) use whitespace_around_named_parameter_equals::*; pub(crate) use whitespace_before_comment::*; pub(crate) use whitespace_before_parameters::*; -use std::fmt::{Debug, Formatter}; -use std::iter::FusedIterator; - -use bitflags::bitflags; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; - -use ruff_python_parser::{TokenKind, Tokens}; -use ruff_python_trivia::is_python_whitespace; -use ruff_source_file::Locator; - use crate::rules::pycodestyle::helpers::is_non_logical_token; +use crate::Locator; mod extraneous_whitespace; mod indentation; @@ -579,7 +578,8 @@ impl TypeParamsState { #[cfg(test)] mod tests { use ruff_python_parser::parse_module; - use ruff_source_file::Locator; + + use crate::Locator; use super::LogicalLines; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs index 78f2dbf08b004..68a8e11240ab7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/redundant_backslash.rs @@ -2,10 +2,11 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_index::Indexer; use ruff_python_parser::TokenKind; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; +use crate::Locator; use super::LogicalLine; diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs index ca85aff9ddaf3..19cfa0736def8 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/whitespace_before_comment.rs @@ -2,11 +2,12 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_parser::TokenKind; use ruff_python_trivia::PythonWhitespace; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::logical_lines::LogicalLinesContext; use crate::rules::pycodestyle::rules::logical_lines::LogicalLine; +use crate::Locator; /// ## What it does /// Checks if inline comments are separated by at least two spaces. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs index cef5d251df1ce..a4b7e158e9f91 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/missing_newline_at_end_of_file.rs @@ -1,9 +1,9 @@ -use ruff_text_size::{TextLen, TextRange}; - use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; +use ruff_text_size::{TextLen, TextRange}; + +use crate::Locator; /// ## What it does /// Checks for files missing a new line at the end of the file. diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs index e54715330cbe4..85193ed1b9946 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs @@ -6,10 +6,11 @@ use ruff_python_ast::{Alias, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::indentation_at_offset; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Check for multiple imports on one line. @@ -68,7 +69,7 @@ fn split_imports( indexer: &Indexer, stylist: &Stylist, ) -> Fix { - if indexer.in_multi_statement_line(stmt, locator) { + if indexer.in_multi_statement_line(stmt, locator.contents()) { // Ex) `x = 1; import os, sys` (convert to `x = 1; import os; import sys`) let replacement = names .iter() @@ -90,7 +91,8 @@ fn split_imports( Fix::safe_edit(Edit::range_replacement(replacement, stmt.range())) } else { // Ex) `import os, sys` (convert to `import os\nimport sys`) - let indentation = indentation_at_offset(stmt.start(), locator).unwrap_or_default(); + let indentation = + indentation_at_offset(stmt.start(), locator.contents()).unwrap_or_default(); // Generate newline-delimited imports. let replacement = names diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs index 50022dee784ee..7a479ac83470f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/not_tests.rs @@ -105,7 +105,7 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) { comparators, unary_op.into(), checker.comment_ranges(), - checker.locator(), + checker.source(), ), unary_op.range(), checker.locator(), @@ -126,7 +126,7 @@ pub(crate) fn not_tests(checker: &mut Checker, unary_op: &ast::ExprUnaryOp) { comparators, unary_op.into(), checker.comment_ranges(), - checker.locator(), + checker.source(), ), unary_op.range(), checker.locator(), diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs index 38e40128a384b..500072a918399 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/tab_indentation.rs @@ -1,9 +1,11 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_index::Indexer; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; +use crate::Locator; + /// ## What it does /// Checks for indentation that uses tabs. /// diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs index fee7bc5ffbcb5..0124e1bf44cb8 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -1,11 +1,12 @@ use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_index::Indexer; -use ruff_source_file::{Line, Locator}; +use ruff_source_file::Line; use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::registry::Rule; use crate::settings::LinterSettings; +use crate::Locator; /// ## What it does /// Checks for superfluous trailing whitespace. @@ -100,7 +101,7 @@ pub(crate) fn trailing_whitespace( diagnostic.set_fix(Fix::applicable_edit( Edit::range_deletion(TextRange::new( indexer - .preceded_by_continuations(line.start(), locator) + .preceded_by_continuations(line.start(), locator.contents()) .unwrap_or(range.start()), range.end(), )), diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs index 299d6f44b4c6e..1c9057550d419 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/blank_before_after_class.rs @@ -1,7 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::{indentation_at_offset, PythonWhitespace}; -use ruff_source_file::{Line, UniversalNewlineIterator}; +use ruff_source_file::{Line, LineRanges, UniversalNewlineIterator}; use ruff_text_size::Ranged; use ruff_text_size::{TextLen, TextRange}; @@ -240,7 +240,7 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr if let Some(first_line) = &first_line { let trailing = first_line.as_str().trim_whitespace_start(); if let Some(next_statement) = trailing.strip_prefix(';') { - let indentation = indentation_at_offset(docstring.start(), checker.locator()) + let indentation = indentation_at_offset(docstring.start(), checker.source()) .expect("Own line docstring must have indentation"); let mut diagnostic = Diagnostic::new(OneBlankLineAfterClass, docstring.range()); let line_ending = checker.stylist().line_ending().as_str(); diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs index f10eced7ababb..8a4afc90e1c55 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/multi_line_summary_start.rs @@ -2,7 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::str::{is_triple_quote, leading_quote}; use ruff_python_semantic::Definition; -use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlineIterator}; +use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlineIterator}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs index 647e53a24ee61..f34dba528d85d 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/sections.rs @@ -1464,7 +1464,7 @@ fn blanks_and_section_underline( // Otherwise, documentation generators will not recognize the directive. let is_sphinx = checker .locator() - .line(blank_lines_after_dashes_end) + .line_str(blank_lines_after_dashes_end) .trim_start() .starts_with(".. "); @@ -1569,7 +1569,7 @@ fn blanks_and_section_underline( // Otherwise, documentation generators will not recognize the directive. let is_sphinx = checker .locator() - .line(blank_lines_end) + .line_str(blank_lines_end) .trim_start() .starts_with(".. "); diff --git a/crates/ruff_linter/src/rules/pyflakes/fixes.rs b/crates/ruff_linter/src/rules/pyflakes/fixes.rs index 7f0675de9e818..ed80041587e03 100644 --- a/crates/ruff_linter/src/rules/pyflakes/fixes.rs +++ b/crates/ruff_linter/src/rules/pyflakes/fixes.rs @@ -5,10 +5,10 @@ use ruff_python_ast as ast; use ruff_python_codegen::Stylist; use ruff_python_semantic::Binding; use ruff_python_trivia::{BackwardsTokenizer, SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::cst::matchers::{match_call_mut, match_dict, transform_expression}; +use crate::Locator; /// Generate a [`Edit`] to remove unused keys from format dict. pub(super) fn remove_unused_format_arguments_from_dict( diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 5e42a7e0e2cb6..c01397206f2be 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -12,15 +12,12 @@ mod tests { use anyhow::Result; use regex::Regex; use rustc_hash::FxHashMap; - use test_case::test_case; use ruff_python_ast::PySourceType; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; - use ruff_python_trivia::textwrap::dedent; - use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::linter::check_path; @@ -31,6 +28,7 @@ mod tests { use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; use crate::test::{test_contents, test_path, test_snippet}; + use crate::Locator; use crate::{assert_messages, directives}; #[test_case(Rule::UnusedImport, Path::new("F401_0.py"))] @@ -712,8 +710,8 @@ mod tests { let parsed = ruff_python_parser::parse_unchecked_source(source_kind.source_code(), source_type); let locator = Locator::new(&contents); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); let directives = directives::extract_directives( parsed.tokens(), directives::Flags::from_settings(&settings), diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs index dd740fac70a1d..da3cccab4c3f5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/default_except_not_last.rs @@ -1,9 +1,9 @@ -use ruff_python_ast::{self as ast, ExceptHandler}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::identifier::except; -use ruff_source_file::Locator; +use ruff_python_ast::{self as ast, ExceptHandler}; + +use crate::Locator; /// ## What it does /// Checks for `except` blocks that handle all exceptions, but are not the last diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs index 122e78d63d3b8..9a6decf6c7b17 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/f_string_missing_placeholders.rs @@ -1,10 +1,10 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for f-strings that do not contain any placeholder expressions. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs index ecba0975d00a7..3f6efbba07aaa 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs @@ -295,7 +295,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut continue; }; - let name = binding.name(checker.locator()); + let name = binding.name(checker.source()); // If an import is marked as required, avoid treating it as unused, regardless of whether // it was _actually_ used. diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs index 6da056c137cfc..1bd813f65a54c 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs @@ -183,7 +183,7 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option { }; } } else { - let name = binding.name(checker.locator()); + let name = binding.name(checker.source()); let renamed = format!("_{name}"); if checker.settings.dummy_variable_rgx.is_match(&renamed) { let edit = Edit::range_replacement(renamed, binding.range()); diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 0e1833778590e..a79a07b9e2087 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -1,11 +1,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::Cursor; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, ParsedFileExemption}; use crate::settings::types::PreviewMode; +use crate::Locator; /// ## What it does /// Check for `noqa` annotations that suppress all diagnostics, as opposed to diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs index 662d76709968f..7995415492a49 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_type_ignore.rs @@ -1,14 +1,16 @@ +use std::sync::LazyLock; + use anyhow::{anyhow, Result}; use memchr::memchr_iter; use regex::Regex; -use std::sync::LazyLock; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::TextSize; +use crate::Locator; + /// ## What it does /// Check for `type: ignore` annotations that suppress all type warnings, as /// opposed to targeting specific type warnings. diff --git a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs index aae432959fa84..6c55fd405dfb0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs @@ -6,11 +6,12 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, ElifElseClause, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::adjust_indentation; +use crate::Locator; /// ## What it does /// Checks for `else` blocks that consist of a single `if` statement. @@ -113,7 +114,7 @@ fn convert_to_elif( let trivia_range = TextRange::new(else_line_end, inner_if_line_start); // Identify the indentation of the outer clause - let Some(indentation) = indentation(locator, else_clause) else { + let Some(indentation) = indentation(locator.contents(), else_clause) else { return Err(anyhow::anyhow!("`else` is expected to be on its own line")); }; diff --git a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs index 1c741cf322073..873d6c9096028 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs @@ -1,9 +1,11 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::{is_python_whitespace, CommentRanges}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; +use crate::Locator; + /// ## What it does /// Checks for a # symbol appearing on a line not followed by an actual comment. /// @@ -47,7 +49,7 @@ pub(crate) fn empty_comments( comment_ranges: &CommentRanges, locator: &Locator, ) { - let block_comments = comment_ranges.block_comments(locator); + let block_comments = comment_ranges.block_comments(locator.contents()); for range in comment_ranges { // Ignore comments that are part of multi-line "comment blocks". diff --git a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs index 16a0b8ba794e5..621eb028c74cb 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/invalid_string_characters.rs @@ -1,11 +1,11 @@ -use ruff_text_size::{TextLen, TextRange, TextSize}; - use ruff_diagnostics::AlwaysFixableViolation; use ruff_diagnostics::Edit; use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_parser::TokenKind; -use ruff_source_file::Locator; +use ruff_text_size::{TextLen, TextRange, TextSize}; + +use crate::Locator; /// ## What it does /// Checks for strings that contain the control character `BS`. diff --git a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index 039e4ace219a1..c6799864ba6bc 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs @@ -157,7 +157,7 @@ pub(crate) fn nested_min_max( let mut diagnostic = Diagnostic::new(NestedMinMax { func: min_max }, expr.range()); if !checker .comment_ranges() - .has_comments(expr, checker.locator()) + .has_comments(expr, checker.source()) { let flattened_expr = Expr::Call(ast::ExprCall { func: Box::new(func.clone()), diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs index 10d3390553771..dc01fb788f9ee 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_method_decorator.rs @@ -175,7 +175,7 @@ fn get_undecorated_methods(checker: &mut Checker, class_stmt: &Stmt, method_type TextRange::new(stmt.range().start(), stmt.range().start()), ); - let indentation = indentation_at_offset(stmt.range().start(), checker.locator()); + let indentation = indentation_at_offset(stmt.range().start(), checker.source()); match indentation { Some(indentation) => { diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs index 02ba51ae599dc..ea5a862646759 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs @@ -3,9 +3,10 @@ use std::fmt; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_semantic::{Binding, BindingKind}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; +use crate::Locator; + /// ## What it does /// Checks for the use of non-ASCII characters in variable names. /// @@ -44,7 +45,7 @@ impl Violation for NonAsciiName { /// PLC2401 pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option { - let name = binding.name(locator); + let name = binding.name(locator.contents()); if name.is_ascii() { return None; } diff --git a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs index 56f2343343033..e20a15a2447ee 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/repeated_equality_comparison.rs @@ -9,11 +9,11 @@ use ruff_python_ast::hashable::HashableExpr; use ruff_python_ast::helpers::{any_over_expr, contains_effect}; use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr}; use ruff_python_semantic::SemanticModel; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::Checker; use crate::fix::snippet::SourceCodeSnippet; +use crate::Locator; /// ## What it does /// Checks for repeated equality comparisons that can rewritten as a membership diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs index 818b9e4a8a8fb..13bee17799c8c 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs @@ -7,11 +7,12 @@ use ruff_python_ast::identifier; use ruff_python_ast::{self as ast, ExceptHandler, MatchCase, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::adjust_indentation; +use crate::Locator; /// ## What it does /// Checks for `else` clauses on loops without a `break` statement. @@ -146,7 +147,7 @@ fn remove_else( return Err(anyhow::anyhow!("Empty `else` clause")); }; - let start_indentation = indentation(locator, start); + let start_indentation = indentation(locator.contents(), start); if start_indentation.is_none() { // Inline `else` block (e.g., `else: x = 1`). Ok(Fix::safe_edit(Edit::deletion( @@ -155,7 +156,7 @@ fn remove_else( ))) } else { // Identify the indentation of the loop itself (e.g., the `while` or `for`). - let Some(desired_indentation) = indentation(locator, stmt) else { + let Some(desired_indentation) = indentation(locator.contents(), stmt) else { return Err(anyhow::anyhow!("Compound statement cannot be inlined")); }; diff --git a/crates/ruff_linter/src/rules/pyupgrade/fixes.rs b/crates/ruff_linter/src/rules/pyupgrade/fixes.rs index 65486abee28e7..3e0cdef305685 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/fixes.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/fixes.rs @@ -1,8 +1,9 @@ use ruff_python_ast::StmtImportFrom; use ruff_python_parser::{TokenKind, Tokens}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; +use crate::Locator; + /// Remove any imports matching `members` from an import-from statement. pub(crate) fn remove_import_members( locator: &Locator<'_>, @@ -66,7 +67,8 @@ pub(crate) fn remove_import_members( #[cfg(test)] mod tests { use ruff_python_parser::parse_module; - use ruff_source_file::Locator; + + use crate::Locator; use super::remove_import_members; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs index 971b02beb307b..9044bda474bd6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_named_tuple_functional_to_class.rs @@ -8,6 +8,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Key use ruff_python_codegen::Generator; use ruff_python_semantic::SemanticModel; use ruff_python_stdlib::identifiers::is_identifier; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs index cb9948fbfa568..c00e8757996f0 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/convert_typed_dict_functional_to_class.rs @@ -5,6 +5,7 @@ use ruff_python_ast::{self as ast, Arguments, Expr, ExprContext, Identifier, Key use ruff_python_codegen::Generator; use ruff_python_semantic::SemanticModel; use ruff_python_stdlib::identifiers::is_identifier; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 8f1f00567139b..afea8339ca5c9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -1,18 +1,17 @@ use itertools::Itertools; -use ruff_python_ast::{Alias, StmtImportFrom}; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::whitespace::indentation; +use ruff_python_ast::{Alias, StmtImportFrom}; use ruff_python_codegen::Stylist; use ruff_python_parser::Tokens; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; - use crate::rules::pyupgrade::fixes; use crate::settings::types::PythonVersion; +use crate::Locator; /// An import was moved and renamed as part of a deprecation. /// For example, `typing.AbstractSet` was moved to `collections.abc.Set`. @@ -617,7 +616,7 @@ impl<'a> ImportReplacer<'a> { let fix = Some(matched); Some((operation, fix)) } else { - let indentation = indentation(self.locator, self.import_from_stmt); + let indentation = indentation(self.locator.contents(), self.import_from_stmt); // If we have matched _and_ unmatched names, but the import is not on its own // line, we can't add a statement after it. For example, if we have diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs index bd7dbd3287966..0b878fd6ae9f2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_mock_import.rs @@ -12,12 +12,12 @@ use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{self as ast, Stmt}; use ruff_python_codegen::Stylist; use ruff_python_semantic::Modules; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::cst::matchers::{match_import, match_import_from, match_statement}; use crate::fix::codemods::CodegenStylist; +use crate::Locator; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub(crate) enum MockReference { @@ -282,7 +282,7 @@ pub(crate) fn deprecated_mock_import(checker: &mut Checker, stmt: &Stmt) { .any(|name| &name.name == "mock" || &name.name == "mock.mock") { // Generate the fix, if needed, which is shared between all `mock` imports. - let content = if let Some(indent) = indentation(checker.locator(), stmt) { + let content = if let Some(indent) = indentation(checker.source(), stmt) { match format_import(stmt, indent, checker.locator(), checker.stylist()) { Ok(content) => Some(content), Err(e) => { @@ -330,7 +330,7 @@ pub(crate) fn deprecated_mock_import(checker: &mut Checker, stmt: &Stmt) { }, stmt.range(), ); - if let Some(indent) = indentation(checker.locator(), stmt) { + if let Some(indent) = indentation(checker.source(), stmt) { diagnostic.try_set_fix(|| { format_import_from(stmt, indent, checker.locator(), checker.stylist()) .map(|content| Edit::range_replacement(content, stmt.range())) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs index 0131b40c8e780..a46441056ff03 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/extraneous_parentheses.rs @@ -1,11 +1,11 @@ use std::slice::Iter; +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; use ruff_python_parser::{Token, TokenKind, Tokens}; use ruff_text_size::{Ranged, TextRange}; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_source_file::Locator; +use crate::Locator; /// ## What it does /// Checks for extraneous parentheses. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs index c73806d7de677..9259d08236e69 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs @@ -12,13 +12,13 @@ use ruff_python_literal::format::{ FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate, }; use ruff_python_parser::TokenKind; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; - use crate::rules::pyflakes::format::FormatSummary; use crate::rules::pyupgrade::helpers::{curly_escape, curly_unescape}; +use crate::Locator; /// ## What it does /// Checks for `str.format` calls that can be replaced with f-strings. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs index dd1f4e943d6f4..c3955b4ae0630 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs @@ -1,13 +1,13 @@ +use std::sync::LazyLock; + use anyhow::{anyhow, Result}; use libcst_native::{Arg, Expression}; use regex::Regex; -use std::sync::LazyLock; use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -15,8 +15,8 @@ use crate::cst::matchers::{ match_attribute, match_call_mut, match_expression, transform_expression_text, }; use crate::fix::codemods::CodegenStylist; - use crate::rules::pyflakes::format::FormatSummary; +use crate::Locator; /// ## What it does /// Checks for unnecessary positional indices in format strings. diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index ca7faba02cdd5..c4894db891b2c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs @@ -8,6 +8,7 @@ use ruff_python_ast::helpers::map_subscript; use ruff_python_ast::stmt_if::{if_elif_branches, BranchKind, IfElifBranch}; use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{self as ast, CmpOp, ElifElseClause, Expr, Int, StmtIf}; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange}; use crate::checkers::ast::Checker; @@ -299,7 +300,7 @@ fn fix_always_false_branch( }) => { let start = body.first()?; let end = body.last()?; - if indentation(checker.locator(), start).is_none() { + if indentation(checker.source(), start).is_none() { // Inline `else` block (e.g., `else: x = 1`). Some(Fix::unsafe_edit(Edit::range_replacement( checker @@ -309,7 +310,7 @@ fn fix_always_false_branch( stmt_if.range(), ))) } else { - indentation(checker.locator(), stmt_if) + indentation(checker.source(), stmt_if) .and_then(|indentation| { adjust_indentation( TextRange::new( @@ -377,7 +378,7 @@ fn fix_always_true_branch( // the rest. let start = branch.body.first()?; let end = branch.body.last()?; - if indentation(checker.locator(), start).is_none() { + if indentation(checker.source(), start).is_none() { // Inline `if` block (e.g., `if ...: x = 1`). Some(Fix::unsafe_edit(Edit::range_replacement( checker @@ -387,7 +388,7 @@ fn fix_always_true_branch( stmt_if.range, ))) } else { - indentation(checker.locator(), &stmt_if) + indentation(checker.source(), &stmt_if) .and_then(|indentation| { adjust_indentation( TextRange::new(checker.locator().line_start(start.start()), end.end()), diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index 2c66fd26f1a3c..9dc930cc1b9d6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -10,12 +10,12 @@ use ruff_python_literal::cformat::{ }; use ruff_python_parser::TokenKind; use ruff_python_stdlib::identifiers::is_identifier; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; - use crate::rules::pyupgrade::helpers::curly_escape; +use crate::Locator; /// ## What it does /// Checks for `printf`-style string formatting, and offers to replace it with @@ -251,7 +251,7 @@ fn clean_params_dictionary(right: &Expr, locator: &Locator, stylist: &Stylist) - seen.push(key_string.to_str()); if is_multi_line { if indent.is_none() { - indent = indentation(locator, key); + indent = indentation(locator.contents(), key); } } diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs index f48c05a474264..d8ded816db5e2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_coding_comment.rs @@ -1,13 +1,16 @@ -use regex::Regex; use std::sync::LazyLock; +use regex::Regex; + use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_index::Indexer; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; +use crate::Locator; + /// ## What it does /// Checks for unnecessary UTF-8 encoding declarations. /// @@ -75,7 +78,7 @@ pub(crate) fn unnecessary_coding_comment( // x = 2 // ``` if indexer - .preceded_by_continuations(line_range.start(), locator) + .preceded_by_continuations(line_range.start(), locator.contents()) .is_some() { continue; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs index da5853540ed4c..1b7b672e7f5be 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_encode_utf8.rs @@ -2,11 +2,11 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Arguments, Expr, Keyword}; use ruff_python_parser::{TokenKind, Tokens}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::fix::edits::{pad, remove_argument, Parentheses}; +use crate::Locator; /// ## What it does /// Checks for unnecessary calls to `encode` as UTF-8. diff --git a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs index 7e8475085dcfb..4b09aa6ebb3b8 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/if_exp_instead_of_or_operator.rs @@ -8,10 +8,10 @@ use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::parenthesize::parenthesized_range; use ruff_python_ast::Expr; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for ternary `if` expressions that can be replaced with the `or` diff --git a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs index ce634a8e40651..2a56ce706ef40 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/reimplemented_operator.rs @@ -2,19 +2,19 @@ use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter}; use anyhow::Result; - use itertools::Itertools; + use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::any_over_expr; use ruff_python_ast::identifier::Identifier; use ruff_python_ast::{self as ast, Expr, ExprSlice, ExprSubscript, ExprTuple, Parameters, Stmt}; use ruff_python_semantic::SemanticModel; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; use crate::importer::{ImportRequest, Importer}; +use crate::Locator; /// ## What it does /// Checks for lambda expressions and function definitions that can be replaced with a function from diff --git a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index 85f7fe9ec5628..4185cf3760543 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs @@ -84,7 +84,7 @@ pub(crate) fn single_item_membership_test( &[item.clone()], expr.into(), checker.comment_ranges(), - checker.locator(), + checker.source(), ), expr.range(), checker.locator(), diff --git a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs index 44443032f242f..7322a00636df7 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/slice_to_remove_prefix_or_suffix.rs @@ -1,11 +1,12 @@ -use crate::{checkers::ast::Checker, settings::types::PythonVersion}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_semantic::SemanticModel; -use ruff_source_file::Locator; use ruff_text_size::Ranged; +use crate::Locator; +use crate::{checkers::ast::Checker, settings::types::PythonVersion}; + /// ## What it does /// Checks for the removal of a prefix or suffix from a string by assigning /// the string to a slice after checking `.startswith()` or `.endswith()`, respectively. diff --git a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs index 9940707c0fb66..5464c9d39c636 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/ambiguous_unicode_character.rs @@ -5,7 +5,6 @@ use bitflags::bitflags; use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, StringLike}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::checkers::ast::Checker; @@ -13,6 +12,7 @@ use crate::registry::AsRule; use crate::rules::ruff::rules::confusables::confusable; use crate::rules::ruff::rules::Context; use crate::settings::LinterSettings; +use crate::Locator; /// ## What it does /// Checks for ambiguous Unicode characters in strings. diff --git a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index 1a69095473d1b..0689a2957a30a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs @@ -200,7 +200,7 @@ pub(crate) fn collection_literal_concatenation(checker: &mut Checker, expr: &Exp ); if !checker .comment_ranges() - .has_comments(expr, checker.locator()) + .has_comments(expr, checker.source()) { // This suggestion could be unsafe if the non-literal expression in the // expression has overridden the `__add__` (or `__radd__`) magic methods. diff --git a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs index b02b731a3e49c..f78cad69f70d2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/decimal_from_float_literal.rs @@ -4,10 +4,10 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::Locator; /// ## What it does /// Checks for `Decimal` calls passing a float literal. diff --git a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs index 289a228264e28..2e814faae21c4 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/default_factory_kwarg.rs @@ -5,12 +5,12 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::is_constant; use ruff_python_ast::{self as ast, Expr}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::{remove_argument, Parentheses}; use crate::fix::snippet::SourceCodeSnippet; +use crate::Locator; /// ## What it does /// Checks for incorrect usages of `default_factory` as a keyword argument when diff --git a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs index 68a8ac136e8d0..86ca73ba673cb 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/explicit_f_string_type_conversion.rs @@ -4,7 +4,6 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Arguments, Expr}; use ruff_python_codegen::Stylist; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -12,6 +11,7 @@ use crate::cst::matchers::{ match_call_mut, match_formatted_string, match_formatted_string_expression, match_name, transform_expression, }; +use crate::Locator; /// ## What it does /// Checks for uses of `str()`, `repr()`, and `ascii()` as explicit type diff --git a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs index 57201331c9f4d..a8f525869533a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs @@ -1,16 +1,17 @@ use std::fmt::Display; +use smallvec::SmallVec; + use ast::{StmtClassDef, StmtFunctionDef}; use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, helpers::comment_indentation_after, AnyNodeRef}; use ruff_python_trivia::{indentation_at_offset, SuppressionKind}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextLen, TextRange}; -use smallvec::SmallVec; use crate::checkers::ast::Checker; use crate::fix::edits::delete_comment; +use crate::Locator; use super::suppression_comment_visitor::{ CaptureSuppressionComment, SuppressionComment, SuppressionCommentData, @@ -166,11 +167,14 @@ impl<'src, 'loc> UselessSuppressionComments<'src, 'loc> { { if following.is_first_statement_in_alternate_body(enclosing) { // check indentation - let comment_indentation = - comment_indentation_after(preceding, comment.range, self.locator); + let comment_indentation = comment_indentation_after( + preceding, + comment.range, + self.locator.contents(), + ); let preceding_indentation = - indentation_at_offset(preceding.start(), self.locator) + indentation_at_offset(preceding.start(), self.locator.contents()) .unwrap_or_default() .text_len(); if comment_indentation != preceding_indentation { diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 7ecfe19b1346d..8025fd829244c 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -1,3 +1,6 @@ +use memchr::memchr2_iter; +use rustc_hash::FxHashSet; + use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; @@ -5,14 +8,11 @@ use ruff_python_literal::format::FormatSpec; use ruff_python_parser::parse_expression; use ruff_python_semantic::analyze::logging::is_logger_candidate; use ruff_python_semantic::{Modules, SemanticModel}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; -use memchr::memchr2_iter; -use rustc_hash::FxHashSet; - use crate::checkers::ast::Checker; use crate::rules::fastapi::rules::is_fastapi_route_call; +use crate::Locator; /// ## What it does /// Searches for strings that look like they were meant to be f-strings, but are missing an `f` prefix. diff --git a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index a13949f167a70..61a321b6e154a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs @@ -5,6 +5,7 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_semantic::{Scope, ScopeKind}; use ruff_python_trivia::{indentation_at_offset, textwrap}; +use ruff_source_file::LineRanges; use ruff_text_size::Ranged; use crate::{checkers::ast::Checker, importer::ImportRequest}; @@ -175,7 +176,7 @@ fn use_initvar( } }; - let indentation = indentation_at_offset(post_init_def.start(), checker.locator()) + let indentation = indentation_at_offset(post_init_def.start(), checker.source()) .context("Failed to calculate leading indentation of `__post_init__` method")?; let content = textwrap::indent(&content, indentation); diff --git a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs index 9f4ce6129cabc..7790f8ce39da2 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs @@ -6,16 +6,18 @@ use std::borrow::Cow; use std::cmp::Ordering; +use is_macro; +use natord; + use ruff_python_ast as ast; use ruff_python_codegen::Stylist; use ruff_python_parser::{TokenKind, Tokens}; use ruff_python_stdlib::str::is_cased_uppercase; use ruff_python_trivia::{first_non_trivia_token, leading_indentation, SimpleTokenKind}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; -use is_macro; -use natord; +use crate::Locator; /// An enumeration of the different sorting styles /// currently supported for displays of string literals @@ -422,7 +424,7 @@ impl<'a> MultilineStringSequenceValue<'a> { // let newline = stylist.line_ending().as_str(); let start_offset = self.start(); - let leading_indent = leading_indentation(locator.full_line(start_offset)); + let leading_indent = leading_indentation(locator.full_line_str(start_offset)); let item_indent = format!("{}{}", leading_indent, stylist.indentation().as_str()); let prelude = diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs index b63fb68bff046..6548d8077eb55 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_all.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; +use ruff_source_file::LineRanges; use ruff_text_size::TextRange; use crate::checkers::ast::Checker; diff --git a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs index 606b464e3672d..b344e4f018ad7 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs @@ -1,10 +1,12 @@ use std::borrow::Cow; +use itertools::izip; + use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast as ast; use ruff_python_semantic::Binding; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; @@ -12,8 +14,7 @@ use crate::rules::ruff::rules::sequence_sorting::{ sort_single_line_elements_sequence, MultilineStringSequenceValue, SequenceKind, SortClassification, SortingStyle, }; - -use itertools::izip; +use crate::Locator; /// ## What it does /// Checks for `__slots__` definitions that are not ordered according to a diff --git a/crates/ruff_linter/src/rules/ruff/rules/suppression_comment_visitor.rs b/crates/ruff_linter/src/rules/ruff/rules/suppression_comment_visitor.rs index 113f0a000fbe5..b97ed311799c3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/suppression_comment_visitor.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/suppression_comment_visitor.rs @@ -8,9 +8,10 @@ use ruff_python_ast::{ use ruff_python_trivia::{ indentation_at_offset, CommentLinePosition, SimpleTokenizer, SuppressionKind, }; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; +use crate::Locator; + #[derive(Clone, Copy, Debug)] pub(super) struct SuppressionComment { pub(super) range: TextRange, @@ -146,9 +147,11 @@ where // We want `# fmt: on` to be considered a trailing comment of `func(x)` instead of a leading comment // on `func2(y)`. if line_position.is_own_line() { - let comment_indent = comment_indentation_after(node, range, self.locator); + let comment_indent = + comment_indentation_after(node, range, self.locator.contents()); let node_indent = TextSize::of( - indentation_at_offset(node.start(), self.locator).unwrap_or_default(), + indentation_at_offset(node.start(), self.locator.contents()) + .unwrap_or_default(), ); if node_indent >= comment_indent { break; diff --git a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs index a3f2548738662..43fd1f44c767b 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs @@ -16,10 +16,10 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::TextSize; use crate::registry::Rule; +use crate::Locator; /// Check if a comment exists anywhere in a given file fn comment_exists(text: &str, locator: &Locator, comment_ranges: &CommentRanges) -> bool { diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index a4ac856499b7b..a0c1fe4fbab0d 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -18,10 +18,9 @@ use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::ParseError; use ruff_python_trivia::textwrap::dedent; -use ruff_source_file::{Locator, SourceFileBuilder}; +use ruff_source_file::SourceFileBuilder; use ruff_text_size::Ranged; -use crate::directives; use crate::fix::{fix_file, FixResult}; use crate::linter::check_path; use crate::message::{Emitter, EmitterContext, Message, TextEmitter}; @@ -30,6 +29,7 @@ use crate::registry::AsRule; use crate::settings::types::UnsafeFixes; use crate::settings::{flags, LinterSettings}; use crate::source_kind::SourceKind; +use crate::{directives, Locator}; #[cfg(not(fuzzing))] pub(crate) fn test_resource_path(path: impl AsRef) -> std::path::PathBuf { @@ -111,8 +111,8 @@ pub(crate) fn test_contents<'a>( let source_type = PySourceType::from(path); let parsed = ruff_python_parser::parse_unchecked_source(source_kind.source_code(), source_type); let locator = Locator::new(source_kind.source_code()); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); let directives = directives::extract_directives( parsed.tokens(), directives::Flags::from_settings(settings), @@ -174,8 +174,8 @@ pub(crate) fn test_contents<'a>( let parsed = ruff_python_parser::parse_unchecked_source(transformed.source_code(), source_type); let locator = Locator::new(transformed.source_code()); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); let directives = directives::extract_directives( parsed.tokens(), directives::Flags::from_settings(settings), diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 565c67e585f5c..3a95065cbfa67 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -4,7 +4,7 @@ use std::path::Path; use rustc_hash::FxHashMap; use ruff_python_trivia::{indentation_at_offset, CommentRanges, SimpleTokenKind, SimpleTokenizer}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::name::{Name, QualifiedName, QualifiedNameBuilder}; @@ -1333,7 +1333,7 @@ pub fn generate_comparison( comparators: &[Expr], parent: AnyNodeRef, comment_ranges: &CommentRanges, - locator: &Locator, + source: &str, ) -> String { let start = left.start(); let end = comparators.last().map_or_else(|| left.end(), Ranged::end); @@ -1341,10 +1341,8 @@ pub fn generate_comparison( // Add the left side of the comparison. contents.push_str( - locator.slice( - parenthesized_range(left.into(), parent, comment_ranges, locator.contents()) - .unwrap_or(left.range()), - ), + &source[parenthesized_range(left.into(), parent, comment_ranges, source) + .unwrap_or(left.range())], ); for (op, comparator) in ops.iter().zip(comparators) { @@ -1364,15 +1362,8 @@ pub fn generate_comparison( // Add the right side of the comparison. contents.push_str( - locator.slice( - parenthesized_range( - comparator.into(), - parent, - comment_ranges, - locator.contents(), - ) - .unwrap_or(comparator.range()), - ), + &source[parenthesized_range(comparator.into(), parent, comment_ranges, source) + .unwrap_or(comparator.range())], ); } @@ -1512,17 +1503,17 @@ pub fn typing_union(elts: &[Expr], binding: Name) -> Expr { pub fn comment_indentation_after( preceding: AnyNodeRef, comment_range: TextRange, - locator: &Locator, + source: &str, ) -> TextSize { let tokenizer = SimpleTokenizer::new( - locator.contents(), - TextRange::new(locator.full_line_end(preceding.end()), comment_range.end()), + source, + TextRange::new(source.full_line_end(preceding.end()), comment_range.end()), ); tokenizer .filter_map(|token| { if token.kind() == SimpleTokenKind::Comment { - indentation_at_offset(token.start(), locator).map(TextLen::text_len) + indentation_at_offset(token.start(), source).map(TextLen::text_len) } else { None } diff --git a/crates/ruff_python_ast/src/whitespace.rs b/crates/ruff_python_ast/src/whitespace.rs index 7a1ed847c3d04..014f18ce56474 100644 --- a/crates/ruff_python_ast/src/whitespace.rs +++ b/crates/ruff_python_ast/src/whitespace.rs @@ -1,35 +1,35 @@ use ruff_python_trivia::{indentation_at_offset, is_python_whitespace, PythonWhitespace}; -use ruff_source_file::{Locator, UniversalNewlineIterator}; +use ruff_source_file::{LineRanges, UniversalNewlineIterator}; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::Stmt; /// Extract the leading indentation from a line. #[inline] -pub fn indentation<'a, T>(locator: &'a Locator, located: &T) -> Option<&'a str> +pub fn indentation<'a, T>(source: &'a str, located: &T) -> Option<&'a str> where T: Ranged, { - indentation_at_offset(located.start(), locator) + indentation_at_offset(located.start(), source) } /// Return the end offset at which the empty lines following a statement. -pub fn trailing_lines_end(stmt: &Stmt, locator: &Locator) -> TextSize { - let line_end = locator.full_line_end(stmt.end()); - UniversalNewlineIterator::with_offset(locator.after(line_end), line_end) +pub fn trailing_lines_end(stmt: &Stmt, source: &str) -> TextSize { + let line_end = source.full_line_end(stmt.end()); + UniversalNewlineIterator::with_offset(&source[line_end.to_usize()..], line_end) .take_while(|line| line.trim_whitespace().is_empty()) .last() .map_or(line_end, |line| line.full_end()) } /// If a [`Ranged`] has a trailing comment, return the index of the hash. -pub fn trailing_comment_start_offset(located: &T, locator: &Locator) -> Option +pub fn trailing_comment_start_offset(located: &T, source: &str) -> Option where T: Ranged, { - let line_end = locator.line_end(located.end()); + let line_end = source.line_end(located.end()); - let trailing = locator.slice(TextRange::new(located.end(), line_end)); + let trailing = &source[TextRange::new(located.end(), line_end)]; for (index, char) in trailing.char_indices() { if char == '#' { diff --git a/crates/ruff_python_codegen/src/lib.rs b/crates/ruff_python_codegen/src/lib.rs index 64a991edcd750..b1525cce3f48c 100644 --- a/crates/ruff_python_codegen/src/lib.rs +++ b/crates/ruff_python_codegen/src/lib.rs @@ -1,16 +1,14 @@ -mod generator; -mod stylist; - pub use generator::Generator; use ruff_python_parser::{parse_module, ParseError}; -use ruff_source_file::Locator; pub use stylist::Stylist; +mod generator; +mod stylist; + /// Run round-trip source code generation on a given Python code. pub fn round_trip(code: &str) -> Result { - let locator = Locator::new(code); let parsed = parse_module(code)?; - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), code); let mut generator: Generator = (&stylist).into(); generator.unparse_suite(parsed.suite()); Ok(generator.generate()) diff --git a/crates/ruff_python_codegen/src/stylist.rs b/crates/ruff_python_codegen/src/stylist.rs index b78417add2971..20217c279e064 100644 --- a/crates/ruff_python_codegen/src/stylist.rs +++ b/crates/ruff_python_codegen/src/stylist.rs @@ -5,12 +5,12 @@ use std::ops::Deref; use ruff_python_ast::str::Quote; use ruff_python_parser::{Token, TokenKind, Tokens}; -use ruff_source_file::{find_newline, LineEnding, Locator}; +use ruff_source_file::{find_newline, LineEnding, LineRanges}; use ruff_text_size::Ranged; #[derive(Debug, Clone)] pub struct Stylist<'a> { - locator: &'a Locator<'a>, + source: &'a str, indentation: Indentation, quote: Quote, line_ending: OnceCell, @@ -27,18 +27,17 @@ impl<'a> Stylist<'a> { pub fn line_ending(&'a self) -> LineEnding { *self.line_ending.get_or_init(|| { - let contents = self.locator.contents(); - find_newline(contents) + find_newline(self.source) .map(|(_, ending)| ending) .unwrap_or_default() }) } - pub fn from_tokens(tokens: &Tokens, locator: &'a Locator<'a>) -> Self { - let indentation = detect_indentation(tokens, locator); + pub fn from_tokens(tokens: &Tokens, source: &'a str) -> Self { + let indentation = detect_indentation(tokens, source); Self { - locator, + source, indentation, quote: detect_quote(tokens), line_ending: OnceCell::default(), @@ -59,7 +58,7 @@ fn detect_quote(tokens: &[Token]) -> Quote { Quote::default() } -fn detect_indentation(tokens: &[Token], locator: &Locator) -> Indentation { +fn detect_indentation(tokens: &[Token], source: &str) -> Indentation { let indent_range = tokens.iter().find_map(|token| { if matches!(token.kind(), TokenKind::Indent) { Some(token.range()) @@ -69,7 +68,7 @@ fn detect_indentation(tokens: &[Token], locator: &Locator) -> Indentation { }); if let Some(indent_range) = indent_range { - let mut whitespace = locator.slice(indent_range); + let mut whitespace = &source[indent_range]; // https://docs.python.org/3/reference/lexical_analysis.html#indentation // > A formfeed character may be present at the start of the line; it will be ignored for // > the indentation calculations above. Formfeed characters occurring elsewhere in the @@ -98,7 +97,7 @@ fn detect_indentation(tokens: &[Token], locator: &Locator) -> Indentation { // ``` for token in tokens { if token.kind() == TokenKind::NonLogicalNewline { - let line = locator.line(token.end()); + let line = source.line_str(token.end()); let indent_index = line.find(|c: char| !c.is_whitespace()); if let Some(indent_index) = indent_index { if indent_index > 0 { @@ -150,45 +149,39 @@ impl Deref for Indentation { #[cfg(test)] mod tests { use ruff_python_parser::{parse_module, parse_unchecked, Mode}; - use ruff_source_file::{find_newline, LineEnding}; use super::{Indentation, Quote, Stylist}; - use ruff_source_file::Locator; #[test] fn indentation() { let contents = r"x = 1"; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation::default()); let contents = r" if True: pass "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation(" ".to_string())); let contents = r" if True: pass "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation(" ".to_string())); let contents = r" if True: pass "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation("\t".to_string())); let contents = r" @@ -198,9 +191,8 @@ x = ( 3, ) "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation(" ".to_string())); // formfeed indent, see `detect_indentation` comment. @@ -209,9 +201,8 @@ class FormFeedIndent: def __init__(self, a=[]): print(a) "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.indentation(), &Indentation(" ".to_string())); } @@ -224,10 +215,9 @@ x = (  3, ) "; - let locator = Locator::new(contents); let parsed = parse_unchecked(contents, Mode::Module); assert_eq!( - Stylist::from_tokens(parsed.tokens(), &locator).indentation(), + Stylist::from_tokens(parsed.tokens(), contents).indentation(), &Indentation(" ".to_string()) ); } @@ -235,39 +225,33 @@ x = ( #[test] fn quote() { let contents = r"x = 1"; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::default()); let contents = r"x = '1'"; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Single); let contents = r"x = f'1'"; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Single); let contents = r#"x = "1""#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Double); let contents = r#"x = f"1""#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Double); let contents = r#"s = "It's done.""#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Double); // No style if only double quoted docstring (will take default Double) @@ -276,9 +260,8 @@ def f(): """Docstring.""" pass "#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::default()); // Detect from string literal appearing after docstring @@ -287,9 +270,8 @@ def f(): a = 'v' "#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Single); let contents = r#" @@ -297,9 +279,8 @@ a = 'v' a = "v" "#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Double); // Detect from f-string appearing after docstring @@ -308,9 +289,8 @@ a = "v" a = f'v' "#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Single); let contents = r#" @@ -318,17 +298,15 @@ a = f'v' a = f"v" "#; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Double); let contents = r" f'''Module docstring.''' "; - let locator = Locator::new(contents); let parsed = parse_module(contents).unwrap(); - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), contents); assert_eq!(stylist.quote(), Quote::Single); } diff --git a/crates/ruff_python_formatter/src/comments/mod.rs b/crates/ruff_python_formatter/src/comments/mod.rs index c2db89159bafa..40b7f47005788 100644 --- a/crates/ruff_python_formatter/src/comments/mod.rs +++ b/crates/ruff_python_formatter/src/comments/mod.rs @@ -98,7 +98,6 @@ pub(crate) use format::{ use ruff_formatter::{SourceCode, SourceCodeSlice}; use ruff_python_ast::AnyNodeRef; use ruff_python_trivia::{CommentLinePosition, CommentRanges, SuppressionKind}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; pub(crate) use visitor::collect_comments; @@ -258,8 +257,7 @@ impl<'a> Comments<'a> { let map = if comment_ranges.is_empty() { CommentsMap::new() } else { - let mut builder = - CommentsMapBuilder::new(Locator::new(source_code.as_str()), comment_ranges); + let mut builder = CommentsMapBuilder::new(source_code.as_str(), comment_ranges); CommentsVisitor::new(source_code, comment_ranges, &mut builder).visit(root); builder.finish() }; diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index badc8667ffdb2..533fed500d3eb 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -9,8 +9,8 @@ use ruff_python_trivia::{ find_only_token_in_range, first_non_trivia_token, indentation_at_offset, BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, SimpleTokenizer, }; -use ruff_source_file::Locator; -use ruff_text_size::{Ranged, TextLen, TextRange}; +use ruff_source_file::LineRanges; +use ruff_text_size::{Ranged, TextLen, TextRange, TextSlice}; use crate::comments::visitor::{CommentPlacement, DecoratedComment}; use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; @@ -23,12 +23,12 @@ use crate::pattern::pattern_match_sequence::SequenceType; pub(super) fn place_comment<'a>( comment: DecoratedComment<'a>, comment_ranges: &CommentRanges, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { - handle_parenthesized_comment(comment, locator) - .or_else(|comment| handle_end_of_line_comment_around_body(comment, locator)) - .or_else(|comment| handle_own_line_comment_around_body(comment, locator)) - .or_else(|comment| handle_enclosed_comment(comment, comment_ranges, locator)) + handle_parenthesized_comment(comment, source) + .or_else(|comment| handle_end_of_line_comment_around_body(comment, source)) + .or_else(|comment| handle_own_line_comment_around_body(comment, source)) + .or_else(|comment| handle_enclosed_comment(comment, comment_ranges, source)) } /// Handle parenthesized comments. A parenthesized comment is a comment that appears within a @@ -71,7 +71,7 @@ pub(super) fn place_comment<'a>( /// comment is a leading comment of the following node. fn handle_parenthesized_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { // As a special-case, ignore comments within f-strings, like: // ```python @@ -133,7 +133,7 @@ fn handle_parenthesized_comment<'a>( // ] // ``` let range = TextRange::new(preceding.end(), comment.start()); - let tokenizer = SimpleTokenizer::new(locator.contents(), range); + let tokenizer = SimpleTokenizer::new(source, range); if tokenizer .skip_trivia() .take_while(|token| { @@ -146,7 +146,7 @@ fn handle_parenthesized_comment<'a>( debug_assert!( !matches!(token.kind, SimpleTokenKind::Bogus), "Unexpected token between nodes: `{:?}`", - locator.slice(range) + source.slice(range) ); token.kind() == SimpleTokenKind::LParen }) @@ -164,7 +164,7 @@ fn handle_parenthesized_comment<'a>( // ] // ``` let range = TextRange::new(comment.end(), following.start()); - let tokenizer = SimpleTokenizer::new(locator.contents(), range); + let tokenizer = SimpleTokenizer::new(source, range); if tokenizer .skip_trivia() .take_while(|token| { @@ -177,7 +177,7 @@ fn handle_parenthesized_comment<'a>( debug_assert!( !matches!(token.kind, SimpleTokenKind::Bogus), "Unexpected token between nodes: `{:?}`", - locator.slice(range) + source.slice(range) ); token.kind() == SimpleTokenKind::RParen }) @@ -192,61 +192,61 @@ fn handle_parenthesized_comment<'a>( fn handle_enclosed_comment<'a>( comment: DecoratedComment<'a>, comment_ranges: &CommentRanges, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { match comment.enclosing_node() { AnyNodeRef::Parameters(parameters) => { - handle_parameters_separator_comment(comment, parameters, locator).or_else(|comment| { - if are_parameters_parenthesized(parameters, locator.contents()) { - handle_bracketed_end_of_line_comment(comment, locator) + handle_parameters_separator_comment(comment, parameters, source).or_else(|comment| { + if are_parameters_parenthesized(parameters, source) { + handle_bracketed_end_of_line_comment(comment, source) } else { CommentPlacement::Default(comment) } }) } - AnyNodeRef::Parameter(parameter) => handle_parameter_comment(comment, parameter, locator), + AnyNodeRef::Parameter(parameter) => handle_parameter_comment(comment, parameter, source), AnyNodeRef::Arguments(_) | AnyNodeRef::TypeParams(_) | AnyNodeRef::PatternArguments(_) => { - handle_bracketed_end_of_line_comment(comment, locator) + handle_bracketed_end_of_line_comment(comment, source) } AnyNodeRef::Comprehension(comprehension) => { - handle_comprehension_comment(comment, comprehension, locator) + handle_comprehension_comment(comment, comprehension, source) } AnyNodeRef::ExprAttribute(attribute) => { - handle_attribute_comment(comment, attribute, locator) + handle_attribute_comment(comment, attribute, source) } AnyNodeRef::ExprBinOp(binary_expression) => { handle_trailing_binary_expression_left_or_operator_comment( comment, binary_expression, - locator, + source, ) } AnyNodeRef::ExprBoolOp(_) | AnyNodeRef::ExprCompare(_) => { - handle_trailing_binary_like_comment(comment, locator) + handle_trailing_binary_like_comment(comment, source) } - AnyNodeRef::Keyword(keyword) => handle_keyword_comment(comment, keyword, locator), + AnyNodeRef::Keyword(keyword) => handle_keyword_comment(comment, keyword, source), AnyNodeRef::PatternKeyword(pattern_keyword) => { - handle_pattern_keyword_comment(comment, pattern_keyword, locator) + handle_pattern_keyword_comment(comment, pattern_keyword, source) } - AnyNodeRef::ExprUnaryOp(unary_op) => handle_unary_op_comment(comment, unary_op, locator), - AnyNodeRef::ExprNamed(_) => handle_named_expr_comment(comment, locator), - AnyNodeRef::ExprLambda(lambda) => handle_lambda_comment(comment, lambda, locator), - AnyNodeRef::ExprDict(_) => handle_dict_unpacking_comment(comment, locator) - .or_else(|comment| handle_bracketed_end_of_line_comment(comment, locator)) - .or_else(|comment| handle_key_value_comment(comment, locator)), - AnyNodeRef::ExprDictComp(_) => handle_key_value_comment(comment, locator) - .or_else(|comment| handle_bracketed_end_of_line_comment(comment, locator)), - AnyNodeRef::ExprIf(expr_if) => handle_expr_if_comment(comment, expr_if, locator), + AnyNodeRef::ExprUnaryOp(unary_op) => handle_unary_op_comment(comment, unary_op, source), + AnyNodeRef::ExprNamed(_) => handle_named_expr_comment(comment, source), + AnyNodeRef::ExprLambda(lambda) => handle_lambda_comment(comment, lambda, source), + AnyNodeRef::ExprDict(_) => handle_dict_unpacking_comment(comment, source) + .or_else(|comment| handle_bracketed_end_of_line_comment(comment, source)) + .or_else(|comment| handle_key_value_comment(comment, source)), + AnyNodeRef::ExprDictComp(_) => handle_key_value_comment(comment, source) + .or_else(|comment| handle_bracketed_end_of_line_comment(comment, source)), + AnyNodeRef::ExprIf(expr_if) => handle_expr_if_comment(comment, expr_if, source), AnyNodeRef::ExprSlice(expr_slice) => { - handle_slice_comments(comment, expr_slice, comment_ranges, locator) + handle_slice_comments(comment, expr_slice, comment_ranges, source) } AnyNodeRef::ExprStarred(starred) => { - handle_trailing_expression_starred_star_end_of_line_comment(comment, starred, locator) + handle_trailing_expression_starred_star_end_of_line_comment(comment, starred, source) } AnyNodeRef::ExprSubscript(expr_subscript) => { if let Expr::Slice(expr_slice) = expr_subscript.slice.as_ref() { - return handle_slice_comments(comment, expr_slice, comment_ranges, locator); + return handle_slice_comments(comment, expr_slice, comment_ranges, source); } // Handle non-slice subscript end-of-line comments coming after the `[` @@ -262,7 +262,7 @@ fn handle_enclosed_comment<'a>( { // Ensure that there are no tokens between the open bracket and the comment. let mut lexer = SimpleTokenizer::new( - locator.contents(), + source, TextRange::new(expr_subscript.value.end(), comment.start()), ) .skip_trivia(); @@ -288,26 +288,24 @@ fn handle_enclosed_comment<'a>( AnyNodeRef::ModModule(module) => { handle_trailing_module_comment(module, comment).or_else(|comment| { handle_module_level_own_line_comment_before_class_or_function_comment( - comment, locator, + comment, source, ) }) } - AnyNodeRef::WithItem(_) => handle_with_item_comment(comment, locator), + AnyNodeRef::WithItem(_) => handle_with_item_comment(comment, source), AnyNodeRef::PatternMatchSequence(pattern_match_sequence) => { - if SequenceType::from_pattern(pattern_match_sequence, locator.contents()) - .is_parenthesized() - { - handle_bracketed_end_of_line_comment(comment, locator) + if SequenceType::from_pattern(pattern_match_sequence, source).is_parenthesized() { + handle_bracketed_end_of_line_comment(comment, source) } else { CommentPlacement::Default(comment) } } AnyNodeRef::PatternMatchClass(class) => handle_pattern_match_class_comment(comment, class), - AnyNodeRef::PatternMatchAs(_) => handle_pattern_match_as_comment(comment, locator), + AnyNodeRef::PatternMatchAs(_) => handle_pattern_match_as_comment(comment, source), AnyNodeRef::PatternMatchStar(_) => handle_pattern_match_star_comment(comment), AnyNodeRef::PatternMatchMapping(pattern) => { - handle_bracketed_end_of_line_comment(comment, locator) - .or_else(|comment| handle_pattern_match_mapping_comment(comment, pattern, locator)) + handle_bracketed_end_of_line_comment(comment, source) + .or_else(|comment| handle_pattern_match_mapping_comment(comment, pattern, source)) } AnyNodeRef::StmtFunctionDef(_) => handle_leading_function_with_decorators_comment(comment), AnyNodeRef::StmtClassDef(class_def) => { @@ -343,19 +341,19 @@ fn handle_enclosed_comment<'a>( ) { CommentPlacement::trailing(comment.enclosing_node(), comment) } else { - handle_bracketed_end_of_line_comment(comment, locator) + handle_bracketed_end_of_line_comment(comment, source) } } AnyNodeRef::ExprList(_) | AnyNodeRef::ExprSet(_) | AnyNodeRef::ExprListComp(_) - | AnyNodeRef::ExprSetComp(_) => handle_bracketed_end_of_line_comment(comment, locator), + | AnyNodeRef::ExprSetComp(_) => handle_bracketed_end_of_line_comment(comment, source), AnyNodeRef::ExprTuple(ast::ExprTuple { parenthesized: true, .. - }) => handle_bracketed_end_of_line_comment(comment, locator), + }) => handle_bracketed_end_of_line_comment(comment, source), AnyNodeRef::ExprGenerator(generator) if generator.parenthesized => { - handle_bracketed_end_of_line_comment(comment, locator) + handle_bracketed_end_of_line_comment(comment, source) } _ => CommentPlacement::Default(comment), } @@ -364,7 +362,7 @@ fn handle_enclosed_comment<'a>( /// Handle an end-of-line comment around a body. fn handle_end_of_line_comment_around_body<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if comment.line_position().is_own_line() { return CommentPlacement::Default(comment); @@ -379,13 +377,10 @@ fn handle_end_of_line_comment_around_body<'a>( // ``` if let Some(following) = comment.following_node() { if following.is_first_statement_in_body(comment.enclosing_node()) - && SimpleTokenizer::new( - locator.contents(), - TextRange::new(comment.end(), following.start()), - ) - .skip_trivia() - .next() - .is_none() + && SimpleTokenizer::new(source, TextRange::new(comment.end(), following.start())) + .skip_trivia() + .next() + .is_none() { return CommentPlacement::dangling(comment.enclosing_node(), comment); } @@ -436,7 +431,7 @@ fn handle_end_of_line_comment_around_body<'a>( /// ``` fn handle_own_line_comment_around_body<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if comment.line_position().is_end_of_line() { return CommentPlacement::Default(comment); @@ -458,24 +453,22 @@ fn handle_own_line_comment_around_body<'a>( // # default placement comment // def inline_after_else(): ... // ``` - let maybe_token = SimpleTokenizer::new( - locator.contents(), - TextRange::new(preceding.end(), comment.start()), - ) - .skip_trivia() - .next(); + let maybe_token = + SimpleTokenizer::new(source, TextRange::new(preceding.end(), comment.start())) + .skip_trivia() + .next(); if maybe_token.is_some() { return CommentPlacement::Default(comment); } // Check if we're between bodies and should attach to the following body. - handle_own_line_comment_between_branches(comment, preceding, locator) + handle_own_line_comment_between_branches(comment, preceding, source) .or_else(|comment| { // Otherwise, there's no following branch or the indentation is too deep, so attach to the // recursively last statement in the preceding body with the matching indentation. - handle_own_line_comment_after_branch(comment, preceding, locator) + handle_own_line_comment_after_branch(comment, preceding, source) }) - .or_else(|comment| handle_own_line_comment_between_statements(comment, locator)) + .or_else(|comment| handle_own_line_comment_between_statements(comment, source)) } /// Handles own-line comments between statements. If an own-line comment is between two statements, @@ -500,7 +493,7 @@ fn handle_own_line_comment_around_body<'a>( /// ``` fn handle_own_line_comment_between_statements<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let Some(preceding) = comment.preceding_node() else { return CommentPlacement::Default(comment); @@ -540,7 +533,7 @@ fn handle_own_line_comment_between_statements<'a>( // // y = 2 // ``` - if max_empty_lines(locator.slice(TextRange::new(comment.end(), following.start()))) == 0 { + if max_empty_lines(source.slice(TextRange::new(comment.end(), following.start()))) == 0 { CommentPlacement::leading(following, comment) } else { CommentPlacement::trailing(preceding, comment) @@ -559,7 +552,7 @@ fn handle_own_line_comment_between_statements<'a>( fn handle_own_line_comment_between_branches<'a>( comment: DecoratedComment<'a>, preceding: AnyNodeRef<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { // The following statement must be the first statement in an alternate body, otherwise check // if it's a comment after the final body and handle that case @@ -572,9 +565,9 @@ fn handle_own_line_comment_between_branches<'a>( // It depends on the indentation level of the comment if it is a leading comment for the // following branch or if it a trailing comment of the previous body's last statement. - let comment_indentation = comment_indentation_after(preceding, comment.range(), locator); + let comment_indentation = comment_indentation_after(preceding, comment.range(), source); - let preceding_indentation = indentation(locator, &preceding) + let preceding_indentation = indentation(source, &preceding) .unwrap_or_default() .text_len(); @@ -648,7 +641,7 @@ fn handle_own_line_comment_between_branches<'a>( fn handle_own_line_comment_after_branch<'a>( comment: DecoratedComment<'a>, preceding: AnyNodeRef<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let Some(last_child) = preceding.last_child_in_body() else { return CommentPlacement::Default(comment); @@ -656,7 +649,7 @@ fn handle_own_line_comment_after_branch<'a>( // We only care about the length because indentations with mixed spaces and tabs are only valid if // the indent-level doesn't depend on the tab width (the indent level must be the same if the tab width is 1 or 8). - let comment_indentation = comment_indentation_after(preceding, comment.range(), locator); + let comment_indentation = comment_indentation_after(preceding, comment.range(), source); // Keep the comment on the entire statement in case it's a trailing comment // ```python @@ -667,7 +660,7 @@ fn handle_own_line_comment_after_branch<'a>( // # Trailing if comment // ``` // Here we keep the comment a trailing comment of the `if` - let preceding_indentation = indentation_at_offset(preceding.start(), locator) + let preceding_indentation = indentation_at_offset(preceding.start(), source) .unwrap_or_default() .text_len(); if comment_indentation == preceding_indentation { @@ -678,7 +671,7 @@ fn handle_own_line_comment_after_branch<'a>( let mut last_child_in_parent = last_child; loop { - let child_indentation = indentation(locator, &last_child_in_parent) + let child_indentation = indentation(source, &last_child_in_parent) .unwrap_or_default() .text_len(); @@ -739,9 +732,9 @@ fn handle_own_line_comment_after_branch<'a>( fn handle_parameters_separator_comment<'a>( comment: DecoratedComment<'a>, parameters: &Parameters, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { - let (slash, star) = find_parameter_separators(locator.contents(), parameters); + let (slash, star) = find_parameter_separators(source, parameters); let placement = assign_argument_separator_comment_placement( slash.as_ref(), star.as_ref(), @@ -768,10 +761,10 @@ fn handle_parameters_separator_comment<'a>( fn handle_parameter_comment<'a>( comment: DecoratedComment<'a>, parameter: &'a Parameter, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if parameter.annotation.as_deref().is_some() { - let colon = first_non_trivia_token(parameter.name.end(), locator.contents()).expect( + let colon = first_non_trivia_token(parameter.name.end(), source).expect( "A annotated parameter should have a colon following its name when it is valid syntax.", ); @@ -804,7 +797,7 @@ fn handle_parameter_comment<'a>( fn handle_trailing_binary_expression_left_or_operator_comment<'a>( comment: DecoratedComment<'a>, binary_expression: &'a ast::ExprBinOp, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { // Only if there's a preceding node (in which case, the preceding node is `left`). if comment.preceding_node().is_none() || comment.following_node().is_none() { @@ -816,7 +809,7 @@ fn handle_trailing_binary_expression_left_or_operator_comment<'a>( binary_expression.right.start(), ); - let mut tokens = SimpleTokenizer::new(locator.contents(), between_operands_range) + let mut tokens = SimpleTokenizer::new(source, between_operands_range) .skip_trivia() .skip_while(|token| token.kind == SimpleTokenKind::RParen); let operator_offset = tokens @@ -836,10 +829,10 @@ fn handle_trailing_binary_expression_left_or_operator_comment<'a>( CommentPlacement::trailing(binary_expression.left.as_ref(), comment) } else if comment.line_position().is_end_of_line() { // Is the operator on its own line. - if locator.contains_line_break(TextRange::new( + if source.contains_line_break(TextRange::new( binary_expression.left.end(), operator_offset, - )) && locator.contains_line_break(TextRange::new( + )) && source.contains_line_break(TextRange::new( operator_offset, binary_expression.right.start(), )) { @@ -893,7 +886,7 @@ fn handle_trailing_binary_expression_left_or_operator_comment<'a>( /// ``` fn handle_trailing_binary_like_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!( comment.enclosing_node().is_expr_bool_op() || comment.enclosing_node().is_expr_compare() @@ -908,7 +901,7 @@ fn handle_trailing_binary_like_comment<'a>( let between_operands_range = TextRange::new(left_operand.end(), right_operand.start()); - let mut tokens = SimpleTokenizer::new(locator.contents(), between_operands_range) + let mut tokens = SimpleTokenizer::new(source, between_operands_range) .skip_trivia() .skip_while(|token| token.kind == SimpleTokenKind::RParen); let operator_offset = tokens @@ -990,7 +983,7 @@ fn handle_trailing_module_comment<'a>( /// a trailing comment of the previous statement. fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(comment.enclosing_node().is_module()); // Only applies for own line comments on the module level... @@ -1013,7 +1006,7 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>( } // Make the comment a leading comment if there's no empty line between the comment and the function / class header - if max_empty_lines(locator.slice(TextRange::new(comment.end(), following.start()))) == 0 { + if max_empty_lines(source.slice(TextRange::new(comment.end(), following.start()))) == 0 { CommentPlacement::leading(following, comment) } else { // Otherwise attach the comment as trailing comment to the previous statement @@ -1034,7 +1027,7 @@ fn handle_slice_comments<'a>( comment: DecoratedComment<'a>, expr_slice: &'a ast::ExprSlice, comment_ranges: &CommentRanges, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let ast::ExprSlice { range: _, @@ -1045,7 +1038,7 @@ fn handle_slice_comments<'a>( // Check for `foo[ # comment`, but only if they are on the same line let after_lbracket = matches!( - BackwardsTokenizer::up_to(comment.start(), locator.contents(), comment_ranges) + BackwardsTokenizer::up_to(comment.start(), source, comment_ranges) .skip_trivia() .next(), Some(SimpleToken { @@ -1069,7 +1062,7 @@ fn handle_slice_comments<'a>( return CommentPlacement::dangling(comment.enclosing_node(), comment); } - let assignment = assign_comment_in_slice(comment.range(), locator.contents(), expr_slice); + let assignment = assign_comment_in_slice(comment.range(), source, expr_slice); let node = match assignment { ExprSliceCommentSection::Lower => lower, ExprSliceCommentSection::Upper => upper, @@ -1155,7 +1148,7 @@ fn handle_leading_class_with_decorators_comment<'a>( fn handle_keyword_comment<'a>( comment: DecoratedComment<'a>, keyword: &'a ast::Keyword, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let start = keyword.arg.as_ref().map_or(keyword.start(), Ranged::end); @@ -1167,8 +1160,7 @@ fn handle_keyword_comment<'a>( // ) // ) // ``` - let mut tokenizer = - SimpleTokenizer::new(locator.contents(), TextRange::new(start, comment.start())); + let mut tokenizer = SimpleTokenizer::new(source, TextRange::new(start, comment.start())); if tokenizer.any(|token| token.kind == SimpleTokenKind::LParen) { return CommentPlacement::Default(comment); } @@ -1188,7 +1180,7 @@ fn handle_keyword_comment<'a>( fn handle_pattern_keyword_comment<'a>( comment: DecoratedComment<'a>, pattern_keyword: &'a ast::PatternKeyword, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { // If the comment is parenthesized, it should be attached to the value: // ```python @@ -1199,7 +1191,7 @@ fn handle_pattern_keyword_comment<'a>( // ) // ``` let mut tokenizer = SimpleTokenizer::new( - locator.contents(), + source, TextRange::new(pattern_keyword.attr.end(), comment.start()), ); if tokenizer.any(|token| token.kind == SimpleTokenKind::LParen) { @@ -1221,7 +1213,7 @@ fn handle_pattern_keyword_comment<'a>( /// ``` fn handle_dict_unpacking_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(matches!(comment.enclosing_node(), AnyNodeRef::ExprDict(_))); @@ -1236,12 +1228,9 @@ fn handle_dict_unpacking_comment<'a>( Some(preceding) => preceding.end(), None => comment.enclosing_node().start(), }; - let mut tokens = SimpleTokenizer::new( - locator.contents(), - TextRange::new(preceding_end, comment.start()), - ) - .skip_trivia() - .skip_while(|token| token.kind == SimpleTokenKind::RParen); + let mut tokens = SimpleTokenizer::new(source, TextRange::new(preceding_end, comment.start())) + .skip_trivia() + .skip_while(|token| token.kind == SimpleTokenKind::RParen); // if the remaining tokens from the previous node are exactly `**`, // re-assign the comment to the one that follows the stars. @@ -1264,7 +1253,7 @@ fn handle_dict_unpacking_comment<'a>( /// ``` fn handle_key_value_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(matches!( comment.enclosing_node(), @@ -1284,10 +1273,7 @@ fn handle_key_value_comment<'a>( // } // ``` // This prevents against detecting comments on starred expressions as key-value comments. - let tokens = SimpleTokenizer::new( - locator.contents(), - TextRange::new(preceding.end(), following.start()), - ); + let tokens = SimpleTokenizer::new(source, TextRange::new(preceding.end(), following.start())); if tokens .skip_trivia() .any(|token| token.kind == SimpleTokenKind::Colon) @@ -1334,7 +1320,7 @@ fn handle_call_comment(comment: DecoratedComment) -> CommentPlacement { fn handle_attribute_comment<'a>( comment: DecoratedComment<'a>, attribute: &'a ast::ExprAttribute, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if comment.preceding_node().is_none() { // ```text @@ -1365,7 +1351,7 @@ fn handle_attribute_comment<'a>( // .attribute // ) // ``` - if let Some(right_paren) = SimpleTokenizer::starts_at(attribute.value.end(), locator.contents()) + if let Some(right_paren) = SimpleTokenizer::starts_at(attribute.value.end(), source) .skip_trivia() .take_while(|token| token.kind == SimpleTokenKind::RParen) .last() @@ -1398,7 +1384,7 @@ fn handle_attribute_comment<'a>( let dot_token = find_only_token_in_range( TextRange::new(attribute.value.end(), attribute.attr.start()), SimpleTokenKind::Dot, - locator.contents(), + source, ); if comment.end() < dot_token.start() { return CommentPlacement::trailing(attribute.value.as_ref(), comment); @@ -1426,7 +1412,7 @@ fn handle_attribute_comment<'a>( fn handle_expr_if_comment<'a>( comment: DecoratedComment<'a>, expr_if: &'a ast::ExprIf, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let ast::ExprIf { range: _, @@ -1442,7 +1428,7 @@ fn handle_expr_if_comment<'a>( let if_token = find_only_token_in_range( TextRange::new(body.end(), test.start()), SimpleTokenKind::If, - locator.contents(), + source, ); // Between `if` and `test` if if_token.start() < comment.start() && comment.start() < test.start() { @@ -1452,7 +1438,7 @@ fn handle_expr_if_comment<'a>( let else_token = find_only_token_in_range( TextRange::new(test.end(), orelse.start()), SimpleTokenKind::Else, - locator.contents(), + source, ); // Between `else` and `orelse` if else_token.start() < comment.start() && comment.start() < orelse.start() { @@ -1477,13 +1463,11 @@ fn handle_expr_if_comment<'a>( fn handle_trailing_expression_starred_star_end_of_line_comment<'a>( comment: DecoratedComment<'a>, starred: &'a ast::ExprStarred, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if comment.following_node().is_some() { - let tokenizer = SimpleTokenizer::new( - locator.contents(), - TextRange::new(starred.start(), comment.start()), - ); + let tokenizer = + SimpleTokenizer::new(source, TextRange::new(starred.start(), comment.start())); if !tokenizer .skip_trivia() .any(|token| token.kind() == SimpleTokenKind::LParen) @@ -1508,7 +1492,7 @@ fn handle_trailing_expression_starred_star_end_of_line_comment<'a>( /// ``` fn handle_with_item_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(comment.enclosing_node().is_with_item()); @@ -1522,7 +1506,7 @@ fn handle_with_item_comment<'a>( let as_token = find_only_token_in_range( TextRange::new(context_expr.end(), optional_vars.start()), SimpleTokenKind::As, - locator.contents(), + source, ); if comment.end() < as_token.start() { @@ -1567,7 +1551,7 @@ fn handle_pattern_match_class_comment<'a>( /// ``` fn handle_pattern_match_as_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(comment.enclosing_node().is_pattern_match_as()); @@ -1575,7 +1559,7 @@ fn handle_pattern_match_as_comment<'a>( return CommentPlacement::Default(comment); }; - let mut tokens = SimpleTokenizer::starts_at(pattern.end(), locator.contents()) + let mut tokens = SimpleTokenizer::starts_at(pattern.end(), source) .skip_trivia() .skip_while(|token| token.kind == SimpleTokenKind::RParen); @@ -1625,7 +1609,7 @@ fn handle_pattern_match_star_comment(comment: DecoratedComment) -> CommentPlacem fn handle_pattern_match_mapping_comment<'a>( comment: DecoratedComment<'a>, pattern: &'a ast::PatternMatchMapping, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { // The `**` has to come at the end, so there can't be another node after it. (The identifier, // like `rest` above, isn't a node.) @@ -1649,11 +1633,8 @@ fn handle_pattern_match_mapping_comment<'a>( Some(preceding) => preceding.end(), None => comment.enclosing_node().start(), }; - let mut tokens = SimpleTokenizer::new( - locator.contents(), - TextRange::new(preceding_end, comment.start()), - ) - .skip_trivia(); + let mut tokens = + SimpleTokenizer::new(source, TextRange::new(preceding_end, comment.start())).skip_trivia(); // If the remaining tokens from the previous node include `**`, mark as a dangling comment. if tokens.any(|token| token.kind == SimpleTokenKind::DoubleStar) { @@ -1682,7 +1663,7 @@ fn handle_pattern_match_mapping_comment<'a>( /// ``` fn handle_named_expr_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { debug_assert!(comment.enclosing_node().is_expr_named()); @@ -1693,7 +1674,7 @@ fn handle_named_expr_comment<'a>( let colon_equal = find_only_token_in_range( TextRange::new(target.end(), value.start()), SimpleTokenKind::ColonEqual, - locator.contents(), + source, ); if comment.end() < colon_equal.start() { @@ -1738,7 +1719,7 @@ fn handle_named_expr_comment<'a>( fn handle_lambda_comment<'a>( comment: DecoratedComment<'a>, lambda: &'a ast::ExprLambda, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if let Some(parameters) = lambda.parameters.as_deref() { // Comments between the `lambda` and the parameters are dangling on the lambda: @@ -1770,10 +1751,8 @@ fn handle_lambda_comment<'a>( // ) // ) // ``` - let tokenizer = SimpleTokenizer::new( - locator.contents(), - TextRange::new(parameters.end(), comment.start()), - ); + let tokenizer = + SimpleTokenizer::new(source, TextRange::new(parameters.end(), comment.start())); if tokenizer .skip_trivia() .any(|token| token.kind == SimpleTokenKind::LParen) @@ -1801,10 +1780,8 @@ fn handle_lambda_comment<'a>( // ) // ) // ``` - let tokenizer = SimpleTokenizer::new( - locator.contents(), - TextRange::new(lambda.start(), comment.start()), - ); + let tokenizer = + SimpleTokenizer::new(source, TextRange::new(lambda.start(), comment.start())); if tokenizer .skip_trivia() .any(|token| token.kind == SimpleTokenKind::LParen) @@ -1834,10 +1811,10 @@ fn handle_lambda_comment<'a>( fn handle_unary_op_comment<'a>( comment: DecoratedComment<'a>, unary_op: &'a ast::ExprUnaryOp, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let mut tokenizer = SimpleTokenizer::new( - locator.contents(), + source, TextRange::new(unary_op.start(), unary_op.operand.start()), ) .skip_trivia(); @@ -1883,12 +1860,12 @@ fn handle_unary_op_comment<'a>( /// that it remains on the same line as open bracket. fn handle_bracketed_end_of_line_comment<'a>( comment: DecoratedComment<'a>, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { if comment.line_position().is_end_of_line() { // Ensure that there are no tokens between the open bracket and the comment. let mut lexer = SimpleTokenizer::new( - locator.contents(), + source, TextRange::new(comment.enclosing_node().start(), comment.start()), ) .skip_trivia(); @@ -2006,7 +1983,7 @@ fn handle_with_comment<'a>( fn handle_comprehension_comment<'a>( comment: DecoratedComment<'a>, comprehension: &'a Comprehension, - locator: &Locator, + source: &str, ) -> CommentPlacement<'a> { let is_own_line = comment.line_position().is_own_line(); @@ -2031,7 +2008,7 @@ fn handle_comprehension_comment<'a>( let in_token = find_only_token_in_range( TextRange::new(comprehension.target.end(), comprehension.iter.start()), SimpleTokenKind::In, - locator.contents(), + source, ); // Comments between the target and the `in` @@ -2094,7 +2071,7 @@ fn handle_comprehension_comment<'a>( let if_token = find_only_token_in_range( TextRange::new(last_end, if_node.start()), SimpleTokenKind::If, - locator.contents(), + source, ); if is_own_line { if last_end < comment.start() && comment.start() < if_token.start() { diff --git a/crates/ruff_python_formatter/src/comments/visitor.rs b/crates/ruff_python_formatter/src/comments/visitor.rs index 6ba2879eab734..52bd5d2009784 100644 --- a/crates/ruff_python_formatter/src/comments/visitor.rs +++ b/crates/ruff_python_formatter/src/comments/visitor.rs @@ -9,7 +9,6 @@ use ruff_python_ast::{Mod, Stmt}; #[allow(clippy::wildcard_imports)] use ruff_python_ast::visitor::source_order::*; use ruff_python_trivia::{CommentLinePosition, CommentRanges}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::comments::node_key::NodeRefEqualityKey; @@ -531,12 +530,12 @@ pub(super) struct CommentsMapBuilder<'a> { comments: CommentsMap<'a>, /// We need those for backwards lexing comment_ranges: &'a CommentRanges, - locator: Locator<'a>, + source: &'a str, } impl<'a> PushComment<'a> for CommentsMapBuilder<'a> { fn push_comment(&mut self, placement: DecoratedComment<'a>) { - let placement = place_comment(placement, self.comment_ranges, &self.locator); + let placement = place_comment(placement, self.comment_ranges, self.source); match placement { CommentPlacement::Leading { node, comment } => { self.push_leading_comment(node, comment); @@ -598,11 +597,11 @@ impl<'a> PushComment<'a> for CommentsMapBuilder<'a> { } impl<'a> CommentsMapBuilder<'a> { - pub(crate) fn new(locator: Locator<'a>, comment_ranges: &'a CommentRanges) -> Self { + pub(crate) fn new(source: &'a str, comment_ranges: &'a CommentRanges) -> Self { Self { comments: CommentsMap::default(), comment_ranges, - locator, + source, } } diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index b449b95eca692..10d41cb5de95a 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -1,12 +1,13 @@ -use crate::comments::Comments; -use crate::other::f_string_element::FStringExpressionElementContext; -use crate::PyFormatOptions; +use std::fmt::{Debug, Formatter}; +use std::ops::{Deref, DerefMut}; + use ruff_formatter::{Buffer, FormatContext, GroupId, IndentWidth, SourceCode}; use ruff_python_ast::str::Quote; use ruff_python_parser::Tokens; -use ruff_source_file::Locator; -use std::fmt::{Debug, Formatter}; -use std::ops::{Deref, DerefMut}; + +use crate::comments::Comments; +use crate::other::f_string_element::FStringExpressionElementContext; +use crate::PyFormatOptions; pub struct PyFormatContext<'a> { options: PyFormatOptions, @@ -51,10 +52,6 @@ impl<'a> PyFormatContext<'a> { self.contents } - pub(crate) fn locator(&self) -> Locator<'a> { - Locator::new(self.contents) - } - pub(crate) fn set_node_level(&mut self, level: NodeLevel) { self.node_level = level; } diff --git a/crates/ruff_python_formatter/src/expression/expr_f_string.rs b/crates/ruff_python_formatter/src/expression/expr_f_string.rs index a4b0325d7976a..8976f3801d061 100644 --- a/crates/ruff_python_formatter/src/expression/expr_f_string.rs +++ b/crates/ruff_python_formatter/src/expression/expr_f_string.rs @@ -1,6 +1,5 @@ use ruff_python_ast::{AnyNodeRef, ExprFString, StringLike}; -use ruff_source_file::Locator; -use ruff_text_size::Ranged; +use ruff_text_size::TextSlice; use crate::expression::parentheses::{ in_parentheses_only_group, NeedsParentheses, OptionalParentheses, @@ -18,11 +17,8 @@ impl FormatNodeRule for FormatExprFString { let ExprFString { value, .. } = item; if let [f_string_part] = value.as_slice() { - FormatFStringPart::new( - f_string_part, - f_string_quoting(item, &f.context().locator()), - ) - .fmt(f) + FormatFStringPart::new(f_string_part, f_string_quoting(item, f.context().source())) + .fmt(f) } else { // Always join fstrings that aren't parenthesized and thus, are always on a single line. if !f.context().node_level().is_parenthesized() { @@ -73,9 +69,9 @@ impl NeedsParentheses for ExprFString { } } -pub(crate) fn f_string_quoting(f_string: &ExprFString, locator: &Locator) -> Quoting { - let unprefixed = locator - .slice(f_string.range()) +pub(crate) fn f_string_quoting(f_string: &ExprFString, source: &str) -> Quoting { + let unprefixed = source + .slice(f_string) .trim_start_matches(|c| c != '"' && c != '\''); let triple_quoted = unprefixed.starts_with(r#"""""#) || unprefixed.starts_with(r"'''"); @@ -84,7 +80,7 @@ pub(crate) fn f_string_quoting(f_string: &ExprFString, locator: &Locator) -> Quo .elements() .filter_map(|element| element.as_expression()) .any(|expression| { - let string_content = locator.slice(expression.range()); + let string_content = source.slice(expression); if triple_quoted { string_content.contains(r#"""""#) || string_content.contains("'''") } else { diff --git a/crates/ruff_python_formatter/src/expression/expr_number_literal.rs b/crates/ruff_python_formatter/src/expression/expr_number_literal.rs index ab46ab3f0beaf..ca6be5fa34e64 100644 --- a/crates/ruff_python_formatter/src/expression/expr_number_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_number_literal.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{ExprNumberLiteral, Number}; -use ruff_text_size::{Ranged, TextSize}; +use ruff_text_size::{Ranged, TextSize, TextSlice}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::prelude::*; @@ -14,28 +14,26 @@ impl FormatNodeRule for FormatExprNumberLiteral { fn fmt_fields(&self, item: &ExprNumberLiteral, f: &mut PyFormatter) -> FormatResult<()> { match item.value { Number::Int(_) => { - let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(item); let normalized = normalize_integer(content); match normalized { - Cow::Borrowed(_) => source_text_slice(range).fmt(f), + Cow::Borrowed(_) => source_text_slice(item.range()).fmt(f), Cow::Owned(normalized) => text(&normalized).fmt(f), } } Number::Float(_) => { - let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(item); let normalized = normalize_floating_number(content); match normalized { - Cow::Borrowed(_) => source_text_slice(range).fmt(f), + Cow::Borrowed(_) => source_text_slice(item.range()).fmt(f), Cow::Owned(normalized) => text(&normalized).fmt(f), } } Number::Complex { .. } => { let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(item); let normalized = normalize_floating_number(content.trim_end_matches(['j', 'J'])); match normalized { diff --git a/crates/ruff_python_formatter/src/lib.rs b/crates/ruff_python_formatter/src/lib.rs index fcb512c63c16c..cbe56762f269d 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -8,7 +8,6 @@ use ruff_python_ast::AstNode; use ruff_python_ast::Mod; use ruff_python_parser::{parse, AsMode, ParseError, Parsed}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use crate::comments::{ has_skip_comment, leading_comments, trailing_comments, Comments, SourceComment, @@ -127,10 +126,9 @@ pub fn format_module_ast<'a>( ) -> FormatResult>> { let source_code = SourceCode::new(source); let comments = Comments::from_ast(parsed.syntax(), source_code, comment_ranges); - let locator = Locator::new(source); let formatted = format!( - PyFormatContext::new(options, locator.contents(), comments, parsed.tokens()), + PyFormatContext::new(options, source, comments, parsed.tokens()), [parsed.syntax().format()] )?; formatted diff --git a/crates/ruff_python_formatter/src/other/f_string.rs b/crates/ruff_python_formatter/src/other/f_string.rs index a8e792643243d..409697dfadf36 100644 --- a/crates/ruff_python_formatter/src/other/f_string.rs +++ b/crates/ruff_python_formatter/src/other/f_string.rs @@ -1,6 +1,7 @@ use ruff_formatter::write; use ruff_python_ast::{AnyStringFlags, FString, StringFlags}; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; +use ruff_text_size::Ranged; use crate::prelude::*; use crate::preview::is_f_string_formatting_enabled; @@ -27,8 +28,6 @@ impl<'a> FormatFString<'a> { impl Format> for FormatFString<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let locator = f.context().locator(); - // If the preview style is enabled, make the decision on what quotes to use locally for each // f-string instead of globally for the entire f-string expression. let quoting = if is_f_string_formatting_enabled(f.context()) { @@ -66,7 +65,7 @@ impl Format> for FormatFString<'_> { let context = FStringContext::new( string_kind, - FStringLayout::from_f_string(self.value, &locator), + FStringLayout::from_f_string(self.value, f.context().source()), ); // Starting prefix and quote @@ -117,7 +116,7 @@ pub(crate) enum FStringLayout { } impl FStringLayout { - pub(crate) fn from_f_string(f_string: &FString, locator: &Locator) -> Self { + pub(crate) fn from_f_string(f_string: &FString, source: &str) -> Self { // Heuristic: Allow breaking the f-string expressions across multiple lines // only if there already is at least one multiline expression. This puts the // control in the hands of the user to decide if they want to break the @@ -133,7 +132,7 @@ impl FStringLayout { if f_string .elements .expressions() - .any(|expr| memchr::memchr2(b'\n', b'\r', locator.slice(expr).as_bytes()).is_some()) + .any(|expr| source.contains_line_break(expr.range())) { Self::Multiline } else { diff --git a/crates/ruff_python_formatter/src/other/f_string_element.rs b/crates/ruff_python_formatter/src/other/f_string_element.rs index 77daaca0d25a4..7139b72af63dc 100644 --- a/crates/ruff_python_formatter/src/other/f_string_element.rs +++ b/crates/ruff_python_formatter/src/other/f_string_element.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{ AnyStringFlags, ConversionFlag, Expr, FStringElement, FStringExpressionElement, FStringLiteralElement, StringFlags, }; -use ruff_text_size::Ranged; +use ruff_text_size::{Ranged, TextSlice}; use crate::comments::{dangling_open_parenthesis_comments, trailing_comments}; use crate::context::{FStringState, NodeLevel, WithFStringState, WithNodeLevel}; @@ -60,7 +60,7 @@ impl<'a> FormatFStringLiteralElement<'a> { impl Format> for FormatFStringLiteralElement<'_> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let literal_content = f.context().locator().slice(self.element.range()); + let literal_content = f.context().source().slice(self.element); let normalized = normalize_string(literal_content, 0, self.fstring_flags, false, false, true); match &normalized { diff --git a/crates/ruff_python_formatter/src/range.rs b/crates/ruff_python_formatter/src/range.rs index 33f7d2a1d2fe7..c0f5cfa0c3294 100644 --- a/crates/ruff_python_formatter/src/range.rs +++ b/crates/ruff_python_formatter/src/range.rs @@ -10,7 +10,6 @@ use ruff_python_parser::{parse, AsMode}; use ruff_python_trivia::{ indentation_at_offset, BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, }; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::comments::Comments; @@ -300,8 +299,7 @@ fn narrow_range( enclosing_node: AnyNodeRef, context: &PyFormatContext, ) -> TextRange { - let locator = context.locator(); - let enclosing_indent = indentation_at_offset(enclosing_node.start(), &locator) + let enclosing_indent = indentation_at_offset(enclosing_node.start(), context.source()) .expect("Expected enclosing to never be a same line body statement."); let mut visitor = NarrowRange { @@ -513,7 +511,7 @@ impl NarrowRange<'_> { // dedent the second line to 0 spaces and the `indent` then adds a 2 space indentation to match the indentation in the source. // This is incorrect because the leading whitespace is the content of the string and not indentation, resulting in changed string content. if let Some(indentation) = - indentation_at_offset(first_child.start(), &self.context.locator()) + indentation_at_offset(first_child.start(), self.context.source()) { let relative_indent = indentation.strip_prefix(self.enclosing_indent).unwrap(); let expected_indents = self.level; @@ -718,8 +716,7 @@ impl Format> for FormatEnclosingNode<'_> { /// # Panics /// If `offset` is outside of `source`. fn indent_level(offset: TextSize, source: &str, options: &PyFormatOptions) -> Option { - let locator = Locator::new(source); - let indentation = indentation_at_offset(offset, &locator)?; + let indentation = indentation_at_offset(offset, source)?; let level = match options.indent_style() { IndentStyle::Tab => { diff --git a/crates/ruff_python_formatter/src/string/docstring.rs b/crates/ruff_python_formatter/src/string/docstring.rs index 3279d40106438..b078c5c6b3c90 100644 --- a/crates/ruff_python_formatter/src/string/docstring.rs +++ b/crates/ruff_python_formatter/src/string/docstring.rs @@ -2,23 +2,22 @@ // "reStructuredText." #![allow(clippy::doc_markdown)] -use itertools::Itertools; use std::cmp::Ordering; use std::sync::LazyLock; use std::{borrow::Cow, collections::VecDeque}; +use itertools::Itertools; use regex::Regex; + use ruff_formatter::printer::SourceMapGeneration; use ruff_python_ast::{str::Quote, AnyStringFlags, StringFlags}; use ruff_python_trivia::CommentRanges; use { ruff_formatter::{write, FormatOptions, IndentStyle, LineWidth, Printed}, ruff_python_trivia::{is_python_whitespace, PythonWhitespace}, - ruff_source_file::Locator, ruff_text_size::{Ranged, TextLen, TextRange, TextSize}, }; -use super::NormalizedString; use crate::preview::{ is_docstring_code_block_in_docstring_indent_enabled, is_join_implicit_concatenated_string_enabled, @@ -26,6 +25,8 @@ use crate::preview::{ use crate::string::StringQuotes; use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError}; +use super::NormalizedString; + /// Format a docstring by trimming whitespace and adjusting the indentation. /// /// Summary of changes we make: @@ -1592,9 +1593,8 @@ fn docstring_format_source( let comment_ranges = CommentRanges::from(parsed.tokens()); let source_code = ruff_formatter::SourceCode::new(source); let comments = crate::Comments::from_ast(parsed.syntax(), source_code, &comment_ranges); - let locator = Locator::new(source); - let ctx = PyFormatContext::new(options, locator.contents(), comments, parsed.tokens()) + let ctx = PyFormatContext::new(options, source, comments, parsed.tokens()) .in_docstring(docstring_quote_style); let formatted = crate::format!(ctx, [parsed.syntax().format()])?; formatted diff --git a/crates/ruff_python_formatter/src/string/implicit.rs b/crates/ruff_python_formatter/src/string/implicit.rs index 0ef4aae5afab6..f3944a5d1b37f 100644 --- a/crates/ruff_python_formatter/src/string/implicit.rs +++ b/crates/ruff_python_formatter/src/string/implicit.rs @@ -6,7 +6,8 @@ use ruff_python_ast::str_prefix::{ AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, }; use ruff_python_ast::{AnyStringFlags, FStringElement, StringFlags, StringLike, StringLikePart}; -use ruff_text_size::{Ranged, TextRange}; +use ruff_source_file::LineRanges; +use ruff_text_size::{Ranged, TextRange, TextSlice}; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space; @@ -72,7 +73,7 @@ impl<'a> FormatImplicitConcatenatedStringExpanded<'a> { impl Format> for FormatImplicitConcatenatedStringExpanded<'_> { fn fmt(&self, f: &mut Formatter>) -> FormatResult<()> { let comments = f.context().comments().clone(); - let quoting = self.string.quoting(&f.context().locator()); + let quoting = self.string.quoting(f.context().source()); let join_implicit_concatenated_string_enabled = is_join_implicit_concatenated_string_enabled(f.context()); @@ -158,10 +159,9 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { if let StringLikePart::FString(fstring) = part { if fstring.elements.iter().any(|element| match element { // Same as for other literals. Multiline literals can't fit on a single line. - FStringElement::Literal(literal) => context - .locator() - .slice(literal.range()) - .contains(['\n', '\r']), + FStringElement::Literal(literal) => { + context.source().contains_line_break(literal.range()) + } FStringElement::Expression(expression) => { if is_f_string_formatting_enabled(context) { // Expressions containing comments can't be joined. @@ -169,7 +169,7 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> { } else { // Multiline f-string expressions can't be joined if the f-string formatting is disabled because // the string gets inserted in verbatim preserving the newlines. - context.locator().slice(expression).contains(['\n', '\r']) + context.source().contains_line_break(expression.range()) } } }) { @@ -270,7 +270,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { assert!(part.is_string_literal()); if f.context() - .locator() + .source() .slice(part.content_range()) .trim() .is_empty() @@ -300,7 +300,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { if first_non_empty { first_non_empty = f .context() - .locator() + .source() .slice(part.content_range()) .trim_start() .is_empty(); @@ -328,7 +328,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { self.flags, FStringLayout::from_f_string( f_string, - &f.context().locator(), + f.context().source(), ), ); @@ -365,7 +365,7 @@ struct FormatLiteralContent { impl Format> for FormatLiteralContent { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let content = f.context().locator().slice(self.range); + let content = f.context().source().slice(self.range); let mut normalized = normalize_string( content, 0, diff --git a/crates/ruff_python_formatter/src/string/mod.rs b/crates/ruff_python_formatter/src/string/mod.rs index dba9adc555bee..eaff5efac2cf8 100644 --- a/crates/ruff_python_formatter/src/string/mod.rs +++ b/crates/ruff_python_formatter/src/string/mod.rs @@ -7,7 +7,6 @@ use ruff_python_ast::{ str_prefix::{AnyStringPrefix, StringLiteralPrefix}, AnyStringFlags, StringFlags, }; -use ruff_source_file::Locator; use ruff_text_size::Ranged; use crate::expression::expr_f_string::f_string_quoting; @@ -89,16 +88,16 @@ impl From for QuoteStyle { // Extension trait that adds formatter specific helper methods to `StringLike`. pub(crate) trait StringLikeExtensions { - fn quoting(&self, locator: &Locator<'_>) -> Quoting; + fn quoting(&self, source: &str) -> Quoting; fn is_multiline(&self, source: &str) -> bool; } impl StringLikeExtensions for ast::StringLike<'_> { - fn quoting(&self, locator: &Locator<'_>) -> Quoting { + fn quoting(&self, source: &str) -> Quoting { match self { Self::String(_) | Self::Bytes(_) => Quoting::CanChange, - Self::FString(f_string) => f_string_quoting(f_string, locator), + Self::FString(f_string) => f_string_quoting(f_string, source), } } diff --git a/crates/ruff_python_formatter/src/string/normalize.rs b/crates/ruff_python_formatter/src/string/normalize.rs index 3defefb75fa67..961b38e3dc8ec 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -7,7 +7,7 @@ use ruff_python_ast::visitor::source_order::SourceOrderVisitor; use ruff_python_ast::{ str::Quote, AnyStringFlags, BytesLiteral, FString, StringFlags, StringLikePart, StringLiteral, }; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::{Ranged, TextRange, TextSlice}; use crate::context::FStringState; use crate::prelude::*; @@ -152,7 +152,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { /// Computes the strings preferred quotes. pub(crate) fn choose_quotes(&self, string: StringLikePart) -> QuoteSelection { - let raw_content = self.context.locator().slice(string.content_range()); + let raw_content = self.context.source().slice(string.content_range()); let first_quote_or_normalized_char_offset = raw_content .bytes() .position(|b| matches!(b, b'\\' | b'"' | b'\'' | b'\r' | b'{')); @@ -196,7 +196,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { /// Computes the strings preferred quotes and normalizes its content. pub(crate) fn normalize(&self, string: StringLikePart) -> NormalizedString<'src> { - let raw_content = self.context.locator().slice(string.content_range()); + let raw_content = self.context.source().slice(string.content_range()); let quote_selection = self.choose_quotes(string); let normalized = if let Some(first_quote_or_escape_offset) = @@ -256,7 +256,7 @@ impl QuoteMetadata { ) -> Self { match part { StringLikePart::String(_) | StringLikePart::Bytes(_) => { - let text = context.locator().slice(part.content_range()); + let text = context.source().slice(part.content_range()); Self::from_str(text, part.flags(), preferred_quote) } @@ -277,7 +277,7 @@ impl QuoteMetadata { }; let mut metadata = QuoteMetadata::from_str( - context.locator().slice(first.range()), + context.source().slice(first), fstring.flags.into(), preferred_quote, ); @@ -285,7 +285,7 @@ impl QuoteMetadata { for literal in literals { metadata = metadata .merge(&QuoteMetadata::from_str( - context.locator().slice(literal.range()), + context.source().slice(literal), fstring.flags.into(), preferred_quote, )) @@ -294,7 +294,7 @@ impl QuoteMetadata { metadata } else { - let text = context.locator().slice(part.content_range()); + let text = context.source().slice(part.content_range()); Self::from_str(text, part.flags(), preferred_quote) } @@ -893,7 +893,7 @@ pub(super) fn is_fstring_with_quoted_debug_expression( ) -> bool { if fstring.elements.expressions().any(|expression| { if expression.debug_text.is_some() { - let content = context.locator().slice(expression.range()); + let content = context.source().slice(expression); match fstring.flags.quote_style() { Quote::Single => { if fstring.flags.is_triple_quoted() { @@ -969,10 +969,7 @@ pub(super) fn is_fstring_with_triple_quoted_literal_expression_containing_quotes } fn contains_quote(&self, range: TextRange, flags: AnyStringFlags) -> bool { - self.context - .locator() - .slice(range) - .contains(flags.quote_style().as_char()) + self.context.source()[range].contains(flags.quote_style().as_char()) } } diff --git a/crates/ruff_python_formatter/src/verbatim.rs b/crates/ruff_python_formatter/src/verbatim.rs index df70e6e2986be..942eda70c859d 100644 --- a/crates/ruff_python_formatter/src/verbatim.rs +++ b/crates/ruff_python_formatter/src/verbatim.rs @@ -7,7 +7,7 @@ use ruff_python_ast::AnyNodeRef; use ruff_python_ast::Stmt; use ruff_python_parser::{self as parser, TokenKind}; use ruff_python_trivia::lines_before; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::comments::format::{empty_lines, format_comment}; @@ -647,7 +647,7 @@ struct Indentation(u32); impl Indentation { fn from_stmt(stmt: &Stmt, source: &str) -> Indentation { - let line_start = Locator::new(source).line_start(stmt.start()); + let line_start = source.line_start(stmt.start()); let mut indentation = 0u32; for c in source[TextRange::new(line_start, stmt.start())].chars() { @@ -878,7 +878,7 @@ impl Format> for VerbatimText { }, ))); - match normalize_newlines(f.context().locator().slice(self.verbatim_range), ['\r']) { + match normalize_newlines(&f.context().source()[self.verbatim_range], ['\r']) { Cow::Borrowed(_) => { write!(f, [source_text_slice(self.verbatim_range)])?; } diff --git a/crates/ruff_python_index/src/indexer.rs b/crates/ruff_python_index/src/indexer.rs index 596aa812b88ed..bf1d53c64d9f3 100644 --- a/crates/ruff_python_index/src/indexer.rs +++ b/crates/ruff_python_index/src/indexer.rs @@ -6,7 +6,7 @@ use ruff_python_parser::{TokenKind, Tokens}; use ruff_python_trivia::{ has_leading_content, has_trailing_content, is_python_whitespace, CommentRanges, }; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::fstring_ranges::{FStringRanges, FStringRangesBuilder}; @@ -27,8 +27,8 @@ pub struct Indexer { } impl Indexer { - pub fn from_tokens(tokens: &Tokens, locator: &Locator<'_>) -> Self { - assert!(TextSize::try_from(locator.contents().len()).is_ok()); + pub fn from_tokens(tokens: &Tokens, source: &str) -> Self { + assert!(TextSize::try_from(source.len()).is_ok()); let mut fstring_ranges_builder = FStringRangesBuilder::default(); let mut multiline_ranges_builder = MultilineRangesBuilder::default(); @@ -40,7 +40,7 @@ impl Indexer { let mut line_start = TextSize::default(); for token in tokens { - let trivia = locator.slice(TextRange::new(prev_end, token.start())); + let trivia = &source[TextRange::new(prev_end, token.start())]; // Get the trivia between the previous and the current token and detect any newlines. // This is necessary because `RustPython` doesn't emit `[Tok::Newline]` tokens @@ -69,7 +69,7 @@ impl Indexer { TokenKind::String => { // If the previous token was a string, find the start of the line that contains // the closing delimiter, since the token itself can span multiple lines. - line_start = locator.line_start(token.end()); + line_start = source.line_start(token.end()); } TokenKind::Comment => { comment_ranges.push(token.range()); @@ -109,25 +109,20 @@ impl Indexer { } /// Returns `true` if the given offset is part of a continuation line. - pub fn is_continuation(&self, offset: TextSize, locator: &Locator) -> bool { - let line_start = locator.line_start(offset); + pub fn is_continuation(&self, offset: TextSize, source: &str) -> bool { + let line_start = source.line_start(offset); self.continuation_lines.binary_search(&line_start).is_ok() } /// Given an offset at the end of a line (including newlines), return the offset of the /// continuation at the end of that line. - fn find_continuation(&self, offset: TextSize, locator: &Locator) -> Option { + fn find_continuation(&self, offset: TextSize, source: &str) -> Option { let newline_pos = usize::from(offset).saturating_sub(1); // Skip the newline. - let newline_len = match locator.contents().as_bytes()[newline_pos] { + let newline_len = match source.as_bytes()[newline_pos] { b'\n' => { - if locator - .contents() - .as_bytes() - .get(newline_pos.saturating_sub(1)) - == Some(&b'\r') - { + if source.as_bytes().get(newline_pos.saturating_sub(1)) == Some(&b'\r') { 2 } else { 1 @@ -138,7 +133,7 @@ impl Indexer { _ => return None, }; - self.is_continuation(offset - TextSize::from(newline_len), locator) + self.is_continuation(offset - TextSize::from(newline_len), source) .then(|| offset - TextSize::from(newline_len) - TextSize::from(1)) } @@ -164,35 +159,28 @@ impl Indexer { /// /// When passed the offset of `y`, this function will again return the offset of the backslash at /// the end of the first line. - pub fn preceded_by_continuations( - &self, - offset: TextSize, - locator: &Locator, - ) -> Option { + pub fn preceded_by_continuations(&self, offset: TextSize, source: &str) -> Option { // Find the first preceding continuation. If the offset isn't the first non-whitespace // character on the line, then we can't have a continuation. - let previous_line_end = locator.line_start(offset); - if !locator - .slice(TextRange::new(previous_line_end, offset)) + let previous_line_end = source.line_start(offset); + if !source[TextRange::new(previous_line_end, offset)] .chars() .all(is_python_whitespace) { return None; } - let mut continuation = self.find_continuation(previous_line_end, locator)?; + let mut continuation = self.find_continuation(previous_line_end, source)?; // Continue searching for continuations, in the unlikely event that we have multiple // continuations in a row. loop { - let previous_line_end = locator.line_start(continuation); - if locator - .slice(TextRange::new(previous_line_end, continuation)) + let previous_line_end = source.line_start(continuation); + if source[TextRange::new(previous_line_end, continuation)] .chars() .all(is_python_whitespace) { - if let Some(next_continuation) = self.find_continuation(previous_line_end, locator) - { + if let Some(next_continuation) = self.find_continuation(previous_line_end, source) { continuation = next_continuation; continue; } @@ -205,38 +193,36 @@ impl Indexer { /// Return `true` if a [`Stmt`] appears to be preceded by other statements in a multi-statement /// line. - pub fn preceded_by_multi_statement_line(&self, stmt: &Stmt, locator: &Locator) -> bool { - has_leading_content(stmt.start(), locator) + pub fn preceded_by_multi_statement_line(&self, stmt: &Stmt, source: &str) -> bool { + has_leading_content(stmt.start(), source) || self - .preceded_by_continuations(stmt.start(), locator) + .preceded_by_continuations(stmt.start(), source) .is_some() } /// Return `true` if a [`Stmt`] appears to be followed by other statements in a multi-statement /// line. - pub fn followed_by_multi_statement_line(&self, stmt: &Stmt, locator: &Locator) -> bool { - has_trailing_content(stmt.end(), locator) + pub fn followed_by_multi_statement_line(&self, stmt: &Stmt, source: &str) -> bool { + has_trailing_content(stmt.end(), source) } /// Return `true` if a [`Stmt`] appears to be part of a multi-statement line. - pub fn in_multi_statement_line(&self, stmt: &Stmt, locator: &Locator) -> bool { - self.followed_by_multi_statement_line(stmt, locator) - || self.preceded_by_multi_statement_line(stmt, locator) + pub fn in_multi_statement_line(&self, stmt: &Stmt, source: &str) -> bool { + self.followed_by_multi_statement_line(stmt, source) + || self.preceded_by_multi_statement_line(stmt, source) } } #[cfg(test)] mod tests { use ruff_python_parser::parse_module; - use ruff_source_file::Locator; use ruff_text_size::{TextRange, TextSize}; use crate::Indexer; fn new_indexer(contents: &str) -> Indexer { let parsed = parse_module(contents).unwrap(); - let locator = Locator::new(contents); - Indexer::from_tokens(parsed.tokens(), &locator) + Indexer::from_tokens(parsed.tokens(), contents) } #[test] diff --git a/crates/ruff_python_semantic/Cargo.toml b/crates/ruff_python_semantic/Cargo.toml index 86de14b27141b..e939bbf656de4 100644 --- a/crates/ruff_python_semantic/Cargo.toml +++ b/crates/ruff_python_semantic/Cargo.toml @@ -17,7 +17,6 @@ ruff_macros = { workspace = true } ruff_python_ast = { workspace = true } ruff_python_parser = { workspace = true } ruff_python_stdlib = { workspace = true } -ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } bitflags = { workspace = true } diff --git a/crates/ruff_python_semantic/src/binding.rs b/crates/ruff_python_semantic/src/binding.rs index 9b9c74aa81fe2..87c887fc0f1da 100644 --- a/crates/ruff_python_semantic/src/binding.rs +++ b/crates/ruff_python_semantic/src/binding.rs @@ -8,7 +8,6 @@ use ruff_index::{newtype_index, IndexSlice, IndexVec}; use ruff_python_ast::helpers::extract_handled_exceptions; use ruff_python_ast::name::QualifiedName; use ruff_python_ast::{self as ast, Stmt}; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::context::ExecutionContext; @@ -228,8 +227,8 @@ impl<'a> Binding<'a> { } /// Returns the name of the binding (e.g., `x` in `x = 1`). - pub fn name<'b>(&self, locator: &Locator<'b>) -> &'b str { - locator.slice(self.range) + pub fn name<'b>(&self, source: &'b str) -> &'b str { + &source[self.range] } /// Returns the statement in which the binding was defined. diff --git a/crates/ruff_python_semantic/src/reference.rs b/crates/ruff_python_semantic/src/reference.rs index 1b79347684bea..1b9b7364b0b27 100644 --- a/crates/ruff_python_semantic/src/reference.rs +++ b/crates/ruff_python_semantic/src/reference.rs @@ -4,7 +4,6 @@ use bitflags::bitflags; use ruff_index::{newtype_index, IndexSlice, IndexVec}; use ruff_python_ast::ExprContext; -use ruff_source_file::Locator; use ruff_text_size::{Ranged, TextRange}; use crate::scope::ScopeId; @@ -157,8 +156,8 @@ pub struct UnresolvedReference { impl UnresolvedReference { /// Returns the name of the reference. - pub fn name<'a>(&self, locator: &Locator<'a>) -> &'a str { - locator.slice(self.range) + pub fn name<'a>(&self, source: &'a str) -> &'a str { + &source[self.range] } /// The range of the reference in the source code. diff --git a/crates/ruff_python_trivia/src/comment_ranges.rs b/crates/ruff_python_trivia/src/comment_ranges.rs index 673a4aefd6d36..9610a3d387e63 100644 --- a/crates/ruff_python_trivia/src/comment_ranges.rs +++ b/crates/ruff_python_trivia/src/comment_ranges.rs @@ -2,8 +2,8 @@ use std::fmt::{Debug, Formatter}; use std::ops::Deref; use itertools::Itertools; -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::{has_leading_content, has_trailing_content, is_python_whitespace}; @@ -50,19 +50,19 @@ impl CommentRanges { } /// Returns `true` if a statement or expression includes at least one comment. - pub fn has_comments(&self, node: &T, locator: &Locator) -> bool + pub fn has_comments(&self, node: &T, source: &str) -> bool where T: Ranged, { - let start = if has_leading_content(node.start(), locator) { + let start = if has_leading_content(node.start(), source) { node.start() } else { - locator.line_start(node.start()) + source.line_start(node.start()) }; - let end = if has_trailing_content(node.end(), locator) { + let end = if has_trailing_content(node.end(), source) { node.end() } else { - locator.line_end(node.end()) + source.line_end(node.end()) }; self.intersects(TextRange::new(start, end)) @@ -98,7 +98,7 @@ impl CommentRanges { /// # contained within a multi-line string/comment /// """ /// ``` - pub fn block_comments(&self, locator: &Locator) -> Vec { + pub fn block_comments(&self, source: &str) -> Vec { let mut block_comments: Vec = Vec::new(); let mut current_block: Vec = Vec::new(); @@ -109,12 +109,12 @@ impl CommentRanges { for comment_range in &self.raw { let offset = comment_range.start(); - let line_start = locator.line_start(offset); - let line_end = locator.full_line_end(offset); + let line_start = source.line_start(offset); + let line_end = source.full_line_end(offset); let column = offset - line_start; // If this is an end-of-line comment, reset the current block. - if !Self::is_own_line(offset, locator) { + if !Self::is_own_line(offset, source) { // Push the current block, and reset. if current_block.len() > 1 && current_block_non_empty { block_comments.extend(current_block); @@ -129,7 +129,7 @@ impl CommentRanges { // If there's a blank line between this comment and the previous // comment, reset the current block. if prev_line_end.is_some_and(|prev_line_end| { - locator.contains_line_break(TextRange::new(prev_line_end, line_start)) + source.contains_line_break(TextRange::new(prev_line_end, line_start)) }) { // Push the current block. if current_block.len() > 1 && current_block_non_empty { @@ -139,7 +139,7 @@ impl CommentRanges { // Reset the block state. current_block = vec![offset]; current_block_column = Some(column); - current_block_non_empty = !Self::is_empty(*comment_range, locator); + current_block_non_empty = !Self::is_empty(*comment_range, source); prev_line_end = Some(line_end); continue; } @@ -148,7 +148,7 @@ impl CommentRanges { if column == current_column { // Add the comment to the current block. current_block.push(offset); - current_block_non_empty |= !Self::is_empty(*comment_range, locator); + current_block_non_empty |= !Self::is_empty(*comment_range, source); prev_line_end = Some(line_end); } else { // Push the current block. @@ -159,7 +159,7 @@ impl CommentRanges { // Reset the block state. current_block = vec![offset]; current_block_column = Some(column); - current_block_non_empty = !Self::is_empty(*comment_range, locator); + current_block_non_empty = !Self::is_empty(*comment_range, source); prev_line_end = Some(line_end); } } else { @@ -171,7 +171,7 @@ impl CommentRanges { // Reset the block state. current_block = vec![offset]; current_block_column = Some(column); - current_block_non_empty = !Self::is_empty(*comment_range, locator); + current_block_non_empty = !Self::is_empty(*comment_range, source); prev_line_end = Some(line_end); } } @@ -185,18 +185,14 @@ impl CommentRanges { } /// Returns `true` if the given range is an empty comment. - fn is_empty(range: TextRange, locator: &Locator) -> bool { - locator - .slice(range) - .chars() - .skip(1) - .all(is_python_whitespace) + fn is_empty(range: TextRange, source: &str) -> bool { + source[range].chars().skip(1).all(is_python_whitespace) } /// Returns `true` if a comment is an own-line comment (as opposed to an end-of-line comment). - pub fn is_own_line(offset: TextSize, locator: &Locator) -> bool { - let range = TextRange::new(locator.line_start(offset), offset); - locator.slice(range).chars().all(is_python_whitespace) + pub fn is_own_line(offset: TextSize, source: &str) -> bool { + let range = TextRange::new(source.line_start(offset), offset); + source[range].chars().all(is_python_whitespace) } } diff --git a/crates/ruff_python_trivia/src/whitespace.rs b/crates/ruff_python_trivia/src/whitespace.rs index 7b8b5e90ea005..c9c4b7294b242 100644 --- a/crates/ruff_python_trivia/src/whitespace.rs +++ b/crates/ruff_python_trivia/src/whitespace.rs @@ -1,29 +1,28 @@ -use ruff_source_file::Locator; +use ruff_source_file::LineRanges; use ruff_text_size::{TextRange, TextSize}; /// Extract the leading indentation from a line. -pub fn indentation_at_offset<'a>(offset: TextSize, locator: &'a Locator) -> Option<&'a str> { - let line_start = locator.line_start(offset); - let indentation = locator.slice(TextRange::new(line_start, offset)); +pub fn indentation_at_offset(offset: TextSize, source: &str) -> Option<&str> { + let line_start = source.line_start(offset); + let indentation = &source[TextRange::new(line_start, offset)]; - if indentation.chars().all(is_python_whitespace) { - Some(indentation) - } else { - None - } + indentation + .chars() + .all(is_python_whitespace) + .then_some(indentation) } /// Return `true` if the node starting the given [`TextSize`] has leading content. -pub fn has_leading_content(offset: TextSize, locator: &Locator) -> bool { - let line_start = locator.line_start(offset); - let leading = locator.slice(TextRange::new(line_start, offset)); +pub fn has_leading_content(offset: TextSize, source: &str) -> bool { + let line_start = source.line_start(offset); + let leading = &source[TextRange::new(line_start, offset)]; leading.chars().any(|char| !is_python_whitespace(char)) } /// Return `true` if the node ending at the given [`TextSize`] has trailing content. -pub fn has_trailing_content(offset: TextSize, locator: &Locator) -> bool { - let line_end = locator.line_end(offset); - let trailing = locator.slice(TextRange::new(offset, line_end)); +pub fn has_trailing_content(offset: TextSize, source: &str) -> bool { + let line_end = source.line_end(offset); + let trailing = &source[TextRange::new(offset, line_end)]; for char in trailing.chars() { if char == '#' { diff --git a/crates/ruff_python_trivia_integration_tests/Cargo.toml b/crates/ruff_python_trivia_integration_tests/Cargo.toml index 7089c32214b17..749001b388541 100644 --- a/crates/ruff_python_trivia_integration_tests/Cargo.toml +++ b/crates/ruff_python_trivia_integration_tests/Cargo.toml @@ -14,7 +14,6 @@ license.workspace = true [dev-dependencies] ruff_python_parser = { workspace = true } ruff_python_trivia = { workspace = true } -ruff_source_file = { workspace = true } ruff_text_size = { workspace = true } insta = { workspace = true } diff --git a/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs b/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs index d93abf4ca47ef..13bec0bc43da6 100644 --- a/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs +++ b/crates/ruff_python_trivia_integration_tests/tests/block_comments.rs @@ -1,6 +1,5 @@ use ruff_python_parser::{parse_unchecked, Mode}; use ruff_python_trivia::CommentRanges; -use ruff_source_file::Locator; use ruff_text_size::TextSize; #[test] @@ -8,11 +7,10 @@ fn block_comments_two_line_block_at_start() { // arrange let source = "# line 1\n# line 2\n"; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, vec![TextSize::new(0), TextSize::new(9)]); @@ -23,11 +21,10 @@ fn block_comments_indented_block() { // arrange let source = " # line 1\n # line 2\n"; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, vec![TextSize::new(4), TextSize::new(17)]); @@ -38,11 +35,10 @@ fn block_comments_single_line_is_not_a_block() { // arrange let source = "\n"; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, Vec::::new()); @@ -53,11 +49,10 @@ fn block_comments_lines_with_code_not_a_block() { // arrange let source = "x = 1 # line 1\ny = 2 # line 2\n"; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, Vec::::new()); @@ -68,11 +63,10 @@ fn block_comments_sequential_lines_not_in_block() { // arrange let source = " # line 1\n # line 2\n"; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, Vec::::new()); @@ -88,11 +82,10 @@ fn block_comments_lines_in_triple_quotes_not_a_block() { """ "#; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!(block_comments, Vec::::new()); @@ -125,11 +118,10 @@ y = 2 # do not form a block comment """ "#; let parsed = parse_unchecked(source, Mode::Module); - let locator = Locator::new(source); let comment_ranges = CommentRanges::from(parsed.tokens()); // act - let block_comments = comment_ranges.block_comments(&locator); + let block_comments = comment_ranges.block_comments(source); // assert assert_eq!( diff --git a/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs b/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs index d73e2052b3ef4..feb2268615f4b 100644 --- a/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs +++ b/crates/ruff_python_trivia_integration_tests/tests/whitespace.rs @@ -1,6 +1,5 @@ use ruff_python_parser::{parse_module, ParseError}; use ruff_python_trivia::has_trailing_content; -use ruff_source_file::Locator; use ruff_text_size::Ranged; #[test] @@ -8,26 +7,22 @@ fn trailing_content() -> Result<(), ParseError> { let contents = "x = 1"; let suite = parse_module(contents)?.into_suite(); let stmt = suite.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); + assert!(!has_trailing_content(stmt.end(), contents)); let contents = "x = 1; y = 2"; let suite = parse_module(contents)?.into_suite(); let stmt = suite.first().unwrap(); - let locator = Locator::new(contents); - assert!(has_trailing_content(stmt.end(), &locator)); + assert!(has_trailing_content(stmt.end(), contents)); let contents = "x = 1 "; let suite = parse_module(contents)?.into_suite(); let stmt = suite.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); + assert!(!has_trailing_content(stmt.end(), contents)); let contents = "x = 1 # Comment"; let suite = parse_module(contents)?.into_suite(); let stmt = suite.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); + assert!(!has_trailing_content(stmt.end(), contents)); let contents = r" x = 1 @@ -36,8 +31,7 @@ y = 2 .trim(); let suite = parse_module(contents)?.into_suite(); let stmt = suite.first().unwrap(); - let locator = Locator::new(contents); - assert!(!has_trailing_content(stmt.end(), &locator)); + assert!(!has_trailing_content(stmt.end(), contents)); Ok(()) } diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index f5c967aeea074..cb3fe3499b35d 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -1,6 +1,5 @@ //! Access to the Ruff linting API for the LSP -use ruff_python_parser::ParseError; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -13,11 +12,13 @@ use ruff_linter::{ registry::AsRule, settings::flags, source_kind::SourceKind, + Locator, }; use ruff_notebook::Notebook; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; -use ruff_source_file::{LineIndex, Locator}; +use ruff_python_parser::ParseError; +use ruff_source_file::LineIndex; use ruff_text_size::{Ranged, TextRange}; use crate::{ @@ -97,16 +98,14 @@ pub(crate) fn check( // Parse once. let parsed = ruff_python_parser::parse_unchecked_source(source_kind.source_code(), source_type); - let index = LineIndex::from_source_text(source_kind.source_code()); - // Map row and column locations to byte slices (lazily). - let locator = Locator::with_index(source_kind.source_code(), index.clone()); + let locator = Locator::new(source_kind.source_code()); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = extract_directives(parsed.tokens(), Flags::all(), &locator, &indexer); @@ -154,14 +153,25 @@ pub(crate) fn check( .into_iter() .zip(noqa_edits) .map(|(diagnostic, noqa_edit)| { - to_lsp_diagnostic(diagnostic, &noqa_edit, &source_kind, &index, encoding) + to_lsp_diagnostic( + diagnostic, + &noqa_edit, + &source_kind, + locator.to_index(), + encoding, + ) }); let lsp_diagnostics = lsp_diagnostics.chain( show_syntax_errors .then(|| { parsed.errors().iter().map(|parse_error| { - parse_error_to_lsp_diagnostic(parse_error, &source_kind, &index, encoding) + parse_error_to_lsp_diagnostic( + parse_error, + &source_kind, + locator.to_index(), + encoding, + ) }) }) .into_iter() diff --git a/crates/ruff_source_file/src/lib.rs b/crates/ruff_source_file/src/lib.rs index a0de92b1c14a0..5bf43e3a1d493 100644 --- a/crates/ruff_source_file/src/lib.rs +++ b/crates/ruff_source_file/src/lib.rs @@ -8,14 +8,14 @@ use serde::{Deserialize, Serialize}; use ruff_text_size::{Ranged, TextRange, TextSize}; pub use crate::line_index::{LineIndex, OneIndexed}; -pub use crate::locator::Locator; +pub use crate::line_ranges::LineRanges; pub use crate::newlines::{ find_newline, Line, LineEnding, NewlineWithTrailingNewline, UniversalNewlineIterator, UniversalNewlines, }; mod line_index; -mod locator; +mod line_ranges; mod newlines; /// Gives access to the source code of a file and allows mapping between [`TextSize`] and [`SourceLocation`]. diff --git a/crates/ruff_source_file/src/line_ranges.rs b/crates/ruff_source_file/src/line_ranges.rs new file mode 100644 index 0000000000000..7fb7a50d6b3e2 --- /dev/null +++ b/crates/ruff_source_file/src/line_ranges.rs @@ -0,0 +1,358 @@ +use crate::find_newline; +use memchr::{memchr2, memrchr2}; +use ruff_text_size::{TextLen, TextRange, TextSize}; +use std::ops::Add; + +/// Extension trait for [`str`] that provides methods for working with ranges of lines. +pub trait LineRanges { + /// Computes the start position of the line of `offset`. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::TextSize; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\rthird line"; + /// + /// assert_eq!(text.line_start(TextSize::from(0)), TextSize::from(0)); + /// assert_eq!(text.line_start(TextSize::from(4)), TextSize::from(0)); + /// + /// assert_eq!(text.line_start(TextSize::from(14)), TextSize::from(11)); + /// assert_eq!(text.line_start(TextSize::from(28)), TextSize::from(23)); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn line_start(&self, offset: TextSize) -> TextSize; + + /// Computes the start position of the file contents: either the first byte, or the byte after + /// the BOM. + fn bom_start_offset(&self) -> TextSize; + + /// Returns `true` if `offset` is at the start of a line. + fn is_at_start_of_line(&self, offset: TextSize) -> bool { + self.line_start(offset) == offset + } + + /// Computes the offset that is right after the newline character that ends `offset`'s line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.full_line_end(TextSize::from(3)), TextSize::from(11)); + /// assert_eq!(text.full_line_end(TextSize::from(14)), TextSize::from(24)); + /// assert_eq!(text.full_line_end(TextSize::from(28)), TextSize::from(34)); + /// ``` + /// + /// ## Panics + /// + /// If `offset` is passed the end of the content. + fn full_line_end(&self, offset: TextSize) -> TextSize; + + /// Computes the offset that is right before the newline character that ends `offset`'s line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.line_end(TextSize::from(3)), TextSize::from(10)); + /// assert_eq!(text.line_end(TextSize::from(14)), TextSize::from(22)); + /// assert_eq!(text.line_end(TextSize::from(28)), TextSize::from(34)); + /// ``` + /// + /// ## Panics + /// + /// If `offset` is passed the end of the content. + fn line_end(&self, offset: TextSize) -> TextSize; + + /// Computes the range of this `offset`s line. + /// + /// The range starts at the beginning of the line and goes up to, and including, the new line character + /// at the end of the line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.full_line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(11))); + /// assert_eq!(text.full_line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(24))); + /// assert_eq!(text.full_line_range(TextSize::from(28)), TextRange::new(TextSize::from(24), TextSize::from(34))); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn full_line_range(&self, offset: TextSize) -> TextRange { + TextRange::new(self.line_start(offset), self.full_line_end(offset)) + } + + /// Computes the range of this `offset`s line ending before the newline character. + /// + /// The range starts at the beginning of the line and goes up to, but excluding, the new line character + /// at the end of the line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(10))); + /// assert_eq!(text.line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(22))); + /// assert_eq!(text.line_range(TextSize::from(28)), TextRange::new(TextSize::from(24), TextSize::from(34))); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn line_range(&self, offset: TextSize) -> TextRange { + TextRange::new(self.line_start(offset), self.line_end(offset)) + } + + /// Returns the text of the `offset`'s line. + /// + /// The line includes the newline characters at the end of the line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.full_line_str(TextSize::from(3)), "First line\n"); + /// assert_eq!(text.full_line_str(TextSize::from(14)), "second line\r\n"); + /// assert_eq!(text.full_line_str(TextSize::from(28)), "third line"); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn full_line_str(&self, offset: TextSize) -> &str; + + /// Returns the text of the `offset`'s line. + /// + /// Excludes the newline characters at the end of the line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(text.line_str(TextSize::from(3)), "First line"); + /// assert_eq!(text.line_str(TextSize::from(14)), "second line"); + /// assert_eq!(text.line_str(TextSize::from(28)), "third line"); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn line_str(&self, offset: TextSize) -> &str; + + /// Computes the range of all lines that this `range` covers. + /// + /// The range starts at the beginning of the line at `range.start()` and goes up to, and including, the new line character + /// at the end of `range.ends()`'s line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// text.full_lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// TextRange::new(TextSize::from(0), TextSize::from(11)) + /// ); + /// assert_eq!( + /// text.full_lines_range(TextRange::new(TextSize::from(3), TextSize::from(14))), + /// TextRange::new(TextSize::from(0), TextSize::from(24)) + /// ); + /// ``` + /// + /// ## Panics + /// If the start or end of `range` is out of bounds. + fn full_lines_range(&self, range: TextRange) -> TextRange { + TextRange::new( + self.line_start(range.start()), + self.full_line_end(range.end()), + ) + } + + /// Computes the range of all lines that this `range` covers. + /// + /// The range starts at the beginning of the line at `range.start()` and goes up to, but excluding, the new line character + /// at the end of `range.end()`'s line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// text.lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// TextRange::new(TextSize::from(0), TextSize::from(10)) + /// ); + /// assert_eq!( + /// text.lines_range(TextRange::new(TextSize::from(3), TextSize::from(14))), + /// TextRange::new(TextSize::from(0), TextSize::from(22)) + /// ); + /// ``` + /// + /// ## Panics + /// If the start or end of `range` is out of bounds. + fn lines_range(&self, range: TextRange) -> TextRange { + TextRange::new(self.line_start(range.start()), self.line_end(range.end())) + } + + /// Returns true if the text of `range` contains any line break. + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert!( + /// !text.contains_line_break(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// ); + /// assert!( + /// text.contains_line_break(TextRange::new(TextSize::from(3), TextSize::from(14))), + /// ); + /// ``` + /// + /// ## Panics + /// If the `range` is out of bounds. + fn contains_line_break(&self, range: TextRange) -> bool; + + /// Returns the text of all lines that include `range`. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// text.lines_str(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// "First line" + /// ); + /// assert_eq!( + /// text.lines_str(TextRange::new(TextSize::from(3), TextSize::from(14))), + /// "First line\nsecond line" + /// ); + /// ``` + /// + /// ## Panics + /// If the start or end of `range` is out of bounds. + fn lines_str(&self, range: TextRange) -> &str; + + /// Returns the text of all lines that include `range`. + /// + /// Includes the newline characters of the last line. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::{Ranged, TextRange, TextSize}; + /// # use ruff_source_file::LineRanges; + /// + /// let text = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// text.full_lines_str(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// "First line\n" + /// ); + /// assert_eq!( + /// text.full_lines_str(TextRange::new(TextSize::from(3), TextSize::from(14))), + /// "First line\nsecond line\r\n" + /// ); + /// ``` + /// + /// ## Panics + /// If the start or end of `range` is out of bounds. + fn full_lines_str(&self, range: TextRange) -> &str; +} + +impl LineRanges for str { + fn line_start(&self, offset: TextSize) -> TextSize { + let bytes = self[TextRange::up_to(offset)].as_bytes(); + if let Some(index) = memrchr2(b'\n', b'\r', bytes) { + // SAFETY: Safe because `index < offset` + TextSize::try_from(index).unwrap().add(TextSize::from(1)) + } else { + self.bom_start_offset() + } + } + + fn bom_start_offset(&self) -> TextSize { + if self.starts_with('\u{feff}') { + // Skip the BOM. + '\u{feff}'.text_len() + } else { + // Start of file. + TextSize::default() + } + } + + fn full_line_end(&self, offset: TextSize) -> TextSize { + let slice = &self[usize::from(offset)..]; + if let Some((index, line_ending)) = find_newline(slice) { + offset + TextSize::try_from(index).unwrap() + line_ending.text_len() + } else { + self.text_len() + } + } + + fn line_end(&self, offset: TextSize) -> TextSize { + let slice = &self[offset.to_usize()..]; + if let Some(index) = memchr2(b'\n', b'\r', slice.as_bytes()) { + offset + TextSize::try_from(index).unwrap() + } else { + self.text_len() + } + } + + fn full_line_str(&self, offset: TextSize) -> &str { + &self[self.full_line_range(offset)] + } + + fn line_str(&self, offset: TextSize) -> &str { + &self[self.line_range(offset)] + } + + fn contains_line_break(&self, range: TextRange) -> bool { + memchr2(b'\n', b'\r', self[range].as_bytes()).is_some() + } + + fn lines_str(&self, range: TextRange) -> &str { + &self[self.lines_range(range)] + } + + fn full_lines_str(&self, range: TextRange) -> &str { + &self[self.full_lines_range(range)] + } +} diff --git a/crates/ruff_source_file/src/locator.rs b/crates/ruff_source_file/src/locator.rs deleted file mode 100644 index f133377a9e195..0000000000000 --- a/crates/ruff_source_file/src/locator.rs +++ /dev/null @@ -1,483 +0,0 @@ -//! Struct used to efficiently slice source code at (row, column) Locations. - -use memchr::{memchr2, memrchr2}; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; -use std::cell::OnceCell; -use std::ops::Add; - -use crate::newlines::find_newline; -use crate::{LineIndex, OneIndexed, SourceCode, SourceLocation}; - -#[derive(Debug)] -pub struct Locator<'a> { - contents: &'a str, - index: OnceCell, -} - -impl<'a> Locator<'a> { - pub const fn new(contents: &'a str) -> Self { - Self { - contents, - index: OnceCell::new(), - } - } - - pub fn with_index(contents: &'a str, index: LineIndex) -> Self { - Self { - contents, - index: OnceCell::from(index), - } - } - - #[deprecated( - note = "This is expensive, avoid using outside of the diagnostic phase. Prefer the other `Locator` methods instead." - )] - pub fn compute_line_index(&self, offset: TextSize) -> OneIndexed { - self.to_index().line_index(offset) - } - - #[deprecated( - note = "This is expensive, avoid using outside of the diagnostic phase. Prefer the other `Locator` methods instead." - )] - pub fn compute_source_location(&self, offset: TextSize) -> SourceLocation { - self.to_source_code().source_location(offset) - } - - fn to_index(&self) -> &LineIndex { - self.index - .get_or_init(|| LineIndex::from_source_text(self.contents)) - } - - pub fn line_index(&self) -> Option<&LineIndex> { - self.index.get() - } - - pub fn to_source_code(&self) -> SourceCode { - SourceCode { - index: self.to_index(), - text: self.contents, - } - } - - /// Computes the start position of the line of `offset`. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::TextSize; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\rthird line"); - /// - /// assert_eq!(locator.line_start(TextSize::from(0)), TextSize::from(0)); - /// assert_eq!(locator.line_start(TextSize::from(4)), TextSize::from(0)); - /// - /// assert_eq!(locator.line_start(TextSize::from(14)), TextSize::from(11)); - /// assert_eq!(locator.line_start(TextSize::from(28)), TextSize::from(23)); - /// ``` - /// - /// ## Panics - /// If `offset` is out of bounds. - pub fn line_start(&self, offset: TextSize) -> TextSize { - let bytes = self.contents[TextRange::up_to(offset)].as_bytes(); - if let Some(index) = memrchr2(b'\n', b'\r', bytes) { - // SAFETY: Safe because `index < offset` - TextSize::try_from(index).unwrap().add(TextSize::from(1)) - } else { - self.contents_start() - } - } - - /// Computes the start position of the file contents: either the first byte, or the byte after - /// the BOM. - pub fn contents_start(&self) -> TextSize { - if self.contents.starts_with('\u{feff}') { - // Skip the BOM. - '\u{feff}'.text_len() - } else { - // Start of file. - TextSize::default() - } - } - - /// Returns `true` if `offset` is at the start of a line. - pub fn is_at_start_of_line(&self, offset: TextSize) -> bool { - self.line_start(offset) == offset - } - - /// Computes the offset that is right after the newline character that ends `offset`'s line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.full_line_end(TextSize::from(3)), TextSize::from(11)); - /// assert_eq!(locator.full_line_end(TextSize::from(14)), TextSize::from(24)); - /// assert_eq!(locator.full_line_end(TextSize::from(28)), TextSize::from(34)); - /// ``` - /// - /// ## Panics - /// - /// If `offset` is passed the end of the content. - pub fn full_line_end(&self, offset: TextSize) -> TextSize { - let slice = &self.contents[usize::from(offset)..]; - if let Some((index, line_ending)) = find_newline(slice) { - offset + TextSize::try_from(index).unwrap() + line_ending.text_len() - } else { - self.contents.text_len() - } - } - - /// Computes the offset that is right before the newline character that ends `offset`'s line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.line_end(TextSize::from(3)), TextSize::from(10)); - /// assert_eq!(locator.line_end(TextSize::from(14)), TextSize::from(22)); - /// assert_eq!(locator.line_end(TextSize::from(28)), TextSize::from(34)); - /// ``` - /// - /// ## Panics - /// - /// If `offset` is passed the end of the content. - pub fn line_end(&self, offset: TextSize) -> TextSize { - let slice = &self.contents[usize::from(offset)..]; - if let Some(index) = memchr2(b'\n', b'\r', slice.as_bytes()) { - offset + TextSize::try_from(index).unwrap() - } else { - self.contents.text_len() - } - } - - /// Computes the range of this `offset`s line. - /// - /// The range starts at the beginning of the line and goes up to, and including, the new line character - /// at the end of the line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.full_line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(11))); - /// assert_eq!(locator.full_line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(24))); - /// assert_eq!(locator.full_line_range(TextSize::from(28)), TextRange::new(TextSize::from(24), TextSize::from(34))); - /// ``` - /// - /// ## Panics - /// If `offset` is out of bounds. - pub fn full_line_range(&self, offset: TextSize) -> TextRange { - TextRange::new(self.line_start(offset), self.full_line_end(offset)) - } - - /// Computes the range of this `offset`s line ending before the newline character. - /// - /// The range starts at the beginning of the line and goes up to, but excluding, the new line character - /// at the end of the line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(10))); - /// assert_eq!(locator.line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(22))); - /// assert_eq!(locator.line_range(TextSize::from(28)), TextRange::new(TextSize::from(24), TextSize::from(34))); - /// ``` - /// - /// ## Panics - /// If `offset` is out of bounds. - pub fn line_range(&self, offset: TextSize) -> TextRange { - TextRange::new(self.line_start(offset), self.line_end(offset)) - } - - /// Returns the text of the `offset`'s line. - /// - /// The line includes the newline characters at the end of the line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.full_line(TextSize::from(3)), "First line\n"); - /// assert_eq!(locator.full_line(TextSize::from(14)), "second line\r\n"); - /// assert_eq!(locator.full_line(TextSize::from(28)), "third line"); - /// ``` - /// - /// ## Panics - /// If `offset` is out of bounds. - pub fn full_line(&self, offset: TextSize) -> &'a str { - &self.contents[self.full_line_range(offset)] - } - - /// Returns the text of the `offset`'s line. - /// - /// Excludes the newline characters at the end of the line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!(locator.line(TextSize::from(3)), "First line"); - /// assert_eq!(locator.line(TextSize::from(14)), "second line"); - /// assert_eq!(locator.line(TextSize::from(28)), "third line"); - /// ``` - /// - /// ## Panics - /// If `offset` is out of bounds. - pub fn line(&self, offset: TextSize) -> &'a str { - &self.contents[self.line_range(offset)] - } - - /// Computes the range of all lines that this `range` covers. - /// - /// The range starts at the beginning of the line at `range.start()` and goes up to, and including, the new line character - /// at the end of `range.ends()`'s line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!( - /// locator.full_lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), - /// TextRange::new(TextSize::from(0), TextSize::from(11)) - /// ); - /// assert_eq!( - /// locator.full_lines_range(TextRange::new(TextSize::from(3), TextSize::from(14))), - /// TextRange::new(TextSize::from(0), TextSize::from(24)) - /// ); - /// ``` - /// - /// ## Panics - /// If the start or end of `range` is out of bounds. - pub fn full_lines_range(&self, range: TextRange) -> TextRange { - TextRange::new( - self.line_start(range.start()), - self.full_line_end(range.end()), - ) - } - - /// Computes the range of all lines that this `range` covers. - /// - /// The range starts at the beginning of the line at `range.start()` and goes up to, but excluding, the new line character - /// at the end of `range.end()`'s line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!( - /// locator.lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), - /// TextRange::new(TextSize::from(0), TextSize::from(10)) - /// ); - /// assert_eq!( - /// locator.lines_range(TextRange::new(TextSize::from(3), TextSize::from(14))), - /// TextRange::new(TextSize::from(0), TextSize::from(22)) - /// ); - /// ``` - /// - /// ## Panics - /// If the start or end of `range` is out of bounds. - pub fn lines_range(&self, range: TextRange) -> TextRange { - TextRange::new(self.line_start(range.start()), self.line_end(range.end())) - } - - /// Returns true if the text of `range` contains any line break. - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert!( - /// !locator.contains_line_break(TextRange::new(TextSize::from(3), TextSize::from(5))), - /// ); - /// assert!( - /// locator.contains_line_break(TextRange::new(TextSize::from(3), TextSize::from(14))), - /// ); - /// ``` - /// - /// ## Panics - /// If the `range` is out of bounds. - pub fn contains_line_break(&self, range: TextRange) -> bool { - let text = &self.contents[range]; - text.contains(['\n', '\r']) - } - - /// Returns the text of all lines that include `range`. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!( - /// locator.lines(TextRange::new(TextSize::from(3), TextSize::from(5))), - /// "First line" - /// ); - /// assert_eq!( - /// locator.lines(TextRange::new(TextSize::from(3), TextSize::from(14))), - /// "First line\nsecond line" - /// ); - /// ``` - /// - /// ## Panics - /// If the start or end of `range` is out of bounds. - pub fn lines(&self, range: TextRange) -> &'a str { - &self.contents[self.lines_range(range)] - } - - /// Returns the text of all lines that include `range`. - /// - /// Includes the newline characters of the last line. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("First line\nsecond line\r\nthird line"); - /// - /// assert_eq!( - /// locator.full_lines(TextRange::new(TextSize::from(3), TextSize::from(5))), - /// "First line\n" - /// ); - /// assert_eq!( - /// locator.full_lines(TextRange::new(TextSize::from(3), TextSize::from(14))), - /// "First line\nsecond line\r\n" - /// ); - /// ``` - /// - /// ## Panics - /// If the start or end of `range` is out of bounds. - pub fn full_lines(&self, range: TextRange) -> &'a str { - &self.contents[self.full_lines_range(range)] - } - - /// Take the source code up to the given [`TextSize`]. - #[inline] - pub fn up_to(&self, offset: TextSize) -> &'a str { - &self.contents[TextRange::up_to(offset)] - } - - /// Take the source code after the given [`TextSize`]. - #[inline] - pub fn after(&self, offset: TextSize) -> &'a str { - &self.contents[usize::from(offset)..] - } - - /// Finds the closest [`TextSize`] not exceeding the offset for which `is_char_boundary` is - /// `true`. - /// - /// Can be replaced with `str::floor_char_boundary` once it's stable. - /// - /// ## Examples - /// - /// ``` - /// # use ruff_text_size::{Ranged, TextRange, TextSize}; - /// # use ruff_source_file::Locator; - /// - /// let locator = Locator::new("Hello"); - /// - /// assert_eq!( - /// locator.floor_char_boundary(TextSize::from(0)), - /// TextSize::from(0) - /// ); - /// - /// assert_eq!( - /// locator.floor_char_boundary(TextSize::from(5)), - /// TextSize::from(5) - /// ); - /// - /// let locator = Locator::new("α"); - /// - /// assert_eq!( - /// locator.floor_char_boundary(TextSize::from(0)), - /// TextSize::from(0) - /// ); - /// - /// assert_eq!( - /// locator.floor_char_boundary(TextSize::from(1)), - /// TextSize::from(0) - /// ); - /// - /// assert_eq!( - /// locator.floor_char_boundary(TextSize::from(2)), - /// TextSize::from(2) - /// ); - /// ``` - pub fn floor_char_boundary(&self, offset: TextSize) -> TextSize { - if offset >= self.text_len() { - self.text_len() - } else { - // We know that the character boundary is within four bytes. - (0u32..=3u32) - .map(TextSize::from) - .filter_map(|index| offset.checked_sub(index)) - .find(|offset| self.contents.is_char_boundary(offset.to_usize())) - .unwrap_or_default() - } - } - - /// Take the source code between the given [`TextRange`]. - #[inline] - pub fn slice(&self, ranged: T) -> &'a str { - &self.contents[ranged.range()] - } - - /// Return the underlying source code. - pub fn contents(&self) -> &'a str { - self.contents - } - - /// Return the number of bytes in the source code. - pub const fn len(&self) -> usize { - self.contents.len() - } - - pub fn text_len(&self) -> TextSize { - self.contents.text_len() - } - - /// Return `true` if the source code is empty. - pub const fn is_empty(&self) -> bool { - self.contents.is_empty() - } -} diff --git a/crates/ruff_text_size/src/lib.rs b/crates/ruff_text_size/src/lib.rs index ea1b197dfaa6a..0276e0b32b1e8 100644 --- a/crates/ruff_text_size/src/lib.rs +++ b/crates/ruff_text_size/src/lib.rs @@ -28,7 +28,9 @@ mod schemars_impls; #[cfg(feature = "serde")] mod serde_impls; -pub use crate::{range::TextRange, size::TextSize, traits::Ranged, traits::TextLen}; +pub use crate::{ + range::TextRange, size::TextSize, traits::Ranged, traits::TextLen, traits::TextSlice, +}; #[cfg(target_pointer_width = "16")] compile_error!("text-size assumes usize >= u32 and does not work on 16-bit targets"); diff --git a/crates/ruff_text_size/src/traits.rs b/crates/ruff_text_size/src/traits.rs index a17bb07112398..0ea015135a3fe 100644 --- a/crates/ruff_text_size/src/traits.rs +++ b/crates/ruff_text_size/src/traits.rs @@ -76,3 +76,24 @@ where T::range(self) } } + +/// A slice of the source text. +pub trait TextSlice: Sealed { + /// Returns the slice of the text within the given `range`. + /// + /// ## Note + /// + /// This is the same as `&self[range]` if `self` is a `str` and `range` a `TextRange`. + /// + /// ## Panics + /// If the range is out of bounds. + fn slice(&self, range: impl Ranged) -> &str; +} + +impl Sealed for str {} + +impl TextSlice for str { + fn slice(&self, ranged: impl Ranged) -> &str { + &self[ranged.range()] + } +} diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 121f3d81ff40c..04d2e568b4903 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -1,7 +1,6 @@ use std::path::Path; use js_sys::Error; -use ruff_python_trivia::CommentRanges; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -14,12 +13,14 @@ use ruff_linter::registry::AsRule; use ruff_linter::settings::types::PythonVersion; use ruff_linter::settings::{flags, DEFAULT_SELECTORS, DUMMY_VARIABLE_RGX}; use ruff_linter::source_kind::SourceKind; +use ruff_linter::Locator; use ruff_python_ast::{Mod, PySourceType}; use ruff_python_codegen::Stylist; use ruff_python_formatter::{format_module_ast, pretty_comments, PyFormatContext, QuoteStyle}; use ruff_python_index::Indexer; use ruff_python_parser::{parse, parse_unchecked, parse_unchecked_source, Mode, Parsed}; -use ruff_source_file::{Locator, SourceLocation}; +use ruff_python_trivia::CommentRanges; +use ruff_source_file::SourceLocation; use ruff_text_size::Ranged; use ruff_workspace::configuration::Configuration; use ruff_workspace::options::{FormatOptions, LintCommonOptions, LintOptions, Options}; @@ -167,10 +168,10 @@ impl Workspace { let locator = Locator::new(contents); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), locator.contents()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), locator.contents()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( diff --git a/fuzz/fuzz_targets/ruff_parse_simple.rs b/fuzz/fuzz_targets/ruff_parse_simple.rs index 805c04cd6753d..e803f7682e396 100644 --- a/fuzz/fuzz_targets/ruff_parse_simple.rs +++ b/fuzz/fuzz_targets/ruff_parse_simple.rs @@ -6,7 +6,6 @@ use libfuzzer_sys::{fuzz_target, Corpus}; use ruff_python_codegen::{Generator, Stylist}; use ruff_python_parser::{parse_module, ParseError}; -use ruff_source_file::Locator; use ruff_text_size::Ranged; fn do_fuzz(case: &[u8]) -> Corpus { @@ -15,7 +14,6 @@ fn do_fuzz(case: &[u8]) -> Corpus { }; // just round-trip it once to trigger both parse and unparse - let locator = Locator::new(code); let parsed = match parse_module(code) { Ok(parsed) => parsed, Err(ParseError { location, .. }) => { @@ -44,7 +42,7 @@ fn do_fuzz(case: &[u8]) -> Corpus { ); } - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), code); let mut generator: Generator = (&stylist).into(); generator.unparse_suite(parsed.suite()); From b7aedffcbd8c9d838cd118c05e3edff618864936 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 28 Oct 2024 08:47:53 +0100 Subject: [PATCH 2/2] Code review feedback, reduce usage of slice --- crates/red_knot_test/src/assertion.rs | 6 +----- crates/ruff_formatter/src/builders.rs | 2 +- .../src/rules/ruff/rules/sequence_sorting.rs | 3 --- .../src/comments/placement.rs | 10 +++++----- .../ruff_python_formatter/src/string/implicit.rs | 16 ++++------------ .../src/string/normalize.rs | 8 ++++---- 6 files changed, 15 insertions(+), 30 deletions(-) diff --git a/crates/red_knot_test/src/assertion.rs b/crates/red_knot_test/src/assertion.rs index d9e46206e5892..b03a0b8547167 100644 --- a/crates/red_knot_test/src/assertion.rs +++ b/crates/red_knot_test/src/assertion.rs @@ -67,10 +67,6 @@ impl InlineFileAssertions { } } - fn range_text(&self, range: impl Ranged) -> &str { - &self.source[range.range()] - } - fn line_number(&self, range: &impl Ranged) -> OneIndexed { self.lines.line_index(range.start()) } @@ -133,7 +129,7 @@ impl<'a> Iterator for AssertionWithRangeIterator<'a> { fn next(&mut self) -> Option { loop { let inner_next = self.inner.next()?; - let comment = self.file_assertions.range_text(inner_next); + let comment = &self.file_assertions.source[inner_next]; if let Some(assertion) = Assertion::from_comment(comment) { return Some(AssertionWithRange(assertion, inner_next)); }; diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index cd97dcc03eb5e..21ab988b5e38e 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -372,7 +372,7 @@ impl std::fmt::Debug for Text<'_> { } /// Emits a text as it is written in the source document. Optimized to avoid allocations. -pub fn source_text_slice(range: TextRange) -> SourceTextSliceBuilder { +pub const fn source_text_slice(range: TextRange) -> SourceTextSliceBuilder { SourceTextSliceBuilder { range } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs index 7790f8ce39da2..ad77ea03368d9 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/sequence_sorting.rs @@ -6,9 +6,6 @@ use std::borrow::Cow; use std::cmp::Ordering; -use is_macro; -use natord; - use ruff_python_ast as ast; use ruff_python_codegen::Stylist; use ruff_python_parser::{TokenKind, Tokens}; diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 533fed500d3eb..02c61e2faadd4 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -10,7 +10,7 @@ use ruff_python_trivia::{ CommentRanges, SimpleToken, SimpleTokenKind, SimpleTokenizer, }; use ruff_source_file::LineRanges; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSlice}; +use ruff_text_size::{Ranged, TextLen, TextRange}; use crate::comments::visitor::{CommentPlacement, DecoratedComment}; use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; @@ -146,7 +146,7 @@ fn handle_parenthesized_comment<'a>( debug_assert!( !matches!(token.kind, SimpleTokenKind::Bogus), "Unexpected token between nodes: `{:?}`", - source.slice(range) + &source[range] ); token.kind() == SimpleTokenKind::LParen }) @@ -177,7 +177,7 @@ fn handle_parenthesized_comment<'a>( debug_assert!( !matches!(token.kind, SimpleTokenKind::Bogus), "Unexpected token between nodes: `{:?}`", - source.slice(range) + &source[range] ); token.kind() == SimpleTokenKind::RParen }) @@ -533,7 +533,7 @@ fn handle_own_line_comment_between_statements<'a>( // // y = 2 // ``` - if max_empty_lines(source.slice(TextRange::new(comment.end(), following.start()))) == 0 { + if max_empty_lines(&source[TextRange::new(comment.end(), following.start())]) == 0 { CommentPlacement::leading(following, comment) } else { CommentPlacement::trailing(preceding, comment) @@ -1006,7 +1006,7 @@ fn handle_module_level_own_line_comment_before_class_or_function_comment<'a>( } // Make the comment a leading comment if there's no empty line between the comment and the function / class header - if max_empty_lines(source.slice(TextRange::new(comment.end(), following.start()))) == 0 { + if max_empty_lines(&source[TextRange::new(comment.end(), following.start())]) == 0 { CommentPlacement::leading(following, comment) } else { // Otherwise attach the comment as trailing comment to the previous statement diff --git a/crates/ruff_python_formatter/src/string/implicit.rs b/crates/ruff_python_formatter/src/string/implicit.rs index f3944a5d1b37f..2f057a49d8481 100644 --- a/crates/ruff_python_formatter/src/string/implicit.rs +++ b/crates/ruff_python_formatter/src/string/implicit.rs @@ -7,7 +7,7 @@ use ruff_python_ast::str_prefix::{ }; use ruff_python_ast::{AnyStringFlags, FStringElement, StringFlags, StringLike, StringLikePart}; use ruff_source_file::LineRanges; -use ruff_text_size::{Ranged, TextRange, TextSlice}; +use ruff_text_size::{Ranged, TextRange}; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space; @@ -269,12 +269,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { for part in self.string.parts().rev() { assert!(part.is_string_literal()); - if f.context() - .source() - .slice(part.content_range()) - .trim() - .is_empty() - { + if f.context().source()[part.content_range()].trim().is_empty() { // Don't format the part. parts.next_back(); } else { @@ -298,10 +293,7 @@ impl Format> for FormatImplicitConcatenatedStringFlat<'_> { .fmt(f)?; if first_non_empty { - first_non_empty = f - .context() - .source() - .slice(part.content_range()) + first_non_empty = f.context().source()[part.content_range()] .trim_start() .is_empty(); } @@ -365,7 +357,7 @@ struct FormatLiteralContent { impl Format> for FormatLiteralContent { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { - let content = f.context().source().slice(self.range); + let content = &f.context().source()[self.range]; let mut normalized = normalize_string( content, 0, diff --git a/crates/ruff_python_formatter/src/string/normalize.rs b/crates/ruff_python_formatter/src/string/normalize.rs index 961b38e3dc8ec..d23e359958d5c 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -152,7 +152,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { /// Computes the strings preferred quotes. pub(crate) fn choose_quotes(&self, string: StringLikePart) -> QuoteSelection { - let raw_content = self.context.source().slice(string.content_range()); + let raw_content = &self.context.source()[string.content_range()]; let first_quote_or_normalized_char_offset = raw_content .bytes() .position(|b| matches!(b, b'\\' | b'"' | b'\'' | b'\r' | b'{')); @@ -196,7 +196,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> { /// Computes the strings preferred quotes and normalizes its content. pub(crate) fn normalize(&self, string: StringLikePart) -> NormalizedString<'src> { - let raw_content = self.context.source().slice(string.content_range()); + let raw_content = &self.context.source()[string.content_range()]; let quote_selection = self.choose_quotes(string); let normalized = if let Some(first_quote_or_escape_offset) = @@ -256,7 +256,7 @@ impl QuoteMetadata { ) -> Self { match part { StringLikePart::String(_) | StringLikePart::Bytes(_) => { - let text = context.source().slice(part.content_range()); + let text = &context.source()[part.content_range()]; Self::from_str(text, part.flags(), preferred_quote) } @@ -294,7 +294,7 @@ impl QuoteMetadata { metadata } else { - let text = context.source().slice(part.content_range()); + let text = &context.source()[part.content_range()]; Self::from_str(text, part.flags(), preferred_quote) }