MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / flushValueRefs

Method flushValueRefs

src/extraction/tree-sitter.ts:777–931  ·  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

775 * additive. Shadowed targets are pruned — see below.
776 */
777 private flushValueRefs(): void {
778 const scopes = this.valueRefScopes;
779 const targets = this.fileScopeValues;
780 const fileScopeCounts = this.fileScopeValueCounts;
781 this.valueRefScopes = [];
782 this.fileScopeValues = new Map();
783 this.fileScopeValueCounts = new Map();
784 if (!this.valueRefsEnabled || !TreeSitterExtractor.VALUE_REF_LANGS.has(this.language)) return;
785 if (targets.size === 0 || scopes.length === 0 || isGeneratedFile(this.filePath)) return;
786
787 // Prune SHADOWED targets. A target re-bound in an INNER scope (a
788 // bundled/Emscripten `const Module` re-declared as a nested `var Module`; a
789 // Go package `const Timeout` shadowed by a local `Timeout := …`; a Python
790 // module `CONFIG` shadowed by a local `CONFIG = …`) resolves to the inner
791 // binding for nested readers, so a file-scope edge is a false positive.
792 // Inner re-bindings aren't graph nodes, so detect them at the syntax level:
793 // count every declarator of the name across the tree and compare against how
794 // many FILE-SCOPE nodes carry it. A real shadow makes (declarators >
795 // file-scope nodes) — the excess is the local binding. A conditional
796 // module-level def (`try: X = a; except: X = b`) makes them EQUAL (both
797 // declarators are file-scope nodes), so it's correctly kept. Complements the
798 // path-based isGeneratedFile() check, which can't catch content-minified
799 // bundles.
800 //
801 // Declarator node types are per-grammar; a file only contains its own
802 // language's nodes, so matching all of them in one switch is safe.
803 if (this.tree) {
804 const declCounts = new Map<string, number>();
805 const bump = (nameNode: SyntaxNode | null) => {
806 // `simple_identifier` is Kotlin's name node (a property declarator's name).
807 if (nameNode && (nameNode.type === 'identifier' || nameNode.type === 'simple_identifier')) {
808 const nm = getNodeText(nameNode, this.source);
809 if (targets.has(nm)) declCounts.set(nm, (declCounts.get(nm) ?? 0) + 1);
810 }
811 };
812 const dstack: SyntaxNode[] = [this.tree.rootNode];
813 let dvisited = 0;
814 while (dstack.length > 0 && dvisited < TreeSitterExtractor.MAX_VALUE_REF_NODES) {
815 const n = dstack.pop()!;
816 dvisited++;
817 switch (n.type) {
818 case 'variable_declarator': // TS/JS/tsx
819 case 'const_spec': // Go `const X = …`
820 case 'var_spec': // Go `var X = …`
821 bump(n.namedChild(0));
822 break;
823 case 'const_item': // Rust `const X: T = …`
824 case 'static_item': // Rust `static X: T = …`
825 bump(getChildByField(n, 'name'));
826 break;
827 case 'let_declaration': // Rust `let x = …` (locals — the shadow source)
828 case 'short_var_declaration': // Go `x, Y := …`
829 case 'assignment': { // Python `X = …` / `X: T = …` / `A, B = …`
830 const left = getChildByField(n, 'left') ?? getChildByField(n, 'pattern') ?? n.namedChild(0);
831 if (left?.type === 'identifier') bump(left);
832 else if (left) for (const c of left.namedChildren) bump(c);
833 break;
834 }

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