Skip to content

Commit 782b3a1

Browse files
committed
fix: use diff line index as 1-based line numbers
After doing some experiments, I found that the `Diff` API (from `gix-imara-diff` crate) uses 0-based line indexes instead of 1-based line numbers. Furthermore the `UnifiedDiff` API (again from `gix-imara-diff` crate) uses 1-based line numbers when printing hunk headers (`@@ -3, 5 +3,6 @@`). Thus, printing the unified diff into a patch file remains the same as it was already accurate. So this patch gracefully increments the line indexes by 1 so they can be compared to ranges of 1-based line numbers. I left the `print_diff()` function in the test module as helper/proof when analyzing future regressions.
1 parent 4958912 commit 782b3a1

2 files changed

Lines changed: 67 additions & 33 deletions

File tree

cpp-linter/src/clang_tools/clang_format.rs

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@ pub fn run_clang_format(
119119
replacements: diff
120120
.hunks()
121121
.filter_map(|hunk| {
122+
let altered_start = hunk.after.start.saturating_add(1); // convert to 1-based line numbers
123+
let og_start = hunk.before.start.saturating_add(1); // convert to 1-based line numbers
124+
let og_end = hunk.before.end; // exclusive end is inclusive for 1-based line numbers
122125
let replacement = if hunk.is_pure_insertion() {
123-
RangeInclusive::new(hunk.after.start, hunk.after.start)
126+
RangeInclusive::new(altered_start, altered_start)
124127
} else {
125-
RangeInclusive::new(hunk.before.start, hunk.before.end.saturating_sub(1))
128+
RangeInclusive::new(og_start, og_end)
126129
};
127130
if ranges.is_empty() {
128131
Some(replacement)
@@ -241,14 +244,14 @@ fn three_way_diff(ranges: &[RangeInclusive<u32>], tidy_diff: Diff) -> Vec<RangeI
241244

242245
while let Some(tidy_hunk) = tidy_iter.peek() {
243246
// alias for readability and prevent some repeated calculations
244-
let before_start = tidy_hunk.before.start;
245-
let before_end = tidy_hunk.before.end.saturating_sub(1);
246-
let after_start = tidy_hunk.after.start;
247-
let after_end = tidy_hunk.after.end.saturating_sub(1);
247+
let before_start = tidy_hunk.before.start.saturating_add(1); // convert to 1-based line numbers
248+
let before_end = tidy_hunk.before.end; // exclusive end is inclusive for 1-based line numbers
249+
let after_start = tidy_hunk.after.start.saturating_add(1); // convert to 1-based line numbers
250+
let after_end = tidy_hunk.after.end; // exclusive end is inclusive for 1-based line numbers
248251
let delta = tidy_hunk.after.len() as i32 - tidy_hunk.before.len() as i32;
249252

250-
// The tidy hunk is a pure removal that exactly matches the og range.
251-
if tidy_hunk.is_pure_removal() && before_start == og_start && before_end == og_end {
253+
// The tidy hunk is a pure removal that encompasses the og range.
254+
if tidy_hunk.is_pure_removal() && before_start <= og_start && before_end >= og_end {
252255
// Skip the og range and tidy hunk entirely.
253256
// The line shift must still be adjusted for the pure removal though
254257
line_shift += delta;
@@ -272,15 +275,15 @@ fn three_way_diff(ranges: &[RangeInclusive<u32>], tidy_diff: Diff) -> Vec<RangeI
272275
}
273276

274277
// tidy hunk overlaps with the og range in some way (case 4).
275-
if tidy_hunk.before.contains(&og_start) {
278+
if (before_start..=before_end).contains(&og_start) {
276279
merged_start = after_start;
277280
}
278281

279282
// commit the line shift now that the tidy hunk start is checked.
280283
line_shift += delta;
281284

282285
// tidy hunk suffixes the og range.
283-
if tidy_hunk.before.contains(&og_end) {
286+
if (before_start..=before_end).contains(&og_end) {
284287
merged_end = after_end;
285288
tidy_iter.next(); // this tidy hunk is handled.
286289
break; // break from loop to push the merged range into joint_ranges.
@@ -313,9 +316,8 @@ mod tests {
313316

314317
use std::ops::RangeInclusive;
315318

316-
use gix_imara_diff::{Diff, InternedInput};
317-
318319
use super::{summarize_style, three_way_diff};
320+
use crate::clang_tools::make_patch;
319321

320322
fn formalize_style(style: &str, expected: &str) {
321323
assert_eq!(summarize_style(style), expected);
@@ -339,38 +341,70 @@ mod tests {
339341
#[test]
340342
fn three_way_diff_mixed() {
341343
const OG_SRC: &str =
342-
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11";
343-
// TIDY_SRC replaces line3->StringA (hunk before=2..3) and
344-
// line8+line9+line10->StringB+StringC (hunk before=7..10), then appends StringE.
345-
// The second hunk's before=7..10 contains og_end=9 but not og_start=6,
346-
// which exercises the "tidy hunk suffixes og range" branch.
344+
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12";
345+
// The first hunk line2-3->StringA-B (hunk 2..4 prefixes og range[3..=5]).
346+
// The second hunk line8-11->StringC-D\nline11\n (hunk 8..12 suffixes og range[7..=9]).
347347
const TIDY_SRC: &str =
348-
"line1\nline2\nStringA\nline4\nline5\nline6\nline7\nStringB\nStringC\nline11\nStringE";
349-
let input = InternedInput::new(OG_SRC, TIDY_SRC);
350-
let mut tidy_diff = Diff::compute(gix_imara_diff::Algorithm::Histogram, &input);
351-
tidy_diff.postprocess_lines(&input);
352-
let ranges = vec![RangeInclusive::new(2, 4), RangeInclusive::new(6, 9)];
348+
"line1\nStringA\nStringB\nline4\nline5\nline6\nline7\nline8\nStringC\nStringD\nline11";
349+
let (tidy_diff, _input) = make_patch(TIDY_SRC, OG_SRC);
350+
#[cfg(feature = "bin")]
351+
print_diff(OG_SRC, TIDY_SRC, &tidy_diff, &_input);
352+
let ranges = vec![RangeInclusive::new(3, 5), RangeInclusive::new(7, 9)];
353353
println!("tidy diff: {tidy_diff:#?}\ncompared to og ranges: {ranges:?}");
354354
let joint_ranges = three_way_diff(&ranges, tidy_diff);
355355
println!("joint ranges: {joint_ranges:#?}");
356-
assert_eq!(joint_ranges, vec![2..=4, 6..=10]);
356+
assert_eq!(joint_ranges, vec![2..=5, 7..=11]);
357357
}
358358

359359
#[test]
360360
fn three_way_diff_separated() {
361361
const OG_SRC: &str =
362362
"line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11";
363-
// TIDY_SRC removes "line3" (index 2) which decrements offsets in ranges[5,8] and removes ranges[2,2].
363+
// TIDY_SRC removes "line3" which decrements offsets in ranges[5,8] and removes ranges[3,3].
364364
// TIDY_SRC appends StringE, which handles remaining tidy hunks after done iterating ranges
365365
const TIDY_SRC: &str =
366366
"line1\nline2\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nStringE";
367-
let input = InternedInput::new(OG_SRC, TIDY_SRC);
368-
let mut tidy_diff = Diff::compute(gix_imara_diff::Algorithm::Histogram, &input);
369-
tidy_diff.postprocess_lines(&input);
370-
let ranges = vec![2..=2, 5..=8];
367+
let (tidy_diff, _input) = make_patch(TIDY_SRC, OG_SRC);
368+
#[cfg(feature = "bin")]
369+
print_diff(OG_SRC, TIDY_SRC, &tidy_diff, &_input);
370+
let ranges = vec![3..=3, 5..=8];
371371
println!("tidy diff: {tidy_diff:#?}\ncompared to og ranges: {ranges:?}");
372372
let joint_ranges = three_way_diff(&ranges, tidy_diff);
373373
println!("joint ranges: {joint_ranges:#?}");
374374
assert_eq!(joint_ranges, vec![4..=7, 9..=10]);
375375
}
376+
377+
#[cfg(feature = "bin")]
378+
fn print_diff(
379+
og: &str,
380+
altered: &str,
381+
diff: &gix_imara_diff::Diff,
382+
input: &gix_imara_diff::InternedInput<&str>,
383+
) {
384+
use clap::builder::styling::{AnsiColor, Color, Style};
385+
use gix_imara_diff::{BasicLineDiffPrinter, UnifiedDiffConfig};
386+
387+
println!("---\nOG SRC:");
388+
for (i, l) in og.lines().enumerate() {
389+
println!("{i:>2}|{l}");
390+
}
391+
println!("---\nALTERED SRC:");
392+
for (i, l) in altered.lines().enumerate() {
393+
println!("{i:>2}|{l}");
394+
}
395+
let printer = BasicLineDiffPrinter(&input.interner);
396+
let mut config = UnifiedDiffConfig::default();
397+
config.context_len(0);
398+
let unified = diff.unified_diff(&printer, config, input).to_string();
399+
for l in unified.lines() {
400+
let style = if l.starts_with('+') {
401+
Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)))
402+
} else if l.starts_with('-') {
403+
Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)))
404+
} else {
405+
Style::new()
406+
};
407+
println!("{style}{l}{style:#}");
408+
}
409+
}
376410
}

cpp-linter/src/common_fs.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ impl FileObj {
133133
pub fn is_hunk_in_diff(&self, hunk: &Hunk) -> Option<(u32, u32)> {
134134
let (start_line, end_line) = if !hunk.before.is_empty() {
135135
// if old hunk's total lines is > 0
136-
let start = hunk.before.start;
137-
(start, start + hunk.before.len() as u32 - 1)
136+
let start = hunk.before.start.saturating_add(1); // convert to 1-based line numbers
137+
(start, hunk.before.start + hunk.before.len() as u32)
138138
} else {
139139
// old hunk's total lines is 0, meaning changes were only added
140-
let start = hunk.after.start;
140+
let start = hunk.after.start.saturating_add(1); // convert to 1-based line numbers
141141
// make old hunk's range span 1 line
142142
(start, start)
143143
};
@@ -207,7 +207,7 @@ impl FileObj {
207207
.map_err(FileObjError::OpenPatchFileFailed)?;
208208
patch_file
209209
.write_all(
210-
format!("--- a/{file_name}\n+++ b/{file_name}\n{unified_diff}",).as_bytes(),
210+
format!("--- a/{file_name}\n+++ b/{file_name}\n{unified_diff}").as_bytes(),
211211
)
212212
.map_err(FileObjError::WritePatchFailed)?;
213213
}
@@ -332,7 +332,7 @@ impl FileObj {
332332
format!(
333333
"Please remove the line(s)\n- {}",
334334
hunk.before
335-
.map(|l| l.to_string())
335+
.map(|l| l.saturating_add(1).to_string())
336336
.collect::<Vec<String>>()
337337
.join("\n- ")
338338
)

0 commit comments

Comments
 (0)