oxmutate 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -2,4 +2,3 @@
2
2
  import { run } from 'burgee';
3
3
  import { program } from './cli.js';
4
4
  await run(program);
5
- //# sourceMappingURL=bin.js.map
package/dist/checker.js CHANGED
@@ -1,46 +1,22 @@
1
- /**
2
- * The type checker (Stryker's `checkers: ['typescript']`): a surviving mutant that does not
3
- * type-check is a CompileError, not a survivor. A survivor is what someone writes a test for, and
4
- * a test cannot kill a program the compiler refuses; Stryker's TypeScript checker exists for the
5
- * same reason.
6
- *
7
- * It checks survivors only, where Stryker checks every mutant before running it. A killed mutant
8
- * that would not have compiled changes nothing anyone acts on, and a full project type check per
9
- * mutant is the costliest step there is. The difference: such a mutant is Killed here where Stryker
10
- * would score it CompileError, which can move a score that enables the checker by its share.
11
- *
12
- * The project's own compiler, `tsgo` when installed and `tsc` otherwise, over its own tsconfig.
13
- * Survivors are checked in batches with at most one mutant per file, and each error is laid at the
14
- * mutant in the file it names. An error in a file no mutant of the batch touched (a type that
15
- * flowed across files) sends that batch back one mutant at a time, so no verdict is a guess.
16
- */
17
1
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
18
2
  import path from 'node:path';
19
3
  import { ambientRuntime, isWindows, run as spawn } from 'bellpull';
20
4
  import { makeSandbox } from './command-runner.js';
21
5
  const OUTPUT_TAIL = 1500;
22
- /**
23
- * `src/a.ts(3,7): error TS2322: …` — tsc's and tsgo's `--pretty false` shape. No anchors: the lazy
24
- * first group already makes the leftmost match start the line, and the greedy last one ends it.
25
- */
26
6
  const DIAGNOSTIC = /(.+?)\((\d+),(\d+)\): error (TS\d+: .*)/;
27
- /** The source with one mutant applied. Columns are UTF-16 and 1-based, as JavaScript counts. */
28
7
  export function applyMutant(source, m) {
29
8
  const starts = [0];
30
9
  for (let i = source.indexOf('\n'); i !== -1; i = source.indexOf('\n', i + 1))
31
10
  starts.push(i + 1);
32
- // Stryker disable next-line NumericLiteral: `starts` holds every line the checker reports, so the fallback never runs
33
11
  const at = (p) => (starts[p.line - 1] ?? 0) + p.column - 1;
34
12
  return source.slice(0, at(m.start)) + m.replacement + source.slice(at(m.end));
35
13
  }
