@mutineerjs/mutineer 0.1.0
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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/admin/assets/index-B7nXq-e7.js +32 -0
- package/dist/admin/assets/index-B7nXq-e7.js.map +1 -0
- package/dist/admin/assets/index-BDQLkBUE.js +32 -0
- package/dist/admin/assets/index-BDQLkBUE.js.map +1 -0
- package/dist/admin/assets/index-DVkP-Tc7.css +1 -0
- package/dist/admin/index.html +13 -0
- package/dist/admin/server/admin.d.ts +6 -0
- package/dist/admin/server/admin.js +234 -0
- package/dist/bin/mutate-vitest.d.ts +2 -0
- package/dist/bin/mutate-vitest.js +90 -0
- package/dist/bin/mutineer.d.ts +2 -0
- package/dist/bin/mutineer.js +46 -0
- package/dist/core/__tests__/module.spec.d.ts +1 -0
- package/dist/core/__tests__/module.spec.js +6 -0
- package/dist/core/module.d.ts +11 -0
- package/dist/core/module.js +14 -0
- package/dist/core/sfc.d.ts +12 -0
- package/dist/core/sfc.js +54 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.js +1 -0
- package/dist/core/variant-utils.d.ts +30 -0
- package/dist/core/variant-utils.js +54 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/mutators/__tests__/registry.spec.d.ts +1 -0
- package/dist/mutators/__tests__/registry.spec.js +43 -0
- package/dist/mutators/__tests__/utils.spec.d.ts +1 -0
- package/dist/mutators/__tests__/utils.spec.js +15 -0
- package/dist/mutators/registry.d.ts +37 -0
- package/dist/mutators/registry.js +101 -0
- package/dist/mutators/types.d.ts +39 -0
- package/dist/mutators/types.js +7 -0
- package/dist/mutators/utils.d.ts +37 -0
- package/dist/mutators/utils.js +151 -0
- package/dist/plugin/viteMutate.d.ts +15 -0
- package/dist/plugin/viteMutate.js +52 -0
- package/dist/plugin/vitest.setup.d.ts +47 -0
- package/dist/plugin/vitest.setup.js +118 -0
- package/dist/plugin/withVitest.d.ts +13 -0
- package/dist/plugin/withVitest.js +30 -0
- package/dist/runner/__tests__/discover.spec.d.ts +1 -0
- package/dist/runner/__tests__/discover.spec.js +59 -0
- package/dist/runner/__tests__/orchestrator.spec.d.ts +1 -0
- package/dist/runner/__tests__/orchestrator.spec.js +55 -0
- package/dist/runner/adapters/__tests__/jest.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/jest.spec.js +88 -0
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +59 -0
- package/dist/runner/adapters/__tests__/vitest.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/vitest.spec.js +118 -0
- package/dist/runner/adapters/index.d.ts +10 -0
- package/dist/runner/adapters/index.js +9 -0
- package/dist/runner/adapters/jest/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/adapters/jest/__tests__/index.spec.js +88 -0
- package/dist/runner/adapters/jest/index.d.ts +24 -0
- package/dist/runner/adapters/jest/index.js +216 -0
- package/dist/runner/adapters/jest/worker-runtime.d.ts +37 -0
- package/dist/runner/adapters/jest/worker-runtime.js +171 -0
- package/dist/runner/adapters/jest-worker-runtime.d.ts +37 -0
- package/dist/runner/adapters/jest-worker-runtime.js +171 -0
- package/dist/runner/adapters/jest.d.ts +24 -0
- package/dist/runner/adapters/jest.js +216 -0
- package/dist/runner/adapters/types.d.ts +89 -0
- package/dist/runner/adapters/types.js +8 -0
- package/dist/runner/adapters/vitest/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/adapters/vitest/__tests__/index.spec.js +118 -0
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +59 -0
- package/dist/runner/adapters/vitest/index.d.ts +33 -0
- package/dist/runner/adapters/vitest/index.js +267 -0
- package/dist/runner/adapters/vitest/worker-runtime.d.ts +25 -0
- package/dist/runner/adapters/vitest/worker-runtime.js +118 -0
- package/dist/runner/adapters/vitest-worker-runtime.d.ts +25 -0
- package/dist/runner/adapters/vitest-worker-runtime.js +118 -0
- package/dist/runner/adapters/vitest.d.ts +33 -0
- package/dist/runner/adapters/vitest.js +267 -0
- package/dist/runner/args.d.ts +50 -0
- package/dist/runner/args.js +123 -0
- package/dist/runner/cache.d.ts +38 -0
- package/dist/runner/cache.js +118 -0
- package/dist/runner/changed.d.ts +22 -0
- package/dist/runner/changed.js +210 -0
- package/dist/runner/cleanup.d.ts +4 -0
- package/dist/runner/cleanup.js +21 -0
- package/dist/runner/config.d.ts +13 -0
- package/dist/runner/config.js +94 -0
- package/dist/runner/discover.d.ts +7 -0
- package/dist/runner/discover.js +258 -0
- package/dist/runner/jest/__tests__/adapter.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/adapter.spec.js +110 -0
- package/dist/runner/jest/adapter.d.ts +24 -0
- package/dist/runner/jest/adapter.js +191 -0
- package/dist/runner/jest/index.d.ts +8 -0
- package/dist/runner/jest/index.js +7 -0
- package/dist/runner/jest/pool.d.ts +47 -0
- package/dist/runner/jest/pool.js +307 -0
- package/dist/runner/jest/resolver.cjs +61 -0
- package/dist/runner/jest/resolver.d.cts +11 -0
- package/dist/runner/jest/worker-runtime.d.ts +30 -0
- package/dist/runner/jest/worker-runtime.js +98 -0
- package/dist/runner/jest/worker.d.mts +1 -0
- package/dist/runner/jest/worker.mjs +55 -0
- package/dist/runner/orchestrator.d.ts +13 -0
- package/dist/runner/orchestrator.js +387 -0
- package/dist/runner/pool/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/index.spec.js +83 -0
- package/dist/runner/pool/__tests__/pool-plugin.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +59 -0
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +78 -0
- package/dist/runner/pool/index.d.ts +8 -0
- package/dist/runner/pool/index.js +9 -0
- package/dist/runner/pool/jest/pool.d.ts +52 -0
- package/dist/runner/pool/jest/pool.js +309 -0
- package/dist/runner/pool/jest/worker.d.mts +1 -0
- package/dist/runner/pool/jest/worker.mjs +60 -0
- package/dist/runner/pool/jest-pool.d.ts +52 -0
- package/dist/runner/pool/jest-pool.js +309 -0
- package/dist/runner/pool/jest-worker.d.mts +1 -0
- package/dist/runner/pool/jest-worker.mjs +60 -0
- package/dist/runner/pool/plugin.d.ts +18 -0
- package/dist/runner/pool/plugin.js +60 -0
- package/dist/runner/pool/pool-plugin.d.ts +18 -0
- package/dist/runner/pool/pool-plugin.js +60 -0
- package/dist/runner/pool/pool-redirect-loader.d.ts +19 -0
- package/dist/runner/pool/pool-redirect-loader.js +116 -0
- package/dist/runner/pool/pool-redirect-loader.mjs +146 -0
- package/dist/runner/pool/redirect-loader.d.ts +19 -0
- package/dist/runner/pool/redirect-loader.js +116 -0
- package/dist/runner/pool/vitest/pool.d.ts +70 -0
- package/dist/runner/pool/vitest/pool.js +376 -0
- package/dist/runner/pool/vitest/worker.d.mts +15 -0
- package/dist/runner/pool/vitest/worker.mjs +96 -0
- package/dist/runner/pool/vitest-worker.d.mts +15 -0
- package/dist/runner/pool/vitest-worker.mjs +96 -0
- package/dist/runner/shared/index.d.ts +9 -0
- package/dist/runner/shared/index.js +8 -0
- package/dist/runner/shared/mutant-paths.d.ts +15 -0
- package/dist/runner/shared/mutant-paths.js +30 -0
- package/dist/runner/shared/redirect-state.d.ts +45 -0
- package/dist/runner/shared/redirect-state.js +50 -0
- package/dist/runner/shared-module-redirect.d.ts +56 -0
- package/dist/runner/shared-module-redirect.js +84 -0
- package/dist/runner/types.d.ts +88 -0
- package/dist/runner/types.js +8 -0
- package/dist/runner/variants.d.ts +21 -0
- package/dist/runner/variants.js +66 -0
- package/dist/runner/vitest/__tests__/adapter.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/adapter.spec.js +131 -0
- package/dist/runner/vitest/__tests__/plugin.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/plugin.spec.js +65 -0
- package/dist/runner/vitest/__tests__/pool.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/pool.spec.js +106 -0
- package/dist/runner/vitest/__tests__/redirect-loader.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/redirect-loader.spec.js +87 -0
- package/dist/runner/vitest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/worker-runtime.spec.js +75 -0
- package/dist/runner/vitest/adapter.d.ts +33 -0
- package/dist/runner/vitest/adapter.js +277 -0
- package/dist/runner/vitest/index.d.ts +11 -0
- package/dist/runner/vitest/index.js +10 -0
- package/dist/runner/vitest/plugin.d.ts +12 -0
- package/dist/runner/vitest/plugin.js +49 -0
- package/dist/runner/vitest/pool.d.ts +65 -0
- package/dist/runner/vitest/pool.js +376 -0
- package/dist/runner/vitest/redirect-loader.d.ts +30 -0
- package/dist/runner/vitest/redirect-loader.js +123 -0
- package/dist/runner/vitest/worker-runtime.d.ts +16 -0
- package/dist/runner/vitest/worker-runtime.js +105 -0
- package/dist/runner/vitest/worker.d.mts +15 -0
- package/dist/runner/vitest/worker.mjs +92 -0
- package/dist/types/api.d.ts +20 -0
- package/dist/types/api.js +1 -0
- package/dist/types/config.d.ts +48 -0
- package/dist/types/config.js +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.js +11 -0
- package/dist/types/mutant.d.ts +44 -0
- package/dist/types/mutant.js +7 -0
- package/dist/utils/PoolSpinner.d.ts +5 -0
- package/dist/utils/PoolSpinner.js +6 -0
- package/dist/utils/ProgressBar.d.ts +11 -0
- package/dist/utils/ProgressBar.js +9 -0
- package/dist/utils/__tests__/coverage.spec.d.ts +1 -0
- package/dist/utils/__tests__/coverage.spec.js +91 -0
- package/dist/utils/__tests__/progress.spec.d.ts +1 -0
- package/dist/utils/__tests__/progress.spec.js +50 -0
- package/dist/utils/__tests__/summary.spec.d.ts +1 -0
- package/dist/utils/__tests__/summary.spec.js +54 -0
- package/dist/utils/coverage.d.ts +57 -0
- package/dist/utils/coverage.js +204 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +18 -0
- package/dist/utils/progress.d.ts +25 -0
- package/dist/utils/progress.js +90 -0
- package/dist/utils/summary.d.ts +12 -0
- package/dist/utils/summary.js +107 -0
- package/package.json +59 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getRegistry } from '../registry.js';
|
|
3
|
+
const ALL_NAMES = [
|
|
4
|
+
'relaxLE',
|
|
5
|
+
'relaxGE',
|
|
6
|
+
'tightenLT',
|
|
7
|
+
'tightenGT',
|
|
8
|
+
'andToOr',
|
|
9
|
+
'orToAnd',
|
|
10
|
+
'nullishToOr',
|
|
11
|
+
'flipEQ',
|
|
12
|
+
'flipNEQ',
|
|
13
|
+
'flipStrictEQ',
|
|
14
|
+
'flipStrictNEQ',
|
|
15
|
+
'addToSub',
|
|
16
|
+
'subToAdd',
|
|
17
|
+
'mulToDiv',
|
|
18
|
+
'divToMul',
|
|
19
|
+
'modToMul',
|
|
20
|
+
'powerToMul',
|
|
21
|
+
];
|
|
22
|
+
describe('mutator registry', () => {
|
|
23
|
+
it('returns all mutators by default in declared order', () => {
|
|
24
|
+
const all = getRegistry().map((m) => m.name);
|
|
25
|
+
expect(all).toEqual([...ALL_NAMES]);
|
|
26
|
+
});
|
|
27
|
+
it('can include only specified mutators', () => {
|
|
28
|
+
const only = getRegistry(['andToOr', 'flipEQ', 'subToAdd']).map((m) => m.name);
|
|
29
|
+
expect(only).toEqual(['andToOr', 'flipEQ', 'subToAdd']);
|
|
30
|
+
});
|
|
31
|
+
it('can exclude specific mutators', () => {
|
|
32
|
+
const filtered = getRegistry(undefined, [
|
|
33
|
+
'flipEQ',
|
|
34
|
+
'relaxGE',
|
|
35
|
+
'modToMul',
|
|
36
|
+
]).map((m) => m.name);
|
|
37
|
+
expect(filtered).toEqual(ALL_NAMES.filter((n) => !['flipEQ', 'relaxGE', 'modToMul'].includes(n)));
|
|
38
|
+
});
|
|
39
|
+
it('exclude still filters after include selection', () => {
|
|
40
|
+
const filtered = getRegistry(['flipEQ', 'relaxGE', 'nullishToOr'], ['flipEQ']).map((m) => m.name);
|
|
41
|
+
expect(filtered).toEqual(['relaxGE', 'nullishToOr']);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { collectOperatorTargets } from '../utils.js';
|
|
3
|
+
describe('collectOperatorTargets', () => {
|
|
4
|
+
it('honors mutineer disable comments', () => {
|
|
5
|
+
const src = `// mutineer-disable-next-line
|
|
6
|
+
const a = b && c
|
|
7
|
+
const b = c && d // mutineer-disable-line
|
|
8
|
+
const c = d && e /* mutineer-disable */
|
|
9
|
+
const d = e && f
|
|
10
|
+
`;
|
|
11
|
+
const targets = collectOperatorTargets(src, '&&');
|
|
12
|
+
const lines = targets.map((t) => t.line);
|
|
13
|
+
expect(lines).toEqual([5]);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutator registry and factory.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to create operator mutators and retrieve registered mutators
|
|
5
|
+
* with optional include/exclude filtering.
|
|
6
|
+
*/
|
|
7
|
+
import type { ASTMutator } from './types.js';
|
|
8
|
+
export declare const relaxLE: ASTMutator;
|
|
9
|
+
export declare const relaxGE: ASTMutator;
|
|
10
|
+
export declare const tightenLT: ASTMutator;
|
|
11
|
+
export declare const tightenGT: ASTMutator;
|
|
12
|
+
export declare const andToOr: ASTMutator;
|
|
13
|
+
export declare const orToAnd: ASTMutator;
|
|
14
|
+
export declare const nullishToOr: ASTMutator;
|
|
15
|
+
export declare const flipEQ: ASTMutator;
|
|
16
|
+
export declare const flipNEQ: ASTMutator;
|
|
17
|
+
export declare const flipStrictEQ: ASTMutator;
|
|
18
|
+
export declare const flipStrictNEQ: ASTMutator;
|
|
19
|
+
export declare const addToSub: ASTMutator;
|
|
20
|
+
export declare const subToAdd: ASTMutator;
|
|
21
|
+
export declare const mulToDiv: ASTMutator;
|
|
22
|
+
export declare const divToMul: ASTMutator;
|
|
23
|
+
export declare const modToMul: ASTMutator;
|
|
24
|
+
export declare const powerToMul: ASTMutator;
|
|
25
|
+
/**
|
|
26
|
+
* Get a filtered registry of mutators based on include/exclude options.
|
|
27
|
+
*
|
|
28
|
+
* If include list provided, only those mutators are returned.
|
|
29
|
+
* If exclude list provided, those mutators are filtered out.
|
|
30
|
+
* Include list takes precedence over exclude list.
|
|
31
|
+
*
|
|
32
|
+
* @param include - Optional list of mutator names to include
|
|
33
|
+
* @param exclude - Optional list of mutator names to exclude
|
|
34
|
+
* @returns Filtered array of mutators
|
|
35
|
+
*/
|
|
36
|
+
export declare function getRegistry(include?: readonly string[], exclude?: readonly string[]): ASTMutator[];
|
|
37
|
+
export type { ASTMutator, AnyMutator, MutationOutput } from './types.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutator registry and factory.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to create operator mutators and retrieve registered mutators
|
|
5
|
+
* with optional include/exclude filtering.
|
|
6
|
+
*/
|
|
7
|
+
import { collectOperatorTargets } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Factory to build an operator mutator using AST traversal and token analysis.
|
|
10
|
+
* Creates a reusable mutator that finds and replaces a specific operator throughout the code.
|
|
11
|
+
*
|
|
12
|
+
* @param name - Name of the mutator (e.g., 'andToOr')
|
|
13
|
+
* @param description - Human-readable description
|
|
14
|
+
* @param fromOp - The operator to find (e.g., '&&')
|
|
15
|
+
* @param toOp - The operator to replace it with (e.g., '||')
|
|
16
|
+
* @returns An ASTMutator that applies this transformation
|
|
17
|
+
*/
|
|
18
|
+
function makeOperatorMutator(name, description, fromOp, toOp) {
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
description,
|
|
22
|
+
apply(src) {
|
|
23
|
+
// 1) Collect exact operator token locations from matching nodes
|
|
24
|
+
const targets = collectOperatorTargets(src, fromOp);
|
|
25
|
+
// 2) For each occurrence, produce a mutated version of the programme
|
|
26
|
+
const outputs = [];
|
|
27
|
+
for (const target of targets) {
|
|
28
|
+
const code = src.slice(0, target.start) + toOp + src.slice(target.end);
|
|
29
|
+
outputs.push({
|
|
30
|
+
line: target.line,
|
|
31
|
+
col: target.col1,
|
|
32
|
+
code,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return outputs;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/* === Concrete mutators (AST-safe) === */
|
|
40
|
+
export const relaxLE = makeOperatorMutator('relaxLE', "Change '<=' to '<' (relax boundary)", '<=', '<');
|
|
41
|
+
export const relaxGE = makeOperatorMutator('relaxGE', "Change '>=' to '>' (relax boundary)", '>=', '>');
|
|
42
|
+
export const tightenLT = makeOperatorMutator('tightenLT', "Change '<' to '<=' (tighten boundary)", '<', '<=');
|
|
43
|
+
export const tightenGT = makeOperatorMutator('tightenGT', "Change '>' to '>=' (tighten boundary)", '>', '>=');
|
|
44
|
+
export const andToOr = makeOperatorMutator('andToOr', "Change '&&' to '||' in boolean expressions", '&&', '||');
|
|
45
|
+
export const orToAnd = makeOperatorMutator('orToAnd', "Change '||' to '&&' in boolean expressions", '||', '&&');
|
|
46
|
+
export const nullishToOr = makeOperatorMutator('nullishToOr', "Change '??' to '||' to prefer boolean fallback", '??', '||');
|
|
47
|
+
export const flipEQ = makeOperatorMutator('flipEQ', "Change '==' to '!='", '==', '!=');
|
|
48
|
+
export const flipNEQ = makeOperatorMutator('flipNEQ', "Change '!=' to '=='", '!=', '==');
|
|
49
|
+
export const flipStrictEQ = makeOperatorMutator('flipStrictEQ', "Change '===' to '!=='", '===', '!==');
|
|
50
|
+
export const flipStrictNEQ = makeOperatorMutator('flipStrictNEQ', "Change '!==' to '==='", '!==', '===');
|
|
51
|
+
export const addToSub = makeOperatorMutator('addToSub', "Change '+' to '-' in arithmetic expressions", '+', '-');
|
|
52
|
+
export const subToAdd = makeOperatorMutator('subToAdd', "Change '-' to '+' in arithmetic expressions", '-', '+');
|
|
53
|
+
export const mulToDiv = makeOperatorMutator('mulToDiv', "Change '*' to '/' in arithmetic expressions", '*', '/');
|
|
54
|
+
export const divToMul = makeOperatorMutator('divToMul', "Change '/' to '*' in arithmetic expressions", '/', '*');
|
|
55
|
+
export const modToMul = makeOperatorMutator('modToMul', "Change '%' to '*' in arithmetic expressions", '%', '*');
|
|
56
|
+
export const powerToMul = makeOperatorMutator('powerToMul', "Change '**' to '*' in arithmetic expressions", '**', '*');
|
|
57
|
+
/**
|
|
58
|
+
* All registered mutators in order of precedence.
|
|
59
|
+
*/
|
|
60
|
+
const ALL = [
|
|
61
|
+
relaxLE,
|
|
62
|
+
relaxGE,
|
|
63
|
+
tightenLT,
|
|
64
|
+
tightenGT,
|
|
65
|
+
andToOr,
|
|
66
|
+
orToAnd,
|
|
67
|
+
nullishToOr,
|
|
68
|
+
flipEQ,
|
|
69
|
+
flipNEQ,
|
|
70
|
+
flipStrictEQ,
|
|
71
|
+
flipStrictNEQ,
|
|
72
|
+
addToSub,
|
|
73
|
+
subToAdd,
|
|
74
|
+
mulToDiv,
|
|
75
|
+
divToMul,
|
|
76
|
+
modToMul,
|
|
77
|
+
powerToMul,
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Get a filtered registry of mutators based on include/exclude options.
|
|
81
|
+
*
|
|
82
|
+
* If include list provided, only those mutators are returned.
|
|
83
|
+
* If exclude list provided, those mutators are filtered out.
|
|
84
|
+
* Include list takes precedence over exclude list.
|
|
85
|
+
*
|
|
86
|
+
* @param include - Optional list of mutator names to include
|
|
87
|
+
* @param exclude - Optional list of mutator names to exclude
|
|
88
|
+
* @returns Filtered array of mutators
|
|
89
|
+
*/
|
|
90
|
+
export function getRegistry(include, exclude) {
|
|
91
|
+
let list = ALL;
|
|
92
|
+
// If include list provided, filter to only those mutators
|
|
93
|
+
if (include?.length) {
|
|
94
|
+
list = list.filter((m) => include.includes(m.name));
|
|
95
|
+
}
|
|
96
|
+
// If exclude list provided, remove those mutators
|
|
97
|
+
if (exclude?.length) {
|
|
98
|
+
list = list.filter((m) => !exclude.includes(m.name));
|
|
99
|
+
}
|
|
100
|
+
return list;
|
|
101
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for mutation strategies.
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface that all mutators must implement and the output format
|
|
5
|
+
* for mutations. This allows for different mutator implementations to be plugged in.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Output of a single mutation, including location info and mutated code.
|
|
9
|
+
*/
|
|
10
|
+
export interface MutationOutput {
|
|
11
|
+
readonly line: number;
|
|
12
|
+
readonly col: number;
|
|
13
|
+
readonly code: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Base interface for AST-based mutators.
|
|
17
|
+
* Implementations should parse source code and generate mutations by analyzing the AST.
|
|
18
|
+
*/
|
|
19
|
+
export interface ASTMutator {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
apply(src: string): readonly MutationOutput[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Union type for different mutator kinds.
|
|
26
|
+
* Potentially extensible in future to RegexMutator, TextMutator, etc.
|
|
27
|
+
*/
|
|
28
|
+
export type AnyMutator = ASTMutator;
|
|
29
|
+
/**
|
|
30
|
+
* Internal interface for operator target locations.
|
|
31
|
+
* Used by operator mutators to track exact positions of operators in source code.
|
|
32
|
+
*/
|
|
33
|
+
export interface OperatorTarget {
|
|
34
|
+
readonly start: number;
|
|
35
|
+
readonly end: number;
|
|
36
|
+
readonly line: number;
|
|
37
|
+
readonly col1: number;
|
|
38
|
+
readonly op: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for AST-based mutation.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for parsing, traversing, and analyzing code ASTs.
|
|
5
|
+
* These utilities are used by operator mutators to locate and replace operators.
|
|
6
|
+
*/
|
|
7
|
+
import * as t from '@babel/types';
|
|
8
|
+
import type { OperatorTarget } from './types.js';
|
|
9
|
+
export declare const traverse: typeof import('@babel/traverse').default;
|
|
10
|
+
/**
|
|
11
|
+
* Tab width used for converting character columns to visual columns.
|
|
12
|
+
* This helps report correct column positions for terminals that render tabs.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TAB_WIDTH = 4;
|
|
15
|
+
/**
|
|
16
|
+
* Convert a character-based column offset to a visual column, accounting for tabs.
|
|
17
|
+
* For example, a tab at the start of a line counts as multiple spaces visually.
|
|
18
|
+
*
|
|
19
|
+
* @param src - The source code
|
|
20
|
+
* @param charOffset - The character offset (0-based)
|
|
21
|
+
* @returns The visual column (1-based)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getVisualColumn(src: string, charOffset: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if a node is a BinaryExpression or LogicalExpression.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isBinaryOrLogical(node: t.Node): node is t.BinaryExpression | t.LogicalExpression;
|
|
28
|
+
/**
|
|
29
|
+
* Collect the operator tokens for a given operator and return their exact locations.
|
|
30
|
+
* Uses AST traversal to find BinaryExpression/LogicalExpression nodes, then maps them
|
|
31
|
+
* to token positions for accurate column reporting.
|
|
32
|
+
*
|
|
33
|
+
* @param src - The source code
|
|
34
|
+
* @param opValue - The operator to search for (e.g., '&&', '<=')
|
|
35
|
+
* @returns Array of target locations for the operator
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectOperatorTargets(src: string, opValue: string): OperatorTarget[];
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for AST-based mutation.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for parsing, traversing, and analyzing code ASTs.
|
|
5
|
+
* These utilities are used by operator mutators to locate and replace operators.
|
|
6
|
+
*/
|
|
7
|
+
import { parse } from '@babel/parser';
|
|
8
|
+
import traverseModule from '@babel/traverse';
|
|
9
|
+
// Normalize the default export shape of @babel/traverse
|
|
10
|
+
const traverseModuleNormalized = traverseModule;
|
|
11
|
+
export const traverse = traverseModuleNormalized.default ?? traverseModuleNormalized;
|
|
12
|
+
/**
|
|
13
|
+
* Parser configuration for Babel.
|
|
14
|
+
* Enables support for TypeScript, JSX, decorators, and modern JavaScript features.
|
|
15
|
+
*/
|
|
16
|
+
const parserOptsTs = {
|
|
17
|
+
sourceType: 'unambiguous',
|
|
18
|
+
plugins: [
|
|
19
|
+
'typescript',
|
|
20
|
+
'jsx',
|
|
21
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
22
|
+
'classProperties',
|
|
23
|
+
'classPrivateProperties',
|
|
24
|
+
'classPrivateMethods',
|
|
25
|
+
'objectRestSpread',
|
|
26
|
+
'optionalChaining',
|
|
27
|
+
'nullishCoalescingOperator',
|
|
28
|
+
],
|
|
29
|
+
tokens: true,
|
|
30
|
+
};
|
|
31
|
+
const parserOptsFlow = {
|
|
32
|
+
sourceType: 'unambiguous',
|
|
33
|
+
plugins: [
|
|
34
|
+
'flow',
|
|
35
|
+
'flowComments',
|
|
36
|
+
'jsx',
|
|
37
|
+
['decorators', { decoratorsBeforeExport: true }],
|
|
38
|
+
'classProperties',
|
|
39
|
+
'classPrivateProperties',
|
|
40
|
+
'classPrivateMethods',
|
|
41
|
+
'objectRestSpread',
|
|
42
|
+
'optionalChaining',
|
|
43
|
+
'nullishCoalescingOperator',
|
|
44
|
+
],
|
|
45
|
+
tokens: true,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Tab width used for converting character columns to visual columns.
|
|
49
|
+
* This helps report correct column positions for terminals that render tabs.
|
|
50
|
+
*/
|
|
51
|
+
export const TAB_WIDTH = 4;
|
|
52
|
+
/**
|
|
53
|
+
* Convert a character-based column offset to a visual column, accounting for tabs.
|
|
54
|
+
* For example, a tab at the start of a line counts as multiple spaces visually.
|
|
55
|
+
*
|
|
56
|
+
* @param src - The source code
|
|
57
|
+
* @param charOffset - The character offset (0-based)
|
|
58
|
+
* @returns The visual column (1-based)
|
|
59
|
+
*/
|
|
60
|
+
export function getVisualColumn(src, charOffset) {
|
|
61
|
+
const lineStartIdx = src.lastIndexOf('\n', charOffset - 1) + 1;
|
|
62
|
+
const linePrefix = src.slice(lineStartIdx, charOffset);
|
|
63
|
+
let visualCol = 1;
|
|
64
|
+
for (const ch of linePrefix) {
|
|
65
|
+
if (ch === '\t') {
|
|
66
|
+
// Advance to next tab stop (multiples of TAB_WIDTH)
|
|
67
|
+
const nextStop = (Math.floor((visualCol - 1) / TAB_WIDTH) + 1) * TAB_WIDTH + 1;
|
|
68
|
+
visualCol = nextStop;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
visualCol++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return visualCol;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Type guard to check if a node is a BinaryExpression or LogicalExpression.
|
|
78
|
+
*/
|
|
79
|
+
export function isBinaryOrLogical(node) {
|
|
80
|
+
return node.type === 'BinaryExpression' || node.type === 'LogicalExpression';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Collect the operator tokens for a given operator and return their exact locations.
|
|
84
|
+
* Uses AST traversal to find BinaryExpression/LogicalExpression nodes, then maps them
|
|
85
|
+
* to token positions for accurate column reporting.
|
|
86
|
+
*
|
|
87
|
+
* @param src - The source code
|
|
88
|
+
* @param opValue - The operator to search for (e.g., '&&', '<=')
|
|
89
|
+
* @returns Array of target locations for the operator
|
|
90
|
+
*/
|
|
91
|
+
export function collectOperatorTargets(src, opValue) {
|
|
92
|
+
let ast;
|
|
93
|
+
try {
|
|
94
|
+
ast = parse(src, parserOptsTs);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Flow-typed React sources fail under TS parsing; fall back to Flow plugins.
|
|
98
|
+
ast = parse(src, parserOptsFlow);
|
|
99
|
+
}
|
|
100
|
+
const fileAst = ast;
|
|
101
|
+
const tokens = fileAst.tokens ?? [];
|
|
102
|
+
const comments = fileAst.comments ?? [];
|
|
103
|
+
const out = [];
|
|
104
|
+
const ignoreLines = new Set();
|
|
105
|
+
for (const comment of comments) {
|
|
106
|
+
const text = comment.value.trim();
|
|
107
|
+
if (!text)
|
|
108
|
+
continue;
|
|
109
|
+
if (!comment.loc)
|
|
110
|
+
continue;
|
|
111
|
+
const startLine = comment.loc.start.line;
|
|
112
|
+
const endLine = comment.loc.end.line;
|
|
113
|
+
if (text.includes('mutineer-disable-next-line')) {
|
|
114
|
+
ignoreLines.add(endLine + 1);
|
|
115
|
+
}
|
|
116
|
+
if (text.includes('mutineer-disable-line') ||
|
|
117
|
+
text.includes('mutineer-disable')) {
|
|
118
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
119
|
+
ignoreLines.add(line);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
traverse(ast, {
|
|
124
|
+
enter(p) {
|
|
125
|
+
if (!isBinaryOrLogical(p.node))
|
|
126
|
+
return;
|
|
127
|
+
const n = p.node;
|
|
128
|
+
if (n.operator !== opValue)
|
|
129
|
+
return;
|
|
130
|
+
// Find the exact operator token inside the node span
|
|
131
|
+
const nodeStart = n.start ?? 0;
|
|
132
|
+
const nodeEnd = n.end ?? 0;
|
|
133
|
+
const tok = tokens.find((tk) => tk.start >= nodeStart && tk.end <= nodeEnd && tk.value === opValue);
|
|
134
|
+
if (tok) {
|
|
135
|
+
// Convert Babel's character-based column to a visual column for accurate reporting
|
|
136
|
+
const line = tok.loc.start.line;
|
|
137
|
+
if (ignoreLines.has(line))
|
|
138
|
+
return;
|
|
139
|
+
const visualCol = getVisualColumn(src, tok.start);
|
|
140
|
+
out.push({
|
|
141
|
+
start: tok.start,
|
|
142
|
+
end: tok.end,
|
|
143
|
+
line,
|
|
144
|
+
col1: visualCol, // convert to 1-based
|
|
145
|
+
op: opValue,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PluginOption } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Vite plugin that intercepts module loading during mutation testing.
|
|
4
|
+
*
|
|
5
|
+
* When MUTATE_FILE and MUTATE_CODE environment variables are set, this plugin
|
|
6
|
+
* will replace the target file's code with the mutated version. This allows
|
|
7
|
+
* vitest to run tests against each mutation variant.
|
|
8
|
+
*
|
|
9
|
+
* Environment Variables:
|
|
10
|
+
* - MUTATE_FILE: Absolute path to the file being mutated
|
|
11
|
+
* - MUTATE_CODE: The mutated source code to inject
|
|
12
|
+
*
|
|
13
|
+
* @returns Vite plugin option
|
|
14
|
+
*/
|
|
15
|
+
export declare function viteMutineerPlugin(): PluginOption;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Vite plugin that intercepts module loading during mutation testing.
|
|
4
|
+
*
|
|
5
|
+
* When MUTATE_FILE and MUTATE_CODE environment variables are set, this plugin
|
|
6
|
+
* will replace the target file's code with the mutated version. This allows
|
|
7
|
+
* vitest to run tests against each mutation variant.
|
|
8
|
+
*
|
|
9
|
+
* Environment Variables:
|
|
10
|
+
* - MUTATE_FILE: Absolute path to the file being mutated
|
|
11
|
+
* - MUTATE_CODE: The mutated source code to inject
|
|
12
|
+
*
|
|
13
|
+
* @returns Vite plugin option
|
|
14
|
+
*/
|
|
15
|
+
export function viteMutineerPlugin() {
|
|
16
|
+
// Normalize and cache the target path during initialization
|
|
17
|
+
const mutateFile = process.env.MUTATE_FILE;
|
|
18
|
+
const mutateCode = process.env.MUTATE_CODE;
|
|
19
|
+
let normalizedTarget = null;
|
|
20
|
+
if (mutateFile) {
|
|
21
|
+
try {
|
|
22
|
+
normalizedTarget = path.resolve(mutateFile);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.warn(`[mutineer] Failed to resolve MUTATE_FILE: ${mutateFile}`, err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
name: 'mutineer:swap',
|
|
30
|
+
enforce: 'pre',
|
|
31
|
+
load(id) {
|
|
32
|
+
// Early exit if no mutation is active
|
|
33
|
+
if (!normalizedTarget || !mutateCode) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
// Normalize the module ID, handling query strings
|
|
37
|
+
const cleanId = id.split('?')[0];
|
|
38
|
+
let normalizedId;
|
|
39
|
+
try {
|
|
40
|
+
normalizedId = path.resolve(cleanId);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
// Return mutated code only if this is the target file
|
|
46
|
+
if (normalizedId === normalizedTarget) {
|
|
47
|
+
return mutateCode;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest setup file that decorates test titles with mutation metadata.
|
|
3
|
+
*
|
|
4
|
+
* When a mutation is active (MUTATE_ACTIVE=1), this file wraps the global
|
|
5
|
+
* test functions (it, test, describe) to append mutation labels to test titles.
|
|
6
|
+
* This helps identify which mutation caused which test failures in the output.
|
|
7
|
+
*
|
|
8
|
+
* Environment Variables:
|
|
9
|
+
* - MUTATE_ACTIVE: Set to '1' to enable mutation decoration
|
|
10
|
+
* - MUTATE_ID: Unique identifier for the mutation (e.g., 'relaxLE#0')
|
|
11
|
+
* - MUTATE_NAME: Human-readable name of the mutation (e.g., 'relaxLE')
|
|
12
|
+
*
|
|
13
|
+
* Example output: "should validate input @relaxLE#0 relaxLE"
|
|
14
|
+
*/
|
|
15
|
+
declare const active: boolean;
|
|
16
|
+
declare const id: string;
|
|
17
|
+
declare const mutationName: string;
|
|
18
|
+
declare const label: string;
|
|
19
|
+
/**
|
|
20
|
+
* Appends mutation label to test title if not already present.
|
|
21
|
+
*/
|
|
22
|
+
declare function decorateTitle(title: unknown): unknown;
|
|
23
|
+
type ItLike = typeof it;
|
|
24
|
+
type DescribeLike = typeof describe;
|
|
25
|
+
type AnyCallable = (...args: any[]) => unknown;
|
|
26
|
+
type Chainers = 'skip' | 'only' | 'todo' | 'concurrent' | 'sequential' | 'fails' | 'retry' | 'runIf' | 'skipIf';
|
|
27
|
+
type ChainableRecord = {
|
|
28
|
+
each?: AnyCallable;
|
|
29
|
+
} & Partial<Record<Chainers, AnyCallable>>;
|
|
30
|
+
type ChainableCallable = AnyCallable & ChainableRecord;
|
|
31
|
+
/** Chainers that need to be forwarded from original to wrapped callable. */
|
|
32
|
+
declare const CHAINERS: readonly Chainers[];
|
|
33
|
+
/**
|
|
34
|
+
* Helper to attach .each(...) method to a callable.
|
|
35
|
+
* Ensures title decoration applies to parameterized tests.
|
|
36
|
+
*/
|
|
37
|
+
declare function attachEachMethod(callable: AnyCallable, each: AnyCallable): AnyCallable;
|
|
38
|
+
/**
|
|
39
|
+
* Wraps a test function (it/test/describe) to decorate titles with mutation metadata.
|
|
40
|
+
* Preserves all chainers, static properties, and symbols from the original function.
|
|
41
|
+
*/
|
|
42
|
+
declare function wrapCallable<T extends AnyCallable>(original: T): T;
|
|
43
|
+
declare const g: typeof globalThis & {
|
|
44
|
+
it?: ItLike;
|
|
45
|
+
test?: ItLike;
|
|
46
|
+
describe?: DescribeLike;
|
|
47
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vitest setup file that decorates test titles with mutation metadata.
|
|
4
|
+
*
|
|
5
|
+
* When a mutation is active (MUTATE_ACTIVE=1), this file wraps the global
|
|
6
|
+
* test functions (it, test, describe) to append mutation labels to test titles.
|
|
7
|
+
* This helps identify which mutation caused which test failures in the output.
|
|
8
|
+
*
|
|
9
|
+
* Environment Variables:
|
|
10
|
+
* - MUTATE_ACTIVE: Set to '1' to enable mutation decoration
|
|
11
|
+
* - MUTATE_ID: Unique identifier for the mutation (e.g., 'relaxLE#0')
|
|
12
|
+
* - MUTATE_NAME: Human-readable name of the mutation (e.g., 'relaxLE')
|
|
13
|
+
*
|
|
14
|
+
* Example output: "should validate input @relaxLE#0 relaxLE"
|
|
15
|
+
*/
|
|
16
|
+
const active = process.env.MUTATE_ACTIVE === '1';
|
|
17
|
+
// Early exit if mutation is not active
|
|
18
|
+
if (!active) {
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
const id = process.env.MUTATE_ID || '';
|
|
22
|
+
const mutationName = process.env.MUTATE_NAME || '';
|
|
23
|
+
const label = `@${id} ${mutationName}`;
|
|
24
|
+
/**
|
|
25
|
+
* Appends mutation label to test title if not already present.
|
|
26
|
+
*/
|
|
27
|
+
function decorateTitle(title) {
|
|
28
|
+
return typeof title === 'string' && !title.includes(label)
|
|
29
|
+
? `${title} ${label}`
|
|
30
|
+
: title;
|
|
31
|
+
}
|
|
32
|
+
/** Chainers that need to be forwarded from original to wrapped callable. */
|
|
33
|
+
const CHAINERS = [
|
|
34
|
+
'skip',
|
|
35
|
+
'only',
|
|
36
|
+
'todo',
|
|
37
|
+
'concurrent',
|
|
38
|
+
'sequential',
|
|
39
|
+
'fails',
|
|
40
|
+
'retry',
|
|
41
|
+
'runIf',
|
|
42
|
+
'skipIf'
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Helper to attach .each(...) method to a callable.
|
|
46
|
+
* Ensures title decoration applies to parameterized tests.
|
|
47
|
+
*/
|
|
48
|
+
function attachEachMethod(callable, each) {
|
|
49
|
+
return ((...tableArgs) => {
|
|
50
|
+
const chain = each(...tableArgs);
|
|
51
|
+
return (title, fn, timeout) => chain(decorateTitle(title), fn, timeout);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Wraps a test function (it/test/describe) to decorate titles with mutation metadata.
|
|
56
|
+
* Preserves all chainers, static properties, and symbols from the original function.
|
|
57
|
+
*/
|
|
58
|
+
function wrapCallable(original) {
|
|
59
|
+
// Base wrapper that decorates the title
|
|
60
|
+
const wrapped = ((title, fn, timeout) => {
|
|
61
|
+
return original(decorateTitle(title), fn, timeout);
|
|
62
|
+
});
|
|
63
|
+
const wrappedChainable = wrapped;
|
|
64
|
+
const originalChainable = original;
|
|
65
|
+
// Forward chainers (skip, only, todo, etc.)
|
|
66
|
+
for (const key of CHAINERS) {
|
|
67
|
+
const method = originalChainable[key];
|
|
68
|
+
if (typeof method !== 'function')
|
|
69
|
+
continue;
|
|
70
|
+
const forwarded = ((...args) => method.apply(original, args));
|
|
71
|
+
wrappedChainable[key] = forwarded;
|
|
72
|
+
// Attach .each(...) to chainers that support it
|
|
73
|
+
const each = method.each;
|
|
74
|
+
if (typeof each === 'function') {
|
|
75
|
+
forwarded.each = attachEachMethod(forwarded, each);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Attach top-level .each(...)
|
|
79
|
+
const topEach = originalChainable.each;
|
|
80
|
+
if (typeof topEach === 'function') {
|
|
81
|
+
wrappedChainable.each = attachEachMethod(wrapped, topEach);
|
|
82
|
+
}
|
|
83
|
+
// Preserve static properties and symbols from original
|
|
84
|
+
const wrappedRecord = wrapped;
|
|
85
|
+
for (const key of Object.getOwnPropertyNames(original)) {
|
|
86
|
+
if (key in wrappedRecord)
|
|
87
|
+
continue;
|
|
88
|
+
try {
|
|
89
|
+
const desc = Object.getOwnPropertyDescriptor(original, key);
|
|
90
|
+
if (desc)
|
|
91
|
+
Object.defineProperty(wrapped, key, desc);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Silently skip properties that cannot be copied
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const sym of Object.getOwnPropertySymbols(original)) {
|
|
98
|
+
if (sym in wrappedRecord)
|
|
99
|
+
continue;
|
|
100
|
+
try {
|
|
101
|
+
const desc = Object.getOwnPropertyDescriptor(original, sym);
|
|
102
|
+
if (desc)
|
|
103
|
+
Object.defineProperty(wrapped, sym, desc);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Silently skip symbols that cannot be copied
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return wrapped;
|
|
110
|
+
}
|
|
111
|
+
// Wrap global test functions
|
|
112
|
+
const g = globalThis;
|
|
113
|
+
if (g.it)
|
|
114
|
+
g.it = wrapCallable(g.it);
|
|
115
|
+
if (g.test)
|
|
116
|
+
g.test = wrapCallable(g.test);
|
|
117
|
+
if (g.describe)
|
|
118
|
+
g.describe = wrapCallable(g.describe);
|