Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
820ff83
chore: remove dead duplicate type declarations from types.ts (closes …
carlos-alm Jul 2, 2026
9fa4279
chore: remove unused iterComplexity export from complexity-query.ts (…
carlos-alm Jul 2, 2026
0f9bbe6
refactor: extract shared aggregate and typed-array helpers from leide…
carlos-alm Jul 2, 2026
f3e1119
refactor: extract shared name-map scanner into scripts/lib/name-map.mjs
carlos-alm Jul 2, 2026
e945bca
fix: replace event-loop-blocking Atomics.wait with shared sleepSync i…
carlos-alm Jul 2, 2026
4a348db
refactor: extract shared resolveFileTree helper from dataflow.ts and …
carlos-alm Jul 2, 2026
8fed8bc
refactor: extend DEFAULTS with previously-hardcoded config constants
carlos-alm Jul 2, 2026
f0a4882
refactor: move generatePlotHTML from features/graph-enrichment.ts to …
carlos-alm Jul 2, 2026
370f336
refactor: decompose extractDestructuredBindingsWalk/handleVariableDec…
carlos-alm Jul 2, 2026
ad14cf7
refactor: decompose resolveFallbackTargets/buildEdges/buildCallEdgesN…
carlos-alm Jul 2, 2026
51c3816
refactor: extract NativeOrchestrationSession from tryNativeOrchestrat…
carlos-alm Jul 2, 2026
63ab855
refactor: split embedRemote into request-executor and response-valida…
carlos-alm Jul 2, 2026
f31468c
fix: correct in-place mutation bug in applyExcludeTestsShorthand and …
carlos-alm Jul 2, 2026
57d3782
fix: correct connection-leak ordering in openReadonlyWithNative, dedu…
carlos-alm Jul 2, 2026
506e2ce
fix: add debug() logging to silent catch blocks across builder pipeli…
carlos-alm Jul 2, 2026
dbf34b8
refactor: split buildChaContext into three focused builder functions …
carlos-alm Jul 2, 2026
a1946af
refactor: split purgeAndAddReverseDeps and wire fast-skip-diag via co…
carlos-alm Jul 2, 2026
0e83ba0
refactor: extract getOrCreateBatchStmt, dedupe batch-insert helpers (…
carlos-alm Jul 2, 2026
c0c1f7d
fix: address quality issues in domain/graph/resolver (docs check ackn…
carlos-alm Jul 2, 2026
57143a8
fix: adopt buildFileConditionSQL in prepare.ts and move console.log o…
carlos-alm Jul 2, 2026
21db9a9
fix: address quality issues in graph unified model (model.ts merge() …
carlos-alm Jul 2, 2026
f7ce310
fix: address quality issues in features/complexity-query.ts (docs che…
carlos-alm Jul 2, 2026
5b708ee
fix: address quality issues in features/cochange.ts (docs check ackno…
carlos-alm Jul 2, 2026
7c3b869
fix: address quality issues in features/branch-compare.ts (docs check…
carlos-alm Jul 2, 2026
ce84353
fix: address quality issues in ast-analysis (docs check acknowledged)
carlos-alm Jul 2, 2026
9946db5
fix: decompose renderAuditFunction, adopt typed AuditResult (docs che…
carlos-alm Jul 2, 2026
210abd2
fix: decompose highest-complexity extractor functions (docs check ack…
carlos-alm Jul 2, 2026
cb5bc85
refactor: split execute() into printEngineInfo/printNativeVersionInfo…
carlos-alm Jul 2, 2026
387dabe
refactor: extract timeMedian helper in token-benchmark.ts
carlos-alm Jul 2, 2026
360b16d
fix: remove non-null assertion on loadNative() in printNativeVersionI…
carlos-alm Jul 4, 2026
cff78f6
fix: drop unneeded double cast in audit() call to outputResult (#1790)
carlos-alm Jul 4, 2026
65f9cab
refactor: address warnings in benchmark tracer tooling and ast-analys…
carlos-alm Jul 5, 2026
50069a4
fix: resolve merge conflicts with main
carlos-alm Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 29 additions & 30 deletions scripts/token-benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,20 @@ function round1(n) {
return Math.round(n * 10) / 10;
}

/**
* Run `fn` `runs` times (default `PERF_RUNS`), recording the elapsed
* milliseconds per run, and return the median duration.
*/
async function timeMedian(fn, runs = PERF_RUNS) {
const timings = [];
for (let i = 0; i < runs; i++) {
const start = performance.now();
await fn();
timings.push(performance.now() - start);
}
return median(timings);
}

/**
* Run build/query/stats benchmarks against the Next.js graph.
* Reuses the same codegraph APIs as the existing benchmark scripts.
Expand Down Expand Up @@ -306,24 +320,18 @@ async function runPerfBenchmarks(nextjsDir) {
const buildResults = {};
for (const engine of engines) {
console.error(` Full build (${engine})...`);
const timings = [];
for (let i = 0; i < PERF_RUNS; i++) {
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
const start = performance.now();
await buildGraph(nextjsDir, { engine, incremental: false });
timings.push(performance.now() - start);
}
const fullBuildMs = Math.round(median(timings));
const fullBuildMs = Math.round(
await timeMedian(async () => {
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
await buildGraph(nextjsDir, { engine, incremental: false });
}),
);

// No-op rebuild
console.error(` No-op rebuild (${engine})...`);
const noopTimings = [];
for (let i = 0; i < PERF_RUNS; i++) {
const start = performance.now();
await buildGraph(nextjsDir, { engine, incremental: true });
noopTimings.push(performance.now() - start);
}
const noopRebuildMs = Math.round(median(noopTimings));
const noopRebuildMs = Math.round(
await timeMedian(() => buildGraph(nextjsDir, { engine, incremental: true })),
);

buildResults[engine] = { fullBuildMs, noopRebuildMs };
console.error(` full=${fullBuildMs}ms noop=${noopRebuildMs}ms`);
Expand Down Expand Up @@ -370,23 +378,14 @@ async function runPerfBenchmarks(nextjsDir) {

for (const depth of [1, 3, 5]) {
// fnDeps
const depsTimings = [];
for (let i = 0; i < PERF_RUNS; i++) {
const start = performance.now();
fnDepsData(hubName, dbPath, { depth, noTests: true });
depsTimings.push(performance.now() - start);
}
queryResults[`fnDeps_depth${depth}Ms`] = round1(
await timeMedian(() => fnDepsData(hubName, dbPath, { depth, noTests: true })),
);

// fnImpact
const impactTimings = [];
for (let i = 0; i < PERF_RUNS; i++) {
const start = performance.now();
fnImpactData(hubName, dbPath, { depth, noTests: true });
impactTimings.push(performance.now() - start);
}

queryResults[`fnDeps_depth${depth}Ms`] = round1(median(depsTimings));
queryResults[`fnImpact_depth${depth}Ms`] = round1(median(impactTimings));
queryResults[`fnImpact_depth${depth}Ms`] = round1(
await timeMedian(() => fnImpactData(hubName, dbPath, { depth, noTests: true })),
);
}

console.error(
Expand Down
167 changes: 115 additions & 52 deletions src/ast-analysis/visitor-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,81 @@ export function extractParams(
return result;
}

/**
* Resolution result for a single node in the parameter-name worklist: either
* a base case with names to record, or intermediate `next` nodes that still
* need to be resolved.
*/
type ParamNodeResolution = { names?: string[]; next?: TreeSitterNode[] };

/** One entry in the node-type -> handler dispatch table used by `resolveParamNode`. */
interface ParamNodeHandler {
matches(nodeType: string, rules: LanguageRules): boolean;
resolve(node: TreeSitterNode, rules: LanguageRules): ParamNodeResolution | null;
}

function resolveWrapperParam(node: TreeSitterNode): ParamNodeResolution | null {
const pattern = node.childForFieldName('pattern') || node.childForFieldName('name');
return pattern ? { next: [pattern] } : null;
}

function resolveDefaultParam(node: TreeSitterNode): ParamNodeResolution | null {
const left = node.childForFieldName('left') || node.childForFieldName('name');
return left ? { next: [left] } : null;
}

function resolveRestParam(node: TreeSitterNode, rules: LanguageRules): ParamNodeResolution | null {
const nameNode = node.childForFieldName('name');
if (nameNode) return { names: [nameNode.text] };
for (const child of node.namedChildren) {
if (child.type === rules.paramIdentifier) return { names: [child.text] };
}
return null;
}

function resolveObjectDestructParam(
node: TreeSitterNode,
rules: LanguageRules,
): ParamNodeResolution {
return { next: collectObjectDestructChildren(node, rules) };
}

function resolveArrayDestructParam(node: TreeSitterNode): ParamNodeResolution {
return { next: [...node.namedChildren] };
}

/**
* Ordered node-type -> handler dispatch table for `resolveParamNode`. Order
* matters: earlier entries take precedence, matching the original
* if/else-if cascade exactly.
*/
const PARAM_NODE_HANDLERS: ParamNodeHandler[] = [
{
matches: (t, rules) => t === rules.paramIdentifier,
resolve: (node) => ({ names: [node.text] }),
},
{
matches: (t, rules) => rules.paramWrapperTypes.has(t),
resolve: resolveWrapperParam,
},
{
matches: (t, rules) => !!rules.defaultParamType && t === rules.defaultParamType,
resolve: resolveDefaultParam,
},
{
matches: (t, rules) => !!rules.restParamType && t === rules.restParamType,
resolve: resolveRestParam,
},
{
matches: (t, rules) => !!rules.objectDestructType && t === rules.objectDestructType,
resolve: resolveObjectDestructParam,
},
{
matches: (t, rules) => !!rules.arrayDestructType && t === rules.arrayDestructType,
resolve: resolveArrayDestructParam,
},
];

/**
* Resolve a single parameter node to either a direct list of names (base case)
* or a list of child nodes that still need processing. Returns `null` if the
Expand All @@ -102,46 +177,16 @@ export function extractParams(
* `extractParamNames`, breaking the 3-node mutual recursion cycle between
* `extractParamNames`, `extractObjectDestructNames`, and `extractArrayDestructNames`.
*/
function resolveParamNode(
node: TreeSitterNode,
rules: LanguageRules,
): { names?: string[]; next?: TreeSitterNode[] } | null {
const t = node.type;

function resolveParamNode(node: TreeSitterNode, rules: LanguageRules): ParamNodeResolution | null {
if (rules.extractParamName) {
const result = rules.extractParamName(node);
if (result) return { names: result };
}

if (t === rules.paramIdentifier) return { names: [node.text] };

if (rules.paramWrapperTypes.has(t)) {
const pattern = node.childForFieldName('pattern') || node.childForFieldName('name');
return pattern ? { next: [pattern] } : null;
}

if (rules.defaultParamType && t === rules.defaultParamType) {
const left = node.childForFieldName('left') || node.childForFieldName('name');
return left ? { next: [left] } : null;
}

if (rules.restParamType && t === rules.restParamType) {
const nameNode = node.childForFieldName('name');
if (nameNode) return { names: [nameNode.text] };
for (const child of node.namedChildren) {
if (child.type === rules.paramIdentifier) return { names: [child.text] };
}
return null;
}

if (rules.objectDestructType && t === rules.objectDestructType) {
return { next: collectObjectDestructChildren(node, rules) };
}

if (rules.arrayDestructType && t === rules.arrayDestructType) {
return { next: [...node.namedChildren] };
const t = node.type;
for (const handler of PARAM_NODE_HANDLERS) {
if (handler.matches(t, rules)) return handler.resolve(node, rules);
}

return null;
}

Expand Down Expand Up @@ -170,6 +215,41 @@ function collectObjectDestructChildren(
return next;
}

/** Is this node a shorthand identifier inside an object destructuring pattern? */
function isShorthandPropPattern(node: TreeSitterNode, rules: LanguageRules): boolean {
return !!rules.shorthandPropPattern && node.type === rules.shorthandPropPattern;
}

/**
* Push nodes onto the worklist stack in reverse order so that popping them
* (LIFO) visits them in the same left-to-right order as the original
* recursive traversal.
*/
function pushParamWorklist(stack: TreeSitterNode[], nodes: TreeSitterNode[]): void {
for (let i = nodes.length - 1; i >= 0; i--) {
const child = nodes[i];
if (child) stack.push(child);
}
}

/** Resolve one worklist entry: record any names, queue any further nodes to visit. */
function visitParamWorklistNode(
current: TreeSitterNode,
rules: LanguageRules,
names: string[],
stack: TreeSitterNode[],
): void {
if (isShorthandPropPattern(current, rules)) {
names.push(current.text);
return;
}

const resolved = resolveParamNode(current, rules);
if (!resolved) return;
if (resolved.names) names.push(...resolved.names);
if (resolved.next) pushParamWorklist(stack, resolved.next);
}

/**
* Extract parameter names from a single parameter node.
*
Expand All @@ -184,24 +264,7 @@ export function extractParamNames(node: TreeSitterNode | null, rules: LanguageRu

while (stack.length > 0) {
const current = stack.pop();
if (!current) continue;

// Shorthand identifier inside an object destructuring is just the node's text.
if (rules.shorthandPropPattern && current.type === rules.shorthandPropPattern) {
names.push(current.text);
continue;
}

const resolved = resolveParamNode(current, rules);
if (!resolved) continue;
if (resolved.names) names.push(...resolved.names);
if (resolved.next) {
// Push in reverse so traversal order matches the previous recursive order.
for (let i = resolved.next.length - 1; i >= 0; i--) {
const child = resolved.next[i];
if (child) stack.push(child);
}
}
if (current) visitParamWorklistNode(current, rules, names, stack);
}

return names;
Expand Down
Loading
Loading