Repository navigation
TS/JS AST extractor creates no node for export const with a scalar initializer #2266
Description
TS/JS AST extractor creates no node for export const with a scalar initializer
Version: graphifyy 0.9.26
Environment: Python 3.12.13, Linux x86_64 (glibc 2.31)
Summary
graphify.extract.extract() emits a node for an exported const only when the initializer is a composite or callable expression. Exported constants initialized to a scalar — string, number, boolean, template literal, member expression, logical expression, or ternary — produce no node at all, so they are absent from the graph and any edge referencing them dangles.
This silently drops a large share of a typical TS codebase's public surface: env-var constants, feature flags, string prefixes, numeric limits.
Reproducer
mkdir -p /tmp/gr2 && cd /tmp/gr2
cat > w.ts <<'EOF'
export const W1 = 42;
export const W2 = `tpl${x}`;
export const W3 = [1, 2, 3];
export const W4 = () => 1;
export const W5 = function () { return 1; };
export const W6 = new Map();
export const W7 = foo();
export const W8 = { a: 1 };
export const W9 = true;
export const W10 = process.env.X;
export const W11 = process.env.X ?? 'fallback';
export const W12 = cond ? 'a' : 'b';
EOF
python -c "
from pathlib import Path
from graphify.extract import extract
r = extract([Path('/tmp/gr2/w.ts')], cache_root=Path('/tmp/gr2'))
print(sorted(n['id'] for n in r['nodes']))
"Actual:
['w', 'w_w3', 'w_w4', 'w_w5', 'w_w6', 'w_w7', 'w_w8']
| initializer | node created |
|---|---|
| number / string / boolean literal | no |
| template literal | no |
member expression (process.env.X) |
no |
logical (??, ||) |
no |
| ternary | no |
| array literal | yes |
| object literal | yes |
| arrow function | yes |
| function expression | yes |
new expression |
yes |
| call expression | yes |
Knock-on effect: dangling import edges
A different module importing the missing symbol still gets an edge, which then dangles:
cat > /tmp/gr2/a.ts <<'EOF'
export const A_PREFIX = process.env.A_PREFIX ?? 'X>';
export const A_MAX = Number(process.env.A_MAX || 10);
EOF
cat > /tmp/gr2/b.ts <<'EOF'
import { A_PREFIX, A_MAX } from './a';
EOFyields b --imports--> a_a_prefix where a_a_prefix does not exist, while a_a_max resolves — because Number(...) is a call expression and process.env.X ?? 'X>' is not. This is one of the contributors to the spurious GRAPH HEALTH WARNING reported separately.
Real-world case
In a Next.js repo, lib/send.ts exports two adjacent constants:
export const SEND_PREFIX = process.env.SEND_PREFIX ?? 'PMW>'; // no node
export const SEND_MAX_LEN = Number(process.env.SEND_MAX_LEN || 190); // node createdSEND_PREFIX is a semantically important value (the message-attribution prefix, referenced by name in the project's own design docs), yet it is queryable only through the doc-derived semantic nodes — graphify explain "SEND_PREFIX" finds no code symbol, and the import edge from lib/send.test.ts dangles.
Expected
An exported binding should produce a node regardless of its initializer's expression type. If the current behaviour is intentional (e.g. to avoid noise from trivial locals), it should at minimum apply only to non-exported declarations, since exported constants are part of a module's public API and are exactly what cross-file imports edges point at.
Activity
drmzperx commentedon Jul 28, 2026
Related: #2265 — the dangling import edges described under "knock-on effect" are counted there as part of the spurious
GRAPH HEALTH WARNING.chore: bump to 0.9.31; changelog for #2308/#2299/#2324/#2309/#2266/#2313
safishamsi commentedon Jul 30, 2026
Fixed in v0.9.31 (#2300, thanks @oleksii-tumanov).
export const X = <scalar>now emits a graph node, so a named import of a scalar export is no longer left dangling. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.31chore: bump to 0.9.31; changelog for Graphify-Labs#2308/Graphify-Labs…