From ca4c006f7daeaba68a6292df8af16a37449fed35 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Sat, 26 Oct 2024 12:26:18 +0200 Subject: [PATCH] Experiment with Located trait --- crates/red_knot_test/src/assertion.rs | 2 +- .../src/checkers/ast/analyze/bindings.rs | 6 +- .../ast/analyze/unresolved_references.rs | 6 +- crates/ruff_linter/src/checkers/ast/mod.rs | 4 + .../src/checkers/physical_lines.rs | 4 +- crates/ruff_linter/src/directives.rs | 2 +- crates/ruff_linter/src/fix/edits.rs | 8 +- crates/ruff_linter/src/importer/insertion.rs | 4 +- crates/ruff_linter/src/linter.rs | 65 +-- crates/ruff_linter/src/noqa.rs | 2 +- .../rules/mutable_argument_default.rs | 6 +- .../rules/string_in_exception.rs | 7 +- .../rules/unconventional_import_alias.rs | 2 +- .../rules/unnecessary_placeholder.rs | 2 +- .../unaliased_collections_abc_set_import.rs | 2 +- .../flake8_pytest_style/rules/assertion.rs | 8 +- .../src/rules/flake8_return/rules/function.rs | 6 +- .../flake8_simplify/rules/ast_bool_op.rs | 2 +- .../flake8_simplify/rules/collapsible_if.rs | 2 +- .../rules/flake8_simplify/rules/fix_with.rs | 2 +- .../if_else_block_instead_of_dict_get.rs | 4 +- .../rules/if_else_block_instead_of_if_exp.rs | 2 +- .../flake8_simplify/rules/needless_bool.rs | 2 +- .../rules/suppressible_exception.rs | 2 +- .../src/rules/isort/rules/organize_imports.rs | 8 +- .../pycodestyle/rules/compound_statements.rs | 2 +- .../pycodestyle/rules/lambda_assignment.rs | 4 +- .../pycodestyle/rules/literal_comparisons.rs | 2 +- .../rules/multiple_imports_on_one_line.rs | 5 +- .../src/rules/pycodestyle/rules/not_tests.rs | 4 +- .../pycodestyle/rules/trailing_whitespace.rs | 2 +- .../rules/blank_before_after_class.rs | 2 +- crates/ruff_linter/src/rules/pyflakes/mod.rs | 4 +- .../src/rules/pyflakes/rules/unused_import.rs | 2 +- .../rules/pyflakes/rules/unused_variable.rs | 2 +- .../rules/pylint/rules/collapsible_else_if.rs | 2 +- .../src/rules/pylint/rules/empty_comment.rs | 2 +- .../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 +- .../pylint/rules/useless_else_on_loop.rs | 4 +- .../pyupgrade/rules/deprecated_import.rs | 2 +- .../pyupgrade/rules/deprecated_mock_import.rs | 4 +- .../pyupgrade/rules/outdated_version_block.rs | 8 +- .../rules/printf_string_formatting.rs | 2 +- .../rules/unnecessary_coding_comment.rs | 2 +- .../rules/single_item_membership_test.rs | 2 +- .../rules/collection_literal_concatenation.rs | 2 +- .../invalid_formatter_suppression_comment.rs | 9 +- .../src/rules/ruff/rules/post_init_default.rs | 2 +- .../ruff/rules/suppression_comment_visitor.rs | 6 +- .../src/rules/ruff/rules/test_rules.rs | 38 +- crates/ruff_linter/src/test.rs | 8 +- crates/ruff_python_ast/src/helpers.rs | 32 +- crates/ruff_python_ast/src/whitespace.rs | 18 +- crates/ruff_python_codegen/src/lib.rs | 4 +- crates/ruff_python_codegen/src/stylist.rs | 77 ++-- .../ruff_python_formatter/src/comments/mod.rs | 4 +- .../src/comments/placement.rs | 287 ++++++------ .../src/comments/visitor.rs | 9 +- crates/ruff_python_formatter/src/context.rs | 5 - .../src/expression/expr_f_string.rs | 18 +- .../src/expression/expr_number_literal.rs | 7 +- crates/ruff_python_formatter/src/lib.rs | 20 +- .../src/other/f_string.rs | 10 +- .../src/other/f_string_element.rs | 3 +- crates/ruff_python_formatter/src/range.rs | 9 +- .../src/string/docstring.rs | 4 +- .../src/string/implicit.rs | 20 +- .../ruff_python_formatter/src/string/mod.rs | 7 +- .../src/string/normalize.rs | 17 +- crates/ruff_python_formatter/src/verbatim.rs | 6 +- crates/ruff_python_index/src/indexer.rs | 67 ++- crates/ruff_python_semantic/src/binding.rs | 6 +- crates/ruff_python_semantic/src/reference.rs | 6 +- .../ruff_python_trivia/src/comment_ranges.rs | 40 +- crates/ruff_python_trivia/src/whitespace.rs | 29 +- .../tests/block_comments.rs | 22 +- .../tests/whitespace.rs | 16 +- crates/ruff_server/src/lint.rs | 4 +- crates/ruff_source_file/src/lib.rs | 2 + crates/ruff_source_file/src/located.rs | 422 ++++++++++++++++++ crates/ruff_source_file/src/locator.rs | 59 +-- crates/ruff_wasm/src/lib.rs | 4 +- 84 files changed, 926 insertions(+), 599 deletions(-) create mode 100644 crates/ruff_source_file/src/located.rs diff --git a/crates/red_knot_test/src/assertion.rs b/crates/red_knot_test/src/assertion.rs index 2355b19f2f..d9191b5d30 100644 --- a/crates/red_knot_test/src/assertion.rs +++ b/crates/red_knot_test/src/assertion.rs @@ -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()) } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs index fe6faeafa6..04ed28fb44 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(), ); @@ -52,7 +52,7 @@ pub(crate) fn bindings(checker: &mut Checker) { } } if checker.enabled(Rule::NonAsciiName) { - if let Some(diagnostic) = pylint::rules::non_ascii_name(binding, checker.locator) { + if let Some(diagnostic) = pylint::rules::non_ascii_name(binding, checker.source()) { checker.diagnostics.push(diagnostic); } } 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 e0d7705204..22bfd90568 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 821d74eb4d..1f36c92a4a 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -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/physical_lines.rs b/crates/ruff_linter/src/checkers/physical_lines.rs index 7f1940a61b..087962b93e 100644 --- a/crates/ruff_linter/src/checkers/physical_lines.rs +++ b/crates/ruff_linter/src/checkers/physical_lines.rs @@ -106,8 +106,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(), line); + let stylist = Stylist::from_tokens(parsed.tokens(), line); let check_with_max_line_length = |line_length: LineLength| { check_physical_lines( diff --git a/crates/ruff_linter/src/directives.rs b/crates/ruff_linter/src/directives.rs index 50d23c40bc..ce7291741a 100644 --- a/crates/ruff_linter/src/directives.rs +++ b/crates/ruff_linter/src/directives.rs @@ -377,7 +377,7 @@ mod tests { 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(), contents); extract_noqa_line_for(parsed.tokens(), &locator, &indexer) } diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index 7b1fa7ebb7..459d2067d9 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -48,9 +48,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()); @@ -726,7 +728,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(), raw); add_to_dunder_all(names.iter().copied(), parsed.expr(), &stylist) }; let diag = { diff --git a/crates/ruff_linter/src/importer/insertion.rs b/crates/ruff_linter/src/importer/insertion.rs index 715405e19e..3a621aede2 100644 --- a/crates/ruff_linter/src/importer/insertion.rs +++ b/crates/ruff_linter/src/importer/insertion.rs @@ -329,7 +329,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(), contents); Ok(Insertion::start_of_file(parsed.suite(), &locator, &stylist)) } @@ -440,7 +440,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(), contents); Insertion::start_of_block(offset, &locator, &stylist, parsed.tokens()) } diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 6958b298cf..996b59aa7d 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -206,40 +206,53 @@ pub fn check_path( } let diagnostic = match test_rule { Rule::StableTestRule => { - test_rules::StableTestRule::diagnostic(locator, comment_ranges) - } - Rule::StableTestRuleSafeFix => { - test_rules::StableTestRuleSafeFix::diagnostic(locator, comment_ranges) - } - Rule::StableTestRuleUnsafeFix => { - test_rules::StableTestRuleUnsafeFix::diagnostic(locator, comment_ranges) + test_rules::StableTestRule::diagnostic(locator.contents(), comment_ranges) } + Rule::StableTestRuleSafeFix => test_rules::StableTestRuleSafeFix::diagnostic( + locator.contents(), + comment_ranges, + ), + Rule::StableTestRuleUnsafeFix => test_rules::StableTestRuleUnsafeFix::diagnostic( + locator.contents(), + comment_ranges, + ), Rule::StableTestRuleDisplayOnlyFix => { - test_rules::StableTestRuleDisplayOnlyFix::diagnostic(locator, comment_ranges) + test_rules::StableTestRuleDisplayOnlyFix::diagnostic( + locator.contents(), + comment_ranges, + ) } Rule::PreviewTestRule => { - test_rules::PreviewTestRule::diagnostic(locator, comment_ranges) + test_rules::PreviewTestRule::diagnostic(locator.contents(), comment_ranges) } Rule::DeprecatedTestRule => { - test_rules::DeprecatedTestRule::diagnostic(locator, comment_ranges) + test_rules::DeprecatedTestRule::diagnostic(locator.contents(), comment_ranges) } Rule::AnotherDeprecatedTestRule => { - test_rules::AnotherDeprecatedTestRule::diagnostic(locator, comment_ranges) + test_rules::AnotherDeprecatedTestRule::diagnostic( + locator.contents(), + comment_ranges, + ) } Rule::RemovedTestRule => { - test_rules::RemovedTestRule::diagnostic(locator, comment_ranges) - } - Rule::AnotherRemovedTestRule => { - test_rules::AnotherRemovedTestRule::diagnostic(locator, comment_ranges) + test_rules::RemovedTestRule::diagnostic(locator.contents(), comment_ranges) } + Rule::AnotherRemovedTestRule => test_rules::AnotherRemovedTestRule::diagnostic( + locator.contents(), + comment_ranges, + ), Rule::RedirectedToTestRule => { - test_rules::RedirectedToTestRule::diagnostic(locator, comment_ranges) - } - Rule::RedirectedFromTestRule => { - test_rules::RedirectedFromTestRule::diagnostic(locator, comment_ranges) + test_rules::RedirectedToTestRule::diagnostic(locator.contents(), comment_ranges) } + Rule::RedirectedFromTestRule => test_rules::RedirectedFromTestRule::diagnostic( + locator.contents(), + comment_ranges, + ), Rule::RedirectedFromPrefixTestRule => { - test_rules::RedirectedFromPrefixTestRule::diagnostic(locator, comment_ranges) + test_rules::RedirectedFromPrefixTestRule::diagnostic( + locator.contents(), + comment_ranges, + ) } _ => unreachable!("All test rules must have an implementation"), }; @@ -335,10 +348,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(), source_kind.source_code()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), source_kind.source_code()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( @@ -393,10 +406,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(), source_kind.source_code()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), source_kind.source_code()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( @@ -495,10 +508,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(), transformed.source_code()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), transformed.source_code()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives( diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index bfacc88a63..e1cfa33331 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -369,7 +369,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(), contents).is_none() { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); let path_display = relativize_path(path); 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 e15ca868ab..dfbebf979a 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 @@ -145,7 +145,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 +170,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 +186,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_errmsg/rules/string_in_exception.rs b/crates/ruff_linter/src/rules/flake8_errmsg/rules/string_in_exception.rs index 23f2bf5881..df5dddd6cc 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 @@ -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_import_conventions/rules/unconventional_import_alias.rs b/crates/ruff_linter/src/rules/flake8_import_conventions/rules/unconventional_import_alias.rs index 9a16bb8ed3..2ebe87f48d 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_pie/rules/unnecessary_placeholder.rs b/crates/ruff_linter/src/rules/flake8_pie/rules/unnecessary_placeholder.rs index 09a58552d6..85fafcac08 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_pyi/rules/unaliased_collections_abc_set_import.rs b/crates/ruff_linter/src/rules/flake8_pyi/rules/unaliased_collections_abc_set_import.rs index 24aa32e1ad..bb24ed01f0 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 dc520f1738..ebf8285861 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 @@ -386,7 +386,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,8 +622,8 @@ 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()); @@ -747,7 +747,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_return/rules/function.rs b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs index 35a4553f91..3bc9fc6f09 100644 --- a/crates/ruff_linter/src/rules/flake8_return/rules/function.rs +++ b/crates/ruff_linter/src/rules/flake8_return/rules/function.rs @@ -453,7 +453,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 +851,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 f7d8c3b924..d61dd56bed 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 b4516f7dfa..a4cdad0b77 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 @@ -292,7 +292,7 @@ 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"); }; 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 9360b5fcfb..cd9ab4d314 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 @@ -18,7 +18,7 @@ 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"); }; 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 4a754ac30c..f14374dfa9 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 03e0dacec0..d113bd46c9 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/needless_bool.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/needless_bool.rs index 8f7a474551..b6dedfd098 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/suppressible_exception.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/suppressible_exception.rs index d8bd58a5d3..8cd1136a18 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 @@ -127,7 +127,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/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index 88af0b9055..95ec37615b 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -1,7 +1,6 @@ use std::path::Path; use itertools::{EitherOrBoth, Itertools}; - use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::whitespace::trailing_lines_end; @@ -98,8 +97,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 +114,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/pycodestyle/rules/compound_statements.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs index 98278ae0c4..e98dbe9c9f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/compound_statements.rs @@ -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/lambda_assignment.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs index 5c41c5c44a..3eec15c49a 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -79,8 +79,8 @@ 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 indentation = leading_indentation(first_line); 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 0a59cdb866..0ae56cdf44 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/multiple_imports_on_one_line.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/multiple_imports_on_one_line.rs index e54715330c..cac044cc58 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 @@ -68,7 +68,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 +90,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 50022dee78..7a479ac834 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/trailing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs index fee7bc5ffb..2741b0d8b4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs @@ -100,7 +100,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 299d6f44b4..ef5c9181ce 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 @@ -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/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 5e42a7e0e2..22a23a7bf2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -712,8 +712,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(), &contents); + let indexer = Indexer::from_tokens(parsed.tokens(), &contents); let directives = directives::extract_directives( parsed.tokens(), directives::Flags::from_settings(&settings), 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 ecba0975d0..3f6efbba07 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 6da056c137..1bd813f65a 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/pylint/rules/collapsible_else_if.rs b/crates/ruff_linter/src/rules/pylint/rules/collapsible_else_if.rs index aae432959f..eba064b579 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 @@ -113,7 +113,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 1c741cf322..eb3c6fcf21 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/empty_comment.rs @@ -47,7 +47,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/nested_min_max.rs b/crates/ruff_linter/src/rules/pylint/rules/nested_min_max.rs index 039e4ace21..c6799864ba 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 10d3390553..dc01fb788f 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 02ba51ae59..141878f8fb 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,7 +3,6 @@ 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; /// ## What it does @@ -43,8 +42,8 @@ impl Violation for NonAsciiName { } /// PLC2401 -pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option { - let name = binding.name(locator); +pub(crate) fn non_ascii_name(binding: &Binding, source: &str) -> Option { + let name = binding.name(source); if name.is_ascii() { return None; } 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 818b9e4a8a..731c652ac0 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 @@ -146,7 +146,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 +155,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/rules/deprecated_import.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs index 8f1f005671..78a71440f7 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/deprecated_import.rs @@ -617,7 +617,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 bd7dbd3287..90ca89c957 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 @@ -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/outdated_version_block.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/outdated_version_block.rs index ca7faba02c..4de29d8ab8 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 @@ -299,7 +299,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 +309,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 +377,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 +387,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 2c66fd26f1..b2b701eb01 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 @@ -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 f48c05a474..608984b829 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 @@ -75,7 +75,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/refurb/rules/single_item_membership_test.rs b/crates/ruff_linter/src/rules/refurb/rules/single_item_membership_test.rs index 85f7fe9ec5..4185cf3760 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/ruff/rules/collection_literal_concatenation.rs b/crates/ruff_linter/src/rules/ruff/rules/collection_literal_concatenation.rs index 1a69095473..0689a2957a 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/invalid_formatter_suppression_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/invalid_formatter_suppression_comment.rs index 57201331c9..6a730801f5 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 @@ -166,11 +166,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/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index a13949f167..2646aea31f 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 @@ -175,7 +175,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/suppression_comment_visitor.rs b/crates/ruff_linter/src/rules/ruff/rules/suppression_comment_visitor.rs index 113f0a000f..5b300b1e9e 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 @@ -146,9 +146,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 a3f2548738..825ecb14d3 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/test_rules.rs @@ -16,15 +16,15 @@ 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_source_file::Located; use ruff_text_size::TextSize; use crate::registry::Rule; /// Check if a comment exists anywhere in a given file -fn comment_exists(text: &str, locator: &Locator, comment_ranges: &CommentRanges) -> bool { +fn comment_exists(text: &str, source: &str, comment_ranges: &CommentRanges) -> bool { for range in comment_ranges { - let comment_text = locator.slice(range); + let comment_text = source.slice(range); if text.trim_end() == comment_text { return true; } @@ -48,7 +48,7 @@ pub(crate) const TEST_RULES: &[Rule] = &[ ]; pub(crate) trait TestRule { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option; + fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option; } /// ## What it does @@ -79,7 +79,7 @@ impl Violation for StableTestRule { } impl TestRule for StableTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( StableTestRule, ruff_text_size::TextRange::default(), @@ -115,9 +115,9 @@ impl Violation for StableTestRuleSafeFix { } impl TestRule for StableTestRuleSafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option { let comment = format!("# fix from stable-test-rule-safe-fix\n"); - if comment_exists(&comment, locator, comment_ranges) { + if comment_exists(&comment, source, comment_ranges) { None } else { Some( @@ -156,9 +156,9 @@ impl Violation for StableTestRuleUnsafeFix { } impl TestRule for StableTestRuleUnsafeFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option { let comment = format!("# fix from stable-test-rule-unsafe-fix\n"); - if comment_exists(&comment, locator, comment_ranges) { + if comment_exists(&comment, source, comment_ranges) { None } else { Some( @@ -200,9 +200,9 @@ impl Violation for StableTestRuleDisplayOnlyFix { } impl TestRule for StableTestRuleDisplayOnlyFix { - fn diagnostic(locator: &Locator, comment_ranges: &CommentRanges) -> Option { + fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option { let comment = format!("# fix from stable-test-rule-display-only-fix\n"); - if comment_exists(&comment, locator, comment_ranges) { + if comment_exists(&comment, source, comment_ranges) { None } else { Some( @@ -247,7 +247,7 @@ impl Violation for PreviewTestRule { } impl TestRule for PreviewTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( PreviewTestRule, ruff_text_size::TextRange::default(), @@ -283,7 +283,7 @@ impl Violation for DeprecatedTestRule { } impl TestRule for DeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( DeprecatedTestRule, ruff_text_size::TextRange::default(), @@ -319,7 +319,7 @@ impl Violation for AnotherDeprecatedTestRule { } impl TestRule for AnotherDeprecatedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( AnotherDeprecatedTestRule, ruff_text_size::TextRange::default(), @@ -355,7 +355,7 @@ impl Violation for RemovedTestRule { } impl TestRule for RemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( RemovedTestRule, ruff_text_size::TextRange::default(), @@ -391,7 +391,7 @@ impl Violation for AnotherRemovedTestRule { } impl TestRule for AnotherRemovedTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( AnotherRemovedTestRule, ruff_text_size::TextRange::default(), @@ -427,7 +427,7 @@ impl Violation for RedirectedFromTestRule { } impl TestRule for RedirectedFromTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( RedirectedFromTestRule, ruff_text_size::TextRange::default(), @@ -463,7 +463,7 @@ impl Violation for RedirectedToTestRule { } impl TestRule for RedirectedToTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( RedirectedToTestRule, ruff_text_size::TextRange::default(), @@ -499,7 +499,7 @@ impl Violation for RedirectedFromPrefixTestRule { } impl TestRule for RedirectedFromPrefixTestRule { - fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option { + fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option { Some(Diagnostic::new( RedirectedFromPrefixTestRule, ruff_text_size::TextRange::default(), diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index a4ac856499..1a28a05699 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -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(), source_kind.source_code()); + let indexer = Indexer::from_tokens(parsed.tokens(), source_kind.source_code()); 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(), transformed.source_code()); + let indexer = Indexer::from_tokens(parsed.tokens(), transformed.source_code()); 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 565c67e585..2ea8650bd7 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::Located; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::name::{Name, QualifiedName, QualifiedNameBuilder}; @@ -1333,19 +1333,16 @@ 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); let mut contents = String::with_capacity(usize::from(end - start)); // 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()), - ), - ); + contents.push_str(source.slice( + parenthesized_range(left.into(), parent, comment_ranges, source).unwrap_or(left.range()), + )); for (op, comparator) in ops.iter().zip(comparators) { // Add the operator. @@ -1364,14 +1361,9 @@ 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.slice( + parenthesized_range(comparator.into(), parent, comment_ranges, source) + .unwrap_or(comparator.range()), ), ); } @@ -1512,17 +1504,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 7a1ed847c3..c8d8949b31 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::{Located, 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, ranged: &T) -> Option<&'a str> where T: Ranged, { - indentation_at_offset(located.start(), locator) + indentation_at_offset(ranged.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.after(line_end), 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.slice(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 64a991edcd..6940b918ae 100644 --- a/crates/ruff_python_codegen/src/lib.rs +++ b/crates/ruff_python_codegen/src/lib.rs @@ -3,14 +3,12 @@ mod stylist; pub use generator::Generator; use ruff_python_parser::{parse_module, ParseError}; -use ruff_source_file::Locator; pub use stylist::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 b78417add2..990c20eac5 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, Located}; 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.slice(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 { @@ -154,41 +153,36 @@ mod tests { 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 +192,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 +202,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 +216,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 +226,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 +261,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 +271,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 +280,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 +290,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 +299,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 c2db89159b..40b7f47005 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 badc8667ff..493b34192b 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -1,5 +1,9 @@ -use std::cmp::Ordering; - +use crate::comments::visitor::{CommentPlacement, DecoratedComment}; +use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; +use crate::other::parameters::{ + assign_argument_separator_comment_placement, find_parameter_separators, +}; +use crate::pattern::pattern_match_sequence::SequenceType; use ast::helpers::comment_indentation_after; use ruff_python_ast::whitespace::indentation; use ruff_python_ast::{ @@ -9,26 +13,20 @@ 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_source_file::Located; use ruff_text_size::{Ranged, TextLen, TextRange}; - -use crate::comments::visitor::{CommentPlacement, DecoratedComment}; -use crate::expression::expr_slice::{assign_comment_in_slice, ExprSliceCommentSection}; -use crate::other::parameters::{ - assign_argument_separator_comment_placement, find_parameter_separators, -}; -use crate::pattern::pattern_match_sequence::SequenceType; +use std::cmp::Ordering; /// Manually attach comments to nodes that the default placement gets wrong. pub(super) fn place_comment<'a>( comment: DecoratedComment<'a>, comment_ranges: &CommentRanges, - locator: &Locator, + source: &'a 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 +69,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 +131,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 +144,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 +162,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 +175,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 +190,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 +260,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 +286,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 +339,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 +360,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 +375,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 +429,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 +451,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 +491,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 +531,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 +550,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 +563,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 +639,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 +647,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 +658,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 +669,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 +730,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 +759,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 +795,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 +807,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 +827,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 +884,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 +899,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 +981,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 +1004,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 +1025,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 +1036,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 +1060,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 +1146,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 +1158,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 +1178,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 +1189,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 +1211,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 +1226,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 +1251,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 +1271,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 +1318,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 +1349,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 +1382,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 +1410,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 +1426,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 +1436,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 +1461,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 +1490,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 +1504,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 +1549,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 +1557,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 +1607,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 +1631,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 +1661,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 +1672,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 +1717,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 +1749,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 +1778,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 +1809,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 +1858,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 +1981,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 +2006,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 +2069,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 6ba2879eab..52bd5d2009 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 b449b95eca..cebeda1799 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -4,7 +4,6 @@ use crate::PyFormatOptions; 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}; @@ -51,10 +50,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 a4b0325d79..7e034cfef8 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_source_file::Located; 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 ab46ab3f0b..98929d149c 100644 --- a/crates/ruff_python_formatter/src/expression/expr_number_literal.rs +++ b/crates/ruff_python_formatter/src/expression/expr_number_literal.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use ruff_python_ast::AnyNodeRef; use ruff_python_ast::{ExprNumberLiteral, Number}; +use ruff_source_file::Located; use ruff_text_size::{Ranged, TextSize}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; @@ -15,7 +16,7 @@ impl FormatNodeRule for FormatExprNumberLiteral { match item.value { Number::Int(_) => { let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(range); let normalized = normalize_integer(content); match normalized { @@ -25,7 +26,7 @@ impl FormatNodeRule for FormatExprNumberLiteral { } Number::Float(_) => { let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(range); let normalized = normalize_floating_number(content); match normalized { @@ -35,7 +36,7 @@ impl FormatNodeRule for FormatExprNumberLiteral { } Number::Complex { .. } => { let range = item.range(); - let content = f.context().locator().slice(range); + let content = f.context().source().slice(range); 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 fcb512c63c..c80f91eeba 100644 --- a/crates/ruff_python_formatter/src/lib.rs +++ b/crates/ruff_python_formatter/src/lib.rs @@ -1,15 +1,6 @@ use thiserror::Error; use tracing::Level; -pub use range::format_range; -use ruff_formatter::prelude::*; -use ruff_formatter::{format, write, FormatError, Formatted, PrintError, Printed, SourceCode}; -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, }; @@ -21,6 +12,14 @@ pub use crate::options::{ use crate::range::is_logical_line; pub use crate::shared_traits::{AsFormat, FormattedIter, FormattedIterExt, IntoFormat}; use crate::verbatim::suppressed_node; +pub use range::format_range; +use ruff_formatter::prelude::*; +use ruff_formatter::{format, write, FormatError, Formatted, PrintError, Printed, SourceCode}; +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::Located; pub(crate) mod builders; pub mod cli; @@ -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.as_str(), 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 a8e7926432..81dada8c4c 100644 --- a/crates/ruff_python_formatter/src/other/f_string.rs +++ b/crates/ruff_python_formatter/src/other/f_string.rs @@ -1,6 +1,6 @@ use ruff_formatter::write; use ruff_python_ast::{AnyStringFlags, FString, StringFlags}; -use ruff_source_file::Locator; +use ruff_source_file::Located; use crate::prelude::*; use crate::preview::is_f_string_formatting_enabled; @@ -27,8 +27,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 +64,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 +115,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 +131,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| memchr::memchr2(b'\n', b'\r', source.slice(expr).as_bytes()).is_some()) { 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 77daaca0d2..3e50ec7376 100644 --- a/crates/ruff_python_formatter/src/other/f_string_element.rs +++ b/crates/ruff_python_formatter/src/other/f_string_element.rs @@ -5,6 +5,7 @@ use ruff_python_ast::{ AnyStringFlags, ConversionFlag, Expr, FStringElement, FStringExpressionElement, FStringLiteralElement, StringFlags, }; +use ruff_source_file::Located; use ruff_text_size::Ranged; use crate::comments::{dangling_open_parenthesis_comments, trailing_comments}; @@ -60,7 +61,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 33f7d2a1d2..c0f5cfa0c3 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 3279d40106..c5bc185ffe 100644 --- a/crates/ruff_python_formatter/src/string/docstring.rs +++ b/crates/ruff_python_formatter/src/string/docstring.rs @@ -14,7 +14,6 @@ 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}, }; @@ -1592,9 +1591,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 0ef4aae5af..d6c7fb4d80 100644 --- a/crates/ruff_python_formatter/src/string/implicit.rs +++ b/crates/ruff_python_formatter/src/string/implicit.rs @@ -6,6 +6,7 @@ use ruff_python_ast::str_prefix::{ AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix, }; use ruff_python_ast::{AnyStringFlags, FStringElement, StringFlags, StringLike, StringLikePart}; +use ruff_source_file::Located; use ruff_text_size::{Ranged, TextRange}; use crate::comments::{leading_comments, trailing_comments}; @@ -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().slice(literal).contains(['\n', '\r']) + } 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().slice(expression).contains(['\n', '\r']) } } }) { @@ -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 dba9adc555..a803014256 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, locator: &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 3defefb75f..2df64843bf 100644 --- a/crates/ruff_python_formatter/src/string/normalize.rs +++ b/crates/ruff_python_formatter/src/string/normalize.rs @@ -7,6 +7,7 @@ use ruff_python_ast::visitor::source_order::SourceOrderVisitor; use ruff_python_ast::{ str::Quote, AnyStringFlags, BytesLiteral, FString, StringFlags, StringLikePart, StringLiteral, }; +use ruff_source_file::Located; use ruff_text_size::{Ranged, TextRange}; use crate::context::FStringState; @@ -152,7 +153,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 +197,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 +257,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 +278,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 +286,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 +295,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 +894,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() { @@ -970,7 +971,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() + .source() .slice(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 df70e6e298..b693a8bb0e 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::Located; 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().slice(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 596aa812b8..b2a49ba5fe 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::Located; 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.slice(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,15 +159,11 @@ 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 + let previous_line_end = source.line_start(offset); + if !source .slice(TextRange::new(previous_line_end, offset)) .chars() .all(is_python_whitespace) @@ -180,19 +171,18 @@ impl Indexer { 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 + let previous_line_end = source.line_start(continuation); + if source .slice(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 +195,37 @@ 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/src/binding.rs b/crates/ruff_python_semantic/src/binding.rs index 9b9c74aa81..8fcea2f57f 100644 --- a/crates/ruff_python_semantic/src/binding.rs +++ b/crates/ruff_python_semantic/src/binding.rs @@ -8,7 +8,7 @@ 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_source_file::Located; use ruff_text_size::{Ranged, TextRange}; use crate::context::ExecutionContext; @@ -228,8 +228,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.slice(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 1b79347684..d1c0e4f35e 100644 --- a/crates/ruff_python_semantic/src/reference.rs +++ b/crates/ruff_python_semantic/src/reference.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use ruff_index::{newtype_index, IndexSlice, IndexVec}; use ruff_python_ast::ExprContext; -use ruff_source_file::Locator; +use ruff_source_file::Located; use ruff_text_size::{Ranged, TextRange}; use crate::scope::ScopeId; @@ -157,8 +157,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.slice(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 673a4aefd6..e0b66a85a6 100644 --- a/crates/ruff_python_trivia/src/comment_ranges.rs +++ b/crates/ruff_python_trivia/src/comment_ranges.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Formatter}; use std::ops::Deref; use itertools::Itertools; -use ruff_source_file::Locator; +use ruff_source_file::Located; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -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,8 +185,8 @@ impl CommentRanges { } /// Returns `true` if the given range is an empty comment. - fn is_empty(range: TextRange, locator: &Locator) -> bool { - locator + fn is_empty(range: TextRange, source: &str) -> bool { + source .slice(range) .chars() .skip(1) @@ -194,9 +194,9 @@ impl CommentRanges { } /// 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.slice(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 7b8b5e90ea..ce388e3fd1 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::Located; 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.slice(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.slice(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.slice(TextRange::new(offset, line_end)); for char in trailing.chars() { if char == '#' { 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 d93abf4ca4..13bec0bc43 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 d73e2052b3..feb2268615 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 f5c967aeea..1c6f6686d7 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -103,10 +103,10 @@ pub(crate) fn check( let locator = Locator::with_index(source_kind.source_code(), index.clone()); // Detect the current code style (lazily). - let stylist = Stylist::from_tokens(parsed.tokens(), &locator); + let stylist = Stylist::from_tokens(parsed.tokens(), source_kind.source_code()); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), source_kind.source_code()); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = extract_directives(parsed.tokens(), Flags::all(), &locator, &indexer); diff --git a/crates/ruff_source_file/src/lib.rs b/crates/ruff_source_file/src/lib.rs index a0de92b1c1..f6f736fce6 100644 --- a/crates/ruff_source_file/src/lib.rs +++ b/crates/ruff_source_file/src/lib.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use ruff_text_size::{Ranged, TextRange, TextSize}; pub use crate::line_index::{LineIndex, OneIndexed}; +pub use crate::located::Located; pub use crate::locator::Locator; pub use crate::newlines::{ find_newline, Line, LineEnding, NewlineWithTrailingNewline, UniversalNewlineIterator, @@ -15,6 +16,7 @@ pub use crate::newlines::{ }; mod line_index; +mod located; mod locator; mod newlines; diff --git a/crates/ruff_source_file/src/located.rs b/crates/ruff_source_file/src/located.rs new file mode 100644 index 0000000000..2ddd648597 --- /dev/null +++ b/crates/ruff_source_file/src/located.rs @@ -0,0 +1,422 @@ +//! 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::ops::Add; + +use crate::newlines::find_newline; + +pub trait Located { + fn as_str(&self) -> &str; + + /// Computes the start position of the line of `offset`. + /// + /// ## Examples + /// + /// ``` + /// # use ruff_text_size::TextSize; + /// # use ruff_source_file::Located; + /// + /// let located = "First line\nsecond line\rthird line"; + /// + /// assert_eq!(located.line_start(TextSize::from(0)), TextSize::from(0)); + /// assert_eq!(located.line_start(TextSize::from(4)), TextSize::from(0)); + /// + /// assert_eq!(located.line_start(TextSize::from(14)), TextSize::from(11)); + /// assert_eq!(located.line_start(TextSize::from(28)), TextSize::from(23)); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn line_start(&self, offset: TextSize) -> TextSize { + let bytes = self.as_str()[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. + fn contents_start(&self) -> TextSize { + if self.as_str().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. + 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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.full_line_end(TextSize::from(3)), TextSize::from(11)); + /// assert_eq!(located.full_line_end(TextSize::from(14)), TextSize::from(24)); + /// assert_eq!(located.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 { + let slice = &self.as_str()[usize::from(offset)..]; + if let Some((index, line_ending)) = find_newline(slice) { + offset + TextSize::try_from(index).unwrap() + line_ending.text_len() + } else { + self.as_str().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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.line_end(TextSize::from(3)), TextSize::from(10)); + /// assert_eq!(located.line_end(TextSize::from(14)), TextSize::from(22)); + /// assert_eq!(located.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 { + let slice = &self.as_str()[usize::from(offset)..]; + if let Some(index) = memchr2(b'\n', b'\r', slice.as_bytes()) { + offset + TextSize::try_from(index).unwrap() + } else { + self.as_str().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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.full_line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(11))); + /// assert_eq!(located.full_line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(24))); + /// assert_eq!(located.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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.line_range(TextSize::from(3)), TextRange::new(TextSize::from(0), TextSize::from(10))); + /// assert_eq!(located.line_range(TextSize::from(14)), TextRange::new(TextSize::from(11), TextSize::from(22))); + /// assert_eq!(located.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::Locator; + /// # use ruff_source_file::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.full_line_str(TextSize::from(3)), "First line\n"); + /// assert_eq!(located.full_line_str(TextSize::from(14)), "second line\r\n"); + /// assert_eq!(located.full_line_str(TextSize::from(28)), "third line"); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn full_line_str(&self, offset: TextSize) -> &str { + &self.as_str()[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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!(located.line_str(TextSize::from(3)), "First line"); + /// assert_eq!(located.line_str(TextSize::from(14)), "second line"); + /// assert_eq!(located.line_str(TextSize::from(28)), "third line"); + /// ``` + /// + /// ## Panics + /// If `offset` is out of bounds. + fn line_str(&self, offset: TextSize) -> &str { + &self.as_str()[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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// located.full_lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// TextRange::new(TextSize::from(0), TextSize::from(11)) + /// ); + /// assert_eq!( + /// located.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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// located.lines_range(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// TextRange::new(TextSize::from(0), TextSize::from(10)) + /// ); + /// assert_eq!( + /// located.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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert!( + /// !located.contains_line_break(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// ); + /// assert!( + /// located.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 { + let text = &self.as_str()[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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// located.lines_str(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// "First line" + /// ); + /// assert_eq!( + /// located.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 { + &self.as_str()[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::Located; + /// + /// let located = "First line\nsecond line\r\nthird line"; + /// + /// assert_eq!( + /// located.full_lines_str(TextRange::new(TextSize::from(3), TextSize::from(5))), + /// "First line\n" + /// ); + /// assert_eq!( + /// located.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 { + &self.as_str()[self.full_lines_range(range)] + } + + /// Take the source code up to the given [`TextSize`]. + #[inline] + fn up_to(&self, offset: TextSize) -> &str { + &self.as_str()[TextRange::up_to(offset)] + } + + /// Take the source code after the given [`TextSize`]. + #[inline] + fn after(&self, offset: TextSize) -> &str { + &self.as_str()[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::Located; + /// + /// let located = "Hello"; + /// + /// assert_eq!( + /// Located::floor_char_boundary(located, TextSize::from(0)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// Located::floor_char_boundary(located, TextSize::from(5)), + /// TextSize::from(5) + /// ); + /// + /// let located = "α"; + /// + /// assert_eq!( + /// Located::floor_char_boundary(located, TextSize::from(0)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// Located::floor_char_boundary(located, TextSize::from(1)), + /// TextSize::from(0) + /// ); + /// + /// assert_eq!( + /// Located::floor_char_boundary(located, TextSize::from(2)), + /// TextSize::from(2) + /// ); + /// ``` + fn floor_char_boundary(&self, offset: TextSize) -> TextSize { + if offset >= self.as_str().text_len() { + self.as_str().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.as_str().is_char_boundary(offset.to_usize())) + .unwrap_or_default() + } + } + + /// Take the source code between the given [`TextRange`]. + #[inline] + fn slice(&self, ranged: T) -> &str { + &self.as_str()[ranged.range()] + } +} + +impl Located for str { + fn as_str(&self) -> &str { + self + } +} diff --git a/crates/ruff_source_file/src/locator.rs b/crates/ruff_source_file/src/locator.rs index f133377a9e..5a86ea3f1f 100644 --- a/crates/ruff_source_file/src/locator.rs +++ b/crates/ruff_source_file/src/locator.rs @@ -1,12 +1,11 @@ //! Struct used to efficiently slice source code at (row, column) Locations. -use memchr::{memchr2, memrchr2}; +use memchr::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}; +use crate::{LineIndex, Located, OneIndexed, SourceCode, SourceLocation}; #[derive(Debug)] pub struct Locator<'a> { @@ -91,18 +90,12 @@ impl<'a> Locator<'a> { /// 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() - } + self.contents.contents_start() } /// 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 + self.contents.is_at_start_of_line(offset) } /// Computes the offset that is right after the newline character that ends `offset`'s line. @@ -124,12 +117,7 @@ impl<'a> Locator<'a> { /// /// 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() - } + self.contents.full_line_end(offset) } /// Computes the offset that is right before the newline character that ends `offset`'s line. @@ -151,12 +139,7 @@ impl<'a> Locator<'a> { /// /// 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() - } + self.contents.line_end(offset) } /// Computes the range of this `offset`s line. @@ -180,7 +163,7 @@ impl<'a> Locator<'a> { /// ## 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)) + self.contents.full_line_range(offset) } /// Computes the range of this `offset`s line ending before the newline character. @@ -204,7 +187,7 @@ impl<'a> Locator<'a> { /// ## 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)) + self.contents.line_range(offset) } /// Returns the text of the `offset`'s line. @@ -227,7 +210,7 @@ impl<'a> Locator<'a> { /// ## Panics /// If `offset` is out of bounds. pub fn full_line(&self, offset: TextSize) -> &'a str { - &self.contents[self.full_line_range(offset)] + self.contents.full_line_str(offset) } /// Returns the text of the `offset`'s line. @@ -250,7 +233,7 @@ impl<'a> Locator<'a> { /// ## Panics /// If `offset` is out of bounds. pub fn line(&self, offset: TextSize) -> &'a str { - &self.contents[self.line_range(offset)] + self.contents.line_str(offset) } /// Computes the range of all lines that this `range` covers. @@ -279,10 +262,7 @@ impl<'a> Locator<'a> { /// ## 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()), - ) + self.contents.full_lines_range(range) } /// Computes the range of all lines that this `range` covers. @@ -311,7 +291,7 @@ impl<'a> Locator<'a> { /// ## 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())) + self.contents.lines_range(range) } /// Returns true if the text of `range` contains any line break. @@ -333,8 +313,7 @@ impl<'a> Locator<'a> { /// ## 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']) + self.contents.contains_line_break(range) } /// Returns the text of all lines that include `range`. @@ -360,6 +339,7 @@ impl<'a> Locator<'a> { /// ## Panics /// If the start or end of `range` is out of bounds. pub fn lines(&self, range: TextRange) -> &'a str { + // FIXME: `lines` collides with str.lines method &self.contents[self.lines_range(range)] } @@ -388,19 +368,19 @@ impl<'a> Locator<'a> { /// ## 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)] + self.contents.full_lines_str(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)] + self.contents.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)..] + self.contents.after(offset) } /// Finds the closest [`TextSize`] not exceeding the offset for which `is_char_boundary` is @@ -444,6 +424,7 @@ impl<'a> Locator<'a> { /// ); /// ``` pub fn floor_char_boundary(&self, offset: TextSize) -> TextSize { + // FIXME: Method collides with std method if offset >= self.text_len() { self.text_len() } else { @@ -459,11 +440,11 @@ impl<'a> Locator<'a> { /// Take the source code between the given [`TextRange`]. #[inline] pub fn slice(&self, ranged: T) -> &'a str { - &self.contents[ranged.range()] + self.contents.slice(ranged) } /// Return the underlying source code. - pub fn contents(&self) -> &'a str { + pub const fn contents(&self) -> &'a str { self.contents } diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index 121f3d81ff..e84e0ecc98 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -167,10 +167,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(), contents); // Extra indices from the code. - let indexer = Indexer::from_tokens(parsed.tokens(), &locator); + let indexer = Indexer::from_tokens(parsed.tokens(), contents); // Extract the `# noqa` and `# isort: skip` directives from the source. let directives = directives::extract_directives(