MCPcopy
hub / github.com/colbymchenry/codegraph / flushValueRefs

Method flushValueRefs

src/extraction/tree-sitter.ts:703–857  ·  view source on GitHub ↗

* Emit same-file `references` edges from a symbol to the file-scope const/var it reads (TS/JS). * The engine doesn't edge const→consumer, so impact analysis misses "change this table, affect * its readers" (the ReScript-PR false positive). Same-file only (resolution is unambiguous), * disti

()

Source from the content-addressed store, hash-verified

701 * additive. Shadowed targets are pruned — see below.
702 */
703 private flushValueRefs(): void {
704 const scopes = this.valueRefScopes;
705 const targets = this.fileScopeValues;
706 const fileScopeCounts = this.fileScopeValueCounts;
707 this.valueRefScopes = [];
708 this.fileScopeValues = new Map();
709 this.fileScopeValueCounts = new Map();
710 if (!this.valueRefsEnabled || !TreeSitterExtractor.VALUE_REF_LANGS.has(this.language)) return;
711 if (targets.size === 0 || scopes.length === 0 || isGeneratedFile(this.filePath)) return;
712
713 // Prune SHADOWED targets. A target re-bound in an INNER scope (a
714 // bundled/Emscripten `const Module` re-declared as a nested `var Module`; a
715 // Go package `const Timeout` shadowed by a local `Timeout := …`; a Python
716 // module `CONFIG` shadowed by a local `CONFIG = …`) resolves to the inner
717 // binding for nested readers, so a file-scope edge is a false positive.
718 // Inner re-bindings aren't graph nodes, so detect them at the syntax level:
719 // count every declarator of the name across the tree and compare against how
720 // many FILE-SCOPE nodes carry it. A real shadow makes (declarators >
721 // file-scope nodes) — the excess is the local binding. A conditional
722 // module-level def (`try: X = a; except: X = b`) makes them EQUAL (both
723 // declarators are file-scope nodes), so it's correctly kept. Complements the
724 // path-based isGeneratedFile() check, which can't catch content-minified
725 // bundles.
726 //
727 // Declarator node types are per-grammar; a file only contains its own
728 // language's nodes, so matching all of them in one switch is safe.
729 if (this.tree) {
730 const declCounts = new Map<string, number>();
731 const bump = (nameNode: SyntaxNode | null) => {
732 // `simple_identifier` is Kotlin's name node (a property declarator's name).
733 if (nameNode && (nameNode.type === 'identifier' || nameNode.type === 'simple_identifier')) {
734 const nm = getNodeText(nameNode, this.source);
735 if (targets.has(nm)) declCounts.set(nm, (declCounts.get(nm) ?? 0) + 1);
736 }
737 };
738 const dstack: SyntaxNode[] = [this.tree.rootNode];
739 let dvisited = 0;
740 while (dstack.length > 0 && dvisited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
741 const n = dstack.pop()!;
742 dvisited++;
743 switch (n.type) {
744 case 'variable_declarator': // TS/JS/tsx
745 case 'const_spec': // Go `const X = …`
746 case 'var_spec': // Go `var X = …`
747 bump(n.namedChild(0));
748 break;
749 case 'const_item': // Rust `const X: T = …`
750 case 'static_item': // Rust `static X: T = …`
751 bump(getChildByField(n, 'name'));
752 break;
753 case 'let_declaration': // Rust `let x = …` (locals — the shadow source)
754 case 'short_var_declaration': // Go `x, Y := …`
755 case 'assignment': { // Python `X = …` / `X: T = …` / `A, B = …`
756 const left = getChildByField(n, 'left') ?? getChildByField(n, 'pattern') ?? n.namedChild(0);
757 if (left?.type === 'identifier') bump(left);
758 else if (left) for (const c of left.namedChildren) bump(c);
759 break;
760 }

Callers 1

extractMethod · 0.95

Calls 7

isGeneratedFileFunction · 0.90
getChildByFieldFunction · 0.90
getNodeTextFunction · 0.90
cDeclaratorIdentifierFunction · 0.85
firstSimpleIdentifierFunction · 0.85
hasMethod · 0.80
getMethod · 0.65

Tested by

no test coverage detected