36
- /** Errors per file (repository-relative), from the compiler's output. */
37
14
  export function parseDiagnostics(output, cwd) {
38
15
  const errors = new Map();
39
16
  for (const line of output.split('\n')) {
40
17
  const hit = DIAGNOSTIC.exec(line.trim());
41
18
  if (!hit)
42
19
  continue;
43
- // Stryker disable next-line StringLiteral: all four groups take part in every match, so the defaults are never used; they are there for the type
44
20
  const [, name = '', lineNo = '', , message = ''] = hit;
45
21
  const file = path.relative(cwd, path.resolve(cwd, name)).split(path.sep).join('/');
46
22
  if (!errors.has(file))
@@ -48,7 +24,6 @@ export function parseDiagnostics(output, cwd) {
48
24
  }
49
25
  return errors;
50
26
  }
51
- /** Batches of survivors with at most one per file, so each error names its mutant. */
52
27
  export function batches(items) {
53
28
  const out = [];
54
29
  for (const m of items) {
@@ -60,7 +35,6 @@ export function batches(items) {
60
35
  }
61
36
  return out;
62
37
  }
63
- /** npm's .bin entry for tsgo: `tsgo.cmd` on Windows, where the extensionless file beside it is a shell script. */
64
38
  export const tsgoBin = (windows) => (windows ? 'tsgo.cmd' : 'tsgo');
65
39
  function compiler(o) {
66
40
  if (o.command)
@@ -68,12 +42,10 @@ function compiler(o) {
68
42
  const tsgo = path.join(o.root, 'node_modules', '.bin', tsgoBin(isWindows(ambientRuntime())));
69
43
  return existsSync(tsgo) ? [tsgo] : ['npx', 'tsc'];
70
44
  }
71
- /** The undetected mutants that do not type-check, each with the compiler's first error in its file. */
72
45
  export async function checkSurvivors(o, survivors) {
73
46
  const failing = new Map();
74
47
  if (survivors.length === 0)
75
48
  return failing;
76
- // Stryker disable next-line StringLiteral: compiler() never returns an empty list, so the default is never taken; it is there for the type
77
49
  const [command = '', ...prefix] = compiler(o);
78
50
  const args = [...prefix, '--noEmit', '--pretty', 'false', '-p', o.tsconfig];
79
51
  const sandboxes = Array.from({ length: Math.max(1, Math.min(o.concurrency, survivors.length)) }, () => makeSandbox(o.root));
@@ -82,7 +54,6 @@ export async function checkSurvivors(o, survivors) {
82
54
  const r = await spawn(command, args, { cwd: sandbox, runtime: ambientRuntime() });
83
55
  return { ok: r.ok, errors: parseDiagnostics(`${r.stdout}\n${r.stderr}`, sandbox), output: `${r.stdout}${r.stderr}` };
84
56
  };
85
- // Stryker disable next-line StringLiteral: there is always at least one sandbox, so the fallback is never used; it is there for the type
86
57
  const base = await check(sandboxes[0] ?? '');
87
58
  if (!base.ok)
88
59
  throw new Error(`the project does not type-check with no mutant applied (\`${[command, ...args].join(' ')}\`): ${base.output.trim().slice(-OUTPUT_TAIL)}. fix: make the type check pass first, or drop checkers`);
@@ -98,7 +69,7 @@ export async function checkSurvivors(o, survivors) {
98
69
  const touched = new Set(batch.map((m) => m.file));
99
70
  if (batch.length > 1 && [...r.errors.keys()].some((f) => !touched.has(f))) {
100
71
  for (const m of batch)
101
- await run(sandbox, [m]); // an error that crossed files: one at a time
72
+ await run(sandbox, [m]);
102
73
  return;
103
74
  }
104
75
  for (const m of batch) {
@@ -115,9 +86,7 @@ export async function checkSurvivors(o, survivors) {
115
86
  return failing;
116
87
  }
117
88
  finally {
118
- // No `force`: each sandbox was made above and nothing else removes it.
119
89
  for (const s of sandboxes)
120
90
  rmSync(s, { recursive: true });
121
91
  }
122
92
  }
123
- //# sourceMappingURL=checker.js.map
package/dist/child.js CHANGED
@@ -1,15 +1,3 @@
1
- /**
2
- * A Vitest server in its own process. It starts the project's Vitest once and then runs work
3
- * units sent by the coordinator over IPC, re-running the same instance (~0.2 s a run against
4
- * ~1.5 s for a fresh Vitest). The coordinator kills the process when a mutant loops
5
- * synchronously, a hang that no in-worker timeout and no `cancelCurrentRun()` can interrupt
6
- * (both measured, 2026-10-26), and hands the rest of the unit to another server.
7
- *
8
- * node child.js <server.json> IPC: ← { type: 'run', unit } → { type: 'ready' | 'done' }
9
- *
10
- * Vitest is resolved from the project being tested, like Stryker's vitest-runner does, so the
11
- * project's own Vitest version and config run its tests.
12
- */
13
1
  import { readFileSync, writeFileSync } from 'node:fs';
14
2
  import path from 'node:path';
15
3
  import { fileURLToPath } from 'node:url';
@@ -20,7 +8,6 @@ import { ACTIVE_MUTANT_ENV, argv, env, exit, pid } from './runtime.js';
20
8
  import { instrumentedSources } from './vite-plugin.js';
21
9
  const config = JSON.parse(readFileSync(argv[2] ?? '', 'utf8'));
22
10
  env['NODE_ENV'] ??= 'test';
23
- // Before Vitest starts any worker, so each one inherits it (heartbeat.ts).
24
11
  env[BEAT_ENV] = beatFile(path.dirname(config.instrumentedMap), pid);
25
12
  const vitestNode = (await import(resolveEsm('vitest/node', config.root)));
26
13
  const runner = fileURLToPath(new URL('./runner.js', import.meta.url));
@@ -37,25 +24,13 @@ const vitest = await vitestNode.createVitest('test', {
37
24
  reporters: env['OXMUTATE_DEBUG_VITEST'] ? ['default'] : [silent],
38
25
  coverage: { enabled: false },
39
26
  passWithNoTests: true,
40
- // Bail is ours (per mutant); Vitest must run every test it is handed.
41
27
  bail: 0,
42
- // Vitest 5's persistent module cache keys on the file on disk, not on what a plugin serves,
43
- // so it served a previous run's instrumentation: every mutant whose id had changed since got
44
- // no coverage (burgee/paratext, 2026-10-27: 6% agreement until this was off). Off under both
45
- // spellings (top-level in Vitest 5, `experimental` in 4.x).
46
28
  fsModuleCache: false,
47
29
  experimental: { fsModuleCache: false },
48
30
  ...(config.inlineDeps && { server: { deps: { inline: true } } }),
49
- },
50
- // A private Vite cache per run, so no cache outside this run can answer for a mutated file.
51
- { plugins: [instrumentedSources(config.instrumentedMap)], cacheDir: path.join(path.dirname(config.instrumentedMap), 'vite-cache') });
52
- // A config with `projects` resolves each project's `runner` and `isolate` from its own inline
53
- // config, never from these options, so its tests ran without oxmutate's runner and the dry run
54
- // recorded none (burgee, weekly run 40783405734, 2026-10-28). Each project keeps its own pool:
55
- // burgee's run in forks, and its process.env lock fails under threads.
31
+ }, { plugins: [instrumentedSources(config.instrumentedMap)], cacheDir: path.join(path.dirname(config.instrumentedMap), 'vite-cache') });
56
32
  for (const project of vitest.projects)
57
33
  Object.assign(project.config, { runner, isolate: true });
58
- /** Has the runner recorded a kill for this mutant in this unit's results yet? */
59
34
  function killed(results, id) {
60
35
  let text = '';
61
36
  try {
@@ -66,17 +41,11 @@ function killed(results, id) {
66
41
  }
67
42
  return text.split('\n').some((line) => line.includes(`"id":"${id}"`) && line.includes('"killed":true'));
68
43
  }
69
- /** An `isolated` unit: its mutants armed one after another, each across its files. */
70
44
  async function runIsolated(unit) {
71
- // Stop a file at its first failing test: one failure kills the mutant, and the rest of the
72
- // file is wasted time (burgee/paratext hyperlinks.test.ts: 2.3 s a run, the first failure
73
- // typically in its first tests). Read per start, so dry and warm runs still run everything
74
- // (measured on Vitest 4.1, 2026-10-27).
75
45
  vitest.config.bail = 1;
76
46
  try {
77
47
  for (const item of unit.batch ?? []) {
78
48
  env[ACTIVE_MUTANT_ENV] = item.id;
79
- // The runaway guard warm runs have: a loop the mutant causes stops at the limit, not the watchdog.
80
49
  if (item.hitLimit)
81
50
  env['OXMUTATE_HIT_LIMIT'] = String(item.hitLimit);
82
51
  else
@@ -86,8 +55,6 @@ async function runIsolated(unit) {
86
55
  else
87
56
  delete env['OXMUTATE_ONLY_TESTS'];
88
57
  writeFileSync(unit.progress, `${item.id} ${String(Date.now())}\n`);
89
- // One file at a time, cheapest first (the coordinator orders them), stopping at the first
90
- // file that kills the mutant: Stryker runs every file for every isolated mutant.
91
58
  for (const file of item.files) {
92
59
  await vitest.start([file]);
93
60
  if (killed(unit.results, item.id))
@@ -104,8 +71,6 @@ async function runIsolated(unit) {
104
71
  writeFileSync(unit.progress, '');
105
72
  }
106
73
  async function runUnit(unit) {
107
- // Workers copy the environment when they start, so each run sees the unit's settings
108
- // (verified across re-runs of one instance, 2026-10-26).
109
74
  env['OXMUTATE_MODE'] = unit.mode;
110
75
  env['OXMUTATE_RESULTS'] = unit.results;
111
76
  env['OXMUTATE_PROGRESS'] = unit.progress;
@@ -123,30 +88,22 @@ async function runUnit(unit) {
123
88
  if (unit.mode === 'isolated')
124
89
  await runIsolated(unit);
125
90
  else {
126
- // A dry run names itself in the progress file, so the watchdog holds it to its budget.
127
91
  if (unit.mode === 'dry')
128
92
  writeFileSync(unit.progress, `dry ${String(Date.now())}\n`);
129
93
  await vitest.start(unit.files.length ? unit.files : undefined);
130
94
  }
131
95
  }
132
- /** How long `close` waits for `vitest.close()` before the server exits regardless. */
133
96
  const CLOSE_GRACE_MS = 2000;
134
97
  process.on('message', (msg) => {
135
98
  if (msg.type === 'run' && msg.unit) {
136
99
  runUnit(msg.unit).then(() => process.send?.({ type: 'done' }), (error) => process.send?.({ type: 'done', error: error instanceof Error ? error.message : String(error) }));
137
100
  }
138
101
  else if (msg.type === 'close') {
139
- // A worker thread stuck in a runaway mutant never lets `vitest.close()` settle: exit anyway.
140
102
  setTimeout(() => exit(ExitCode.OK), CLOSE_GRACE_MS).unref();
141
103
  void vitest.close().finally(() => exit(ExitCode.OK));
142
104
  }
143
105
  });
144
- // The coordinator is gone (finished, crashed or killed): nothing will read this server's results,
145
- // and a worker thread may still be looping in a mutant. Two such servers were found spinning at
146
- // 100% CPU 11 hours after their run ended, and slowed the next run 50x (2026-10-27).
147
106
  process.on('disconnect', () => exit(ExitCode.OK));
148
- // The server's CPU time, every thread (the test worker included), for the watchdog to tell an idle
149
- // stall from a loop (heartbeat.ts). This thread stays free while a worker loops, so the samples come.
150
107
  const sendCpu = () => {
151
108
  const { user, system } = process.cpuUsage();
152
109
  process.send?.({ type: 'cpu', at: Date.now(), us: user + system });
@@ -154,4 +111,3 @@ const sendCpu = () => {
154
111
  sendCpu();
155
112
  setInterval(sendCpu, CPU_SAMPLE_MS).unref();
156
113
  process.send?.({ type: 'ready' });
157
- //# sourceMappingURL=child.js.map
package/dist/cli.js CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * oxmutate's command line, declared once on burgee: help, `--json`, `--schema`, `--format=agent`,
3
- * completions and the MCP server are projected from this declaration (spec R17). Handlers return
4
- * data; the text a person reads rides along under burgee's text symbol, so the agent surfaces
5
- * carry exactly the data.
6
- */
7
1
  import { existsSync, readFileSync } from 'node:fs';
8
2
  import path from 'node:path';
9
3
  import { defineCommand, defineProgram, ExitCode } from 'burgee';
@@ -31,7 +25,6 @@ function survivorRecord(m) {
31
25
  function summaryData(r, written) {
32
26
  const count = (s) => r.mutants.filter((m) => m.status === s).length;
33
27
  const data = {
34
- // burgee N7: an idempotent command says whether it changed anything (reports were written).
35
28
  changed: written.length > 0,
36
29
  version: VERSION,
37
30
  score: Number(r.score.mutationScore.toFixed(2)),
@@ -50,8 +43,6 @@ function summaryData(r, written) {
50
43
  data[TEXT] = () => textSummary(r);
51
44
  return data;
52
45
  }
53
- /** The `run` flags that override a config key, as the `Partial<Options>` loadOptions applies last. */
54
- /** How many weakened mutants the error line names; the JSON report has them all. */
55
46
  const WEAKENED_SHOWN = 3;
56
47
  function overridesFrom(o, root) {
57
48
  const overrides = {};
@@ -162,4 +153,3 @@ export const program = defineProgram({
162
153
  description: 'Mutation testing on the oxc engine: Stryker-compatible, built for CI and coding agents',
163
154
  commands: [runCommand, survivorsCommand],
164
155
  });
165
- //# sourceMappingURL=cli.js.map
@@ -1,27 +1,9 @@
1
- /**
2
- * The command runner (Stryker's `testRunner: 'command'`): any test command, one process per
3
- * mutant, the escape hatch for a suite that is not Vitest. A non-zero exit kills the mutant, a run
4
- * past its budget is a timeout, and a zero exit is a survivor. There is no coverage, so every
5
- * mutant runs the whole command, as with Stryker's.
6
- *
7
- * An arbitrary command cannot be served sources from memory, so this is the one path with a
8
- * sandbox: a copy of the project under `<root>/.oxmutate-tmp/`, with the instrumented files
9
- * written into it. Inside the project, as Stryker's `.stryker-tmp` is, so module resolution walks
10
- * up to the same `node_modules` (a monorepo's hoisted ones included). The armed mutant reaches the
11
- * instrumented code through `__OXMUTATE_ACTIVE_MUTANT__`, which the runtime header reads.
12
- */
13
1
  import { cpSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
14
2
  import path from 'node:path';
15
3
  import { ambientRuntime, run as spawn } from 'bellpull';
16
4
  import { ACTIVE_MUTANT_ENV, env } from './runtime.js';
17
5
  const SANDBOX_DIR = '.oxmutate-tmp';
18
- /** Stryker's default `ignorePatterns`, plus this sandbox. */
19
6
  const SKIP = new Set(['node_modules', '.git', 'reports', 'coverage', '.stryker-tmp', SANDBOX_DIR]);
20
- /**
21
- * A copy of the project under `<root>/.oxmutate-tmp/`, for the paths that need files on disk (the
22
- * command runner, the type checker). Inside the project so module resolution walks up to the same
23
- * `node_modules`; entry by entry, since a directory cannot be copied into itself.
24
- */
25
7
  export function makeSandbox(root) {
26
8
  const parent = path.join(root, SANDBOX_DIR);
27
9
  mkdirSync(parent, { recursive: true });
@@ -32,21 +14,13 @@ export function makeSandbox(root) {
32
14
  return sandbox;
33
15
  }
34
16
  const MS_PER_MINUTE = 60_000;
35
- /** How much of a failing dry run's output the error keeps. */
36
17
  const OUTPUT_TAIL = 1500;
37
- /**
38
- * The least slack a process-per-mutant run gets: Stryker's own default `timeoutMS`. oxmutate's
39
- * 500 ms default is sized for a warm in-process run; a fresh process's start-up varies by seconds
40
- * on a loaded machine, and at 500 ms the e2e fixture read false timeouts (2026-10-27).
41
- */
42
18
  const PROCESS_SLACK_MS = 5000;
43
- /** Runs the command over every mutant not already decided; returns the dry run's time. */
44
19
  export async function runCommandMutants(o, mutants) {
45
20
  const sandbox = makeSandbox(o.root);
46
21
  try {
47
22
  for (const [file, code] of Object.entries(o.codeMap))
48
23
  writeFileSync(path.join(sandbox, path.relative(o.root, file)), code);
49
- // Stryker splits the command on spaces and runs it without a shell; so does this.
50
24
  const [command = '', ...args] = o.command.trim().split(/\s+/);
51
25
  const once = (timeout, id) => spawn(command, args, { cwd: sandbox, runtime: ambientRuntime(), timeout, env: id ? { ...env, [ACTIVE_MUTANT_ENV]: id } : { ...env } });
52
26
  const dryBudget = o.dryRunTimeoutMinutes * MS_PER_MINUTE;
@@ -80,4 +54,3 @@ export async function runCommandMutants(o, mutants) {
80
54
  rmSync(sandbox, { recursive: true, force: true });
81
55
  }
82
56
  }
83
- //# sourceMappingURL=command-runner.js.map
package/dist/config.js CHANGED
@@ -1,16 +1,8 @@
1
- /**
2
- * Options, read from `stryker.config.*` as it stands (migration is a no-op: the file Stryker reads
3
- * is the file oxmutate reads) and from `oxmutate.config.*`, which wins where both set a key.
4
- * Every key Stryker documents that oxmutate does not use is reported, never silently dropped
5
- * (spec R15).
6
- */
7
1
  import { existsSync, readFileSync } from 'node:fs';
8
2
  import { availableParallelism } from 'node:os';
9
3
  import path from 'node:path';
10
4
  import { pathToFileURL } from 'node:url';
11
- import {} from './native.js';
12
5
  import { resolveEsm } from './resolve.js';
13
- import {} from './spec-oracle.js';
14
6
  const NOT_NEEDED = {
15
7
  inPlace: 'oxmutate never copies or rewrites the project; instrumented sources are served from memory',
16
8
  tempDirName: 'no sandbox directory exists',
@@ -35,10 +27,6 @@ const NOT_NEEDED = {
35
27
  force: 'use --no-incremental',
36
28
  checkerNodeArgs: 'not implemented',
37
29
  };
38
- /**
39
- * Defaults where the config sets nothing. Stryker's, except the per-mutant slack: Stryker's
40
- * `timeoutMS` is 5000, and a warm run needs far less (cli.ts `--mutant-timeout`).
41
- */
42
30
  const MAX_DEFAULT_CONCURRENCY = 8;
43
31
  const DEFAULT_TIMEOUT_MS = 500;
44
32
  const DEFAULT_TIMEOUT_FACTOR = 1.5;
@@ -48,7 +36,6 @@ const DEFAULT_DRY_RUN_MINUTES = 5;
48
36
  const USED = new Set(['mutate', 'vitest', 'concurrency', 'timeoutMS', 'timeoutFactor', 'thresholds', 'ignoreStatic', 'reporters', 'jsonReporter', 'htmlReporter', 'incremental', 'incrementalFile', '$schema', 'mutator', 'dryRunOnly', 'dryRunTimeoutMinutes', 'buildCommand', 'testRunnerNodeArgs', 'allowEmpty', 'warnings', 'testRunner', 'commandRunner', 'checkers', 'tsconfigFile']);
49
37
  async function loadFile(file) {
50
38
  if (file.endsWith('.json')) {
51
- // Stryker disable next-line StringLiteral: without an encoding readFileSync returns a Buffer, which JSON.parse reads as UTF-8 text all the same
52
39
  return JSON.parse(readFileSync(file, 'utf8'));
53
40
  }
54
41
  const mod = (await import(pathToFileURL(file).href));
@@ -63,17 +50,9 @@ function find(root, base) {
63
50
  }
64
51
  return undefined;
65
52
  }
66
- /** The Vite (and Vite+) config files, in the order Vite and Vitest look for them. */
67
53
  const VITE_CONFIGS = ['vite.config', 'vitest.config'].flatMap((base) => ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'].map((ext) => `${base}${ext}`));
68
- /**
69
- * The `mutate` block of the project's Vite config — the Vite+ shape, one file for test, lint, fmt
70
- * and mutate. Loaded with the project's own Vite (`loadConfigFromFile`), so a TypeScript config and
71
- * its imports work as Vite sees them. Inside the block the file list is `include`, as Vitest names
72
- * it; Stryker's `mutate` is accepted too. No Vite installed, or no block: nothing.
73
- */
74
54
  async function fromViteConfig(root) {
75
55
  const file = VITE_CONFIGS.map((f) => path.join(root, f)).find((f) => existsSync(f));
76
- // Stryker disable next-line ConditionalExpression: without the guard Vite searches the root for the same names, finds none, and returns null, which returns undefined below
77
56
  if (!file)
78
57
  return undefined;
79
58
  let vite;
@@ -83,14 +62,11 @@ async function fromViteConfig(root) {
83
62
  catch {
84
63
  return undefined;
85
64
  }
86
- // Stryker disable next-line StringLiteral: the log level only decides whether Vite prints, and a config that loads prints nothing at any level
87
65
  const level = 'silent';
88
66
  const loaded = await vite.loadConfigFromFile({ command: 'serve', mode: 'test' }, file, root, level);
89
- // Stryker disable next-line OptionalChaining: given a file that exists, Vite returns a config or throws; null is its answer only when it found no file
90
67
  const block = loaded?.config['mutate'];
91
68
  if (!block || typeof block !== 'object')
92
69
  return undefined;
93
- // `include` is the Vitest spelling; Stryker's `mutate`, when given too, is spread over it.
94
70
  const { include, ...rest } = block;
95
71
  return { file, block: { mutate: include, ...rest } };
96
72
  }
@@ -140,7 +116,6 @@ export async function loadOptions(root, overrides = {}, configFile) {
140
116
  allowEmpty: raw['allowEmpty'] === true,
141
117
  specOracles: raw['specOracles'] ?? [],
142
118
  ignore: raw['ignore'] ?? [],
143
- // Stryker disable next-line ArrayDeclaration: a placeholder element is not 'typescript' either, so includes() is false as for an empty list
144
119
  typeCheck: (raw['checkers'] ?? []).includes('typescript'),
145
120
  tsconfigFile: raw['tsconfigFile'] ?? 'tsconfig.json',
146
121
  typeChecker: typeof raw['typeChecker'] === 'string' ? raw['typeChecker'] : undefined,
@@ -149,17 +124,14 @@ export async function loadOptions(root, overrides = {}, configFile) {
149
124
  ...overrides,
150
125
  };
151
126
  }
152
- // One `\s` after next-line is enough: the list's class takes any further whitespace, and is trimmed.
153
127
  const DIRECTIVE = /\/\/\s*(?:Stryker|oxmutate)\s+disable\s+next-line\s([\w,\s]+?|all)\s*:\s*(.*)/i;
154
128
  const NO_REASON = /\/\/\s*(?:Stryker|oxmutate)\s+disable\s+next-line\s([\w,\s]+?|all)\s*$/i;
155
- /** Disable directives in a file. A directive without a reason is an error (burgee's own rule). */
156
129
  export function disables(source, file) {
157
130
  const out = [];
158
131
  const errors = [];
159
132
  source.split('\n').forEach((line, i) => {
160
133
  const m = DIRECTIVE.exec(line);
161
134
  if (m) {
162
- // Stryker disable next-line StringLiteral: both groups take part in every match, so the fallback is never used; it is there for the type
163
135
  const [list, reason] = [(m[1] ?? '').trim(), (m[2] ?? '').trim()];
164
136
  out.push({ line: i + 2, mutators: list.toLowerCase() === 'all' ? 'all' : list.split(',').map((s) => s.trim()).filter(Boolean), reason });
165
137
  }
@@ -169,4 +141,3 @@ export function disables(source, file) {
169
141
  });
170
142
  return { disables: out, errors };
171
143
  }
172
- //# sourceMappingURL=config.js.map