@mutineerjs/mutineer 0.2.3 → 0.3.2
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/README.md +48 -42
- package/dist/bin/mutineer.js +0 -0
- package/dist/core/__tests__/module.spec.js +66 -3
- package/dist/core/__tests__/sfc.spec.js +76 -0
- package/dist/core/__tests__/variant-utils.spec.js +93 -0
- package/dist/mutators/__tests__/operator.spec.js +169 -0
- package/dist/mutators/__tests__/registry.spec.js +6 -0
- package/dist/mutators/__tests__/return-value.spec.js +239 -0
- package/dist/mutators/__tests__/utils.spec.js +68 -1
- package/dist/mutators/operator.d.ts +25 -0
- package/dist/mutators/operator.js +50 -0
- package/dist/mutators/registry.d.ts +6 -28
- package/dist/mutators/registry.js +14 -66
- package/dist/mutators/return-value.d.ts +39 -0
- package/dist/mutators/return-value.js +104 -0
- package/dist/mutators/utils.d.ts +21 -0
- package/dist/mutators/utils.js +44 -27
- package/dist/runner/__tests__/args.spec.js +225 -0
- package/dist/runner/__tests__/cache.spec.js +180 -0
- package/dist/runner/__tests__/changed.spec.js +227 -0
- package/dist/runner/__tests__/cleanup.spec.js +41 -0
- package/dist/runner/__tests__/config.spec.js +71 -0
- package/dist/runner/__tests__/coverage-resolver.spec.js +171 -0
- package/dist/runner/__tests__/pool-executor.spec.js +211 -0
- package/dist/runner/__tests__/tasks.spec.js +95 -0
- package/dist/runner/__tests__/variants.spec.js +261 -0
- package/dist/runner/args.d.ts +5 -0
- package/dist/runner/args.js +7 -0
- package/dist/runner/config.js +2 -2
- package/dist/runner/coverage-resolver.d.ts +21 -0
- package/dist/runner/coverage-resolver.js +96 -0
- package/dist/runner/discover.js +2 -1
- package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/jest/__tests__/pool.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/pool.spec.js +211 -0
- package/dist/runner/jest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/worker-runtime.spec.js +148 -0
- package/dist/runner/jest/adapter.js +1 -1
- package/dist/runner/jest/pool.d.ts +1 -1
- package/dist/runner/jest/pool.js +6 -6
- package/dist/runner/jest/worker.mjs +1 -1
- package/dist/runner/orchestrator.js +43 -295
- package/dist/runner/pool-executor.d.ts +17 -0
- package/dist/runner/pool-executor.js +143 -0
- package/dist/runner/shared/__tests__/mutant-paths.spec.d.ts +1 -0
- package/dist/runner/shared/__tests__/mutant-paths.spec.js +66 -0
- package/dist/runner/shared/__tests__/redirect-state.spec.d.ts +1 -0
- package/dist/runner/shared/__tests__/redirect-state.spec.js +56 -0
- package/dist/runner/shared/index.d.ts +1 -1
- package/dist/runner/shared/index.js +1 -1
- package/dist/runner/shared/redirect-state.d.ts +2 -2
- package/dist/runner/shared/redirect-state.js +4 -4
- package/dist/runner/tasks.d.ts +12 -0
- package/dist/runner/tasks.js +25 -0
- package/dist/runner/types.d.ts +1 -1
- package/dist/runner/variants.d.ts +17 -2
- package/dist/runner/variants.js +33 -0
- package/dist/runner/vitest/__tests__/adapter.spec.js +1 -1
- package/dist/runner/vitest/__tests__/pool.spec.js +1 -1
- package/dist/runner/vitest/__tests__/redirect-loader.spec.js +87 -1
- package/dist/runner/vitest/__tests__/worker-runtime.spec.js +84 -0
- package/dist/runner/vitest/adapter.js +1 -1
- package/dist/runner/vitest/index.d.ts +0 -1
- package/dist/runner/vitest/index.js +0 -1
- package/dist/runner/vitest/pool.d.ts +1 -1
- package/dist/runner/vitest/pool.js +7 -7
- package/dist/runner/vitest/redirect-loader.d.ts +1 -1
- package/dist/runner/vitest/redirect-loader.js +1 -1
- package/dist/runner/vitest/worker-runtime.js +3 -3
- package/dist/runner/vitest/worker.mjs +1 -1
- package/dist/utils/__tests__/coverage.spec.js +167 -0
- package/dist/utils/__tests__/logger.spec.d.ts +1 -0
- package/dist/utils/__tests__/logger.spec.js +61 -0
- package/dist/utils/__tests__/normalizePath.spec.d.ts +1 -0
- package/dist/utils/__tests__/normalizePath.spec.js +22 -0
- package/dist/utils/__tests__/progress.spec.js +96 -0
- package/package.json +71 -22
- package/dist/admin/assets/index-B7nXq-e7.js +0 -32
- package/dist/admin/assets/index-B7nXq-e7.js.map +0 -1
- package/dist/admin/assets/index-BDQLkBUE.js +0 -32
- package/dist/admin/assets/index-BDQLkBUE.js.map +0 -1
- package/dist/admin/assets/index-DVkP-Tc7.css +0 -1
- package/dist/admin/index.html +0 -13
- package/dist/admin/server/admin.d.ts +0 -6
- package/dist/admin/server/admin.js +0 -234
- package/dist/bin/mutate-vitest.d.ts +0 -2
- package/dist/bin/mutate-vitest.js +0 -90
- package/dist/plugin/viteMutate.d.ts +0 -15
- package/dist/plugin/viteMutate.js +0 -52
- package/dist/plugin/vitest.setup.d.ts +0 -47
- package/dist/plugin/vitest.setup.js +0 -118
- package/dist/plugin/withVitest.d.ts +0 -13
- package/dist/plugin/withVitest.js +0 -30
- package/dist/runner/__tests__/orchestrator.spec.js +0 -55
- package/dist/runner/adapters/__tests__/jest.spec.js +0 -88
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/__tests__/vitest.spec.js +0 -118
- package/dist/runner/adapters/index.d.ts +0 -10
- package/dist/runner/adapters/index.js +0 -9
- package/dist/runner/adapters/jest/__tests__/index.spec.js +0 -88
- package/dist/runner/adapters/jest/index.d.ts +0 -24
- package/dist/runner/adapters/jest/index.js +0 -216
- package/dist/runner/adapters/jest/worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest/worker-runtime.js +0 -171
- package/dist/runner/adapters/jest-worker-runtime.d.ts +0 -37
- package/dist/runner/adapters/jest-worker-runtime.js +0 -171
- package/dist/runner/adapters/jest.d.ts +0 -24
- package/dist/runner/adapters/jest.js +0 -216
- package/dist/runner/adapters/types.d.ts +0 -89
- package/dist/runner/adapters/types.js +0 -8
- package/dist/runner/adapters/vitest/__tests__/index.spec.js +0 -118
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +0 -59
- package/dist/runner/adapters/vitest/index.d.ts +0 -33
- package/dist/runner/adapters/vitest/index.js +0 -267
- package/dist/runner/adapters/vitest/worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest/worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest-worker-runtime.d.ts +0 -25
- package/dist/runner/adapters/vitest-worker-runtime.js +0 -118
- package/dist/runner/adapters/vitest.d.ts +0 -33
- package/dist/runner/adapters/vitest.js +0 -267
- package/dist/runner/pool/__tests__/index.spec.js +0 -83
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +0 -78
- package/dist/runner/pool/index.d.ts +0 -8
- package/dist/runner/pool/index.js +0 -9
- package/dist/runner/pool/jest/pool.d.ts +0 -52
- package/dist/runner/pool/jest/pool.js +0 -309
- package/dist/runner/pool/jest/worker.mjs +0 -60
- package/dist/runner/pool/jest-pool.d.ts +0 -52
- package/dist/runner/pool/jest-pool.js +0 -309
- package/dist/runner/pool/jest-worker.mjs +0 -60
- package/dist/runner/pool/plugin.d.ts +0 -18
- package/dist/runner/pool/plugin.js +0 -60
- package/dist/runner/pool/pool-plugin.d.ts +0 -18
- package/dist/runner/pool/pool-plugin.js +0 -60
- package/dist/runner/pool/pool-redirect-loader.d.ts +0 -19
- package/dist/runner/pool/pool-redirect-loader.js +0 -116
- package/dist/runner/pool/pool-redirect-loader.mjs +0 -146
- package/dist/runner/pool/redirect-loader.d.ts +0 -19
- package/dist/runner/pool/redirect-loader.js +0 -116
- package/dist/runner/pool/vitest/pool.d.ts +0 -70
- package/dist/runner/pool/vitest/pool.js +0 -376
- package/dist/runner/pool/vitest/worker.d.mts +0 -15
- package/dist/runner/pool/vitest/worker.mjs +0 -96
- package/dist/runner/pool/vitest-worker.d.mts +0 -15
- package/dist/runner/pool/vitest-worker.mjs +0 -96
- package/dist/runner/shared-module-redirect.d.ts +0 -56
- package/dist/runner/shared-module-redirect.js +0 -84
- package/dist/types/api.d.ts +0 -20
- /package/dist/{runner/__tests__/orchestrator.spec.d.ts → core/__tests__/sfc.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → core/__tests__/variant-utils.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
- /package/dist/{runner/adapters/__tests__/vitest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/jest/__tests__/index.spec.d.ts → __tests__/args.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/vitest/__tests__/index.spec.d.ts → __tests__/cache.spec.d.ts} +0 -0
- /package/dist/runner/{adapters/vitest/__tests__/worker-runtime.spec.d.ts → __tests__/changed.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/index.spec.d.ts → __tests__/cleanup.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/pool-plugin.spec.d.ts → __tests__/config.spec.d.ts} +0 -0
- /package/dist/runner/{pool/__tests__/pool-redirect-loader.spec.d.ts → __tests__/coverage-resolver.spec.d.ts} +0 -0
- /package/dist/runner/{pool/jest-worker.d.mts → __tests__/pool-executor.spec.d.ts} +0 -0
- /package/dist/runner/{pool/jest/worker.d.mts → __tests__/tasks.spec.d.ts} +0 -0
- /package/dist/{types/api.js → runner/__tests__/variants.spec.d.ts} +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { returnToNull, returnToUndefined, returnFlipBool, returnZero, returnEmptyStr, returnEmptyArr, } from '../return-value.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// returnToNull
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
describe('returnToNull', () => {
|
|
7
|
+
it('replaces an identifier return with null', () => {
|
|
8
|
+
const src = `function f() { return x }`;
|
|
9
|
+
const [mutation] = returnToNull.apply(src);
|
|
10
|
+
expect(mutation.code).toBe(`function f() { return null }`);
|
|
11
|
+
});
|
|
12
|
+
it('replaces a function call return with null', () => {
|
|
13
|
+
const src = `function f() { return getValue() }`;
|
|
14
|
+
const [mutation] = returnToNull.apply(src);
|
|
15
|
+
expect(mutation.code).toBe(`function f() { return null }`);
|
|
16
|
+
});
|
|
17
|
+
it('replaces a numeric literal return with null', () => {
|
|
18
|
+
const src = `function f() { return 42 }`;
|
|
19
|
+
const [mutation] = returnToNull.apply(src);
|
|
20
|
+
expect(mutation.code).toBe(`function f() { return null }`);
|
|
21
|
+
});
|
|
22
|
+
it('replaces an object expression return with null', () => {
|
|
23
|
+
const src = `function f() { return { a: 1 } }`;
|
|
24
|
+
const [mutation] = returnToNull.apply(src);
|
|
25
|
+
expect(mutation.code).toBe(`function f() { return null }`);
|
|
26
|
+
});
|
|
27
|
+
it('does not mutate bare return;', () => {
|
|
28
|
+
const src = `function f() { return }`;
|
|
29
|
+
const results = returnToNull.apply(src);
|
|
30
|
+
expect(results).toHaveLength(0);
|
|
31
|
+
});
|
|
32
|
+
it('does not mutate return null', () => {
|
|
33
|
+
const src = `function f() { return null }`;
|
|
34
|
+
const results = returnToNull.apply(src);
|
|
35
|
+
expect(results).toHaveLength(0);
|
|
36
|
+
});
|
|
37
|
+
it('produces one mutation per matching return statement', () => {
|
|
38
|
+
const src = `
|
|
39
|
+
function f() {
|
|
40
|
+
if (x) return a
|
|
41
|
+
return b
|
|
42
|
+
}
|
|
43
|
+
`;
|
|
44
|
+
const results = returnToNull.apply(src);
|
|
45
|
+
expect(results).toHaveLength(2);
|
|
46
|
+
expect(results[0].code).toContain('return null');
|
|
47
|
+
expect(results[1].code).toContain('return null');
|
|
48
|
+
});
|
|
49
|
+
it('reports the correct line for the return statement', () => {
|
|
50
|
+
const src = `function f() {\n return x\n}`;
|
|
51
|
+
const [mutation] = returnToNull.apply(src);
|
|
52
|
+
expect(mutation.line).toBe(2);
|
|
53
|
+
});
|
|
54
|
+
it('respects mutineer-disable-next-line', () => {
|
|
55
|
+
const src = `
|
|
56
|
+
function f() {
|
|
57
|
+
// mutineer-disable-next-line
|
|
58
|
+
return x
|
|
59
|
+
return y
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
const results = returnToNull.apply(src);
|
|
63
|
+
expect(results).toHaveLength(1);
|
|
64
|
+
expect(results[0].code).toContain('return null');
|
|
65
|
+
});
|
|
66
|
+
it('respects mutineer-disable-line', () => {
|
|
67
|
+
const src = `
|
|
68
|
+
function f() {
|
|
69
|
+
return x // mutineer-disable-line
|
|
70
|
+
return y
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
const results = returnToNull.apply(src);
|
|
74
|
+
expect(results).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// returnToUndefined
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
describe('returnToUndefined', () => {
|
|
81
|
+
it('replaces an identifier return with undefined', () => {
|
|
82
|
+
const src = `function f() { return x }`;
|
|
83
|
+
const [mutation] = returnToUndefined.apply(src);
|
|
84
|
+
expect(mutation.code).toBe(`function f() { return undefined }`);
|
|
85
|
+
});
|
|
86
|
+
it('replaces a function call return with undefined', () => {
|
|
87
|
+
const src = `function f() { return getValue() }`;
|
|
88
|
+
const [mutation] = returnToUndefined.apply(src);
|
|
89
|
+
expect(mutation.code).toBe(`function f() { return undefined }`);
|
|
90
|
+
});
|
|
91
|
+
it('replaces null with undefined', () => {
|
|
92
|
+
const src = `function f() { return null }`;
|
|
93
|
+
const [mutation] = returnToUndefined.apply(src);
|
|
94
|
+
expect(mutation.code).toBe(`function f() { return undefined }`);
|
|
95
|
+
});
|
|
96
|
+
it('does not mutate bare return;', () => {
|
|
97
|
+
const src = `function f() { return }`;
|
|
98
|
+
const results = returnToUndefined.apply(src);
|
|
99
|
+
expect(results).toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
it('does not mutate return undefined', () => {
|
|
102
|
+
const src = `function f() { return undefined }`;
|
|
103
|
+
const results = returnToUndefined.apply(src);
|
|
104
|
+
expect(results).toHaveLength(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// returnFlipBool
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
describe('returnFlipBool', () => {
|
|
111
|
+
it('flips return true to return false', () => {
|
|
112
|
+
const src = `function f() { return true }`;
|
|
113
|
+
const [mutation] = returnFlipBool.apply(src);
|
|
114
|
+
expect(mutation.code).toBe(`function f() { return false }`);
|
|
115
|
+
});
|
|
116
|
+
it('flips return false to return true', () => {
|
|
117
|
+
const src = `function f() { return false }`;
|
|
118
|
+
const [mutation] = returnFlipBool.apply(src);
|
|
119
|
+
expect(mutation.code).toBe(`function f() { return true }`);
|
|
120
|
+
});
|
|
121
|
+
it('does not mutate non-boolean returns', () => {
|
|
122
|
+
const src = `function f() { return x }`;
|
|
123
|
+
const results = returnFlipBool.apply(src);
|
|
124
|
+
expect(results).toHaveLength(0);
|
|
125
|
+
});
|
|
126
|
+
it('does not mutate bare return;', () => {
|
|
127
|
+
const src = `function f() { return }`;
|
|
128
|
+
const results = returnFlipBool.apply(src);
|
|
129
|
+
expect(results).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
it('handles multiple boolean returns independently', () => {
|
|
132
|
+
const src = `
|
|
133
|
+
function f(x) {
|
|
134
|
+
if (x) return true
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
const results = returnFlipBool.apply(src);
|
|
139
|
+
expect(results).toHaveLength(2);
|
|
140
|
+
expect(results[0].code).toContain('return false');
|
|
141
|
+
expect(results[1].code).toContain('return true');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// returnZero
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
describe('returnZero', () => {
|
|
148
|
+
it('replaces a positive integer return with 0', () => {
|
|
149
|
+
const src = `function f() { return 42 }`;
|
|
150
|
+
const [mutation] = returnZero.apply(src);
|
|
151
|
+
expect(mutation.code).toBe(`function f() { return 0 }`);
|
|
152
|
+
});
|
|
153
|
+
it('replaces a negative number return with 0', () => {
|
|
154
|
+
const src = `function f() { return -1 }`;
|
|
155
|
+
// -1 is a UnaryExpression, not a NumericLiteral — should produce no output
|
|
156
|
+
const results = returnZero.apply(src);
|
|
157
|
+
expect(results).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
it('replaces a float return with 0', () => {
|
|
160
|
+
const src = `function f() { return 3.14 }`;
|
|
161
|
+
const [mutation] = returnZero.apply(src);
|
|
162
|
+
expect(mutation.code).toBe(`function f() { return 0 }`);
|
|
163
|
+
});
|
|
164
|
+
it('does not mutate return 0', () => {
|
|
165
|
+
const src = `function f() { return 0 }`;
|
|
166
|
+
const results = returnZero.apply(src);
|
|
167
|
+
expect(results).toHaveLength(0);
|
|
168
|
+
});
|
|
169
|
+
it('does not mutate non-numeric returns', () => {
|
|
170
|
+
const src = `function f() { return x }`;
|
|
171
|
+
const results = returnZero.apply(src);
|
|
172
|
+
expect(results).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
it('does not mutate bare return;', () => {
|
|
175
|
+
const src = `function f() { return }`;
|
|
176
|
+
const results = returnZero.apply(src);
|
|
177
|
+
expect(results).toHaveLength(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// returnEmptyStr
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
describe('returnEmptyStr', () => {
|
|
184
|
+
it("replaces a non-empty string return with ''", () => {
|
|
185
|
+
const src = `function f() { return 'hello' }`;
|
|
186
|
+
const [mutation] = returnEmptyStr.apply(src);
|
|
187
|
+
expect(mutation.code).toBe(`function f() { return '' }`);
|
|
188
|
+
});
|
|
189
|
+
it('replaces a double-quoted string return', () => {
|
|
190
|
+
const src = `function f() { return "world" }`;
|
|
191
|
+
const [mutation] = returnEmptyStr.apply(src);
|
|
192
|
+
expect(mutation.code).toBe(`function f() { return '' }`);
|
|
193
|
+
});
|
|
194
|
+
it("does not mutate return ''", () => {
|
|
195
|
+
const src = `function f() { return '' }`;
|
|
196
|
+
const results = returnEmptyStr.apply(src);
|
|
197
|
+
expect(results).toHaveLength(0);
|
|
198
|
+
});
|
|
199
|
+
it('does not mutate non-string returns', () => {
|
|
200
|
+
const src = `function f() { return x }`;
|
|
201
|
+
const results = returnEmptyStr.apply(src);
|
|
202
|
+
expect(results).toHaveLength(0);
|
|
203
|
+
});
|
|
204
|
+
it('does not mutate bare return;', () => {
|
|
205
|
+
const src = `function f() { return }`;
|
|
206
|
+
const results = returnEmptyStr.apply(src);
|
|
207
|
+
expect(results).toHaveLength(0);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// returnEmptyArr
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
describe('returnEmptyArr', () => {
|
|
214
|
+
it('replaces a non-empty array return with []', () => {
|
|
215
|
+
const src = `function f() { return [1, 2, 3] }`;
|
|
216
|
+
const [mutation] = returnEmptyArr.apply(src);
|
|
217
|
+
expect(mutation.code).toBe(`function f() { return [] }`);
|
|
218
|
+
});
|
|
219
|
+
it('replaces a single-element array return with []', () => {
|
|
220
|
+
const src = `function f() { return [x] }`;
|
|
221
|
+
const [mutation] = returnEmptyArr.apply(src);
|
|
222
|
+
expect(mutation.code).toBe(`function f() { return [] }`);
|
|
223
|
+
});
|
|
224
|
+
it('does not mutate return []', () => {
|
|
225
|
+
const src = `function f() { return [] }`;
|
|
226
|
+
const results = returnEmptyArr.apply(src);
|
|
227
|
+
expect(results).toHaveLength(0);
|
|
228
|
+
});
|
|
229
|
+
it('does not mutate non-array returns', () => {
|
|
230
|
+
const src = `function f() { return x }`;
|
|
231
|
+
const results = returnEmptyArr.apply(src);
|
|
232
|
+
expect(results).toHaveLength(0);
|
|
233
|
+
});
|
|
234
|
+
it('does not mutate bare return;', () => {
|
|
235
|
+
const src = `function f() { return }`;
|
|
236
|
+
const results = returnEmptyArr.apply(src);
|
|
237
|
+
expect(results).toHaveLength(0);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { collectOperatorTargets } from '../utils.js';
|
|
2
|
+
import { collectOperatorTargets, buildIgnoreLines, parseSource, } from '../utils.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// buildIgnoreLines
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
describe('buildIgnoreLines', () => {
|
|
7
|
+
function comment(text, startLine, endLine = startLine) {
|
|
8
|
+
return {
|
|
9
|
+
type: 'CommentLine',
|
|
10
|
+
value: ` ${text}`,
|
|
11
|
+
start: 0,
|
|
12
|
+
end: 0,
|
|
13
|
+
loc: {
|
|
14
|
+
start: { line: startLine, column: 0, index: 0 },
|
|
15
|
+
end: { line: endLine, column: 0, index: 0 },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
it('ignores the line after mutineer-disable-next-line', () => {
|
|
20
|
+
// The text also contains 'mutineer-disable' as a substring, so the
|
|
21
|
+
// comment line itself is ignored in addition to the following line.
|
|
22
|
+
const lines = buildIgnoreLines([comment('mutineer-disable-next-line', 3)]);
|
|
23
|
+
expect(lines.has(4)).toBe(true);
|
|
24
|
+
expect(lines.has(3)).toBe(true);
|
|
25
|
+
expect(lines.has(5)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it('ignores the comment line itself for mutineer-disable-line', () => {
|
|
28
|
+
const lines = buildIgnoreLines([comment('mutineer-disable-line', 5)]);
|
|
29
|
+
expect(lines.has(5)).toBe(true);
|
|
30
|
+
expect(lines.has(6)).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
it('ignores all lines spanned by a mutineer-disable block comment', () => {
|
|
33
|
+
const lines = buildIgnoreLines([comment('mutineer-disable', 2, 4)]);
|
|
34
|
+
expect(lines.has(2)).toBe(true);
|
|
35
|
+
expect(lines.has(3)).toBe(true);
|
|
36
|
+
expect(lines.has(4)).toBe(true);
|
|
37
|
+
expect(lines.has(1)).toBe(false);
|
|
38
|
+
expect(lines.has(5)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('ignores comments without loc', () => {
|
|
41
|
+
const c = {
|
|
42
|
+
...comment('mutineer-disable-next-line', 1),
|
|
43
|
+
loc: undefined,
|
|
44
|
+
};
|
|
45
|
+
expect(() => buildIgnoreLines([c])).not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
it('returns an empty set when there are no disable comments', () => {
|
|
48
|
+
const lines = buildIgnoreLines([comment('just a normal comment', 1)]);
|
|
49
|
+
expect(lines.size).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// parseSource
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
describe('parseSource', () => {
|
|
56
|
+
it('parses valid TypeScript source without throwing', () => {
|
|
57
|
+
expect(() => parseSource(`const x: number = 1`)).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
it('parses JSX without throwing', () => {
|
|
60
|
+
expect(() => parseSource(`const el = <div />`)).not.toThrow();
|
|
61
|
+
});
|
|
62
|
+
it('returns a File node', () => {
|
|
63
|
+
const ast = parseSource(`const x = 1`);
|
|
64
|
+
expect(ast.type).toBe('File');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// collectOperatorTargets
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
3
70
|
describe('collectOperatorTargets', () => {
|
|
4
71
|
it('honors mutineer disable comments', () => {
|
|
5
72
|
const src = `// mutineer-disable-next-line
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator mutators.
|
|
3
|
+
*
|
|
4
|
+
* Each mutator finds every occurrence of a specific binary/logical operator
|
|
5
|
+
* using AST traversal and produces a mutated source string with that operator
|
|
6
|
+
* replaced by its counterpart.
|
|
7
|
+
*/
|
|
8
|
+
import type { ASTMutator } from './types.js';
|
|
9
|
+
export declare const relaxLE: ASTMutator;
|
|
10
|
+
export declare const relaxGE: ASTMutator;
|
|
11
|
+
export declare const tightenLT: ASTMutator;
|
|
12
|
+
export declare const tightenGT: ASTMutator;
|
|
13
|
+
export declare const andToOr: ASTMutator;
|
|
14
|
+
export declare const orToAnd: ASTMutator;
|
|
15
|
+
export declare const nullishToOr: ASTMutator;
|
|
16
|
+
export declare const flipEQ: ASTMutator;
|
|
17
|
+
export declare const flipNEQ: ASTMutator;
|
|
18
|
+
export declare const flipStrictEQ: ASTMutator;
|
|
19
|
+
export declare const flipStrictNEQ: ASTMutator;
|
|
20
|
+
export declare const addToSub: ASTMutator;
|
|
21
|
+
export declare const subToAdd: ASTMutator;
|
|
22
|
+
export declare const mulToDiv: ASTMutator;
|
|
23
|
+
export declare const divToMul: ASTMutator;
|
|
24
|
+
export declare const modToMul: ASTMutator;
|
|
25
|
+
export declare const powerToMul: ASTMutator;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator mutators.
|
|
3
|
+
*
|
|
4
|
+
* Each mutator finds every occurrence of a specific binary/logical operator
|
|
5
|
+
* using AST traversal and produces a mutated source string with that operator
|
|
6
|
+
* replaced by its counterpart.
|
|
7
|
+
*/
|
|
8
|
+
import { collectOperatorTargets } from './utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Factory to build an operator mutator using AST traversal and token analysis.
|
|
11
|
+
*
|
|
12
|
+
* @param name - Mutator name used in the registry and config include/exclude
|
|
13
|
+
* @param description - Human-readable description shown in reports
|
|
14
|
+
* @param fromOp - The operator to find (e.g., '&&')
|
|
15
|
+
* @param toOp - The operator to replace it with (e.g., '||')
|
|
16
|
+
*/
|
|
17
|
+
function makeOperatorMutator(name, description, fromOp, toOp) {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
description,
|
|
21
|
+
apply(src) {
|
|
22
|
+
return collectOperatorTargets(src, fromOp).map((target) => ({
|
|
23
|
+
line: target.line,
|
|
24
|
+
col: target.col1,
|
|
25
|
+
code: src.slice(0, target.start) + toOp + src.slice(target.end),
|
|
26
|
+
}));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/* === Boundary mutators === */
|
|
31
|
+
export const relaxLE = makeOperatorMutator('relaxLE', "Change '<=' to '<' (relax boundary)", '<=', '<');
|
|
32
|
+
export const relaxGE = makeOperatorMutator('relaxGE', "Change '>=' to '>' (relax boundary)", '>=', '>');
|
|
33
|
+
export const tightenLT = makeOperatorMutator('tightenLT', "Change '<' to '<=' (tighten boundary)", '<', '<=');
|
|
34
|
+
export const tightenGT = makeOperatorMutator('tightenGT', "Change '>' to '>=' (tighten boundary)", '>', '>=');
|
|
35
|
+
/* === Logical mutators === */
|
|
36
|
+
export const andToOr = makeOperatorMutator('andToOr', "Change '&&' to '||'", '&&', '||');
|
|
37
|
+
export const orToAnd = makeOperatorMutator('orToAnd', "Change '||' to '&&'", '||', '&&');
|
|
38
|
+
export const nullishToOr = makeOperatorMutator('nullishToOr', "Change '??' to '||'", '??', '||');
|
|
39
|
+
/* === Equality mutators === */
|
|
40
|
+
export const flipEQ = makeOperatorMutator('flipEQ', "Change '==' to '!='", '==', '!=');
|
|
41
|
+
export const flipNEQ = makeOperatorMutator('flipNEQ', "Change '!=' to '=='", '!=', '==');
|
|
42
|
+
export const flipStrictEQ = makeOperatorMutator('flipStrictEQ', "Change '===' to '!=='", '===', '!==');
|
|
43
|
+
export const flipStrictNEQ = makeOperatorMutator('flipStrictNEQ', "Change '!==' to '==='", '!==', '===');
|
|
44
|
+
/* === Arithmetic mutators === */
|
|
45
|
+
export const addToSub = makeOperatorMutator('addToSub', "Change '+' to '-'", '+', '-');
|
|
46
|
+
export const subToAdd = makeOperatorMutator('subToAdd', "Change '-' to '+'", '-', '+');
|
|
47
|
+
export const mulToDiv = makeOperatorMutator('mulToDiv', "Change '*' to '/'", '*', '/');
|
|
48
|
+
export const divToMul = makeOperatorMutator('divToMul', "Change '/' to '*'", '/', '*');
|
|
49
|
+
export const modToMul = makeOperatorMutator('modToMul', "Change '%' to '*'", '%', '*');
|
|
50
|
+
export const powerToMul = makeOperatorMutator('powerToMul', "Change '**' to '*'", '**', '*');
|
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mutator registry
|
|
2
|
+
* Mutator registry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* with optional include/exclude filtering.
|
|
4
|
+
* Aggregates all mutators and exposes `getRegistry` for filtered access.
|
|
6
5
|
*/
|
|
7
6
|
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
7
|
/**
|
|
26
|
-
* Get a filtered
|
|
8
|
+
* Get a filtered list of mutators.
|
|
27
9
|
*
|
|
28
|
-
* If include
|
|
29
|
-
* If exclude
|
|
30
|
-
*
|
|
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
|
|
10
|
+
* If `include` is provided, only those mutators are returned.
|
|
11
|
+
* If `exclude` is provided, those mutators are removed.
|
|
12
|
+
* `include` takes precedence over `exclude`.
|
|
35
13
|
*/
|
|
36
14
|
export declare function getRegistry(include?: readonly string[], exclude?: readonly string[]): ASTMutator[];
|
|
37
15
|
export type { ASTMutator, AnyMutator, MutationOutput } from './types.js';
|
|
@@ -1,62 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mutator registry
|
|
2
|
+
* Mutator registry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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.
|
|
4
|
+
* Aggregates all mutators and exposes `getRegistry` for filtered access.
|
|
59
5
|
*/
|
|
6
|
+
import { relaxLE, relaxGE, tightenLT, tightenGT, andToOr, orToAnd, nullishToOr, flipEQ, flipNEQ, flipStrictEQ, flipStrictNEQ, addToSub, subToAdd, mulToDiv, divToMul, modToMul, powerToMul, } from './operator.js';
|
|
7
|
+
import { returnToNull, returnToUndefined, returnFlipBool, returnZero, returnEmptyStr, returnEmptyArr, } from './return-value.js';
|
|
60
8
|
const ALL = [
|
|
61
9
|
relaxLE,
|
|
62
10
|
relaxGE,
|
|
@@ -75,25 +23,25 @@ const ALL = [
|
|
|
75
23
|
divToMul,
|
|
76
24
|
modToMul,
|
|
77
25
|
powerToMul,
|
|
26
|
+
returnToNull,
|
|
27
|
+
returnToUndefined,
|
|
28
|
+
returnFlipBool,
|
|
29
|
+
returnZero,
|
|
30
|
+
returnEmptyStr,
|
|
31
|
+
returnEmptyArr,
|
|
78
32
|
];
|
|
79
33
|
/**
|
|
80
|
-
* Get a filtered
|
|
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.
|
|
34
|
+
* Get a filtered list of mutators.
|
|
85
35
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
36
|
+
* If `include` is provided, only those mutators are returned.
|
|
37
|
+
* If `exclude` is provided, those mutators are removed.
|
|
38
|
+
* `include` takes precedence over `exclude`.
|
|
89
39
|
*/
|
|
90
40
|
export function getRegistry(include, exclude) {
|
|
91
41
|
let list = ALL;
|
|
92
|
-
// If include list provided, filter to only those mutators
|
|
93
42
|
if (include?.length) {
|
|
94
43
|
list = list.filter((m) => include.includes(m.name));
|
|
95
44
|
}
|
|
96
|
-
// If exclude list provided, remove those mutators
|
|
97
45
|
if (exclude?.length) {
|
|
98
46
|
list = list.filter((m) => !exclude.includes(m.name));
|
|
99
47
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return value mutators.
|
|
3
|
+
*
|
|
4
|
+
* These mutators target `return` statements and replace the returned expression
|
|
5
|
+
* with a simpler or opposite value. They catch a different class of test gap
|
|
6
|
+
* from operator mutators: they reveal when callers never check what a function
|
|
7
|
+
* returns (e.g. no null-check, no assertion on the return value).
|
|
8
|
+
*/
|
|
9
|
+
import type { ASTMutator } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Replace any non-null return value with `null`.
|
|
12
|
+
* Reveals callers that never check for null returns.
|
|
13
|
+
*/
|
|
14
|
+
export declare const returnToNull: ASTMutator;
|
|
15
|
+
/**
|
|
16
|
+
* Replace any non-undefined return value with `undefined`.
|
|
17
|
+
* Reveals callers that never guard against undefined returns.
|
|
18
|
+
*/
|
|
19
|
+
export declare const returnToUndefined: ASTMutator;
|
|
20
|
+
/**
|
|
21
|
+
* Flip `return true` ↔ `return false`.
|
|
22
|
+
* Catches missing assertions on boolean-returning functions.
|
|
23
|
+
*/
|
|
24
|
+
export declare const returnFlipBool: ASTMutator;
|
|
25
|
+
/**
|
|
26
|
+
* Replace a non-zero numeric return with `0`.
|
|
27
|
+
* Catches callers that never check the numeric return value.
|
|
28
|
+
*/
|
|
29
|
+
export declare const returnZero: ASTMutator;
|
|
30
|
+
/**
|
|
31
|
+
* Replace a non-empty string return with `''`.
|
|
32
|
+
* Catches callers that never check the string return value.
|
|
33
|
+
*/
|
|
34
|
+
export declare const returnEmptyStr: ASTMutator;
|
|
35
|
+
/**
|
|
36
|
+
* Replace an array expression return with `[]`.
|
|
37
|
+
* Catches callers that never check for an empty array.
|
|
38
|
+
*/
|
|
39
|
+
export declare const returnEmptyArr: ASTMutator;
|