diff --git a/internal/postprocess/collapsing.go b/internal/postprocess/collapsing.go index 5a544720..cf8ff389 100644 --- a/internal/postprocess/collapsing.go +++ b/internal/postprocess/collapsing.go @@ -227,8 +227,9 @@ func suppressInlineParentRedundancy( } } -// suppressCoextensiveWrappers climbs single-line parents and suppresses wrapper actions -// that are truly coextensive with the child (exact same byte range). +// suppressCoextensiveWrappers walks single-line parents and drops wrapper actions +// that share the child's start byte, like single-child statements with trailing +// semicolons or scaffolding. func suppressCoextensiveWrappers( node *treesitter.ASTNode, actionMap map[*treesitter.ASTNode]*actions.Action, @@ -242,14 +243,12 @@ func suppressCoextensiveWrappers( if r != nil && r.IsPair(parent.Type) { continue } - // Strict opening invariant: parent must not start before child (protects {hash}, [array], (expr)) - if parent.StartByte == node.StartByte { - // Only suppress if parent is truly coextensive (exact same EndByte) and is a single-child or scaffolding wrapper - if parent.EndByte == node.EndByte && (len(parent.Children) <= 1 || parent.IsScaffolding()) { - parentAct := actionMap[parent] - if parentAct != nil && !suppressed[parentAct] && !parentAct.Subtree { - suppressed[parentAct] = true - } + // Parent can't start before the child: keeps opening delimiters like {hash}, [array], or (expr) intact. + if parent.StartByte == node.StartByte && + (len(parent.Children) <= 1 || parent.IsScaffolding() || parent.EndByte == node.EndByte) { + parentAct := actionMap[parent] + if parentAct != nil && !suppressed[parentAct] && !parentAct.Subtree { + suppressed[parentAct] = true } } } diff --git a/internal/postprocess/collapsing_test.go b/internal/postprocess/collapsing_test.go index f889707f..641d9531 100644 --- a/internal/postprocess/collapsing_test.go +++ b/internal/postprocess/collapsing_test.go @@ -536,8 +536,7 @@ func TestInlineParentSuppression(t *testing.T) { } }) - // (h) Trailing statement terminator (semicolon) in single-child expression_statement - // must STILL suppress the parent wrapper Insert even though parent.EndByte > dstChild.EndByte. + // (h) Trailing semicolons shouldn't keep single-child wrappers alive when their child moves. t.Run("moved-child-suppresses-inline-parent-insert-with-semicolon", func(t *testing.T) { srcChild := &treesitter.ASTNode{ Type: "assignment_expression", @@ -580,17 +579,17 @@ func TestInlineParentSuppression(t *testing.T) { moveSurvives = true } } - if !parentSurvives { - t.Error("expected parent expression_statement Insert with trailing semicolon to survive for delimiter coverage") + if parentSurvives { + t.Error("expected parent expression_statement Insert with trailing semicolon to be suppressed") } if !moveSurvives { t.Error("expected child assignment_expression Move to survive") } }) - // (i) Bidirectional symmetry: source-side single-line statement wrapper Delete is preserved - // when parent contains trailing delimiter punctuation (e.g. semicolon). - t.Run("moved-child-preserves-source-side-parent-delete-with-semicolon", func(t *testing.T) { + // (i) Symmetrical delete: source-side single-line statement wrappers with trailing + // semicolons get dropped too when their child moves. + t.Run("moved-child-suppresses-source-side-parent-delete-with-semicolon", func(t *testing.T) { srcChild := &treesitter.ASTNode{ Type: "assignment_expression", StartByte: 100, EndByte: 120, @@ -633,8 +632,8 @@ func TestInlineParentSuppression(t *testing.T) { moveSurvives = true } } - if !srcParentSurvives { - t.Error("expected source-side parent expression_statement Delete with trailing semicolon to survive") + if srcParentSurvives { + t.Error("expected source-side parent expression_statement Delete with trailing semicolon to be suppressed") } if !moveSurvives { t.Error("expected child Move action to survive") diff --git a/tests/testdata/php_guzzle_stream_read_buffer/expected_actions.json.gz b/tests/testdata/php_guzzle_stream_read_buffer/expected_actions.json.gz index f47fd60d..8ba7e6b9 100644 Binary files a/tests/testdata/php_guzzle_stream_read_buffer/expected_actions.json.gz and b/tests/testdata/php_guzzle_stream_read_buffer/expected_actions.json.gz differ diff --git a/tests/testdata/php_guzzle_stream_read_buffer/expected_ui.json.gz b/tests/testdata/php_guzzle_stream_read_buffer/expected_ui.json.gz index 4c91eb79..51eb04d8 100644 Binary files a/tests/testdata/php_guzzle_stream_read_buffer/expected_ui.json.gz and b/tests/testdata/php_guzzle_stream_read_buffer/expected_ui.json.gz differ diff --git a/tests/testdata/zig_clap_more_than_2/expected_actions.json.gz b/tests/testdata/zig_clap_more_than_2/expected_actions.json.gz index b0c1cf48..87516d00 100644 Binary files a/tests/testdata/zig_clap_more_than_2/expected_actions.json.gz and b/tests/testdata/zig_clap_more_than_2/expected_actions.json.gz differ diff --git a/tests/testdata/zig_clap_more_than_2/expected_ui.json.gz b/tests/testdata/zig_clap_more_than_2/expected_ui.json.gz index 49aaad00..3024e9e1 100644 Binary files a/tests/testdata/zig_clap_more_than_2/expected_ui.json.gz and b/tests/testdata/zig_clap_more_than_2/expected_ui.json.gz differ diff --git a/tests/testdata/zig_clap_short_only_params/expected_actions.json.gz b/tests/testdata/zig_clap_short_only_params/expected_actions.json.gz index 47c6b2ba..40abf42e 100644 Binary files a/tests/testdata/zig_clap_short_only_params/expected_actions.json.gz and b/tests/testdata/zig_clap_short_only_params/expected_actions.json.gz differ diff --git a/tests/testdata/zig_clap_short_only_params/expected_ui.json.gz b/tests/testdata/zig_clap_short_only_params/expected_ui.json.gz index 21bb0400..eeb85106 100644 Binary files a/tests/testdata/zig_clap_short_only_params/expected_ui.json.gz and b/tests/testdata/zig_clap_short_only_params/expected_ui.json.gz differ diff --git a/tests/testdata/zig_zls_build_build_gen/expected_actions.json.gz b/tests/testdata/zig_zls_build_build_gen/expected_actions.json.gz index 981e6556..9752411e 100644 Binary files a/tests/testdata/zig_zls_build_build_gen/expected_actions.json.gz and b/tests/testdata/zig_zls_build_build_gen/expected_actions.json.gz differ diff --git a/tests/testdata/zig_zls_build_build_gen/expected_ui.json.gz b/tests/testdata/zig_zls_build_build_gen/expected_ui.json.gz index 94e14149..2fde0026 100644 Binary files a/tests/testdata/zig_zls_build_build_gen/expected_ui.json.gz and b/tests/testdata/zig_zls_build_build_gen/expected_ui.json.gz differ diff --git a/tests/testdata/zig_zls_build_log_warning/expected_actions.json.gz b/tests/testdata/zig_zls_build_log_warning/expected_actions.json.gz index 26b88f68..a88ea8eb 100644 Binary files a/tests/testdata/zig_zls_build_log_warning/expected_actions.json.gz and b/tests/testdata/zig_zls_build_log_warning/expected_actions.json.gz differ diff --git a/tests/testdata/zig_zls_build_log_warning/expected_ui.json.gz b/tests/testdata/zig_zls_build_log_warning/expected_ui.json.gz index e4c3dfe2..c51d9f3d 100644 Binary files a/tests/testdata/zig_zls_build_log_warning/expected_ui.json.gz and b/tests/testdata/zig_zls_build_log_warning/expected_ui.json.gz differ