mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 06:53:52 +08:00
fix(js,ts): a call or new in a parameter's default value is the function's call (#2802)
A default runs inside the function each time a call leaves the argument
out, but both extractors pushed the function and walked only its body,
so `function renderWithNote(note = buildNote())` recorded nothing for
the call: no edge and no unresolved reference. Arrows, methods,
constructor parameter properties, class-field arrows, object-literal
methods and destructured parameters lost theirs the same way. Only an
anonymous arrow inside a body was walked whole, params included.
The TS extractor (visitParameterDefaults) and the kernel's TS/JS walker
(visit_parameter_defaults) now hand each default value, and a computed
key in a destructured parameter, to the body walker, with the scope the
body gets on the stack: the function for a named function, method or
component, the enclosing scope for an anonymous function or an
object-literal method. It runs before the body walk at every site that
walks only a body, including a class field's arrow, a forwardRef/memo
render function and a styled .attrs() function. Patterns are recursed
through; the names a parameter binds, its type annotation and a bodiless
signature are not walked. TS/JS only: a Python default runs once, when
the def does, and is left as it was.
Validation: the new test fails 13 of 14 on main 5d81d292 and passes on
both engines, LF and CRLF; the kernel parity fixtures gained a defaults
section. A/B against main on 14 TS/JS repos: 118 edges added, none
removed or retargeted, each a call or `new` written in a default (nuxt
45, mostly kit utilities' `nuxt: Nuxt = useNuxt()`, webdriverio 30,
memfs 17, jest 7, vben 5, social-app 5, vite 4, outline 3, nitro 2;
insomnia, t3-turbo, vue3-antdv-admin, pathe and c12 none). Seven ride
resolution the body already gets: six Vue/Nuxt auto-import self-links
and one member call on memfs's `export default createProcess()`. The
flask and gin control dumps are byte-identical. kernel-parity.mjs over
the 14 repos: 15,408 files byte-parity, 0 with diffs. Index time,
median of 8 interleaved runs on vite, nuxt, webdriverio and insomnia:
+1.6% total, within noise (the last 5 rounds alone: -1.7%). A CLI sync
that adds defaults, adds a file and gives an unchanged file's default
its callee matches a fresh index.
Fixes #2588
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5d81d292fa
commit
d82a4adc9d
@@ -45,7 +45,9 @@ impl<'t> Walker<'t> {
|
||||
if name == "<anonymous>" {
|
||||
// Still walk the body: module wrappers hold named inner functions
|
||||
// and calls that would otherwise be lost (#528).
|
||||
if let Some(body) = body_of(node) {
|
||||
let body = body_of(node);
|
||||
self.visit_parameter_defaults(node, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
return;
|
||||
@@ -68,12 +70,73 @@ impl<'t> Walker<'t> {
|
||||
self.extract_decorators_for(node, row);
|
||||
|
||||
self.stack.push(Scope { row, kind: "function", name });
|
||||
if let Some(body) = body_of(node) {
|
||||
let body = body_of(node);
|
||||
self.visit_parameter_defaults(node, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
/// A parameter's default value is code the function runs, each time a
|
||||
/// call leaves that argument out (#2588). Each default, and a computed key
|
||||
/// in a destructured parameter, is walked as the body is, with the same
|
||||
/// scope on the stack; the names a parameter binds and its type annotation
|
||||
/// are not. A function with no body never runs. Mirrors
|
||||
/// TreeSitterExtractor.visitParameterDefaults.
|
||||
fn visit_parameter_defaults(&mut self, func: Node<'t>, body: Option<Node<'t>>) {
|
||||
let Some(body) = body else { return };
|
||||
// A class field holds its function: `onPick = (item = first()) => {…}`.
|
||||
let owner = if matches!(func.kind(), "public_field_definition" | "field_definition") {
|
||||
body.parent()
|
||||
} else {
|
||||
Some(func)
|
||||
};
|
||||
if let Some(params) = owner.and_then(|o| o.child_by_field_name("parameters")) {
|
||||
self.visit_pattern_defaults(params);
|
||||
}
|
||||
}
|
||||
|
||||
/// The defaults in a parameter list or a destructuring pattern, in source
|
||||
/// order. Mirrors TreeSitterExtractor.visitPatternDefaults.
|
||||
fn visit_pattern_defaults(&mut self, node: Node<'t>) {
|
||||
stack_guard!();
|
||||
match node.kind() {
|
||||
"formal_parameters" | "object_pattern" | "array_pattern" | "rest_pattern" => {
|
||||
for i in 0..node.named_child_count() {
|
||||
if let Some(child) = node.named_child(i) {
|
||||
self.visit_pattern_defaults(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
"required_parameter" | "optional_parameter" => {
|
||||
if let Some(pattern) = node.child_by_field_name("pattern") {
|
||||
self.visit_pattern_defaults(pattern);
|
||||
}
|
||||
if let Some(value) = node.child_by_field_name("value") {
|
||||
self.visit_function_body(value);
|
||||
}
|
||||
}
|
||||
"assignment_pattern" | "object_assignment_pattern" => {
|
||||
if let Some(left) = node.child_by_field_name("left") {
|
||||
self.visit_pattern_defaults(left);
|
||||
}
|
||||
if let Some(right) = node.child_by_field_name("right") {
|
||||
self.visit_function_body(right);
|
||||
}
|
||||
}
|
||||
"pair_pattern" => {
|
||||
if let Some(key) = node.child_by_field_name("key").filter(|k| k.kind() == "computed_property_name") {
|
||||
self.visit_function_body(key);
|
||||
}
|
||||
if let Some(value) = node.child_by_field_name("value") {
|
||||
self.visit_pattern_defaults(value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The property a CommonJS export assignment binds a function to —
|
||||
/// `exports.NAME = <node>` / `module.exports.NAME = <node>` — or None for
|
||||
/// any other assignment. The node must be the assignment's whole
|
||||
@@ -174,7 +237,9 @@ impl<'t> Walker<'t> {
|
||||
self.stack.push(Scope { row, kind: "component", name: name.to_string() });
|
||||
match inner_fn {
|
||||
Some(inner) => {
|
||||
if let Some(body) = body_of(inner) {
|
||||
let body = body_of(inner);
|
||||
self.visit_parameter_defaults(inner, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
}
|
||||
@@ -246,7 +311,9 @@ impl<'t> Walker<'t> {
|
||||
} else if matches!(arg.kind(), "arrow_function" | "function_expression") {
|
||||
// A style or `.attrs()` function runs as the component's
|
||||
// code: its body is walked, the function never named.
|
||||
if let Some(body) = body_of(arg) {
|
||||
let body = body_of(arg);
|
||||
self.visit_parameter_defaults(arg, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
} else {
|
||||
@@ -331,7 +398,9 @@ impl<'t> Walker<'t> {
|
||||
// Object-literal methods are ephemeral: walk the body only.
|
||||
if let Some(parent) = node.parent() {
|
||||
if matches!(parent.kind(), "object" | "object_expression") {
|
||||
if let Some(body) = body_of(node) {
|
||||
let body = body_of(node);
|
||||
self.visit_parameter_defaults(node, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
return;
|
||||
@@ -358,7 +427,9 @@ impl<'t> Walker<'t> {
|
||||
self.extract_decorators_for(node, row);
|
||||
|
||||
self.stack.push(Scope { row, kind: "method", name });
|
||||
if let Some(body) = body_of(node) {
|
||||
let body = body_of(node);
|
||||
self.visit_parameter_defaults(node, body);
|
||||
if let Some(body) = body {
|
||||
self.visit_function_body(body);
|
||||
}
|
||||
self.stack.pop();
|
||||
|
||||
Reference in New Issue
Block a user