Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Add test for insert only suggestion #107

Merged
merged 6 commits into from
May 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions proptest-regressions/replace.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
xs 358148376 3634975642 2528447681 3675516813 # shrinks to ref s = ""
xs 3127423015 3362740891 2605681441 2390162043 # shrinks to ref data = "", ref replacements = [(0..0, [])]
71 changes: 63 additions & 8 deletions src/replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ use std::rc::Rc;
enum State {
Initial,
Replaced(Rc<[u8]>),
Inserted(Rc<[u8]>),
}

impl State {
fn is_inserted(&self) -> bool {
if let &State::Inserted(..) = self {
true
} else {
false
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -36,18 +47,22 @@ impl Data {
Span {
data: State::Initial,
start: 0,
end: data.len(),
end: data.len().saturating_sub(1),
},
],
}
}

/// Render this data as a vector of bytes
pub fn to_vec(&self) -> Vec<u8> {
if self.original.is_empty() {
return Vec::new();
}

self.parts.iter().fold(Vec::new(), |mut acc, d| {
match d.data {
State::Initial => acc.extend_from_slice(&self.original[d.start..d.end]),
State::Replaced(ref d) => acc.extend_from_slice(&d),
State::Initial => acc.extend_from_slice(&self.original[d.start..=d.end]),
State::Replaced(ref d) | State::Inserted(ref d) => acc.extend_from_slice(&d),
};
acc
})
Expand All @@ -61,12 +76,15 @@ impl Data {
up_to_and_including: usize,
data: &[u8],
) -> Result<(), Error> {
let exclusive_end = up_to_and_including + 1;

ensure!(
from <= up_to_and_including,
from <= exclusive_end,
"Invalid range {}...{}, start is larger than end",
from,
up_to_and_including
);

ensure!(
up_to_and_including <= self.original.len(),
"Invalid range {}...{} given, original data is only {} byte long",
Expand All @@ -75,6 +93,8 @@ impl Data {
self.original.len()
);

let insert_only = from == exclusive_end;

// Since we error out when replacing an already replaced chunk of data,
// we can take some shortcuts here. For example, there can be no
// overlapping replacements -- we _always_ split a chunk of 'initial'
Expand All @@ -87,7 +107,9 @@ impl Data {
let new_parts = {
let index_of_part_to_split = self.parts
.iter()
.position(|p| p.start <= from && p.end >= up_to_and_including)
.position(|p| {
!p.data.is_inserted() && p.start <= from && p.end >= up_to_and_including
})
.ok_or_else(|| {
use log::Level::Debug;
if log_enabled!(Debug) {
Expand All @@ -100,6 +122,7 @@ impl Data {
match p.data {
State::Initial => "initial",
State::Replaced(..) => "replaced",
State::Inserted(..) => "inserted",
},
)
})
Expand Down Expand Up @@ -135,7 +158,7 @@ impl Data {
if from > part_to_split.start {
new_parts.push(Span {
start: part_to_split.start,
end: from,
end: from.saturating_sub(1),
data: State::Initial,
});
}
Expand All @@ -144,7 +167,11 @@ impl Data {
new_parts.push(Span {
start: from,
end: up_to_and_including,
data: State::Replaced(data.into()),
data: if insert_only {
State::Inserted(data.into())
} else {
State::Replaced(data.into())
},
});

// Keep initial data on right side of part
Expand Down Expand Up @@ -200,10 +227,38 @@ mod tests {
d.replace_range(6, 10, b"lol").unwrap();
assert_eq!("lorem\nlol\ndolor", str(&d.to_vec()));

d.replace_range(12, 17, b"lol").unwrap();
d.replace_range(12, 16, b"lol").unwrap();
assert_eq!("lorem\nlol\nlol", str(&d.to_vec()));
}

#[test]
fn replace_multiple_lines_with_insert_only() {
let mut d = Data::new(b"foo!");

d.replace_range(3, 2, b"bar").unwrap();
assert_eq!("foobar!", str(&d.to_vec()));

d.replace_range(0, 2, b"baz").unwrap();
assert_eq!("bazbar!", str(&d.to_vec()));

d.replace_range(3, 3, b"?").unwrap();
assert_eq!("bazbar?", str(&d.to_vec()));
}

#[test]
fn replace_invalid_range() {
let mut d = Data::new(b"foo!");

assert!(d.replace_range(2, 0, b"bar").is_err());
assert!(d.replace_range(0, 2, b"bar").is_ok());
}

#[test]
fn empty_to_vec_roundtrip() {
let s = "";
assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
}

#[test]
#[should_panic(expected = "Cannot replace slice of data that was already replaced")]
fn replace_overlapping_stuff_errs() {
Expand Down
8 changes: 8 additions & 0 deletions tests/everything/handle-insert-only.fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
// insert only fix, adds `,` to first match arm
// why doesnt this replace 1 with 1,?
match &Some(3) {
&None => 1,
&Some(x) => x,
};
}
68 changes: 68 additions & 0 deletions tests/everything/handle-insert-only.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"message": "expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`",
"code": null,
"level": "error",
"spans": [
{
"file_name": "./tests/everything/handle-insert-only.rs",
"byte_start": 163,
"byte_end": 165,
"line_start": 6,
"line_end": 6,
"column_start": 18,
"column_end": 20,
"is_primary": true,
"text": [
{
"text": " &Some(x) => x,",
"highlight_start": 18,
"highlight_end": 20
}
],
"label": "expected one of `,`, `.`, `?`, `}`, or an operator here",
"suggested_replacement": null,
"expansion": null
}
],
"children": [
{
"message": "missing a comma here to end this `match` arm",
"code": null,
"level": "help",
"spans": [
{
"file_name": "./tests/everything/handle-insert-only.rs",
"byte_start": 145,
"byte_end": 145,
"line_start": 5,
"line_end": 5,
"column_start": 19,
"column_end": 19,
"is_primary": true,
"text": [
{
"text": " &None => 1",
"highlight_start": 19,
"highlight_end": 19
}
],
"label": null,
"suggested_replacement": ",",
"suggestion_applicability": "Unspecified",
"expansion": null
}
],
"children": [],
"rendered": null
}
],
"rendered": "error: expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`\n --> ./tests/everything/handle-insert-only.rs:6:18\n |\n5 | &None => 1\n | - help: missing a comma here to end this `match` arm\n6 | &Some(x) => x,\n | ^^ expected one of `,`, `.`, `?`, `}`, or an operator here\n\n"
}
{
"message": "aborting due to previous error",
"code": null,
"level": "error",
"spans": [],
"children": [],
"rendered": "error: aborting due to previous error\n\n"
}
8 changes: 8 additions & 0 deletions tests/everything/handle-insert-only.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
// insert only fix, adds `,` to first match arm
// why doesnt this replace 1 with 1,?
match &Some(3) {
&None => 1
&Some(x) => x,
};
}
3 changes: 3 additions & 0 deletions tests/everything/replace-only-one-char.fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
let _x = 42;
}
70 changes: 70 additions & 0 deletions tests/everything/replace-only-one-char.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"message": "unused variable: `x`",
"code": {
"code": "unused_variables",
"explanation": null
},
"level": "warning",
"spans": [
{
"file_name": "replace-only-one-char.rs",
"byte_start": 20,
"byte_end": 21,
"line_start": 2,
"line_end": 2,
"column_start": 9,
"column_end": 10,
"is_primary": true,
"text": [
{
"text": " let x = 42;",
"highlight_start": 9,
"highlight_end": 10
}
],
"label": null,
"suggested_replacement": null,
"expansion": null
}
],
"children": [
{
"message": "#[warn(unused_variables)] on by default",
"code": null,
"level": "note",
"spans": [],
"children": [],
"rendered": null
},
{
"message": "consider using `_x` instead",
"code": null,
"level": "help",
"spans": [
{
"file_name": "replace-only-one-char.rs",
"byte_start": 20,
"byte_end": 21,
"line_start": 2,
"line_end": 2,
"column_start": 9,
"column_end": 10,
"is_primary": true,
"text": [
{
"text": " let x = 42;",
"highlight_start": 9,
"highlight_end": 10
}
],
"label": null,
"suggested_replacement": "_x",
"expansion": null
}
],
"children": [],
"rendered": null
}
],
"rendered": "warning: unused variable: `x`\n --> replace-only-one-char.rs:2:9\n |\n2 | let x = 42;\n | ^ help: consider using `_x` instead\n |\n = note: #[warn(unused_variables)] on by default\n\n"
}
3 changes: 3 additions & 0 deletions tests/everything/replace-only-one-char.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
let x = 42;
}