fix: reintroduce the root node for anchoring flexibility

This commit is contained in:
Himadri Bhattacharjee
2025-08-26 11:57:02 +05:30
parent 7b0f818d38
commit f0c137ade4
4 changed files with 25 additions and 11 deletions

View File

@@ -84,7 +84,7 @@ This API parses code into an AST (Abstract Syntax Tree) via tree-sitter and can
``` kdl
description "describes the mutation collection"
mutation {
expression "some ((beautiful) @adjective) AST expression"
expression "(some ((beautiful) @adjective) AST expression) @root"
substitute {
literal "hello"
capture "adjective"
@@ -93,7 +93,7 @@ mutation {
}
mutation {
expression "another"
expression "(another) @root"
substitute {
literal "multiple mutations work"
literal "as long as their expression"
@@ -104,14 +104,30 @@ mutation {
- `description`: A textual description of the mutation collection.
- `mutation`: Defines individual code changes.
- `expression`: Uses tree-sitter to match and capture AST nodes with `@` prefixes, The special `@root` node is reserved for the entire expression.
- `expression`: Uses tree-sitter to match and capture AST nodes with `@` prefixes,
- The special `@root` node must be specify the expression to be replaced.
- `substitute`: Constructs the modified code using literals and captured arguments.
See the example mutation collection in `./snippets/v2/go/mutations.kdl`.
See the example mutation collection in `./snippets/v2/go/filepath-parent.kdl`.
- The API performs a single-pass substitution based on the closest matching mutation.
- Captured groups are used within the `substitute` block and the mutated code is returned.
> Every capture group must contain the largest atom to be operated on.
For example: if you wish to operate on elements of an array, capture each identifier inside the array
Correct way: Here the `array` and `identifier` only hints about where the expression `root` lies.
```
(array (identifier @root))
```
Incorrect way: Here the root expression matches the block all the array elements inside the braces, not each element.
```
(array ((identifier)*) @entire-block-capture) @root
```
**Further reading**
- [tree-sitter query snytax](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/1-syntax.html) to create mutation expressions.

View File

@@ -1,10 +1,10 @@
description "base64 import"
mutation {
expression "import_spec_list ((import_spec)* @imports)"
expression "(import_spec_list ((import_spec)* @spec)) @root"
substitute {
literal "("
literal "\n"
capture "imports"
capture "spec"
literal "\n"
literal #""base64""#
literal "\n"

View File

@@ -4,7 +4,7 @@ mutation {
(call_expression
function: (_) @func (#eq? @func "filepath.Base")
arguments: (_) @args
)
) @root
"""
substitute {
literal "filepath.Base(filepath.Dir(filepath.Clean"

View File

@@ -73,7 +73,7 @@ pub fn from_path<P: AsRef<Path>>(path: P) -> Result<MutationCollection> {
}
mutations.push(Mutation {
expression,
expression: expression.to_string(),
substitute,
})
}
@@ -156,8 +156,7 @@ pub fn query<'a>(
lang: &Language,
source_bytes: &[u8],
) -> Vec<QueryCooked> {
let expr = format!("({expr}) @root");
let query = Query::new(lang, &expr).unwrap();
let query = Query::new(lang, expr).unwrap();
let mut qc = QueryCursor::new();
let mut query_matches = qc.matches(&query, node, source_bytes);
@@ -194,7 +193,6 @@ pub fn query<'a>(
if *name == "root" {
start = start_pos.unwrap();
end = end_pos.unwrap();
continue;
}
let range = start_pos.unwrap()..end_pos.unwrap();
// println!("match range for {name}: {:#?}", range);