mirror of https://github.com/astral-sh/ruff
229 lines
5.9 KiB
Rust
229 lines
5.9 KiB
Rust
use ruff_text_size::{TextLen, TextRange, TextSize};
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct Replacement {
|
|
pub(crate) source_range: TextRange,
|
|
pub(crate) modified_range: TextRange,
|
|
}
|
|
|
|
impl Replacement {
|
|
/// Creates a [`Replacement`] that describes the `source_range` of `source` to replace
|
|
/// with `modified` sliced by `modified_range`.
|
|
pub(crate) fn between(
|
|
source: &str,
|
|
source_line_starts: &[TextSize],
|
|
modified: &str,
|
|
modified_line_starts: &[TextSize],
|
|
) -> Self {
|
|
let mut source_start = TextSize::default();
|
|
let mut modified_start = TextSize::default();
|
|
|
|
for (source_line_start, modified_line_start) in source_line_starts
|
|
.iter()
|
|
.copied()
|
|
.zip(modified_line_starts.iter().copied())
|
|
.skip(1)
|
|
{
|
|
if source[TextRange::new(source_start, source_line_start)]
|
|
!= modified[TextRange::new(modified_start, modified_line_start)]
|
|
{
|
|
break;
|
|
}
|
|
source_start = source_line_start;
|
|
modified_start = modified_line_start;
|
|
}
|
|
|
|
let mut source_end = source.text_len();
|
|
let mut modified_end = modified.text_len();
|
|
|
|
for (source_line_start, modified_line_start) in source_line_starts
|
|
.iter()
|
|
.rev()
|
|
.copied()
|
|
.zip(modified_line_starts.iter().rev().copied())
|
|
{
|
|
if source_line_start < source_start
|
|
|| modified_line_start < modified_start
|
|
|| source[TextRange::new(source_line_start, source_end)]
|
|
!= modified[TextRange::new(modified_line_start, modified_end)]
|
|
{
|
|
break;
|
|
}
|
|
source_end = source_line_start;
|
|
modified_end = modified_line_start;
|
|
}
|
|
|
|
Replacement {
|
|
source_range: TextRange::new(source_start, source_end),
|
|
modified_range: TextRange::new(modified_start, modified_end),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use ruff_source_file::LineIndex;
|
|
use ruff_text_size::TextRange;
|
|
|
|
use super::Replacement;
|
|
|
|
fn compute_replacement(source: &str, modified: &str) -> (Replacement, String) {
|
|
let source_index = LineIndex::from_source_text(source);
|
|
let modified_index = LineIndex::from_source_text(modified);
|
|
let replacement = Replacement::between(
|
|
source,
|
|
source_index.line_starts(),
|
|
modified,
|
|
modified_index.line_starts(),
|
|
);
|
|
let mut expected = source.to_string();
|
|
expected.replace_range(
|
|
replacement.source_range.start().to_usize()..replacement.source_range.end().to_usize(),
|
|
&modified[replacement.modified_range],
|
|
);
|
|
(replacement, expected)
|
|
}
|
|
|
|
#[test]
|
|
fn delete_first_line() {
|
|
let source = "aaaa
|
|
bbbb
|
|
cccc
|
|
";
|
|
let modified = "bbbb
|
|
cccc
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(replacement.source_range, TextRange::new(0.into(), 5.into()));
|
|
assert_eq!(replacement.modified_range, TextRange::empty(0.into()));
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn delete_middle_line() {
|
|
let source = "aaaa
|
|
bbbb
|
|
cccc
|
|
dddd
|
|
";
|
|
let modified = "aaaa
|
|
bbbb
|
|
dddd
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(
|
|
replacement.source_range,
|
|
TextRange::new(10.into(), 15.into())
|
|
);
|
|
assert_eq!(replacement.modified_range, TextRange::empty(10.into()));
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn delete_multiple_lines() {
|
|
let source = "aaaa
|
|
bbbb
|
|
cccc
|
|
dddd
|
|
eeee
|
|
ffff
|
|
";
|
|
let modified = "aaaa
|
|
cccc
|
|
dddd
|
|
ffff
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(
|
|
replacement.source_range,
|
|
TextRange::new(5.into(), 25.into())
|
|
);
|
|
assert_eq!(
|
|
replacement.modified_range,
|
|
TextRange::new(5.into(), 15.into())
|
|
);
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_first_line() {
|
|
let source = "bbbb
|
|
cccc
|
|
";
|
|
let modified = "aaaa
|
|
bbbb
|
|
cccc
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(replacement.source_range, TextRange::empty(0.into()));
|
|
assert_eq!(
|
|
replacement.modified_range,
|
|
TextRange::new(0.into(), 5.into())
|
|
);
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_middle_line() {
|
|
let source = "aaaa
|
|
cccc
|
|
";
|
|
let modified = "aaaa
|
|
bbbb
|
|
cccc
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(replacement.source_range, TextRange::empty(5.into()));
|
|
assert_eq!(
|
|
replacement.modified_range,
|
|
TextRange::new(5.into(), 10.into())
|
|
);
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn insert_multiple_lines() {
|
|
let source = "aaaa
|
|
cccc
|
|
eeee
|
|
";
|
|
let modified = "aaaa
|
|
bbbb
|
|
cccc
|
|
dddd
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(
|
|
replacement.source_range,
|
|
TextRange::new(5.into(), 15.into())
|
|
);
|
|
assert_eq!(
|
|
replacement.modified_range,
|
|
TextRange::new(5.into(), 20.into())
|
|
);
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
|
|
#[test]
|
|
fn replace_lines() {
|
|
let source = "aaaa
|
|
bbbb
|
|
cccc
|
|
";
|
|
let modified = "aaaa
|
|
bbcb
|
|
cccc
|
|
";
|
|
let (replacement, expected) = compute_replacement(source, modified);
|
|
assert_eq!(
|
|
replacement.source_range,
|
|
TextRange::new(5.into(), 10.into())
|
|
);
|
|
assert_eq!(
|
|
replacement.modified_range,
|
|
TextRange::new(5.into(), 10.into())
|
|
);
|
|
assert_eq!(modified, &expected);
|
|
}
|
|
}
|