Skip to content

Commit afdb659

Browse files
authored
Fix off-by one error in the LineIndex::offset calculation (#13407)
1 parent a8d9104 commit afdb659

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

crates/ruff/tests/format.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,11 +1945,10 @@ fn range_end_only() {
19451945
def foo(arg1, arg2,):
19461946
print("Should format this" )
19471947
1948-
"#), @r###"
1948+
"#), @r#"
19491949
success: true
19501950
exit_code: 0
19511951
----- stdout -----
1952-
19531952
def foo(
19541953
arg1,
19551954
arg2,
@@ -1958,7 +1957,7 @@ def foo(arg1, arg2,):
19581957
19591958
19601959
----- stderr -----
1961-
"###);
1960+
"#);
19621961
}
19631962

19641963
#[test]

crates/ruff_source_file/src/line_index.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,57 @@ impl LineIndex {
222222
}
223223

224224
/// Returns the [byte offset](TextSize) at `line` and `column`.
225+
///
226+
/// ## Examples
227+
///
228+
/// ### ASCII
229+
///
230+
/// ```
231+
/// use ruff_source_file::{LineIndex, OneIndexed};
232+
/// use ruff_text_size::TextSize;
233+
/// let source = r#"a = 4
234+
/// c = "some string"
235+
/// x = b"#;
236+
///
237+
/// let index = LineIndex::from_source_text(source);
238+
///
239+
/// // First line, first column
240+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(0), source), TextSize::new(0));
241+
///
242+
/// // Second line, 4th column
243+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(1), OneIndexed::from_zero_indexed(4), source), TextSize::new(10));
244+
///
245+
/// // Offset past the end of the first line
246+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(10), source), TextSize::new(6));
247+
///
248+
/// // Offset past the end of the file
249+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(3), OneIndexed::from_zero_indexed(0), source), TextSize::new(29));
250+
/// ```
251+
///
252+
/// ### UTF8
253+
///
254+
/// ```
255+
/// use ruff_source_file::{LineIndex, OneIndexed};
256+
/// use ruff_text_size::TextSize;
257+
/// let source = r#"a = 4
258+
/// c = "❤️"
259+
/// x = b"#;
260+
///
261+
/// let index = LineIndex::from_source_text(source);
262+
///
263+
/// // First line, first column
264+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(0), OneIndexed::from_zero_indexed(0), source), TextSize::new(0));
265+
///
266+
/// // Third line, 2nd column, after emoji
267+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(2), OneIndexed::from_zero_indexed(1), source), TextSize::new(20));
268+
///
269+
/// // Offset past the end of the second line
270+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(1), OneIndexed::from_zero_indexed(10), source), TextSize::new(19));
271+
///
272+
/// // Offset past the end of the file
273+
/// assert_eq!(index.offset(OneIndexed::from_zero_indexed(3), OneIndexed::from_zero_indexed(0), source), TextSize::new(24));
274+
/// ```
275+
///
225276
pub fn offset(&self, line: OneIndexed, column: OneIndexed, contents: &str) -> TextSize {
226277
// If start-of-line position after last line
227278
if line.to_zero_indexed() > self.line_starts().len() {
@@ -233,15 +284,15 @@ impl LineIndex {
233284
match self.kind() {
234285
IndexKind::Ascii => {
235286
line_range.start()
236-
+ TextSize::try_from(column.get())
287+
+ TextSize::try_from(column.to_zero_indexed())
237288
.unwrap_or(line_range.len())
238289
.clamp(TextSize::new(0), line_range.len())
239290
}
240291
IndexKind::Utf8 => {
241292
let rest = &contents[line_range];
242293
let column_offset: TextSize = rest
243294
.chars()
244-
.take(column.get())
295+
.take(column.to_zero_indexed())
245296
.map(ruff_text_size::TextLen::text_len)
246297
.sum();
247298
line_range.start() + column_offset

0 commit comments

Comments
 (0)