Compare commits
1 Commits
dcreager/f
...
micha/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca4c006f7d |
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
fn insert(contents: &str) -> Result<Insertion> {
|
||||
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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<Edit> {
|
||||
// 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())
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ pub(super) fn collapse_nested_if(
|
||||
nested_if: NestedIf,
|
||||
) -> Result<Edit> {
|
||||
// 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");
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) fn fix_multiple_with_statements(
|
||||
with_stmt: &ast::StmtWith,
|
||||
) -> Result<Edit> {
|
||||
// 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");
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
)),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -183,7 +183,7 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option<Fix> {
|
||||
};
|
||||
}
|
||||
} 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());
|
||||
|
||||
@@ -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"));
|
||||
};
|
||||
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<Diagnostic> {
|
||||
let name = binding.name(locator);
|
||||
pub(crate) fn non_ascii_name(binding: &Binding, source: &str) -> Option<Diagnostic> {
|
||||
let name = binding.name(source);
|
||||
if name.is_ascii() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Diagnostic>;
|
||||
fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option<Diagnostic>;
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -79,7 +79,7 @@ impl Violation for StableTestRule {
|
||||
}
|
||||
|
||||
impl TestRule for StableTestRule {
|
||||
fn diagnostic(_locator: &Locator, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(source: &str, comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
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<Diagnostic> {
|
||||
fn diagnostic(_source: &str, _comment_ranges: &CommentRanges) -> Option<Diagnostic> {
|
||||
Some(Diagnostic::new(
|
||||
RedirectedFromPrefixTestRule,
|
||||
ruff_text_size::TextRange::default(),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<T>(located: &T, locator: &Locator) -> Option<TextSize>
|
||||
pub fn trailing_comment_start_offset<T>(located: &T, source: &str) -> Option<TextSize>
|
||||
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 == '#' {
|
||||
|
||||
@@ -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<String, ParseError> {
|
||||
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())
|
||||
|
||||
@@ -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<LineEnding>,
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ExprFString> 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 {
|
||||
|
||||
@@ -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<ExprNumberLiteral> 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<ExprNumberLiteral> 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<ExprNumberLiteral> 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 {
|
||||
|
||||
@@ -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<Formatted<PyFormatContext<'a>>> {
|
||||
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
|
||||
|
||||
@@ -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<PyFormatContext<'_>> 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<PyFormatContext<'_>> 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 {
|
||||
|
||||
@@ -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<PyFormatContext<'_>> 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 {
|
||||
|
||||
@@ -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<PyFormatContext<'_>> for FormatEnclosingNode<'_> {
|
||||
/// # Panics
|
||||
/// If `offset` is outside of `source`.
|
||||
fn indent_level(offset: TextSize, source: &str, options: &PyFormatOptions) -> Option<u16> {
|
||||
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 => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<PyFormatContext<'_>> for FormatImplicitConcatenatedStringExpanded<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> 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<PyFormatContext<'_>> 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<PyFormatContext<'_>> 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<PyFormatContext<'_>> for FormatImplicitConcatenatedStringFlat<'_> {
|
||||
self.flags,
|
||||
FStringLayout::from_f_string(
|
||||
f_string,
|
||||
&f.context().locator(),
|
||||
f.context().source(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -365,7 +365,7 @@ struct FormatLiteralContent {
|
||||
|
||||
impl Format<PyFormatContext<'_>> 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,
|
||||
|
||||
@@ -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<Quote> 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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<PyFormatContext<'_>> 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)])?;
|
||||
}
|
||||
|
||||
@@ -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<TextSize> {
|
||||
fn find_continuation(&self, offset: TextSize, source: &str) -> Option<TextSize> {
|
||||
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<TextSize> {
|
||||
pub fn preceded_by_continuations(&self, offset: TextSize, source: &str) -> Option<TextSize> {
|
||||
// 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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<T>(&self, node: &T, locator: &Locator) -> bool
|
||||
pub fn has_comments<T>(&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<TextSize> {
|
||||
pub fn block_comments(&self, source: &str) -> Vec<TextSize> {
|
||||
let mut block_comments: Vec<TextSize> = Vec::new();
|
||||
|
||||
let mut current_block: Vec<TextSize> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 == '#' {
|
||||
|
||||
@@ -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::<TextSize>::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::<TextSize>::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::<TextSize>::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::<TextSize>::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!(
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
422
crates/ruff_source_file/src/located.rs
Normal file
422
crates/ruff_source_file/src/located.rs
Normal file
@@ -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<T: Ranged>(&self, ranged: T) -> &str {
|
||||
&self.as_str()[ranged.range()]
|
||||
}
|
||||
}
|
||||
|
||||
impl Located for str {
|
||||
fn as_str(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -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<T: Ranged>(&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
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user