Fix off-by one error in the LineIndex::offset calculation (#13407)

This commit is contained in:
Micha Reiser 2024-09-19 13:58:45 +02:00 committed by GitHub
parent a8d9104fa3
commit afdb659111
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 5 deletions

View file

@ -1945,11 +1945,10 @@ fn range_end_only() {
def foo(arg1, arg2,): def foo(arg1, arg2,):
print("Should format this" ) print("Should format this" )
"#), @r###" "#), @r#"
success: true success: true
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
def foo( def foo(
arg1, arg1,
arg2, arg2,
@ -1958,7 +1957,7 @@ def foo(arg1, arg2,):
----- stderr ----- ----- stderr -----
"###); "#);
} }
#[test] #[test]

View file

@ -222,6 +222,57 @@ impl LineIndex {
} }
/// Returns the [byte offset](TextSize) at `line` and `column`. /// Returns the [byte offset](TextSize) at `line` and `column`.
///
/// ## Examples
///
/// ### ASCII
///
/// ```
/// use ruff_source_file::{LineIndex, OneIndexed};
/// use ruff_text_size::TextSize;
/// let source = r#"a = 4
/// c = "some string"
/// x = b"#;
///
/// let index = LineIndex::from_source_text(source);
///
/// // First line, first column
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(0), source), TextSize::new(0));
///
/// // Second line, 4th column
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(1), OneIndexed::from_zero_indexed(4), source), TextSize::new(10));
///
/// // Offset past the end of the first line
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(10), source), TextSize::new(6));
///
/// // Offset past the end of the file
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(3), OneIndexed::from_zero_indexed(0), source), TextSize::new(29));
/// ```
///
/// ### UTF8
///
/// ```
/// use ruff_source_file::{LineIndex, OneIndexed};
/// use ruff_text_size::TextSize;
/// let source = r#"a = 4
/// c = "❤️"
/// x = b"#;
///
/// let index = LineIndex::from_source_text(source);
///
/// // First line, first column
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(0), source), TextSize::new(0));
///
/// // Third line, 2nd column, after emoji
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(2), OneIndexed::from_zero_indexed(1), source), TextSize::new(20));
///
/// // Offset past the end of the second line
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(1), OneIndexed::from_zero_indexed(10), source), TextSize::new(19));
///
/// // Offset past the end of the file
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(3), OneIndexed::from_zero_indexed(0), source), TextSize::new(24));
/// ```
///
pub fn offset(&self, line: OneIndexed, column: OneIndexed, contents: &str) -> TextSize { pub fn offset(&self, line: OneIndexed, column: OneIndexed, contents: &str) -> TextSize {
// If start-of-line position after last line // If start-of-line position after last line
if line.to_zero_indexed() > self.line_starts().len() { if line.to_zero_indexed() > self.line_starts().len() {
@ -233,7 +284,7 @@ impl LineIndex {
match self.kind() { match self.kind() {
IndexKind::Ascii => { IndexKind::Ascii => {
line_range.start() line_range.start()
+ TextSize::try_from(column.get()) + TextSize::try_from(column.to_zero_indexed())
.unwrap_or(line_range.len()) .unwrap_or(line_range.len())
.clamp(TextSize::new(0), line_range.len()) .clamp(TextSize::new(0), line_range.len())
} }
@ -241,7 +292,7 @@ impl LineIndex {
let rest = &contents[line_range]; let rest = &contents[line_range];
let column_offset: TextSize = rest let column_offset: TextSize = rest
.chars() .chars()
.take(column.get()) .take(column.to_zero_indexed())
.map(ruff_text_size::TextLen::text_len) .map(ruff_text_size::TextLen::text_len)
.sum(); .sum();
line_range.start() + column_offset line_range.start() + column_offset