@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.
Files changed (162) hide show
  1. package/README.md +48 -42
  2. package/dist/bin/mutineer.js +0 -0
  3. package/dist/core/__tests__/module.spec.js +66 -3
  4. package/dist/core/__tests__/sfc.spec.js +76 -0
  5. package/dist/core/__tests__/variant-utils.spec.js +93 -0
  6. package/dist/mutators/__tests__/operator.spec.js +169 -0
  7. package/dist/mutators/__tests__/registry.spec.js +6 -0
  8. package/dist/mutators/__tests__/return-value.spec.js +239 -0
  9. package/dist/mutators/__tests__/utils.spec.js +68 -1
  10. package/dist/mutators/operator.d.ts +25 -0
  11. package/dist/mutators/operator.js +50 -0
  12. package/dist/mutators/registry.d.ts +6 -28
  13. package/dist/mutators/registry.js +14 -66
  14. package/dist/mutators/return-value.d.ts +39 -0
  15. package/dist/mutators/return-value.js +104 -0
  16. package/dist/mutators/utils.d.ts +21 -0
  17. package/dist/mutators/utils.js +44 -27
  18. package/dist/runner/__tests__/args.spec.js +225 -0
  19. package/dist/runner/__tests__/cache.spec.js +180 -0
  20. package/dist/runner/__tests__/changed.spec.js +227 -0
  21. package/dist/runner/__tests__/cleanup.spec.js +41 -0
  22. package/dist/runner/__tests__/config.spec.js +71 -0
  23. package/dist/runner/__tests__/coverage-resolver.spec.js +171 -0
  24. package/dist/runner/__tests__/pool-executor.spec.js +211 -0
  25. package/dist/runner/__tests__/tasks.spec.js +95 -0
  26. package/dist/runner/__tests__/variants.spec.js +261 -0
  27. package/dist/runner/args.d.ts +5 -0
  28. package/dist/runner/args.js +7 -0
  29. package/dist/runner/config.js +2 -2
  30. package/dist/runner/coverage-resolver.d.ts +21 -0
  31. package/dist/runner/coverage-resolver.js +96 -0
  32. package/dist/runner/discover.js +2 -1
  33. package/dist/runner/jest/__tests__/adapter.spec.js +1 -1
  34. package/dist/runner/jest/__tests__/pool.spec.d.ts +1 -0
  35. package/dist/runner/jest/__tests__/pool.spec.js +211 -0
  36. package/dist/runner/jest/__tests__/worker-runtime.spec.d.ts +1 -0
  37. package/dist/runner/jest/__tests__/worker-runtime.spec.js +148 -0
  38. package/dist/runner/jest/adapter.js +1 -1
  39. package/dist/runner/jest/pool.d.ts +1 -1
  40. package/dist/runner/jest/pool.js +6 -6
  41. package/dist/runner/jest/worker.mjs +1 -1
  42. package/dist/runner/orchestrator.js +43 -295
  43. package/dist/runner/pool-executor.d.ts +17 -0
  44. package/dist/runner/pool-executor.js +143 -0
  45. package/dist/runner/shared/__tests__/mutant-paths.spec.d.ts +1 -0
  46. package/dist/runner/shared/__tests__/mutant-paths.spec.js +66 -0
  47. package/dist/runner/shared/__tests__/redirect-state.spec.d.ts +1 -0
  48. package/dist/runner/shared/__tests__/redirect-state.spec.js +56 -0
  49. package/dist/runner/shared/index.d.ts +1 -1
  50. package/dist/runner/shared/index.js +1 -1
  51. package/dist/runner/shared/redirect-state.d.ts +2 -2
  52. package/dist/runner/shared/redirect-state.js +4 -4
  53. package/dist/runner/tasks.d.ts +12 -0
  54. package/dist/runner/tasks.js +25 -0
  55. package/dist/runner/types.d.ts +1 -1
  56. package/dist/runner/variants.d.ts +17 -2
  57. package/dist/runner/variants.js +33 -0
  58. package/dist/runner/vitest/__tests__/adapter.spec.js +1 -1
  59. package/dist/runner/vitest/__tests__/pool.spec.js +1 -1
  60. package/dist/runner/vitest/__tests__/redirect-loader.spec.js +87 -1
  61. package/dist/runner/vitest/__tests__/worker-runtime.spec.js +84 -0
  62. package/dist/runner/vitest/adapter.js +1 -1
  63. package/dist/runner/vitest/index.d.ts +0 -1
  64. package/dist/runner/vitest/index.js +0 -1
  65. package/dist/runner/vitest/pool.d.ts +1 -1
  66. package/dist/runner/vitest/pool.js +7 -7
  67. package/dist/runner/vitest/redirect-loader.d.ts +1 -1
  68. package/dist/runner/vitest/redirect-loader.js +1 -1
  69. package/dist/runner/vitest/worker-runtime.js +3 -3
  70. package/dist/runner/vitest/worker.mjs +1 -1
  71. package/dist/utils/__tests__/coverage.spec.js +167 -0
  72. package/dist/utils/__tests__/logger.spec.d.ts +1 -0
  73. package/dist/utils/__tests__/logger.spec.js +61 -0
  74. package/dist/utils/__tests__/normalizePath.spec.d.ts +1 -0
  75. package/dist/utils/__tests__/normalizePath.spec.js +22 -0
  76. package/dist/utils/__tests__/progress.spec.js +96 -0
  77. package/package.json +71 -22
  78. package/dist/admin/assets/index-B7nXq-e7.js +0 -32
  79. package/dist/admin/assets/index-B7nXq-e7.js.map +0 -1
  80. package/dist/admin/assets/index-BDQLkBUE.js +0 -32
  81. package/dist/admin/assets/index-BDQLkBUE.js.map +0 -1
  82. package/dist/admin/assets/index-DVkP-Tc7.css +0 -1
  83. package/dist/admin/index.html +0 -13
  84. package/dist/admin/server/admin.d.ts +0 -6
  85. package/dist/admin/server/admin.js +0 -234
  86. package/dist/bin/mutate-vitest.d.ts +0 -2
  87. package/dist/bin/mutate-vitest.js +0 -90
  88. package/dist/plugin/viteMutate.d.ts +0 -15
  89. package/dist/plugin/viteMutate.js +0 -52
  90. package/dist/plugin/vitest.setup.d.ts +0 -47
  91. package/dist/plugin/vitest.setup.js +0 -118
  92. package/dist/plugin/withVitest.d.ts +0 -13
  93. package/dist/plugin/withVitest.js +0 -30
  94. package/dist/runner/__tests__/orchestrator.spec.js +0 -55
  95. package/dist/runner/adapters/__tests__/jest.spec.js +0 -88
  96. package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +0 -59
  97. package/dist/runner/adapters/__tests__/vitest.spec.js +0 -118
  98. package/dist/runner/adapters/index.d.ts +0 -10
  99. package/dist/runner/adapters/index.js +0 -9
  100. package/dist/runner/adapters/jest/__tests__/index.spec.js +0 -88
  101. package/dist/runner/adapters/jest/index.d.ts +0 -24
  102. package/dist/runner/adapters/jest/index.js +0 -216
  103. package/dist/runner/adapters/jest/worker-runtime.d.ts +0 -37
  104. package/dist/runner/adapters/jest/worker-runtime.js +0 -171
  105. package/dist/runner/adapters/jest-worker-runtime.d.ts +0 -37
  106. package/dist/runner/adapters/jest-worker-runtime.js +0 -171
  107. package/dist/runner/adapters/jest.d.ts +0 -24
  108. package/dist/runner/adapters/jest.js +0 -216
  109. package/dist/runner/adapters/types.d.ts +0 -89
  110. package/dist/runner/adapters/types.js +0 -8
  111. package/dist/runner/adapters/vitest/__tests__/index.spec.js +0 -118
  112. package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +0 -59
  113. package/dist/runner/adapters/vitest/index.d.ts +0 -33
  114. package/dist/runner/adapters/vitest/index.js +0 -267
  115. package/dist/runner/adapters/vitest/worker-runtime.d.ts +0 -25
  116. package/dist/runner/adapters/vitest/worker-runtime.js +0 -118
  117. package/dist/runner/adapters/vitest-worker-runtime.d.ts +0 -25
  118. package/dist/runner/adapters/vitest-worker-runtime.js +0 -118
  119. package/dist/runner/adapters/vitest.d.ts +0 -33
  120. package/dist/runner/adapters/vitest.js +0 -267
  121. package/dist/runner/pool/__tests__/index.spec.js +0 -83
  122. package/dist/runner/pool/__tests__/pool-plugin.spec.js +0 -59
  123. package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +0 -78
  124. package/dist/runner/pool/index.d.ts +0 -8
  125. package/dist/runner/pool/index.js +0 -9
  126. package/dist/runner/pool/jest/pool.d.ts +0 -52
  127. package/dist/runner/pool/jest/pool.js +0 -309
  128. package/dist/runner/pool/jest/worker.mjs +0 -60
  129. package/dist/runner/pool/jest-pool.d.ts +0 -52
  130. package/dist/runner/pool/jest-pool.js +0 -309
  131. package/dist/runner/pool/jest-worker.mjs +0 -60
  132. package/dist/runner/pool/plugin.d.ts +0 -18
  133. package/dist/runner/pool/plugin.js +0 -60
  134. package/dist/runner/pool/pool-plugin.d.ts +0 -18
  135. package/dist/runner/pool/pool-plugin.js +0 -60
  136. package/dist/runner/pool/pool-redirect-loader.d.ts +0 -19
  137. package/dist/runner/pool/pool-redirect-loader.js +0 -116
  138. package/dist/runner/pool/pool-redirect-loader.mjs +0 -146
  139. package/dist/runner/pool/redirect-loader.d.ts +0 -19
  140. package/dist/runner/pool/redirect-loader.js +0 -116
  141. package/dist/runner/pool/vitest/pool.d.ts +0 -70
  142. package/dist/runner/pool/vitest/pool.js +0 -376
  143. package/dist/runner/pool/vitest/worker.d.mts +0 -15
  144. package/dist/runner/pool/vitest/worker.mjs +0 -96
  145. package/dist/runner/pool/vitest-worker.d.mts +0 -15
  146. package/dist/runner/pool/vitest-worker.mjs +0 -96
  147. package/dist/runner/shared-module-redirect.d.ts +0 -56
  148. package/dist/runner/shared-module-redirect.js +0 -84
  149. package/dist/types/api.d.ts +0 -20
  150. /package/dist/{runner/__tests__/orchestrator.spec.d.ts → core/__tests__/sfc.spec.d.ts} +0 -0
  151. /package/dist/{runner/adapters/__tests__/jest.spec.d.ts → core/__tests__/variant-utils.spec.d.ts} +0 -0
  152. /package/dist/{runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts → mutators/__tests__/operator.spec.d.ts} +0 -0
  153. /package/dist/{runner/adapters/__tests__/vitest.spec.d.ts → mutators/__tests__/return-value.spec.d.ts} +0 -0
  154. /package/dist/runner/{adapters/jest/__tests__/index.spec.d.ts → __tests__/args.spec.d.ts} +0 -0
  155. /package/dist/runner/{adapters/vitest/__tests__/index.spec.d.ts → __tests__/cache.spec.d.ts} +0 -0
  156. /package/dist/runner/{adapters/vitest/__tests__/worker-runtime.spec.d.ts → __tests__/changed.spec.d.ts} +0 -0
  157. /package/dist/runner/{pool/__tests__/index.spec.d.ts → __tests__/cleanup.spec.d.ts} +0 -0
  158. /package/dist/runner/{pool/__tests__/pool-plugin.spec.d.ts → __tests__/config.spec.d.ts} +0 -0
  159. /package/dist/runner/{pool/__tests__/pool-redirect-loader.spec.d.ts → __tests__/coverage-resolver.spec.d.ts} +0 -0
  160. /package/dist/runner/{pool/jest-worker.d.mts → __tests__/pool-executor.spec.d.ts} +0 -0
  161. /package/dist/runner/{pool/jest/worker.d.mts → __tests__/tasks.spec.d.ts} +0 -0
  162. /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 and factory.
2
+ * Mutator registry.
3
3
  *
4
- * Provides functions to create operator mutators and retrieve registered mutators
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 registry of mutators based on include/exclude options.
8
+ * Get a filtered list of mutators.
27
9
  *
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
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 and factory.
2
+ * Mutator registry.
3
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.
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 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.
34
+ * Get a filtered list of mutators.
85
35
  *
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
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;