style(cfg): apply prettier + drop unused imports across the PDG files (#2195)

The merge of main into this branch pulled in the stricter quality gates
(prettier --check . and eslint .), which surfaced pre-existing formatting in the
PDG/CFG rollout (line-width wrapping across the visitor + harvest files, bench,
tests) plus 9 no-unused-imports errors. Mechanical autofix only — npm run
format + lint:fix equivalent, scoped to gitnexus/: removes unused FunctionCfg/
SiteRecord type imports left by the U8 helper consolidation and stale
FinalizerFrame imports in python.ts/ruby.ts. No behavior change: tsc clean, cfg
unit suite 617 passed, eslint 0 errors. (gitnexus-web class-order noise is a
local tailwind-plugin artifact CI does not flag — left untouched.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-06-14 17:29:48 +00:00
parent 8b704aa9a3
commit 213ae2526e
33 changed files with 307 additions and 230 deletions

View file

@ -403,7 +403,12 @@ function canonicalizeCfg(cfg) {
}
function fingerprint(tk, scenario) {
const out = collectFunctionCfgs(tk.parse(scenario.gen(FP_SIZE)).rootNode, tk.visitor, 'fp', NO_CAP);
const out = collectFunctionCfgs(
tk.parse(scenario.gen(FP_SIZE)).rootNode,
tk.visitor,
'fp',
NO_CAP,
);
const canon = out.cfgs.map(canonicalizeCfg).sort().join('\n====\n');
return {
fingerprint: crypto.createHash('sha256').update(canon).digest('hex'),

View file

@ -79,10 +79,7 @@
* is the documented, intentional trade-off: recover the common goto-cycle case;
* surface anything with a genuine residual anomaly rather than guess.
*/
import {
isExitReachableFromAllBlocks,
type PostDomTree,
} from './post-dominators.js';
import { isExitReachableFromAllBlocks, type PostDomTree } from './post-dominators.js';
import type { CfgEdgeData, FunctionCfg } from './types.js';
/**
@ -285,8 +282,7 @@ export function augmentForPostDom(cfg: FunctionCfg): FunctionCfg {
// acyclic block (ENTRY / the spine) that is exit-unreachable only because it
// FEEDS a trap downstream; it gets its path to EXIT for free once the trap is
// bridged, so it is never bridged on its own.
const isCycle =
comp.length > 1 || cfg.edges.some((e) => e.from === rep && e.to === rep);
const isCycle = comp.length > 1 || cfg.edges.some((e) => e.from === rep && e.to === rep);
if (!isCycle) continue;
// Controlling representative: the entry-reachable member with a branch

View file

@ -193,7 +193,12 @@ class CCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -561,7 +566,9 @@ class CCfgWalk {
finishGotos(): void {
for (const [label, froms] of this.pendingGotos) {
// eslint-disable-next-line no-console
console.warn(`[cfg] unresolved goto label "${label}" routed to EXIT (${froms.length} site(s))`);
console.warn(
`[cfg] unresolved goto label "${label}" routed to EXIT (${froms.length} site(s))`,
);
for (const from of froms) this.builder.edge(from, this.builder.exitIndex, 'seq');
}
this.pendingGotos.clear();

View file

@ -183,7 +183,12 @@ class CsharpCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -555,9 +560,7 @@ class CsharpCfgWalk {
this.cfc.pushSwitch(switchExit);
const body = stmt.childForFieldName('body');
const sections = body
? body.namedChildren.filter((c) => c.type === 'switch_section')
: [];
const sections = body ? body.namedChildren.filter((c) => c.type === 'switch_section') : [];
// Each `switch_section`'s case-test patterns/`when` clauses evaluate before
// the body runs — harvest their uses (and any pattern binding as a may-def)
@ -599,9 +602,7 @@ class CsharpCfgWalk {
/** A switch_section's body statements (everything but its case labels/patterns). */
private sectionStatements(section: SyntaxNode): SyntaxNode[] {
return section.namedChildren.filter(
(c) => this.isStatementLike(c) && c.type !== 'comment',
);
return section.namedChildren.filter((c) => this.isStatementLike(c) && c.type !== 'comment');
}
/** The case-test expressions of a section (constant patterns + when clauses). */
@ -655,13 +656,7 @@ class CsharpCfgWalk {
// The resource (declaration or expression) is the named child before `body`.
const resource = this.usingResource(stmt);
const disposeFacts = resource ? this.harvest.facts(resource) : undefined;
return this.buildProtectedSynthetic(
bodyNode,
stmt,
'dispose',
disposeFacts,
resource ?? stmt,
);
return this.buildProtectedSynthetic(bodyNode, stmt, 'dispose', disposeFacts, resource ?? stmt);
}
/**
@ -704,9 +699,7 @@ class CsharpCfgWalk {
for (const clause of catchClauses) {
const clauseBody = clause.childForFieldName('body');
if (finallyRes) this.handlers.push(finallyRes.entry);
let res: SeqResult = clauseBody
? this.visitSeq(this.statementsOf(clauseBody))
: null;
let res: SeqResult = clauseBody ? this.visitSeq(this.statementsOf(clauseBody)) : null;
if (finallyRes) this.handlers.pop();
if (res === null) {
// Empty `catch {}` still catches — synthesize one block so exception
@ -851,8 +844,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
// The body is a `block` (field `body`) OR an `arrow_expression_clause`
// (expression-bodied member) OR an expression (single-expression lambda).
const body =
fnNode.childForFieldName('body') ??
fnNode.namedChildren.find((c) => c.type === 'block');
fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'block');
if (!body) return undefined; // abstract / partial / interface member — no body
const builder = new CfgBuilder(filePath, startLine, endLine, startColumn);
@ -864,8 +856,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
if (body.type === 'arrow_expression_clause' || body.type !== 'block') {
// Expression-bodied member / single-expression lambda: one block whose
// value is returned. For an arrow clause the value is its inner expression.
const expr =
body.type === 'arrow_expression_clause' ? (body.namedChild(0) ?? body) : body;
const expr = body.type === 'arrow_expression_clause' ? (body.namedChild(0) ?? body) : body;
const blk = builder.newBlock(
startLineOf(expr),
endLineOf(expr),

View file

@ -159,7 +159,8 @@ export class DartHarvester {
const names: SyntaxNode[] = [];
for (const p of list.namedChildren) {
if (p.type !== 'formal_parameter') continue;
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
const name =
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
if (name) names.push(name);
}
return names;
@ -427,7 +428,10 @@ export class DartHarvester {
// `.name` / `(...args)` — a member-access suffix name is not a scalar
// binding; walk the argument part for uses but skip the bare property id.
for (const c of node.namedChildren) {
if (c.type === 'unconditional_assignable_selector' || c.type === 'conditional_assignable_selector') {
if (
c.type === 'unconditional_assignable_selector' ||
c.type === 'conditional_assignable_selector'
) {
continue; // `.name` — property name is not a use
}
this.walkValue(c, acc);

View file

@ -152,13 +152,11 @@ const isComment = (n: SyntaxNode): boolean => COMMENT_TYPES.has(n.type);
/** Whether an `expression_statement` is a bare `throw …;`. */
const isThrowStatement = (n: SyntaxNode): boolean =>
n.type === 'expression_statement' &&
n.namedChildren.some((c) => c.type === 'throw_expression');
n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'throw_expression');
/** Whether an `expression_statement` is a bare `rethrow;`. */
const isRethrowStatement = (n: SyntaxNode): boolean =>
n.type === 'expression_statement' &&
n.namedChildren.some((c) => c.type === 'rethrow_expression');
n.type === 'expression_statement' && n.namedChildren.some((c) => c.type === 'rethrow_expression');
/** A statement sequence that produced no blocks (empty body) is "transparent". */
type SeqResult = TraversalResult | null;
@ -221,7 +219,12 @@ class DartCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -630,7 +633,11 @@ class DartCfgWalk {
const contLabel = this.caseContinueLabel(cases[i]);
if (!res) {
// Empty case — spill to the next case (or switchExit).
this.builder.edge(dispatch, i + 1 < cases.length ? entryOf[i + 1] : switchExit, 'fallthrough');
this.builder.edge(
dispatch,
i + 1 < cases.length ? entryOf[i + 1] : switchExit,
'fallthrough',
);
continue;
}
if (contLabel) {
@ -887,9 +894,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
}
const walk = new DartCfgWalk(builder, harvest);
const res = block
? walk.visitSeq(block.namedChildren.filter((c) => !isComment(c)))
: null;
const res = block ? walk.visitSeq(block.namedChildren.filter((c) => !isComment(c))) : null;
if (!res) {
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body

View file

@ -542,8 +542,7 @@ export class GoHarvester extends ScopeTreeHarvester {
for (const target of targets) {
// A `var_spec` carries its name(s) as children; an identifier resolves
// directly. Skip the blank identifier (`_`), which binds nothing.
const names =
target.type === 'identifier' ? [target] : this.listIdentifiers(target);
const names = target.type === 'identifier' ? [target] : this.listIdentifiers(target);
for (const n of names) {
if (n.text === '_') continue;
defs.push(this.resolve(n));

View file

@ -112,11 +112,7 @@ import type { CfgVisitor, FunctionCfg } from '../types.js';
import { GoHarvester } from './go-harvest.js';
/** Go node types that own a CFG-bearing function body. */
const GO_FUNCTION_TYPES = new Set([
'function_declaration',
'method_declaration',
'func_literal',
]);
const GO_FUNCTION_TYPES = new Set(['function_declaration', 'method_declaration', 'func_literal']);
/** Statement node types that break a basic block (everything else coalesces). */
const CONTROL_FLOW_TYPES = new Set([
@ -212,7 +208,12 @@ class GoCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -347,7 +348,8 @@ class GoCfgWalk {
private visitLabeled(stmt: SyntaxNode): SeqResult {
const labelNode = stmt.childForFieldName('label');
const label = labelNode?.text;
const body = stmt.namedChildren.find((c) => c.id !== labelNode?.id && c.type !== 'comment') ?? null;
const body =
stmt.namedChildren.find((c) => c.id !== labelNode?.id && c.type !== 'comment') ?? null;
if (label !== undefined && body && this.isBreakableStatement(body)) {
// Forward the label to the loop/switch/select frame this statement pushes.
@ -699,9 +701,7 @@ class GoCfgWalk {
if (test) this.builder.attachFacts(dispatch, this.harvest.factsConditional(test));
}
const result = this.buildCases(dispatch, switchExit, cases, (c) =>
this.exprCaseBody(c),
);
const result = this.buildCases(dispatch, switchExit, cases, (c) => this.exprCaseBody(c));
this.cfc.pop();
return result;
}
@ -721,20 +721,14 @@ class GoCfgWalk {
const cases = stmt.namedChildren.filter(
(c) => c.type === 'type_case' || c.type === 'default_case',
);
const result = this.buildCases(dispatch, switchExit, cases, (c) =>
this.typeCaseBody(c),
);
const result = this.buildCases(dispatch, switchExit, cases, (c) => this.typeCaseBody(c));
this.cfc.pop();
return result;
}
private visitSelect(stmt: SyntaxNode): TraversalResult {
const labels = this.takeLabels();
const dispatch = this.builder.newBlock(
startLineOf(stmt),
startLineOf(stmt),
'select',
);
const dispatch = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), 'select');
const selectExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
this.cfc.pushSwitch(selectExit, labels);
@ -751,9 +745,7 @@ class GoCfgWalk {
}
const hasDefault = cases.some((c) => c.type === 'default_case');
const result = this.buildCases(dispatch, selectExit, cases, (c) =>
this.commCaseBody(c),
);
const result = this.buildCases(dispatch, selectExit, cases, (c) => this.commCaseBody(c));
// A `select` with NO default BLOCKS until a case is ready — and a `select {}`
// with no cases at all blocks forever. Either way EXIT must stay

View file

@ -220,7 +220,12 @@ class JavaCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}

View file

@ -151,7 +151,8 @@ export class KotlinHarvester {
if (vd.type === 'variable_declaration') this.declareVariableDeclaration(vd, 'param');
else if (vd.type === 'multi_variable_declaration') {
for (const inner of vd.namedChildren) {
if (inner.type === 'variable_declaration') this.declareVariableDeclaration(inner, 'param');
if (inner.type === 'variable_declaration')
this.declareVariableDeclaration(inner, 'param');
}
}
}

View file

@ -224,7 +224,12 @@ class KotlinCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -835,9 +840,7 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
}
const walk = new KotlinCfgWalk(builder, harvest);
const res = body
? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c)))
: null;
const res = body ? walk.visitSeq(body.namedChildren.filter((c) => !isComment(c))) : null;
if (!res) {
builder.edge(builder.entryIndex, builder.exitIndex, 'seq'); // empty body

View file

@ -625,7 +625,10 @@ export class PhpHarvester {
const accesses: string[] = [];
let cur: SyntaxNode = this.unwrapParen(node);
for (;;) {
if (cur.type === 'member_access_expression' || cur.type === 'nullsafe_member_access_expression') {
if (
cur.type === 'member_access_expression' ||
cur.type === 'nullsafe_member_access_expression'
) {
const field = cur.childForFieldName('name');
accesses.unshift(field?.text ?? '');
const obj = cur.childForFieldName('object');

View file

@ -211,7 +211,12 @@ class PhpCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}

View file

@ -146,9 +146,7 @@ export class PythonHarvester {
private declareParams(fnNode: SyntaxNode): void {
const params =
fnNode.childForFieldName('parameters') ??
fnNode.namedChildren.find(
(c) => c.type === 'parameters' || c.type === 'lambda_parameters',
);
fnNode.namedChildren.find((c) => c.type === 'parameters' || c.type === 'lambda_parameters');
if (!params) return;
for (let i = 0; i < params.namedChildCount; i++) {
const p = params.namedChild(i);

View file

@ -101,7 +101,6 @@ import {
drainFinalizerPending,
wireJumpThroughFinalizers,
} from '../control-flow-context.js';
import type { FinalizerFrame } from '../control-flow-context.js';
import type { TraversalResult } from '../traversal-result.js';
import type { CfgVisitor, FunctionCfg } from '../types.js';
import { PythonHarvester } from './python-harvest.js';
@ -191,7 +190,12 @@ class PythonCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -461,7 +465,11 @@ class PythonCfgWalk {
return alt?.type === 'else_clause' ? alt : undefined;
}
private loopHeaderText(stmt: SyntaxNode, left: SyntaxNode | null, right: SyntaxNode | null): string {
private loopHeaderText(
stmt: SyntaxNode,
left: SyntaxNode | null,
right: SyntaxNode | null,
): string {
const l = left?.text ?? '';
const r = right?.text ?? '';
return l || r ? `for ${l} in ${r}` : stmt.text.split('\n')[0];
@ -550,7 +558,8 @@ class PythonCfgWalk {
// runs OUTSIDE this try's finalizer frame (a return inside finally threads
// only OUTER finallys).
const finallyBlock = finallyClause
? this.bodyBlockOf(finallyClause) ?? finallyClause.namedChildren.find((c) => c.type === 'block')
? (this.bodyBlockOf(finallyClause) ??
finallyClause.namedChildren.find((c) => c.type === 'block'))
: undefined;
const finallyRes = finallyBlock ? this.visitSeq(this.statementsOf(finallyBlock)) : null;
const finFrame = finallyRes ? this.cfc.pushFinalizer(finallyRes.entry) : null;
@ -565,7 +574,13 @@ class PythonCfgWalk {
// The `except E as e:` header binds `e` — its own facts-only block in front
// of the handler body (the binding happens once, on handler entry).
const headFacts = this.harvest.exceptHeadFacts(clause);
const headBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', headFacts);
const headBlock = this.builder.newBlock(
startLineOf(clause),
startLineOf(clause),
'',
'normal',
headFacts,
);
const bodyRes = handlerBlock ? this.visitSeq(this.statementsOf(handlerBlock)) : null;
if (bodyRes) {
this.builder.edge(headBlock, bodyRes.entry, 'seq');
@ -650,7 +665,8 @@ class PythonCfgWalk {
);
const matchExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
const body = stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'block');
const body =
stmt.childForFieldName('body') ?? stmt.namedChildren.find((c) => c.type === 'block');
const cases = body ? body.namedChildren.filter((c) => c.type === 'case_clause') : [];
// A case guard (`case P if g:`) evaluates conditionally — harvest its uses

View file

@ -167,7 +167,8 @@ export class RubyHarvester {
return;
}
if (NAMED_PARAM_TYPES.has(p.type)) {
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
const name =
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
if (name) this.declare(name, 'param');
return;
}
@ -319,7 +320,8 @@ export class RubyHarvester {
return;
}
if (NAMED_PARAM_TYPES.has(p.type)) {
const name = p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
const name =
p.childForFieldName('name') ?? p.namedChildren.find((c) => c.type === 'identifier');
if (name) this.def(name, acc);
return;
}

View file

@ -108,19 +108,12 @@ import {
drainFinalizerPending,
wireJumpThroughFinalizers,
} from '../control-flow-context.js';
import type { FinalizerFrame } from '../control-flow-context.js';
import type { TraversalResult } from '../traversal-result.js';
import type { CfgVisitor, FunctionCfg } from '../types.js';
import { RubyHarvester } from './ruby-harvest.js';
/** Ruby node types that own a CFG-bearing function/closure body. */
const RUBY_FUNCTION_TYPES = new Set([
'method',
'singleton_method',
'do_block',
'block',
'lambda',
]);
const RUBY_FUNCTION_TYPES = new Set(['method', 'singleton_method', 'do_block', 'block', 'lambda']);
/** Statement node types that break a basic block (everything else coalesces). */
const CONTROL_FLOW_TYPES = new Set([
@ -230,7 +223,12 @@ class RubyCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
}
}
@ -666,7 +664,8 @@ class RubyCfgWalk {
const caseExit = this.builder.newBlock(endLineOf(stmt), endLineOf(stmt), '');
const clauses = stmt.namedChildren.filter((c) => c.type === 'in_clause');
const elseClause = stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
const elseClause =
stmt.childForFieldName('else') ?? stmt.namedChildren.find((c) => c.type === 'else');
// An `in`-clause guard (`in P if g`) evaluates conditionally on the dispatch.
for (const c of clauses) {

View file

@ -137,9 +137,7 @@ export class RustHarvester {
private declareParams(fnNode: SyntaxNode): void {
const params =
fnNode.childForFieldName('parameters') ??
fnNode.namedChildren.find(
(c) => c.type === 'parameters' || c.type === 'closure_parameters',
);
fnNode.namedChildren.find((c) => c.type === 'parameters' || c.type === 'closure_parameters');
if (!params) return;
for (let i = 0; i < params.namedChildCount; i++) {
const p = params.namedChild(i);
@ -292,7 +290,8 @@ export class RustHarvester {
// Fallback: declare any identifier / shorthand leaf.
for (let i = 0; i < field.namedChildCount; i++) {
const c = field.namedChild(i);
if (c?.type === 'shorthand_field_identifier' || c?.type === 'identifier') this.declare(c, kind);
if (c?.type === 'shorthand_field_identifier' || c?.type === 'identifier')
this.declare(c, kind);
}
}

View file

@ -186,9 +186,7 @@ class RustCfgWalk {
dangling = [...res.exits];
} else {
const idx =
openSimple === undefined
? this.openBlock(stmt)
: this.extendOpen(openSimple, stmt);
openSimple === undefined ? this.openBlock(stmt) : this.extendOpen(openSimple, stmt);
if (openSimple === undefined) {
if (entry === undefined) entry = idx;
else this.builder.connect(dangling, idx, 'seq');

View file

@ -115,7 +115,9 @@ export class SwiftHarvester {
* `lambda_literal` carries the `statements` directly.
*/
private bodyOf(fnNode: SyntaxNode): SyntaxNode | undefined {
const fb = fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'function_body');
const fb =
fnNode.childForFieldName('body') ??
fnNode.namedChildren.find((c) => c.type === 'function_body');
if (fb && fb.type === 'function_body') {
return fb.namedChildren.find((c) => c.type === 'statements') ?? fb;
}
@ -156,7 +158,9 @@ export class SwiftHarvester {
for (const id of params.namedChildren) {
if (id.type === 'simple_identifier') this.declare(id, 'param');
else if (id.type === 'lambda_parameter') {
const name = id.childForFieldName('name') ?? id.namedChildren.find((c) => c.type === 'simple_identifier');
const name =
id.childForFieldName('name') ??
id.namedChildren.find((c) => c.type === 'simple_identifier');
if (name) this.declare(name, 'param');
}
}
@ -319,7 +323,9 @@ export class SwiftHarvester {
for (const id of params.namedChildren) {
if (id.type === 'simple_identifier') this.def(id, acc);
else if (id.type === 'lambda_parameter') {
const name = id.childForFieldName('name') ?? id.namedChildren.find((c) => c.type === 'simple_identifier');
const name =
id.childForFieldName('name') ??
id.namedChildren.find((c) => c.type === 'simple_identifier');
if (name) this.def(name, acc);
}
}
@ -447,10 +453,11 @@ export class SwiftHarvester {
// `try expr` / `try? expr` / `try! expr` — the wrapped expression's uses.
const expr = node.childForFieldName('expr');
if (expr) this.walkValue(expr, acc);
else for (let i = 0; i < node.namedChildCount; i++) {
const c = node.namedChild(i);
if (c && c.type !== 'try_operator') this.walkValue(c, acc);
}
else
for (let i = 0; i < node.namedChildCount; i++) {
const c = node.namedChild(i);
if (c && c.type !== 'try_operator') this.walkValue(c, acc);
}
return;
}
case 'conjunction_expression':

View file

@ -162,7 +162,9 @@ class SwiftCfgWalk {
/** Statements of a `statements` node, ignoring comments. */
private statementsOf(block: SyntaxNode): SyntaxNode[] {
return block.namedChildren.filter((c) => c.type !== 'comment' && c.type !== 'multiline_comment');
return block.namedChildren.filter(
(c) => c.type !== 'comment' && c.type !== 'multiline_comment',
);
}
/** The body `statements` of a loop/branch node (the LAST `statements` child). */
@ -206,7 +208,12 @@ class SwiftCfgWalk {
openSimple = idx;
dangling = [idx];
} else {
this.builder.extendBlock(openSimple, endLineOf(stmt), stmt.text, this.harvest.facts(stmt));
this.builder.extendBlock(
openSimple,
endLineOf(stmt),
stmt.text,
this.harvest.facts(stmt),
);
}
// A straight-line statement may contain a `try` — it can error-return.
this.wireTryExits(stmt, openSimple);
@ -661,8 +668,10 @@ class SwiftCfgWalk {
}
private entryIsDefault(entry: SyntaxNode): boolean {
return entry.namedChildren.some((c) => c.type === 'default_keyword') ||
entry.children.some((c) => c.type === 'default_keyword');
return (
entry.namedChildren.some((c) => c.type === 'default_keyword') ||
entry.children.some((c) => c.type === 'default_keyword')
);
}
/** A `switch_entry` ends in an explicit `fallthrough` keyword child. */
@ -701,7 +710,13 @@ class SwiftCfgWalk {
}
const errFacts = this.harvest.catchErrorFacts(clause);
if (errFacts) {
const errBlock = this.builder.newBlock(startLineOf(clause), startLineOf(clause), '', 'normal', errFacts);
const errBlock = this.builder.newBlock(
startLineOf(clause),
startLineOf(clause),
'',
'normal',
errFacts,
);
this.builder.edge(errBlock, res.entry, 'seq');
res = { entry: errBlock, exits: res.exits };
}
@ -896,7 +911,8 @@ function buildFunctionCfg(fnNode: SyntaxNode, filePath: string): FunctionCfg | u
*/
function bodyStatementsOf(fnNode: SyntaxNode): SyntaxNode | undefined {
const fb =
fnNode.childForFieldName('body') ?? fnNode.namedChildren.find((c) => c.type === 'function_body');
fnNode.childForFieldName('body') ??
fnNode.namedChildren.find((c) => c.type === 'function_body');
if (fb && fb.type === 'function_body') {
return fb.namedChildren.find((c) => c.type === 'statements');
}

View file

@ -17,11 +17,7 @@ import { emitTsScopeCaptures } from '../../src/core/ingestion/languages/typescri
import { interpretTsImport } from '../../src/core/ingestion/languages/typescript/interpret.js';
import { makeCfgHarness } from './cfg-harness.js';
const harness = makeCfgHarness(
TypeScript.typescript,
createTypeScriptCfgVisitor(),
'fixture.ts',
);
const harness = makeCfgHarness(TypeScript.typescript, createTypeScriptCfgVisitor(), 'fixture.ts');
export const parse = harness.parse;
export const collectFunctions = harness.collectFunctions;

View file

@ -447,8 +447,7 @@ describe('U7 — C-family worker-mode --pdg pipeline', () => {
// pdg:false ≡ pdg-absent — the dominant existing-user path is untouched.
expect(offDigest).toEqual(defaultDigest);
// None of the PDG node/rel types leak into the flag-off graph.
for (const t of ['BasicBlock'] as const)
expect(defaultDigest.byType[t]).toBeUndefined();
for (const t of ['BasicBlock'] as const) expect(defaultDigest.byType[t]).toBeUndefined();
for (const t of ['CFG', 'REACHING_DEF', 'CDG', 'TAINTED', 'SANITIZES'] as const)
expect(defaultDigest.byRelType[t]).toBeUndefined();
// Committed golden: the flag-off graph is pinned by snapshot so a future
@ -470,69 +469,77 @@ describe('U7 — remaining languages worker-mode --pdg pipeline (#2195 capstone)
!vendored || isLanguageAvailable(SupportedLanguages[lang as keyof typeof SupportedLanguages])
? it
: it.skip;
testFn(`${lang}: --pdg on emits BasicBlock + CFG + REACHING_DEF + CDG (> 0) via the worker`, async () => {
const result = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_PDG);
// The CFG is built in the worker — a stale dist silently zeros this.
expect(result.usedWorkerPool, `${lang} used the worker pool`).toBe(true);
const { basicBlocks, cfgEdges, reachingDefs, cdg } = counts(result);
expect(basicBlocks, `${lang} BasicBlock count`).toBeGreaterThan(0);
expect(cfgEdges, `${lang} CFG edge count`).toBeGreaterThan(0);
// def/use harvest populated the data-dependence layer.
expect(reachingDefs, `${lang} REACHING_DEF count`).toBeGreaterThan(0);
// CDG > 0 proves the post-dom/CDG pass ran for SOME function in the fixture.
expect(cdg, `${lang} CDG count`).toBeGreaterThan(0);
// The load-bearing claim (#2197 U3): ≥1 CDG edge is sourced INSIDE the
// non-terminating-loop function itself — so the post-dom/CDG pass was NOT
// skipped for the function whose loop traps EXIT, i.e. EXIT stays
// reverse-reachable end-to-end through the worker even with that loop. The
// whole-fixture aggregate above would pass on any branching function; this
// pins it to the hazard.
if (hazard !== undefined) {
expect(
cdgSourcedInHazardFunction(result, hazard),
`${lang} CDG edge sourced inside the non-terminating-loop function (marker ${JSON.stringify(hazard)})`,
).toBe(true);
}
// Both CFG and CDG endpoints are persisted BasicBlocks; CDG carries a T/F.
const blockIds = new Set<string>();
result.graph.forEachNode((n) => {
if (n.label === 'BasicBlock') blockIds.add(n.id);
});
for (const rel of result.graph.iterRelationships()) {
if (rel.type === 'CFG' || rel.type === 'REACHING_DEF' || rel.type === 'CDG') {
expect(blockIds.has(rel.sourceId), `${lang} ${rel.type} source is a BasicBlock`).toBe(
true,
);
expect(blockIds.has(rel.targetId), `${lang} ${rel.type} target is a BasicBlock`).toBe(
true,
);
testFn(
`${lang}: --pdg on emits BasicBlock + CFG + REACHING_DEF + CDG (> 0) via the worker`,
async () => {
const result = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_PDG);
// The CFG is built in the worker — a stale dist silently zeros this.
expect(result.usedWorkerPool, `${lang} used the worker pool`).toBe(true);
const { basicBlocks, cfgEdges, reachingDefs, cdg } = counts(result);
expect(basicBlocks, `${lang} BasicBlock count`).toBeGreaterThan(0);
expect(cfgEdges, `${lang} CFG edge count`).toBeGreaterThan(0);
// def/use harvest populated the data-dependence layer.
expect(reachingDefs, `${lang} REACHING_DEF count`).toBeGreaterThan(0);
// CDG > 0 proves the post-dom/CDG pass ran for SOME function in the fixture.
expect(cdg, `${lang} CDG count`).toBeGreaterThan(0);
// The load-bearing claim (#2197 U3): ≥1 CDG edge is sourced INSIDE the
// non-terminating-loop function itself — so the post-dom/CDG pass was NOT
// skipped for the function whose loop traps EXIT, i.e. EXIT stays
// reverse-reachable end-to-end through the worker even with that loop. The
// whole-fixture aggregate above would pass on any branching function; this
// pins it to the hazard.
if (hazard !== undefined) {
expect(
cdgSourcedInHazardFunction(result, hazard),
`${lang} CDG edge sourced inside the non-terminating-loop function (marker ${JSON.stringify(hazard)})`,
).toBe(true);
}
if (rel.type === 'CDG') expect(['T', 'F']).toContain(rel.reason);
}
}, 60000);
testFn(`${lang}: --pdg off emits zero PDG nodes/edges (the R3 flag-off gate)`, async () => {
// Default (no pdg flag) and explicit pdg:false must both produce a graph
// with NO PDG layer — the existing-user path stays untouched.
const defaultRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_OFF);
const offRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, {
...WORKER_OFF,
pdg: false,
});
// Both CFG and CDG endpoints are persisted BasicBlocks; CDG carries a T/F.
const blockIds = new Set<string>();
result.graph.forEachNode((n) => {
if (n.label === 'BasicBlock') blockIds.add(n.id);
});
for (const rel of result.graph.iterRelationships()) {
if (rel.type === 'CFG' || rel.type === 'REACHING_DEF' || rel.type === 'CDG') {
expect(blockIds.has(rel.sourceId), `${lang} ${rel.type} source is a BasicBlock`).toBe(
true,
);
expect(blockIds.has(rel.targetId), `${lang} ${rel.type} target is a BasicBlock`).toBe(
true,
);
}
if (rel.type === 'CDG') expect(['T', 'F']).toContain(rel.reason);
}
},
60000,
);
for (const r of [defaultRun, offRun]) {
const { basicBlocks, cfgEdges, reachingDefs, tainted, sanitizes, cdg } = counts(r);
expect(basicBlocks).toBe(0);
expect(cfgEdges).toBe(0);
expect(reachingDefs).toBe(0);
expect(tainted).toBe(0);
expect(sanitizes).toBe(0);
expect(cdg).toBe(0);
}
testFn(
`${lang}: --pdg off emits zero PDG nodes/edges (the R3 flag-off gate)`,
async () => {
// Default (no pdg flag) and explicit pdg:false must both produce a graph
// with NO PDG layer — the existing-user path stays untouched.
const defaultRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, WORKER_OFF);
const offRun = await runPipelineFromRepo(freshLangRepo(fixture), () => {}, {
...WORKER_OFF,
pdg: false,
});
// pdg:false ≡ pdg-absent — the symbolic graph digest is identical.
expect(graphDigest(offRun)).toEqual(graphDigest(defaultRun));
}, 90000);
for (const r of [defaultRun, offRun]) {
const { basicBlocks, cfgEdges, reachingDefs, tainted, sanitizes, cdg } = counts(r);
expect(basicBlocks).toBe(0);
expect(cfgEdges).toBe(0);
expect(reachingDefs).toBe(0);
expect(tainted).toBe(0);
expect(sanitizes).toBe(0);
expect(cdg).toBe(0);
}
// pdg:false ≡ pdg-absent — the symbolic graph digest is identical.
expect(graphDigest(offRun)).toEqual(graphDigest(defaultRun));
},
90000,
);
}
});

View file

@ -5,7 +5,6 @@ import {
createCCfgVisitor,
createCppCfgVisitor,
} from '../../../src/core/ingestion/cfg/visitors/c-cpp.js';
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -158,7 +157,9 @@ describe('C CfgVisitor — switch (C-style fallthrough)', () => {
describe('C CfgVisitor — goto / labels', () => {
it('backward goto wires to an already-seen label block', () => {
const cfg = c.cfgOf(`void f() { int i = 0; loop: work(); i++; if (i < 10) goto loop; done(); }`);
const cfg = c.cfgOf(
`void f() { int i = 0; loop: work(); i++; if (i < 10) goto loop; done(); }`,
);
const gotoB = block(cfg, 'goto loop;');
const label = block(cfg, 'work();');
expect(reaches(cfg, gotoB, label)).toBe(true);

View file

@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { createRequire } from 'node:module';
import { createCsharpCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/csharp.js';
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -61,7 +61,9 @@ describe('C# CfgVisitor — structure', () => {
});
it('constructor and local function are CFG-bearing functions', () => {
const cfgs = cs.cfgsOf(`class C { C(int a) { x = a; } void Outer() { int L(int z) { return z; } L(1); } }`);
const cfgs = cs.cfgsOf(
`class C { C(int a) { x = a; } void Outer() { int L(int z) { return z; } L(1); } }`,
);
// constructor C, Outer, and the local function L = 3 CFGs.
expect(cfgs.length).toBeGreaterThanOrEqual(3);
for (const cfg of cfgs) expect(reaches(cfg, cfg.entryIndex, cfg.exitIndex)).toBe(true);
@ -121,7 +123,9 @@ describe('C# CfgVisitor — loops', () => {
});
it('C-style for: init once, condition header, back-edge through update', () => {
const cfg = cs.cfgOf(`class C { void M(int n) { for (int i = 0; i < n; i++) { step(); } done(); } }`);
const cfg = cs.cfgOf(
`class C { void M(int n) { for (int i = 0; i < n; i++) { step(); } done(); } }`,
);
const init = block(cfg, 'int i = 0');
const header = block(cfg, 'i < n');
const incr = block(cfg, 'i++');
@ -133,7 +137,9 @@ describe('C# CfgVisitor — loops', () => {
});
it('foreach: header + body + loop-back + exit; loop var is a def, source a use', () => {
const cfg = cs.cfgOf(`class C { void M(int[] xs) { foreach (var x in xs) { use(x); } done(); } }`);
const cfg = cs.cfgOf(
`class C { void M(int[] xs) { foreach (var x in xs) { use(x); } done(); } }`,
);
const body = block(cfg, 'use(x);');
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
@ -191,7 +197,9 @@ describe('C# CfgVisitor — switch', () => {
});
it('switch_expression arms each dispatch as a guarded branch (switch-case)', () => {
const cfg = cs.cfgOf(`class C { int M(int x) { return x switch { 1 => a(), 2 => b(), _ => c() }; } }`);
const cfg = cs.cfgOf(
`class C { int M(int x) { return x switch { 1 => a(), 2 => b(), _ => c() }; } }`,
);
// The switch-expression lives inside the return block — it does not break a
// basic block, but the function still has a well-formed single-exit CFG.
expect(reaches(cfg, cfg.entryIndex, cfg.exitIndex)).toBe(true);
@ -275,7 +283,9 @@ describe('C# CfgVisitor — try/catch/finally completion edges', () => {
describe('C# CfgVisitor — goto / labels', () => {
it('backward goto wires to an already-seen label block', () => {
const cfg = cs.cfgOf(`class C { void M() { int i = 0; top: work(); i++; if (i < 10) goto top; done(); } }`);
const cfg = cs.cfgOf(
`class C { void M() { int i = 0; top: work(); i++; if (i < 10) goto top; done(); } }`,
);
const gotoB = block(cfg, 'goto top;');
const label = block(cfg, 'work();');
expect(reaches(cfg, gotoB, label)).toBe(true);
@ -382,7 +392,8 @@ describe('C# CfgVisitor — does not throw on exotic shapes', () => {
// is registered, so these sites produce zero TAINTED edges; they only give the
// deferred per-language source/sink model something to match against.
describe('C# CfgVisitor — call-site sites[] substrate', () => {
const cfgOf = (body: string): FunctionCfg => cs.cfgOf(`class C { void f(int cmd, int x, int a) { ${body} } }`);
const cfgOf = (body: string): FunctionCfg =>
cs.cfgOf(`class C { void f(int cmd, int x, int a) { ${body} } }`);
it('a bare invocation records a `call` site with callee name + arg occurrence', () => {
const cfg = cfgOf(`Exec(cmd);`);

View file

@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { createRequire } from 'node:module';
import { createGoCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/go.js';
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -73,9 +73,7 @@ describe('Go CfgVisitor — structure', () => {
describe('Go CfgVisitor — if', () => {
it('if with initializer: init+cond on the header, both arms reach the join', () => {
const cfg = go.cfgOf(
pkg(`func f() { if v := compute(); v > 0 { a() } else { b() }; c() }`),
);
const cfg = go.cfgOf(pkg(`func f() { if v := compute(); v > 0 { a() } else { b() }; c() }`));
const kinds = edgeKinds(cfg);
expect(kinds.has('cond-true')).toBe(true);
expect(kinds.has('cond-false')).toBe(true);
@ -254,7 +252,9 @@ describe('Go CfgVisitor — defer (LIFO completion legs)', () => {
});
it('defer runs on a return path too (return threads through the defer)', () => {
const cfg = go.cfgOf(pkg(`func f(x bool) int { defer cleanup(); if x { return 1 }; return 2 }`));
const cfg = go.cfgOf(
pkg(`func f(x bool) int { defer cleanup(); if x { return 1 }; return 2 }`),
);
const cleanup = block(cfg, 'defer cleanup()');
expect(reaches(cfg, block(cfg, 'return 1'), cleanup)).toBe(true);
expect(reaches(cfg, block(cfg, 'return 2'), cleanup)).toBe(true);

View file

@ -44,9 +44,11 @@ describe('makeCfgHarness — language-parameterized CFG harness (#2195 U1)', ()
};
},
};
const code = ['function fd() { return 1; }', 'const a = () => 2;', 'const b = (x) => x + 1;'].join(
'\n',
);
const code = [
'function fd() { return 1; }',
'const a = () => 2;',
'const b = (x) => x + 1;',
].join('\n');
const cfgs = makeCfgHarness(TypeScript.typescript, stub, 'stub.ts').cfgsOf(code);
expect(cfgs).toHaveLength(2); // the two arrows; the function_declaration is ignored
expect(built).toEqual(['arrow_function', 'arrow_function']);

View file

@ -1,7 +1,7 @@
import { describe, it, expect, vi } from 'vitest';
import { createRequire } from 'node:module';
import { createJavaCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/java.js';
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -143,9 +143,7 @@ describe('Java CfgVisitor — loops', () => {
});
it('enhanced for (for-each): header + body + loop-back + exit; loop var is a def', () => {
const cfg = java.cfgOf(
`class C { void m(int[] xs) { for (int v : xs) { use(v); } done(); } }`,
);
const cfg = java.cfgOf(`class C { void m(int[] xs) { for (int v : xs) { use(v); } done(); } }`);
const body = block(cfg, 'use(v);');
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
@ -417,7 +415,9 @@ describe('Java CfgVisitor — synchronized (deterministic finalizer)', () => {
});
it('a return inside synchronized crosses the release (finally-return)', () => {
const cfg = java.cfgOf(`class C { int m(Object lock) { synchronized (lock) { return get(); } } }`);
const cfg = java.cfgOf(
`class C { int m(Object lock) { synchronized (lock) { return get(); } } }`,
);
expect(edgeKinds(cfg).has('finally-return')).toBe(true);
});
});
@ -439,13 +439,17 @@ describe('Java CfgVisitor — def/use harvest', () => {
});
it('a && (x = g()) records x as a may-def inside the short-circuit', () => {
const cfg = java.cfgOf(`class C { void m(boolean a) { int x = 0; if (a && (x = g()) > 0) h(x); } }`);
const cfg = java.cfgOf(
`class C { void m(boolean a) { int x = 0; if (a && (x = g()) > 0) h(x); } }`,
);
const x = bindingIdx(cfg, 'x');
expect(hasMayDef(cfg, x)).toBe(true);
});
it('ternary arms record their writes as may-defs', () => {
const cfg = java.cfgOf(`class C { void m(int a) { int x = 0; int y = a > 0 ? (x = 1) : (x = 2); use(y); } }`);
const cfg = java.cfgOf(
`class C { void m(int a) { int x = 0; int y = a > 0 ? (x = 1) : (x = 2); use(y); } }`,
);
const x = bindingIdx(cfg, 'x');
expect(hasMayDef(cfg, x)).toBe(true);
});

View file

@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { createRequire } from 'node:module';
import { createPhpCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/php.js';
import type { FunctionCfg, SiteRecord } from '../../../src/core/ingestion/cfg/types.js';
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -169,7 +169,9 @@ describe('PHP CfgVisitor — loops', () => {
describe('PHP CfgVisitor — switch / match', () => {
it('switch: C-style FALLTHROUGH (a case with no break flows to the next)', () => {
const cfg = php.cfgOf(
wrap(`switch ($x) { case 1: a(); break; case 2: b(); case 3: c(); break; default: d(); } e();`),
wrap(
`switch ($x) { case 1: a(); break; case 2: b(); case 3: c(); break; default: d(); } e();`,
),
);
const kinds = edgeKinds(cfg);
expect(kinds.has('switch-case')).toBe(true);
@ -334,7 +336,9 @@ describe('PHP CfgVisitor — def/use harvest', () => {
});
it('closure use ($b, &$c) captures bind in the closure body', () => {
const cfgs = php.cfgsOf(`<?php function outer($b) { return function ($a) use ($b, &$c) { return $a + $b + $c; }; }`);
const cfgs = php.cfgsOf(
`<?php function outer($b) { return function ($a) use ($b, &$c) { return $a + $b + $c; }; }`,
);
const closure = cfgs.find((c) => (c.bindings ?? []).some((bd) => bd.name === '$a'));
expect(closure).toBeDefined();
expect((closure!.bindings ?? []).some((bd) => bd.name === '$b')).toBe(true);

View file

@ -160,7 +160,9 @@ describe('Python CfgVisitor — for / while with the loop else', () => {
});
it('continue re-tests the loop header', () => {
const cfg = py.cfgOf(`def f(xs):\n for i in xs:\n if i:\n continue\n use(i)\n`);
const cfg = py.cfgOf(
`def f(xs):\n for i in xs:\n if i:\n continue\n use(i)\n`,
);
const header = cfg.blocks.find((b) => b.text.startsWith('for '))!.index;
expect(edgeKinds(cfg).has('continue')).toBe(true);
expect(reaches(cfg, block(cfg, 'continue'), header)).toBe(true);

View file

@ -246,9 +246,7 @@ describe('Ruby CfgVisitor — begin / rescue / else / ensure', () => {
});
it('method-level implicit begin: a bare def + rescue/ensure threads correctly', () => {
const cfg = rb.cfgOf(
`def f\n risky()\nrescue => e\n handle()\nensure\n done()\nend\n`,
);
const cfg = rb.cfgOf(`def f\n risky()\nrescue => e\n handle()\nensure\n done()\nend\n`);
const ensureB = block(cfg, 'done()');
expect(reaches(cfg, block(cfg, 'risky()'), block(cfg, 'handle()'))).toBe(true);
expect(reaches(cfg, block(cfg, 'handle()'), ensureB)).toBe(true);
@ -275,9 +273,7 @@ describe('Ruby CfgVisitor — begin / rescue / else / ensure', () => {
});
it('retry re-enters the begin protected body (loop-back)', () => {
const cfg = rb.cfgOf(
`def f\n begin\n risky()\n rescue\n retry\n end\nend\n`,
);
const cfg = rb.cfgOf(`def f\n begin\n risky()\n rescue\n retry\n end\nend\n`);
expect(edgeKinds(cfg).has('loop-back')).toBe(true);
const retryB = block(cfg, 'retry');
const bodyB = block(cfg, 'risky()');

View file

@ -104,9 +104,7 @@ describe('Rust CfgVisitor — if / else / if let', () => {
});
it('if let with a let-chain (let PAT = e && cond) still binds + branches', () => {
const cfg = rust.cfgOf(
`fn f(opt: Option<i32>) { if let Some(n) = opt && n > 0 { both(); } }`,
);
const cfg = rust.cfgOf(`fn f(opt: Option<i32>) { if let Some(n) = opt && n > 0 { both(); } }`);
expect(edgeKinds(cfg).has('cond-true')).toBe(true);
expect(hasDef(cfg, bindingIdx(cfg, 'n'))).toBe(true);
expect(reaches(cfg, block(cfg, 'both()'), cfg.exitIndex)).toBe(true);
@ -221,9 +219,7 @@ describe('Rust CfgVisitor — match (no fallthrough) + guards', () => {
});
it('a match with NO `_` arm keeps a no-match path to the exit', () => {
const cfg = rust.cfgOf(
`fn f(x: i32) { match x { 1 => a(), 2 => b(), } after(); }`,
);
const cfg = rust.cfgOf(`fn f(x: i32) { match x { 1 => a(), 2 => b(), } after(); }`);
expect(reaches(cfg, cfg.entryIndex, block(cfg, 'after()'))).toBe(true);
expect(isExitReachableFromAllBlocks(cfg)).toBe(true);
});
@ -262,7 +258,7 @@ describe('Rust CfgVisitor — labeled break / continue', () => {
expect(reaches(cfg, block(cfg, "continue 'outer"), outerHeader)).toBe(true);
});
it("a plain (unlabeled) break exits the INNER loop only", () => {
it('a plain (unlabeled) break exits the INNER loop only', () => {
const cfg = rust.cfgOf(
`fn f() {
for i in 0..10 {
@ -281,9 +277,7 @@ describe('Rust CfgVisitor — labeled break / continue', () => {
describe('Rust CfgVisitor — return and the ? operator', () => {
it('return Some(n) flows directly to EXIT (return edge)', () => {
const cfg = rust.cfgOf(
`fn f(n: i32) -> Option<i32> { if n > 0 { return Some(n); } Some(0) }`,
);
const cfg = rust.cfgOf(`fn f(n: i32) -> Option<i32> { if n > 0 { return Some(n); } Some(0) }`);
expect(edgeKinds(cfg).has('return')).toBe(true);
expect(reaches(cfg, block(cfg, 'return Some(n)'), cfg.exitIndex)).toBe(true);
});
@ -324,7 +318,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
});
it('let Some(n) = opt() in a let_else defines n (the tuple-struct inner binds, not the path)', () => {
const cfg = rust.cfgOf(`fn f(opt: Option<i32>) { let Some(n) = opt() else { return; }; use_n(n); }`);
const cfg = rust.cfgOf(
`fn f(opt: Option<i32>) { let Some(n) = opt() else { return; }; use_n(n); }`,
);
expect(hasDef(cfg, bindingIdx(cfg, 'n'))).toBe(true);
expect(hasUse(cfg, bindingIdx(cfg, 'n'))).toBe(true);
// the let_else `else { return; }` jumps to EXIT.
@ -339,7 +335,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
});
it('plain assignment (x = p) defines x; a field write (obj.f = …) is NOT a scalar def', () => {
const cfg = rust.cfgOf(`fn f(p: i32, obj: T) { let mut x = 0; x = p; obj.field = 1; use_x(x); }`);
const cfg = rust.cfgOf(
`fn f(p: i32, obj: T) { let mut x = 0; x = p; obj.field = 1; use_x(x); }`,
);
expect(hasDef(cfg, bindingIdx(cfg, 'x'))).toBe(true);
// `obj` is used (its field is written), never a scalar def for `field`.
expect(hasUse(cfg, bindingIdx(cfg, 'obj'))).toBe(true);
@ -351,7 +349,9 @@ describe('Rust CfgVisitor — def/use harvest (patterns)', () => {
});
it('a closure body is opaque to the enclosing function harvest (own CFG)', () => {
const cfgs = rust.cfgsOf(`fn f() { let cb = || { let inner = 1; use_inner(inner); }; call(cb); }`);
const cfgs = rust.cfgsOf(
`fn f() { let cb = || { let inner = 1; use_inner(inner); }; call(cb); }`,
);
// The OUTER fn has a `cb` binding but NOT `inner` (the closure body is opaque).
const outer = cfgs.find((c) => (c.bindings ?? []).some((b) => b.name === 'cb'));
expect(outer).toBeDefined();

View file

@ -1,7 +1,6 @@
import { describe, it, expect } from 'vitest';
import { requireVendoredGrammar } from '../../../src/core/tree-sitter/vendored-grammars.js';
import { createSwiftCfgVisitor } from '../../../src/core/ingestion/cfg/visitors/swift.js';
import type { FunctionCfg } from '../../../src/core/ingestion/cfg/types.js';
import {
makeCfgHarness,
type CfgHarness,
@ -74,7 +73,9 @@ describe('Swift CfgVisitor — branching', () => {
});
it('else-if chain branches each condition', () => {
const cfg = swift.cfgOf(`func f(x: Int) { if x == 1 { a() } else if x == 2 { b() } else { c() } ; d() }`);
const cfg = swift.cfgOf(
`func f(x: Int) { if x == 1 { a() } else if x == 2 { b() } else { c() } ; d() }`,
);
const join = block(cfg, 'd()');
expect(reaches(cfg, block(cfg, 'a()'), join)).toBe(true);
expect(reaches(cfg, block(cfg, 'b()'), join)).toBe(true);
@ -95,7 +96,9 @@ describe('Swift CfgVisitor — branching', () => {
describe('Swift CfgVisitor — guard', () => {
it('guard let ... else { return }: the else DIVERGES, the body CONTINUES', () => {
const cfg = swift.cfgOf(`func f(opt: Int?) -> Int { guard let y = opt else { return 0 } ; use(y) ; return y }`);
const cfg = swift.cfgOf(
`func f(opt: Int?) -> Int { guard let y = opt else { return 0 } ; use(y) ; return y }`,
);
const header = block(cfg, 'guard');
const elseBlk = block(cfg, 'return 0');
// The else is the cond-false (diverging) arm and returns.