Skip to content

Commit

Permalink
test: another update to add back a caret
Browse files Browse the repository at this point in the history
This change also requires some shuffling to the offsets we generate for
the diagnostic. Previously, we were generating an empty range
immediately *after* the line terminator and immediate before the first
byte of the subsequent line. How this is rendered is somewhat open to
interpretation, but the new version of `annotate-snippets` chooses to
render this at the end of the preceding line instead of the beginning of
the following line.

In this case, we want the diagnostic to point to the beginning of the
following line. So we either need to change `annotate-snippets` to
render such spans at the beginning of the following line, or we need to
change our span to point to the first full character in the following
line. The latter will force `annotate-snippets` to move the caret to the
proper location.

I ended up deciding to change our spans instead of changing how
`annotate-snippets` renders empty spans after a line terminator. While I
didn't investigate it, my guess is that they probably had good reason
for doing so, and it doesn't necessarily strike me as _wrong_.
Furthermore, fixing up our spans seems like a good idea regardless, and
was pretty easy to do.
  • Loading branch information
BurntSushi committed Jan 15, 2025
1 parent 75b4ed5 commit 5021f32
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 12 deletions.
10 changes: 8 additions & 2 deletions crates/ruff_linter/src/checkers/logical_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::{TokenKind, Tokens};
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::{Ranged, TextRange, TextSize};

use crate::line_width::IndentWidth;
use crate::registry::{AsRule, Rule};
Expand Down Expand Up @@ -161,7 +161,13 @@ pub(crate) fn check_logical_lines(
let range = if first_token.kind() == TokenKind::Indent {
first_token.range()
} else {
TextRange::new(locator.line_start(first_token.start()), first_token.start())
let mut range =
TextRange::new(locator.line_start(first_token.start()), first_token.start());
if range.is_empty() {
let end = locator.ceil_char_boundary(range.start() + TextSize::from(1));
range = TextRange::new(range.start(), end);
}
range
};

let indent_level = expand_indent(locator.slice(range), settings.tab_size);
Expand Down
80 changes: 80 additions & 0 deletions crates/ruff_linter/src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,86 @@ impl<'a> Locator<'a> {
}
}

/// Finds the closest [`TextSize`] not less than the offset given for which
/// `is_char_boundary` is `true`. Unless the offset given is greater than
/// the length of the underlying contents, in which case, the length of the
/// contents is returned.
///
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
///
/// # Examples
///
/// From `std`:
///
/// ```
/// use ruff_text_size::{Ranged, TextSize};
/// use ruff_linter::Locator;
///
/// let locator = Locator::new("❤️🧡💛💚💙💜");
/// assert_eq!(locator.text_len(), TextSize::from(26));
/// assert!(!locator.contents().is_char_boundary(13));
///
/// let closest = locator.ceil_char_boundary(TextSize::from(13));
/// assert_eq!(closest, TextSize::from(14));
/// assert_eq!(&locator.contents()[..closest.to_usize()], "❤️🧡💛");
/// ```
///
/// Additional examples:
///
/// ```
/// use ruff_text_size::{Ranged, TextRange, TextSize};
/// use ruff_linter::Locator;
///
/// let locator = Locator::new("Hello");
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(5)),
/// TextSize::from(5)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(6)),
/// TextSize::from(5)
/// );
///
/// let locator = Locator::new("α");
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(1)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(2)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(3)),
/// TextSize::from(2)
/// );
/// ```
pub fn ceil_char_boundary(&self, offset: TextSize) -> TextSize {
let upper_bound = offset
.to_u32()
.saturating_add(4)
.min(self.text_len().to_u32());
(offset.to_u32()..upper_bound)
.map(TextSize::from)
.find(|offset| self.contents.is_char_boundary(offset.to_usize()))
.unwrap_or_else(|| TextSize::from(upper_bound))
}

/// Take the source code between the given [`TextRange`].
#[inline]
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
snapshot_kind: text
---
E11.py:9:1: E112 Expected an indented block
|
7 | #: E112
8 | if False:
9 | print()
| E112
| ^ E112
10 | #: E113
11 | print()
|
Expand Down Expand Up @@ -47,7 +46,7 @@ E11.py:45:1: E112 Expected an indented block
43 | #: E112
44 | if False: #
45 | print()
| E112
| ^ E112
46 | #:
47 | if False:
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
snapshot_kind: text
---
E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
|
Expand Down Expand Up @@ -37,7 +36,7 @@ E11.py:30:1: E115 Expected an indented block (comment)
28 | def start(self):
29 | if True:
30 | # try:
| E115
| ^ E115
31 | # self.master.start()
32 | # except MasterExit:
|
Expand All @@ -47,7 +46,7 @@ E11.py:31:1: E115 Expected an indented block (comment)
29 | if True:
30 | # try:
31 | # self.master.start()
| E115
| ^ E115
32 | # except MasterExit:
33 | # self.shutdown()
|
Expand All @@ -57,7 +56,7 @@ E11.py:32:1: E115 Expected an indented block (comment)
30 | # try:
31 | # self.master.start()
32 | # except MasterExit:
| E115
| ^ E115
33 | # self.shutdown()
34 | # finally:
|
Expand All @@ -67,7 +66,7 @@ E11.py:33:1: E115 Expected an indented block (comment)
31 | # self.master.start()
32 | # except MasterExit:
33 | # self.shutdown()
| E115
| ^ E115
34 | # finally:
35 | # sys.exit()
|
Expand All @@ -77,7 +76,7 @@ E11.py:34:1: E115 Expected an indented block (comment)
32 | # except MasterExit:
33 | # self.shutdown()
34 | # finally:
| E115
| ^ E115
35 | # sys.exit()
36 | self.master.start()
|
Expand All @@ -87,7 +86,7 @@ E11.py:35:1: E115 Expected an indented block (comment)
33 | # self.shutdown()
34 | # finally:
35 | # sys.exit()
| E115
| ^ E115
36 | self.master.start()
37 | #: E117
|
Expand Down

0 comments on commit 5021f32

Please sign in to comment.