diff --git a/crates/plotnik-lib/src/compile/expressions.rs b/crates/plotnik-lib/src/compile/expressions.rs index bbb9c52..563a39b 100644 --- a/crates/plotnik-lib/src/compile/expressions.rs +++ b/crates/plotnik-lib/src/compile/expressions.rs @@ -17,8 +17,7 @@ use crate::parser::ast::{self, Expr}; use super::Compiler; use super::capture::CaptureEffects; use super::navigation::{ - check_trailing_anchor, inner_creates_scope, is_skippable_quantifier, - is_star_or_plus_quantifier, is_truly_empty_scope, + check_trailing_anchor, inner_creates_scope, is_star_or_plus_quantifier, is_truly_empty_scope, }; impl Compiler<'_> { @@ -73,39 +72,52 @@ impl Compiler<'_> { ); } - // Check if first item is skippable - its skip path should bypass the Up. - // When a zero-match quantifier (? or *) is the first child with Down navigation, - // the skip path never descends, so executing Up would ascend too far. - let first_is_skippable = items - .first() - .and_then(|i| i.as_expr()) - .is_some_and(is_skippable_quantifier); + // Split capture.post: Node/Text effects (and their Set) go on entry (need matched_node + // right after match), other effects go on final_exit (after children processing). + // Node/Text capture effects use matched_node which is only valid immediately after the match, + // before descending into children (which may clobber matched_node via backtracking). + use crate::bytecode::EffectOpcode; + + // Find Node/Text effects and their following Set effects (they come in pairs: Node Set) + let mut entry_effects = Vec::new(); + let mut exit_effects = Vec::new(); + let mut iter = capture.post.into_iter().peekable(); + while let Some(eff) = iter.next() { + if matches!(eff.opcode, EffectOpcode::Node | EffectOpcode::Text) { + entry_effects.push(eff); + // Take the following Set if present (Node/Text are always followed by Set) + if iter.peek().is_some_and(|e| e.opcode == EffectOpcode::Set) { + entry_effects.push(iter.next().unwrap()); + } + } else { + exit_effects.push(eff); + } + } - // With items: nav → items → Up → [post_effects] → exit - // If first item is skippable: skip path → exit (bypass Up), match path → Up → exit - let final_exit = self.emit_post_effects_exit(exit, capture.post); + // With items: nav[entry_effects] → items → Up → [exit_effects] → exit + let final_exit = self.emit_post_effects_exit(exit, exit_effects); let up_label = self.fresh_label(); - let skip_exit = first_is_skippable.then_some(exit); let items_entry = self.compile_seq_items_inner( &items, up_label, true, None, CaptureEffects::default(), - skip_exit, + None, // No skip_exit bypass - all paths need Up ); self.instructions .push(MatchIR::epsilon(up_label, final_exit).nav(up_nav).into()); - // Emit entry instruction into the node (only pre_effects here) + // Emit entry instruction with node_effects on post (executes right after match) self.emit_match_with_cascade( MatchIR::epsilon(entry, items_entry) .nav(nav) .node_type(node_type) .neg_fields(neg_fields) - .pre_effects(capture.pre), + .pre_effects(capture.pre) + .post_effects(entry_effects), ); entry diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap index dbaf875..b7da51b 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__alternations_in_quantifier.snap @@ -53,23 +53,22 @@ Test: 06 ε 07 07 ! (object) 08 08 ε [Arr] 10 - 10 ε 39, 20 + 10 ε 37, 12 12 ε [EndArr Set(M5)] 14 14 △ _ 19 15 ε [EndObj Push] 17 - 17 ε 45, 12 + 17 ε 43, 12 19 ▶ - 20 ε [EndArr Set(M5)] 19 - 22 ε [Set(M4)] 15 - 24 ! [Enum(M2)] (pair) [Node Set(M0) EndEnum] 22 - 27 ! [Enum(M3)] (shorthand_property_identifier) [Node Set(M1) EndEnum] 22 - 30 ε 24, 27 - 32 ε [Obj] 30 - 34 ▷ _ 37 - 35 ε 34, 20 - 37 ε 32, 35 - 39 ▽ _ 37 - 40 ▷ _ 43 - 41 ε 40, 12 - 43 ε 32, 41 - 45 ▷ _ 43 + 20 ε [Set(M4)] 15 + 22 ! [Enum(M2)] (pair) [Node Set(M0) EndEnum] 20 + 25 ! [Enum(M3)] (shorthand_property_identifier) [Node Set(M1) EndEnum] 20 + 28 ε 22, 25 + 30 ε [Obj] 28 + 32 ▷ _ 35 + 33 ε 32, 12 + 35 ε 30, 33 + 37 ▽ _ 35 + 38 ▷ _ 41 + 39 ε 38, 12 + 41 ε 30, 39 + 43 ▷ _ 41 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap index 869449a..956f267 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_deeply_nested.snap @@ -48,14 +48,11 @@ _ObjWrap: Test: 06 ε 07 - 07 ! (a) 08 - 08 ▽ (b) 09 - 09 ▽ (c) 10 - 10 ▽ (d) [Node Set(M3)] 12 - 12 △ _ 13 - 13 ε [Node Set(M2)] 15 + 07 ! (a) [Node Set(M0)] 09 + 09 ▽ (b) [Node Set(M1)] 11 + 11 ▽ (c) [Node Set(M2)] 13 + 13 ▽ (d) [Node Set(M3)] 15 15 △ _ 16 - 16 ε [Node Set(M1)] 18 - 18 △ _ 19 - 19 ε [Node Set(M0)] 21 - 21 ▶ + 16 △ _ 17 + 17 △ _ 18 + 18 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap index e3abac1..4915ad6 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__captures_nested_flat.snap @@ -42,11 +42,9 @@ _ObjWrap: Test: 06 ε 07 - 07 ! (a) 08 - 08 ▽ (b) 09 - 09 ▽ (c) [Node Set(M2)] 11 - 11 △ _ 12 - 12 ε [Node Set(M1)] 14 + 07 ! (a) [Node Set(M0)] 09 + 09 ▽ (b) [Node Set(M1)] 11 + 11 ▽ (c) [Node Set(M2)] 13 + 13 △ _ 14 14 △ _ 15 - 15 ε [Node Set(M0)] 17 - 17 ▶ + 15 ▶ diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap index 3b24a48..e65e3fb 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__definitions_nested_capture.snap @@ -56,23 +56,22 @@ Outer: 12 ε 13 13 ! (parent) 14 14 ε [Arr] 16 - 16 ε 42, 26 + 16 ε 40, 18 18 ε [EndArr Set(M2)] 20 20 △ _ 25 21 ε [EndObj Push] 23 - 23 ε 48, 18 + 23 ε 46, 18 25 ▶ - 26 ε [EndArr Set(M2)] 25 - 28 ε [Set(M1)] 21 - 30 ε [EndObj] 28 - 32 ! (Inner) 06 : 30 - 33 ε [Obj] 32 - 35 ε [Obj] 33 - 37 ▷ _ 40 - 38 ε 37, 26 - 40 ε 35, 38 - 42 ▽ _ 40 - 43 ▷ _ 46 - 44 ε 43, 18 - 46 ε 35, 44 - 48 ▷ _ 46 + 26 ε [Set(M1)] 21 + 28 ε [EndObj] 26 + 30 ! (Inner) 06 : 28 + 31 ε [Obj] 30 + 33 ε [Obj] 31 + 35 ▷ _ 38 + 36 ε 35, 18 + 38 ε 33, 36 + 40 ▽ _ 38 + 41 ▷ _ 44 + 42 ε 41, 18 + 44 ε 33, 42 + 46 ▷ _ 44 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap index 86afecd..153ef62 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__optional_null_injection.snap @@ -37,12 +37,12 @@ _ObjWrap: Test: 06 ε 07 07 ! (function_declaration) 08 - 08 ε 21, 14 - 10 ! (decorator) [Node Set(M0)] 12 - 12 △ _ 13 - 13 ▶ - 14 ε [Null Set(M0)] 13 - 16 ▷ _ 19 - 17 ε 16, 14 - 19 ε 10, 17 - 21 ▽ _ 19 + 08 ε 20, 13 + 10 ▶ + 11 ! (decorator) [Node Set(M0)] 21 + 13 ε [Null Set(M0)] 21 + 15 ▷ _ 18 + 16 ε 15, 13 + 18 ε 11, 16 + 20 ▽ _ 18 + 21 △ _ 10 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap index 86afecd..153ef62 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional.snap @@ -37,12 +37,12 @@ _ObjWrap: Test: 06 ε 07 07 ! (function_declaration) 08 - 08 ε 21, 14 - 10 ! (decorator) [Node Set(M0)] 12 - 12 △ _ 13 - 13 ▶ - 14 ε [Null Set(M0)] 13 - 16 ▷ _ 19 - 17 ε 16, 14 - 19 ε 10, 17 - 21 ▽ _ 19 + 08 ε 20, 13 + 10 ▶ + 11 ! (decorator) [Node Set(M0)] 21 + 13 ε [Null Set(M0)] 21 + 15 ▷ _ 18 + 16 ε 15, 13 + 18 ε 11, 16 + 20 ▽ _ 18 + 21 △ _ 10 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap index b32ad97..a01fa06 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_optional_nongreedy.snap @@ -37,12 +37,12 @@ _ObjWrap: Test: 06 ε 07 07 ! (function_declaration) 08 - 08 ε 14, 21 - 10 ! (decorator) [Node Set(M0)] 12 - 12 △ _ 13 - 13 ▶ - 14 ε [Null Set(M0)] 13 - 16 ▷ _ 19 - 17 ε 14, 16 - 19 ε 17, 10 - 21 ▽ _ 19 + 08 ε 13, 20 + 10 ▶ + 11 ! (decorator) [Node Set(M0)] 21 + 13 ε [Null Set(M0)] 21 + 15 ▷ _ 18 + 16 ε 13, 15 + 18 ε 16, 11 + 20 ▽ _ 18 + 21 △ _ 10 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap index 3aee6c7..194b307 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_repeat_navigation.snap @@ -38,18 +38,17 @@ Test: 06 ε 07 07 ! (function_declaration) 08 08 ε [Arr] 10 - 10 ε 27, 20 + 10 ε 25, 12 12 ε [EndArr Set(M0)] 14 14 △ _ 19 15 ! (decorator) [Node Push] 17 - 17 ε 33, 12 + 17 ε 31, 12 19 ▶ - 20 ε [EndArr Set(M0)] 19 - 22 ▷ _ 25 - 23 ε 22, 20 - 25 ε 15, 23 - 27 ▽ _ 25 - 28 ▷ _ 31 - 29 ε 28, 12 - 31 ε 15, 29 - 33 ▷ _ 31 + 20 ▷ _ 23 + 21 ε 20, 12 + 23 ε 15, 21 + 25 ▽ _ 23 + 26 ▷ _ 29 + 27 ε 26, 12 + 29 ε 15, 27 + 31 ▷ _ 29 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap index b2ea475..5285a3f 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__quantifiers_struct_array.snap @@ -48,21 +48,20 @@ Test: 06 ε 07 07 ! (array) 08 08 ε [Arr] 10 - 10 ε 33, 20 + 10 ε 31, 12 12 ε [EndArr Set(M2)] 14 14 △ _ 19 15 ε [EndObj Push] 17 - 17 ε 39, 12 + 17 ε 37, 12 19 ▶ - 20 ε [EndArr Set(M2)] 19 - 22 ▷ (number) [Node Set(M1)] 15 - 24 ! (identifier) [Node Set(M0)] 22 - 26 ε [Obj] 24 - 28 ▷ _ 31 - 29 ε 28, 20 - 31 ε 26, 29 - 33 ▽ _ 31 - 34 ▷ _ 37 - 35 ε 34, 12 - 37 ε 26, 35 - 39 ▷ _ 37 + 20 ▷ (number) [Node Set(M1)] 15 + 22 ! (identifier) [Node Set(M0)] 20 + 24 ε [Obj] 22 + 26 ▷ _ 29 + 27 ε 26, 12 + 29 ε 24, 27 + 31 ▽ _ 29 + 32 ▷ _ 35 + 33 ε 32, 12 + 35 ε 24, 33 + 37 ▷ _ 35 diff --git a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap index 6875afe..10c7bbf 100644 --- a/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap +++ b/crates/plotnik-lib/src/emit/snapshots/plotnik_lib__emit__emit_tests__sequences_in_quantifier.snap @@ -39,19 +39,18 @@ Test: 06 ε 07 07 ! (parent) 08 08 ε [Arr] 10 - 10 ε 28, 20 + 10 ε 26, 12 12 ε [EndArr Set(M0)] 14 14 △ _ 19 15 ▷ (b) [Node Push] 17 - 17 ε 34, 12 + 17 ε 32, 12 19 ▶ - 20 ε [EndArr Set(M0)] 19 - 22 ! (a) 15 - 23 ▷ _ 26 - 24 ε 23, 20 - 26 ε 22, 24 - 28 ▽ _ 26 - 29 ▷ _ 32 - 30 ε 29, 12 - 32 ε 22, 30 - 34 ▷ _ 32 + 20 ! (a) 15 + 21 ▷ _ 24 + 22 ε 21, 12 + 24 ε 20, 22 + 26 ▽ _ 24 + 27 ▷ _ 30 + 28 ε 27, 12 + 30 ε 20, 28 + 32 ▷ _ 30 diff --git a/crates/plotnik-lib/src/engine/engine_tests.rs b/crates/plotnik-lib/src/engine/engine_tests.rs index c0732b5..c344378 100644 --- a/crates/plotnik-lib/src/engine/engine_tests.rs +++ b/crates/plotnik-lib/src/engine/engine_tests.rs @@ -573,3 +573,64 @@ fn regression_unlabeled_alternation_8_branches_captures() { "42" ); } + +/// BUG #7: Node capture on named node with children captured wrong target. +/// +/// For `(class_declaration body: (class_body ...)) @class`, the @class capture +/// was capturing `class_body` instead of `class_declaration`. This happened because +/// Node/Text capture effects were placed on the exit (after children processing), +/// but matched_node gets clobbered by child matches and backtracking. +/// +/// Fix: Split Node/Text effects to entry match's post_effects, where matched_node +/// is still valid from the parent match. +#[test] +fn regression_node_capture_on_parent_with_children() { + snap!( + indoc! {r#" + Q = (program (class_declaration + body: (class_body (method_definition)* @methods) + ) @class) + "#}, + "class A { foo() {} }" + ); +} + +/// BUG #8: Nested quantifiers with empty inner array found only first outer match. +/// +/// For `{ (class_declaration ...) @class }* @classes` with multiple classes where +/// some have empty method arrays, only the first class was captured. The skip path +/// (zero matches on inner array) was bypassing an Up navigation, leaving the cursor +/// at the wrong level for the outer loop. +/// +/// Fix: Remove skip_exit bypass - all paths through children must go through Up, +/// because even "skip" paths descend to check for matches before backtracking. +#[test] +fn regression_nested_quantifiers_empty_inner_array() { + snap!( + indoc! {r#" + Q = (program {(class_declaration + body: (class_body (method_definition)* @methods) + ) @class}* @classes) + "#}, + "class A { } class B { foo() {} }" + ); +} + +/// Combined regression test: nested quantifiers with struct captures and mixed empty/non-empty. +/// +/// This exercises both bugs together: Node capture must capture class_declaration (not class_body), +/// and all classes must be found regardless of whether their method arrays are empty. +#[test] +fn regression_nested_quantifiers_struct_captures_mixed() { + snap!( + indoc! {r#" + Q = (program {(class_declaration + name: (identifier) @name + body: (class_body {(method_definition + name: (property_identifier) @method_name + ) @method}* @methods) + ) @class}* @classes) + "#}, + "class Empty { } class One { foo() {} } class Two { bar() {} baz() {} }" + ); +} diff --git a/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__quantifier_struct_array.snap b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__quantifier_struct_array.snap index b0e58eb..37609f1 100644 --- a/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__quantifier_struct_array.snap +++ b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__quantifier_struct_array.snap @@ -8,16 +8,16 @@ let a, b, c { "decls": [ { - "name": { - "kind": "identifier", + "decl": { + "kind": "variable_declarator", "text": "a", "span": [ 4, 5 ] }, - "decl": { - "kind": "variable_declarator", + "name": { + "kind": "identifier", "text": "a", "span": [ 4, @@ -26,16 +26,16 @@ let a, b, c } }, { - "name": { - "kind": "identifier", + "decl": { + "kind": "variable_declarator", "text": "b", "span": [ 7, 8 ] }, - "decl": { - "kind": "variable_declarator", + "name": { + "kind": "identifier", "text": "b", "span": [ 7, @@ -44,16 +44,16 @@ let a, b, c } }, { - "name": { - "kind": "identifier", + "decl": { + "kind": "variable_declarator", "text": "c", "span": [ 10, 11 ] }, - "decl": { - "kind": "variable_declarator", + "name": { + "kind": "identifier", "text": "c", "span": [ 10, diff --git a/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_empty_inner_array.snap b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_empty_inner_array.snap new file mode 100644 index 0000000..5dca511 --- /dev/null +++ b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_empty_inner_array.snap @@ -0,0 +1,44 @@ +--- +source: crates/plotnik-lib/src/engine/engine_tests.rs +--- +Q = (program {(class_declaration + body: (class_body (method_definition)* @methods) +) @class}* @classes) +--- +class A { } class B { foo() {} } +--- +{ + "classes": [ + { + "class": { + "kind": "class_declaration", + "text": "class A { }", + "span": [ + 0, + 11 + ] + }, + "methods": [] + }, + { + "class": { + "kind": "class_declaration", + "text": "class B { foo() {} }", + "span": [ + 12, + 32 + ] + }, + "methods": [ + { + "kind": "method_definition", + "text": "foo() {}", + "span": [ + 22, + 30 + ] + } + ] + } + ] +} diff --git a/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_struct_captures_mixed.snap b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_struct_captures_mixed.snap new file mode 100644 index 0000000..44ac155 --- /dev/null +++ b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_nested_quantifiers_struct_captures_mixed.snap @@ -0,0 +1,129 @@ +--- +source: crates/plotnik-lib/src/engine/engine_tests.rs +--- +Q = (program {(class_declaration + name: (identifier) @name + body: (class_body {(method_definition + name: (property_identifier) @method_name + ) @method}* @methods) +) @class}* @classes) +--- +class Empty { } class One { foo() {} } class Two { bar() {} baz() {} } +--- +{ + "classes": [ + { + "class": { + "kind": "class_declaration", + "text": "class Empty { }", + "span": [ + 0, + 15 + ] + }, + "name": { + "kind": "identifier", + "text": "Empty", + "span": [ + 6, + 11 + ] + }, + "methods": [] + }, + { + "class": { + "kind": "class_declaration", + "text": "class One { foo() {} }", + "span": [ + 16, + 38 + ] + }, + "name": { + "kind": "identifier", + "text": "One", + "span": [ + 22, + 25 + ] + }, + "methods": [ + { + "method": { + "kind": "method_definition", + "text": "foo() {}", + "span": [ + 28, + 36 + ] + }, + "method_name": { + "kind": "property_identifier", + "text": "foo", + "span": [ + 28, + 31 + ] + } + } + ] + }, + { + "class": { + "kind": "class_declaration", + "text": "class Two { bar() {} baz() {} }", + "span": [ + 39, + 70 + ] + }, + "name": { + "kind": "identifier", + "text": "Two", + "span": [ + 45, + 48 + ] + }, + "methods": [ + { + "method": { + "kind": "method_definition", + "text": "bar() {}", + "span": [ + 51, + 59 + ] + }, + "method_name": { + "kind": "property_identifier", + "text": "bar", + "span": [ + 51, + 54 + ] + } + }, + { + "method": { + "kind": "method_definition", + "text": "baz() {}", + "span": [ + 60, + 68 + ] + }, + "method_name": { + "kind": "property_identifier", + "text": "baz", + "span": [ + 60, + 63 + ] + } + } + ] + } + ] +} diff --git a/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_node_capture_on_parent_with_children.snap b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_node_capture_on_parent_with_children.snap new file mode 100644 index 0000000..d5ed8cb --- /dev/null +++ b/crates/plotnik-lib/src/engine/snapshots/plotnik_lib__engine__engine_tests__regression_node_capture_on_parent_with_children.snap @@ -0,0 +1,29 @@ +--- +source: crates/plotnik-lib/src/engine/engine_tests.rs +--- +Q = (program (class_declaration + body: (class_body (method_definition)* @methods) +) @class) +--- +class A { foo() {} } +--- +{ + "class": { + "kind": "class_declaration", + "text": "class A { foo() {} }", + "span": [ + 0, + 20 + ] + }, + "methods": [ + { + "kind": "method_definition", + "text": "foo() {}", + "span": [ + 10, + 18 + ] + } + ] +}