Skip to content

Commit 25ebb2a

Browse files
committed
Keep exported function headers visible in structural diffs
Include export declarations in context scopes and retain multiline JavaScript and TypeScript signatures through the function body opening. Changes deep in a function now retain its name and parameters instead of exposing an orphaned parameter line. Validation: all 66 plugin tests pass, including a TSX regression; verified the reported ReviewHome commit range. Wrapping behavior is unchanged. AI assistance: Codex. Agent-Session: 01a0cf54-8719-7203-a3ab-7b4959428076
1 parent 39a7364 commit 25ebb2a

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

‎plugins/context/queries/javascript.scm‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,20 @@
2222
(#set! tag "context:scope"))
2323
((return_statement) @fold
2424
(#set! tag "context:scope"))
25+
26+
; Export prefixes belong to the declaration header. Without the wrapper,
27+
; whole-line projection starts the function scope on its first parameter.
28+
((export_statement declaration: [
29+
(function_declaration)
30+
(generator_function_declaration)
31+
(class_declaration)
32+
]) @fold (#set! tag "context:scope"))
33+
34+
; Keep the complete function header up to the body, including destructured
35+
; parameters and multiline return types.
36+
([
37+
(function_declaration body: (statement_block "{" @fold.open "}" @fold.close) @fold)
38+
(generator_function_declaration body: (statement_block "{" @fold.open "}" @fold.close) @fold)
39+
(method_definition body: (statement_block "{" @fold.open "}" @fold.close) @fold)
40+
(arrow_function body: (statement_block "{" @fold.open "}" @fold.close) @fold)
41+
] (#set! tag "context:body"))

‎plugins/context/src/lib.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct Options {
4545
}
4646

4747
/// The first and last line of every scope on `source` that holds a changed
48-
/// line.
48+
/// line, plus its full header when the query marks the function body.
4949
///
5050
/// A scope region is a whole construct: its first line is the line its
5151
/// signature or header starts on and its last is the line that closes it,
@@ -63,6 +63,11 @@ fn scope_rows(source: &Source, changed: &BTreeSet<u32>) -> BTreeSet<u32> {
6363
}
6464
rows.insert(span.start);
6565
rows.insert(span.end - 1);
66+
if let Node::Fold { children } = &region.node {
67+
if let Some(body) = children.iter().find(|child| has_tag(child, "context:body")) {
68+
rows.extend(span.start..body.range.start.line);
69+
}
70+
}
6671
});
6772
rows
6873
}

‎src/plugin/tests/context.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,20 @@ fn a_scope_keeps_the_line_it_closes_on() {
575575
);
576576
}
577577
}
578+
579+
#[test]
580+
fn exported_tsx_functions_keep_complete_headers_above_distant_changes() {
581+
let body = repeated(0..30, |i| format!(" const value_{i} = {i};\n"));
582+
let before = format!("export function ReviewHome({{\n reviews,\n setup,\n}}: Props) {{\n{body} return <h1>Reviews</h1>;\n}}\n");
583+
let after = before.replace("<h1>Reviews</h1>", "<h1>Sessions</h1>");
584+
let sides = shaped("home.tsx", &before, &after, 3);
585+
for source in [lhs(&sides), rhs(&sides)] {
586+
let open = open_leaf_lines(&source.regions);
587+
assert!(
588+
(0..4).all(|line| open.contains(&line)),
589+
"header hidden: {open:?}"
590+
);
591+
assert!(!open.contains(&10), "unrelated body should collapse");
592+
assert!(open.contains(&34), "changed JSX should stay visible");
593+
}
594+
}

0 commit comments

Comments
 (0)