// Conservative render-value analysis. Never executes consumer code. // Only immutable local values, direct component calls and literal conditions // prove a branch absent. Unsupported flows retain NOT VERIFIED navigation. export function navigationAnalysis(records, roots, origin, walk) { const unknown = Symbol('unknown'); const state = new Map(); const sidebarValues = new Map(); const identities = new Map(); const modules = new Map(records.filter(r => r.ast).map(r => [r.file, r])); const active = new Set(); const rank = { dead: 0, maybe: 1, active: 2 }; const mark = (node, value) => { if (!state.has(node) || rank[value] > rank[state.get(node)]) state.set(node, value); }; const fallback = (node, certainty = 'maybe') => walk(node, n => { if (n.type === 'JSXOpeningElement' || n.type === 'CallExpression' && /createElement|^(?:_?jsx|_?jsxs|jsxDEV)$/.test(n.callee?.name ?? n.callee?.property?.name ?? '')) mark(n, certainty); }); const unwrap = n => ['TSAsExpression', 'TSSatisfiesExpression', 'TSNonNullExpression', 'ParenthesizedExpression'].includes(n?.type) ? unwrap(n.expression) : n; function uncertain(node, env, seen = new Set()) { if (!node || seen.has(node)) return; seen.add(node); fallback(node); walk(node, x => { if (x.type !== 'Identifier') return; const b = env.get(x.name); if (b?.fn) uncertain(b.fn, b.env, seen); if (b?.expr) uncertain(b.expr, b.env, seen); }); } function invalidate(body, env) { const taint = (name, seen = new Set()) => { if (seen.has(name)) return; seen.add(name); const previous = env.get(name); if (previous?.expr) { uncertain(previous.expr, previous.env); if (previous.expr.type === 'Identifier') taint(previous.expr.name, seen); } env.set(name, unknown); }; const objectBinding = (name, seen = new Set()) => { if (seen.has(name)) return false; seen.add(name); const b = env.get(name); return b?.expr?.type === 'ObjectExpression' || b?.expr?.type === 'Identifier' && objectBinding(b.expr.name, seen) || b && typeof b === 'object' && !b.expr && !b.fn; }; walk({ type: 'BlockStatement', body }, n => { if (n.type === 'AssignmentExpression' || n.type === 'UpdateExpression') { let lhs = n.left ?? n.argument; while (lhs?.type === 'MemberExpression') lhs = lhs.object; if (lhs?.type === 'Identifier') taint(lhs.name); } // An opaque call can mutate object/props arguments. This may over-report // uncertainty, but must never prove a dead branch from a stale value. if (n.type === 'CallExpression') for (const arg of n.arguments) if (arg.type === 'Identifier' && env.has(arg.name)) { if (objectBinding(arg.name)) taint(arg.name); } }); } function environment(r, body, parent = new Map()) { const env = new Map(parent); for (let stmt of body) { stmt = stmt.declaration ?? stmt; if (stmt.type === 'FunctionDeclaration' && stmt.id) env.set(stmt.id.name, { fn: stmt, r, env }); if (stmt.type === 'VariableDeclaration') for (const d of stmt.declarations) { if (d.id.type === 'Identifier') env.set(d.id.name, { expr: d.init, r, env, mutable: stmt.kind !== 'const' }); else { fallback(d.init); for (const id of d.id.elements ?? []) if (id?.type === 'Identifier') env.set(id.name, unknown); } } } // Even const objects / parameters can be mutated. Do not infer through writes. invalidate(body, env); return env; } for (const r of modules.values()) r.navEnv = environment(r, r.ast.program.body); function exported(r, name, seen = new Set()) { if (!r || seen.has(`${r.file}:${name}`)) return unknown; seen.add(`${r.file}:${name}`); const b = r.exports.get(name); if (b?.edge) return exported(modules.get(b.edge.resolved), b.imported, seen); if (b) { if (b.local === 'default') { const d = r.ast.program.body.find(n => n.type === 'ExportDefaultDeclaration')?.declaration; return value(d, r, r.navEnv); } return value({ type: 'Identifier', name: b.local }, r, r.navEnv); } for (const e of r.stars) { const found = exported(modules.get(e.resolved), name, seen); if (found !== unknown) return found; } return unknown; } const evaluating = new Set(); function value(raw, r, env) { const n = unwrap(raw); if (!n) return undefined; if (['StringLiteral', 'NumericLiteral', 'BooleanLiteral'].includes(n.type)) return n.value; if (n.type === 'NullLiteral') return null; if (n.type === 'TemplateLiteral' && !n.expressions.length) return n.quasis[0].value.cooked; if (n.type === 'Identifier') { if (env.has(n.name)) { const v = env.get(n.name); if (!v?.expr) return v; if (v.mutable || evaluating.has(v)) { uncertain(v.expr, v.env); return unknown; } evaluating.add(v); const result = value(v.expr, v.r, v.env); evaluating.delete(v); return result; } if (n.name === 'undefined') return undefined; const official = origin(r.file, n.name); if (official?.pkg) return { official }; const b = r.bindings.get(n.name); if (b?.edge) return exported(modules.get(b.edge.resolved), b.imported); return unknown; } if (['ArrowFunctionExpression', 'FunctionExpression', 'FunctionDeclaration'].includes(n.type)) return { fn: n, r, env }; if (n.type === 'JSXElement' || n.type === 'JSXFragment') return { jsx: n, r, env }; if (n.type === 'ObjectExpression') { const obj = Object.create(null); for (const p of n.properties) { if (p.type !== 'ObjectProperty' || p.computed) return unknown; obj[p.key.name ?? p.key.value] = value(p.value, r, env); } return obj; } if (n.type === 'MemberExpression') { if (!n.computed && n.object.type === 'Identifier') { const official = origin(r.file, `${n.object.name}.${n.property.name}`); if (official?.pkg && !env.has(n.object.name)) return { official }; } const obj = value(n.object, r, env); const key = n.computed ? value(n.property, r, env) : n.property.name; if (obj?.fn || obj?.official || obj?.jsx || obj?.branches) return unknown; return obj !== unknown && obj != null && key !== unknown ? obj[key] : unknown; } if (n.type === 'UnaryExpression' && n.operator === '!') { const v = value(n.argument, r, env); return v === unknown || v?.branches ? unknown : !v; } if (n.type === 'BinaryExpression' && ['===', '!==', '==', '!='].includes(n.operator)) { const a = value(n.left, r, env), b = value(n.right, r, env); if (a === unknown || b === unknown || a !== null && ['object', 'function'].includes(typeof a) || b !== null && ['object', 'function'].includes(typeof b)) return unknown; return n.operator === '===' ? a === b : n.operator === '!==' ? a !== b : n.operator === '==' ? a == b : a != b; } if (n.type === 'ConditionalExpression') { const test = value(n.test, r, env); return test === unknown || test?.branches ? { branches: [n.consequent, n.alternate], r, env } : value(test ? n.consequent : n.alternate, r, env); } if (n.type === 'LogicalExpression') { const a = value(n.left, r, env); if (a === unknown || a?.branches) return { branches: [n.left, n.right], r, env }; return n.operator === '&&' ? a && value(n.right, r, env) : n.operator === '||' ? a || value(n.right, r, env) : a ?? value(n.right, r, env); } if (n.type === 'ArrayExpression') return { branches: n.elements, r, env, definite: true }; uncertain(n, env); return unknown; } function bind(pattern, v, r, env) { if (!pattern) return; if (pattern.type === 'Identifier') env.set(pattern.name, v); else if (pattern.type === 'AssignmentPattern') bind(pattern.left, v === undefined ? value(pattern.right, r, env) : v, r, env); else if (pattern.type === 'ObjectPattern') for (const p of pattern.properties) { if (p.type === 'ObjectProperty' && !p.computed) bind(p.value, v === unknown || v == null ? unknown : v[p.key.name ?? p.key.value], r, env); else if (p.argument) bind(p.argument, unknown, r, env); } } function invoke(v, props, certainty) { if (!v?.fn) return; const { fn, r } = v; if (active.has(fn)) { fallback(fn); return; } active.add(fn); // Unused JSX initializers are constructed values, not mounted elements. fallback(fn, 'dead'); const env = environment(r, fn.body.body ?? [], v.env); fn.params.forEach((p, i) => bind(p, i === 0 ? props : unknown, r, env)); invalidate(fn.body.body ?? [], env); if (fn.body.type !== 'BlockStatement') render(fn.body, r, env, certainty); else statements(fn.body.body, r, env, certainty); active.delete(fn); } function statements(body, r, env, certainty) { for (const n of body) { if (n.type === 'ReturnStatement') { render(n.argument, r, env, certainty); return; } if (n.type === 'IfStatement') { const t = value(n.test, r, env); const unproven = t === unknown || t?.branches; const branches = unproven ? [n.consequent, n.alternate] : [t ? n.consequent : n.alternate]; for (const branch of branches.filter(Boolean)) statements(branch.type === 'BlockStatement' ? branch.body : [branch], r, environment(r, branch.type === 'BlockStatement' ? branch.body : [branch], env), unproven ? 'maybe' : certainty); // Early-return control flow requires a full CFG; retain uncertainty after it. if (branches.some(b => { let found = false; walk(b, x => { if (x.type === 'ReturnStatement') found = true; }); return found; })) certainty = 'maybe'; } else if (!['VariableDeclaration', 'FunctionDeclaration', 'ExpressionStatement', 'EmptyStatement', 'TSTypeAliasDeclaration', 'TSInterfaceDeclaration'].includes(n.type)) fallback(n); // Effects / callbacks can mount values outside the direct JSX return graph. if (n.type === 'ExpressionStatement') uncertain(n, env); } } function consume(v, certainty) { if (v?.jsx) render(v.jsx, v.r, v.env, certainty); else if (v?.branches) for (const b of v.branches) render(b, v.r, v.env, v.definite ? certainty : 'maybe'); else if (v?.fn) fallback(v.fn); else if (v && typeof v === 'object') Object.values(v).forEach(item => consume(item, 'maybe')); } function render(raw, r, env, certainty = 'active') { const n = unwrap(raw); if (!n) return; if (n.type === 'JSXExpressionContainer') { render(n.expression, r, env, certainty); return; } if (n.type === 'JSXFragment') { n.children.forEach(c => render(c, r, env, certainty)); return; } if (n.type === 'JSXElement') { const o = n.openingElement; mark(o, certainty); const jsxName = name => name.type === 'JSXIdentifier' ? name.name : `${jsxName(name.object)}.${name.property.name}`; const name = jsxName(o.name); const component = name.includes('.') ? value({type:'MemberExpression',object:{type:'Identifier',name:o.name.object.name},property:{type:'Identifier',name:o.name.property.name},computed:false}, r, env) : value({ type: 'Identifier', name }, r, env); const id = component?.official ?? origin(r.file, name); if (component?.official) { if (!identities.has(o)) identities.set(o, []); if (!identities.get(o).some(x => x.name === id.name && x.pkg === id.pkg)) identities.get(o).push(id); } const props = {}; let spread = false; for (const a of o.attributes) { if (a.type === 'JSXSpreadAttribute') { spread = true; uncertain(a.argument, env); continue; } props[a.name.name] = a.value == null ? true : value(a.value.type === 'JSXExpressionContainer' ? a.value.expression : a.value, r, env); } if (name === 'DashboardLayout' || id?.name === 'DashboardLayout') { const candidate = spread ? 'unknown' : !('sidebar' in props) || [undefined, null, false].includes(props.sidebar) ? 'empty' : props.sidebar === unknown || props.sidebar?.branches ? 'unknown' : 'present'; const sv = certainty === 'maybe' && candidate === 'present' ? 'unknown' : candidate; const old = sidebarValues.get(o); sidebarValues.set(o, old === 'present' || sv === 'present' ? 'present' : old === 'unknown' || sv === 'unknown' ? 'unknown' : 'empty'); } if (component?.fn) { if (n.children.some(c => c.type !== 'JSXText')) props.children = { jsx: { type: 'JSXFragment', children: n.children }, r, env }; invoke(component, spread ? unknown : props, certainty); } else { // Official and external components may forward render-valued props/children. Object.values(props).forEach(v => consume(v, certainty)); n.children.forEach(c => render(c, r, env, certainty)); } return; } const v = value(n, r, env); if (v === unknown) uncertain(n, env); else consume(v, certainty); } let mounted = false; for (const r of modules.values()) walk(r.ast, n => { if (n.type === 'CallExpression' && (n.callee.type === 'MemberExpression' && n.callee.property.name === 'render' || n.callee.name === 'hydrateRoot')) { mounted = true; render(n.callee.name === 'hydrateRoot' ? n.arguments[1] : n.arguments[0], r, r.navEnv); } }); if (!mounted) for (const file of roots) invoke(exported(modules.get(file), 'default'), {}, 'active'); return { state, sidebarValues, identities }; }