import type { AwaitExpression, BigIntLiteral, BooleanLiteral, CallExpression, Identifier, MemberExpression, Node, NumericLiteral, ObjectExpression, StringLiteral } from '@babel/types'; /** * The minimal structural slice of Babel's `NodePath` a heuristic mutator uses. * The real `NodePath` (untyped here — `@babel/traverse` ships no typings) is a * structural superset, so a real path passed by `traverse` satisfies this type. * * Each `is*()` predicate is a type guard that narrows `node` to the matching * Babel node, mirroring the real `NodePath` API. We expose only the predicates * the current mutators (and their tests) rely on; add more as new heuristics * need them. */ export interface NodePath { /** The AST node this path points at. */ readonly node: Node; /** * The babel traversal `hub`, present when the AST is traversed wrapped in a * babel `File` (which is exactly what Stryker's `babel-transformer` does: * `new File({ filename }, ...)`). The injected `LLMMutator` reads the current * source file from `hub.file.opts.filename` to key its per-file map lookup. * * OPTIONAL by design: a bare `traverse` over an unwrapped AST (as the * heuristic-mutator tests do) does NOT populate `hub`, so leaving it optional * keeps those fake `NodePath`s assignable, and a missing `hub` degrades the * `LLMMutator` to a clean no-match rather than a throw. Stryker's real * `NodePath` is a structural superset of this slice. */ readonly hub?: { readonly file?: { readonly opts?: { readonly filename?: string; }; }; }; /** Narrows `node` to `NumericLiteral` (e.g. `42`, `3.14`, `0xff`, `1_000`). */ isNumericLiteral(): this is { readonly node: NumericLiteral; }; /** Narrows `node` to `StringLiteral`. */ isStringLiteral(): this is { readonly node: StringLiteral; }; /** Narrows `node` to `BooleanLiteral`. */ isBooleanLiteral(): this is { readonly node: BooleanLiteral; }; /** Narrows `node` to `BigIntLiteral` (`9n`) — distinct from `NumericLiteral`. */ isBigIntLiteral(): this is { readonly node: BigIntLiteral; }; /** Narrows `node` to `Identifier`. */ isIdentifier(): this is { readonly node: Identifier; }; /** * Narrows `node` to `CallExpression` (`f(x)`, `xs.map(f)`, `Promise.all(xs)`). * Used by `CallArgumentTweak`, `ArrayMethodSwap`, `PromiseCombinatorSwap`, * and `StringMethodArgSwap`. */ isCallExpression(): this is { readonly node: CallExpression; }; /** Narrows `node` to `AwaitExpression` (`await x`). Used by `AwaitDrop`. */ isAwaitExpression(): this is { readonly node: AwaitExpression; }; /** Narrows `node` to an expression used as a whole statement. */ isExpressionStatement(): boolean; /** * Narrows `node` to a plain (NON-optional) `MemberExpression` (`a.b`, `a[i]`, * `this.x`). `OptionalMemberExpression` (`a?.b`) is a DISTINCT node type that * this predicate does NOT match, so already-optional members are excluded * automatically. */ isMemberExpression(): this is { readonly node: MemberExpression; }; /** * Narrows `node` to `ObjectExpression` (`{ ...a, b: 1 }`). Used by * `SpreadOperandDrop`. */ isObjectExpression(): this is { readonly node: ObjectExpression; }; /** * The parent `NodePath` in the traversal, present on Stryker's real * `NodePath` (and on `@babel/traverse`'s paths generally). Mutators that need * surrounding placement context inspect it before yielding a replacement. * * OPTIONAL by design, exactly like {@link hub}: the heuristic-mutator unit * tests build fake `NodePath`s via a bare `traverse` that does populate * `parentPath`, but other synthetic paths (and forward-compat) may omit it. * Consumers treat a missing parent as a clean no-match. Stryker's real * `NodePath` is a structural superset. */ readonly parentPath?: NodePath; /** Babel lexical scope; absent synthetic scopes are treated as unsafe. */ readonly scope?: { getBinding(name: string): { readonly kind: string; } | undefined; }; /** Halts the surrounding `traverse` early (used by test helpers). */ stop(): void; } /** * Structural contract a Stryker mutator must satisfy. Any object matching this * shape can be registered in the instrumenter's `allMutators` array and will be * invoked by `transformBabel` for every traversed Babel node. */ export interface NodeMutator { /** * Given a Babel `NodePath`, yield zero or more replacement AST nodes. Each * yielded node becomes one candidate mutant on the visited node. Returning * an empty iterable (the common case for non-matching node types) means "no * mutation here". */ mutate(path: NodePath): Iterable; /** Stable mutator name, surfaced as `mutatorName` on every emitted mutant. */ readonly name: string; }