mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
fix(extraction): a function in an initializer is referenced once, by what the initializer declares (#2743)
PRs #693 and #1802 walk a declaration's initializer under the symbol it declares, so `const routes = { home }` references `home` from `routes`. The dispatcher's declaration-wide function-as-value scan then took the same values again for the enclosing file (or class). Flush-time dedup keys on the source, so both stayed: a second reference to the same function from the same spot. Each initializer walk now records what it walked, and the scan skips it. PR #2738 added that mechanism for component wrappers, and C#, Scala and Dart already had their own. The newly covered paths: - TS/JS module-level constants and variables, owned object literals included (#2300), and TS/JS class properties; - Python assignments, Go var/const specs, Rust const/static items; - Java fields, and Kotlin properties (walkInitializer takes the property's id, so their walk is unchanged). Mirrored in the kernel: tsjs, python, go, rustlang, java and kotlin. References no initializer walk takes are unchanged: a module-level call argument `register(fn)`, C-family tables, Rust's `static CB: fn() = handler` (the declaration's own value, which the scan still reads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ae0fc631e0
commit
51be28d15b
@@ -779,12 +779,11 @@ describe('Function-as-value capture (#756)', () => {
|
||||
|
||||
// The DRF wiring: get_serializer_class → the imported serializer class,
|
||||
// via `return` — the issue's headline gap. The module-level registry
|
||||
// dict rides BOTH the assigned name (the initializer walk, #693) and the
|
||||
// file node (the dispatcher's own scan, which runs either way).
|
||||
// dict rides the assigned name (the initializer walk, #693), and not the
|
||||
// file node as well: the dispatcher's scan skips what that walk took.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'OrgSerializerFull'))).toEqual([
|
||||
'SERIALIZER_REGISTRY',
|
||||
'get_serializer_class',
|
||||
'views.py',
|
||||
]);
|
||||
// Second branch return + a module-level call argument.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'OrgSerializerBrief'))).toEqual([
|
||||
@@ -1581,4 +1580,72 @@ func Unknown(obj interface{}) { Submit(obj.Fetch) }
|
||||
cg.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// An initializer walked under the symbol it declares (#693) takes its
|
||||
// function values for that symbol. The declaration-wide scan took each one
|
||||
// again for the enclosing file or class: a second reference to the same
|
||||
// function from the same spot (TS/JS constants and class fields, Python,
|
||||
// Go, Rust, Java fields, Kotlin properties). Each function here is
|
||||
// referenced only from an initializer, so a second source shows.
|
||||
it('INITIALIZER WALKS: a function in an initializer is referenced once, by what it declares', async () => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-fnref-init-'));
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'app.ts'),
|
||||
[
|
||||
'export function tsHome() {}',
|
||||
'export function tsAbout() {}',
|
||||
'export function tsClick() {}',
|
||||
'export function tsBoot() {}',
|
||||
'function register(fn: () => void) {}',
|
||||
'const routes = { tsHome, about: tsAbout, list: [tsHome] };',
|
||||
'const api = { load() {}, tsAbout };',
|
||||
'export class Widget {',
|
||||
' static handlers = { click: tsClick };',
|
||||
'}',
|
||||
'register(tsBoot);',
|
||||
].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'views.py'),
|
||||
['def py_home():', ' pass', '', 'HANDLERS = {"home": py_home}', 'ROUTES = [py_home]'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'handlers.go'),
|
||||
['package main', '', 'func goHome() {}', '', 'var handlers = map[string]func(){"home": goHome}'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'lib.rs'),
|
||||
['fn rust_home() {}', 'static HANDLERS: &[fn()] = &[rust_home];'].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'Registry.java'),
|
||||
[
|
||||
'class Registry {',
|
||||
' static void javaHome() {}',
|
||||
' static final java.util.List<Runnable> TABLE = java.util.List.of(Registry::javaHome);',
|
||||
'}',
|
||||
].join('\n')
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'handlers.kt'),
|
||||
['fun ktHome() {}', 'val ktHandlers = listOf(::ktHome)', 'class KtWidget {', ' val hooks = listOf(::ktHome)', '}'].join('\n')
|
||||
);
|
||||
|
||||
const cg = CodeGraph.initSync(tmpDir);
|
||||
try {
|
||||
await cg.indexAll();
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsHome'))).toEqual(['routes']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsAbout'))).toEqual(['api', 'routes']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsClick'))).toEqual(['handlers']);
|
||||
// A call argument at module scope is the file's, as before.
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'tsBoot'))).toEqual(['app.ts']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'py_home'))).toEqual(['HANDLERS', 'ROUTES']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'goHome'))).toEqual(['handlers']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'rust_home'))).toEqual(['HANDLERS']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'javaHome'))).toEqual(['TABLE']);
|
||||
expect(sourceNames(cg, fnRefEdgesInto(cg, 'ktHome'))).toEqual(['hooks', 'ktHandlers']);
|
||||
} finally {
|
||||
cg.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user