refactor: simplify and correct Remove op

Follows #999.
This commit is contained in:
William Woodruff 2025-07-01 15:53:58 -04:00
parent ed0586a0b6
commit 1c5a36d1d5
No known key found for this signature in database
3 changed files with 78 additions and 21 deletions

View file

@ -579,12 +579,15 @@ impl Document {
fn query_node(&self, query: &Query, mode: QueryMode) -> Result<Node, QueryError> {
let mut focus_node = self.top_object()?;
for component in &query.route {
dbg!(&focus_node);
match self.descend(&focus_node, component) {
Ok(next) => focus_node = next,
Err(e) => return Err(e),
}
}
dbg!(&focus_node);
focus_node = match mode {
QueryMode::Pretty => {
// If we're in "pretty" mode, we want to return the
@ -594,9 +597,8 @@ impl Document {
//
// NOTE: We might already be on the block/flow pair if we terminated
// with an absent value, in which case we don't need to do this cleanup.
if matches!(query.route.last(), Some(Component::Key(_)))
&& focus_node.kind_id() != self.block_mapping_pair_id
&& focus_node.kind_id() != self.flow_pair_id
if focus_node.kind_id() != self.block_mapping_pair_id
|| focus_node.kind_id() == self.flow_node_id
{
focus_node.parent().unwrap()
} else {
@ -650,6 +652,7 @@ impl Document {
if matches!(mode, QueryMode::Pretty)
&& matches!(query.route.last(), Some(Component::Key(_)))
&& focus_node.kind_id() != self.block_mapping_pair_id
&& focus_node.kind_id() != self.flow_pair_id
{
focus_node = focus_node.parent().unwrap()
}
@ -881,6 +884,24 @@ baz:
);
}
#[test]
fn test_flow_pair_in_sequence() {
let doc = r#"
foo: [1, 2, 3: {abc: def}]
"#;
let doc = Document::new(doc).unwrap();
let query = Query {
route: vec![Component::Key("foo"), Component::Index(2)],
};
let feature = doc.query_pretty(&query).unwrap();
assert_eq!(
doc.extract_with_leading_whitespace(&feature),
"3: {abc: def}"
);
}
#[test]
fn test_feature_comments() {
let doc = r#"

View file

@ -12,7 +12,7 @@ testcase:
queries:
- query: [flow1, foo]
expected: "{ foo: [1, 2, 3: [4, 5, { a: b }]] }"
expected: "foo: [1, 2, 3: [4, 5, { a: b }]]"
- query: [flow1, foo, 2]
# TODO: ideally would be `3: [4, 5, { a: b }]`
@ -28,7 +28,7 @@ queries:
expected: "{ a: }"
- query: [flow4, foo]
expected: "{\n foo: [1, 2, 3: [4, 5, { a: b }]],\n }"
expected: " foo: [1, 2, 3: [4, 5, { a: b }]]"
- query: [flow5, 0]
expected: " abc"
@ -37,4 +37,4 @@ queries:
expected: "def"
- query: [flow5, 2]
expected: " xyz"
expected: " xyz"

View file

@ -168,7 +168,6 @@ pub enum Op<'doc> {
updates: indexmap::IndexMap<String, serde_yaml::Value>,
},
/// Remove the key at the given path
#[allow(dead_code)]
Remove,
}
@ -436,20 +435,16 @@ fn apply_single_patch(
let feature = route_to_feature_pretty(&patch.route, document)?;
// For removal, we need to remove the entire line including leading whitespace
// TODO: This isn't sound, e.g. removing `b:` from `{a: a, b: b}` will
// remove the entire line.
let start_pos = {
let range = line_span(document, feature.location.byte_span.0);
range.start
};
let end_pos = {
let range = line_span(document, feature.location.byte_span.1);
range.end
};
// At the moment, we simply delete the entire "pretty" feature.
// This works well enough, but it will leave any leading
// or trailing whitespace if not captured in the underlying
// tree-sitter-extracted feature.
let mut result = content.to_string();
result.replace_range(start_pos..end_pos, "");
result.replace_range(
feature.location.byte_span.0..feature.location.byte_span.1,
"",
);
yamlpath::Document::new(result).map_err(Error::from)
}
}
@ -1500,7 +1495,7 @@ foo: { bar: abc }
}
#[test]
fn test_remove_preserves_structure() {
fn test_remove_preserves_block_structure() {
let original = r#"
permissions:
contents: read # Keep this comment
@ -1525,6 +1520,47 @@ permissions:
");
}
// #[test]
// fn test_remove_preserves_flow_structure() {
// let original = "foo: { a: a, b: b }";
// let document = yamlpath::Document::new(original).unwrap();
// let operations = vec![Patch {
// route: route!("foo", "b"),
// operation: Op::Remove,
// }];
// let result = apply_yaml_patches(&document, &operations).unwrap();
// // Removes the key while preserving the rest of the structure
// insta::assert_snapshot!(result.source(), @"foo: { a: a }");
// }
#[test]
fn test_remove_empty_key() {
let original = r#"
foo:
bar: abc
baz:
"#;
let document = yamlpath::Document::new(original).unwrap();
let operations = vec![Patch {
route: route!("foo", "baz"),
operation: Op::Remove,
}];
let result = apply_yaml_patches(&document, &operations).unwrap();
// Removes the empty key while preserving the rest of the structure
insta::assert_snapshot!(result.source(), @r"
foo:
bar: abc
");
}
#[test]
fn test_multiple_operations_preserve_comments() {
let original = r#"