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

Method flushValueRefs

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

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

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