23
} from './runner-watch';
24
import * as runnerWorker from './runner-worker';
25
import {execSync} from 'child_process';
26
-import {runMinimize} from './minimize';
26
+import fs from 'fs';
27
+import path from 'path';
28
+import {minimize} from './minimize';
29
+import {parseLanguage, parseSourceType} from './compiler';
30
31
const WORKER_PATH = require.resolve('./runner-worker.js');
32
const NUM_WORKERS = cpus().length - 1;
33
34
readline.emitKeypressEvents(process.stdin);
35
33
-type RunnerOptions = {
36
+type TestOptions = {
37
sync: boolean;
38
workerThreads: boolean;
39
watch: boolean;
40
update: boolean;
41
pattern?: string;
42
debug: boolean;
43
+ verbose: boolean;
44
};
45
42
-async function runTestCommand(opts: RunnerOptions): Promise<void> {
43
- await main(opts);
46
+type MinimizeOptions = {
47
+ path: string;
48
+ update: boolean;
49
+};
50
+
51
+async function runTestCommand(opts: TestOptions): Promise<void> {
52
+ const worker: Worker & typeof runnerWorker = new Worker(WORKER_PATH, {
53
+ enableWorkerThreads: opts.workerThreads,
54
+ numWorkers: NUM_WORKERS,
55
+ }) as any;
56
+ worker.getStderr().pipe(process.stderr);
57
+ worker.getStdout().pipe(process.stdout);
58
+
59
+ // Check if watch mode should be enabled
60
+ const shouldWatch = opts.watch;
61
+
62
+ if (shouldWatch) {
63
+ makeWatchRunner(
64
+ state => onChange(worker, state, opts.sync, opts.verbose),
65
+ opts.debug,
66
+ opts.pattern,
67
+ );
68
+ if (opts.pattern) {
69
+ /**
70
+ * Warm up wormers when in watch mode. Loading the Forget babel plugin
71
+ * and all of its transitive dependencies takes 1-3s (per worker) on a M1.
72
+ * As jest-worker dispatches tasks using a round-robin strategy, we can
73
+ * avoid an additional 1-3s wait on the first num_workers runs by warming
74
+ * up workers eagerly.
75
+ */
76
+ for (let i = 0; i < NUM_WORKERS - 1; i++) {
77
+ worker.transformFixture(
78
+ {
79
+ fixturePath: 'tmp',
80
+ snapshotPath: './tmp.expect.md',
81
+ inputPath: './tmp.js',
82
+ input: `
83
+ function Foo(props) {
84
+ return identity(props);
85
+ }
86
+ `,
87
+ snapshot: null,
88
+ },
89
+ 0,
90
+ false,
91
+ false,
92
+ );
93
+ }
94
+ }
95
+ } else {
96
+ // Non-watch mode. For simplicity we re-use the same watchSrc() function.
97
+ // After the first build completes run tests and exit
98
+ const tsWatch: ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> =
99
+ watchSrc(
100
+ () => {},
101
+ async (isTypecheckSuccess: boolean) => {
102
+ let isSuccess = false;
103
+ if (!isTypecheckSuccess) {
104
+ console.error(
105
+ 'Found typescript errors in Forget source code, skipping test fixtures.',
106
+ );
107
+ } else {
108
+ try {
109
+ execSync('yarn build', {cwd: PROJECT_ROOT});
110
+ console.log('Built compiler successfully with tsup');
111
+
112
+ // Determine which filter to use
113
+ let testFilter: TestFilter | null = null;
114
+ if (opts.pattern) {
115
+ testFilter = {
116
+ paths: [opts.pattern],
117
+ };
118
+ }
119
+
120
+ const results = await runFixtures(
121
+ worker,
122
+ testFilter,
123
+ 0,
124
+ opts.debug,
125
+ false, // no requireSingleFixture in non-watch mode
126
+ opts.sync,
127
+ );
128
+ if (opts.update) {
129
+ update(results);
130
+ isSuccess = true;
131
+ } else {
132
+ isSuccess = report(results, opts.verbose);
133
+ }
134
+ } catch (e) {
135
+ console.warn('Failed to build compiler with tsup:', e);
136
+ }
137
+ }
138
+ tsWatch?.close();
139
+ await worker.end();
140
+ process.exit(isSuccess ? 0 : 1);
141
+ },
142
+ );
143
+ }
144
}
145
46
-async function runMinimizeCommand(path: string): Promise<void> {
47
- await runMinimize({path});
146
+async function runMinimizeCommand(opts: MinimizeOptions): Promise<void> {
147
+ // Resolve the input path
148
+ const inputPath = path.isAbsolute(opts.path)
149
+ ? opts.path
150
+ : path.resolve(process.cwd(), opts.path);
151
+
152
+ // Check if file exists
153
+ if (!fs.existsSync(inputPath)) {
154
+ console.error(`Error: File not found: ${inputPath}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ // Read the input file
159
+ const input = fs.readFileSync(inputPath, 'utf-8');
160
+ const filename = path.basename(inputPath);
161
+ const firstLine = input.substring(0, input.indexOf('\n'));
162
+ const language = parseLanguage(firstLine);
163
+ const sourceType = parseSourceType(firstLine);
164
+
165
+ console.log(`Minimizing: ${inputPath}`);
166
+
167
+ const originalLines = input.split('\n').length;
168
+
169
+ // Run the minimization
170
+ const result = minimize(input, filename, language, sourceType);
171
+
172
+ if (result.kind === 'success') {
173
+ console.log('Could not minimize: the input compiles successfully.');
174
+ process.exit(0);
175
+ }
176
+
177
+ if (result.kind === 'minimal') {
178
+ console.log(
179
+ 'Could not minimize: the input fails but is already minimal and cannot be reduced further.',
180
+ );
181
+ process.exit(0);
182
+ }
183
+
184
+ // Output the minimized code
185
+ console.log('--- Minimized Code ---');
186
+ console.log(result.source);
187
+
188
+ const minimizedLines = result.source.split('\n').length;
189
+ console.log(
190
+ `\nReduced from ${originalLines} lines to ${minimizedLines} lines`,
191
+ );
192
+
193
+ if (opts.update) {
194
+ fs.writeFileSync(inputPath, result.source, 'utf-8');
195
+ console.log(`\nUpdated ${inputPath} with minimized code.`);
196
+ }
197
}
198
199
yargs(hideBin(process.argv))
234
.boolean('debug')
235
.alias('d', 'debug')
236
.describe('debug', 'Enable debug logging to print HIR for each pass')
88
- .default('debug', false);
237
+ .default('debug', false)
238
+ .boolean('verbose')
239
+ .alias('v', 'verbose')
240
+ .describe('verbose', 'Print individual test results')
241
+ .default('verbose', false);
242
},
243
async argv => {
91
- await runTestCommand(argv as RunnerOptions);
244
+ await runTestCommand(argv as TestOptions);
245
},
246
)
247
.command(
252
.string('path')
253
.alias('p', 'path')
254
.describe('path', 'Path to the file to minimize')
102
- .demandOption('path');
255
+ .demandOption('path')
256
+ .boolean('update')
257
+ .alias('u', 'update')
258
+ .describe(
259
+ 'update',
260
+ 'Update the input file in-place with the minimized version',
261
+ )
262
+ .default('update', false);
263
},
264
async argv => {
105
- await runMinimizeCommand(argv.path as string);
265
+ await runMinimizeCommand(argv as unknown as MinimizeOptions);
266
},
267
)
268
.help('help')
322
worker: Worker & typeof runnerWorker,
323
state: RunnerState,
324
sync: boolean,
325
+ verbose: boolean,
326
) {
327
const {compilerVersion, isCompilerBuildValid, mode, filter, debug} = state;
328
if (isCompilerBuildValid) {
355
update(results);
356
state.lastUpdate = end;
357
} else {
197
- report(results);
358
+ report(results, verbose);
359
}
360
console.log(`Completed in ${Math.floor(end - start)} ms`);
361
} else {
377
'[any] - rerun tests\n',
378
);
379
}
219
-
220
-/**
221
- * Runs the compiler in watch or single-execution mode
222
- */
223
-export async function main(opts: RunnerOptions): Promise<void> {
224
- const worker: Worker & typeof runnerWorker = new Worker(WORKER_PATH, {
225
- enableWorkerThreads: opts.workerThreads,
226
- numWorkers: NUM_WORKERS,
227
- }) as any;
228
- worker.getStderr().pipe(process.stderr);
229
- worker.getStdout().pipe(process.stdout);
230
-
231
- // Check if watch mode should be enabled
232
- const shouldWatch = opts.watch;
233
-
234
- if (shouldWatch) {
235
- makeWatchRunner(
236
- state => onChange(worker, state, opts.sync),
237
- opts.debug,
238
- opts.pattern,
239
- );
240
- if (opts.pattern) {
241
- /**
242
- * Warm up wormers when in watch mode. Loading the Forget babel plugin
243
- * and all of its transitive dependencies takes 1-3s (per worker) on a M1.
244
- * As jest-worker dispatches tasks using a round-robin strategy, we can
245
- * avoid an additional 1-3s wait on the first num_workers runs by warming
246
- * up workers eagerly.
247
- */
248
- for (let i = 0; i < NUM_WORKERS - 1; i++) {
249
- worker.transformFixture(
250
- {
251
- fixturePath: 'tmp',
252
- snapshotPath: './tmp.expect.md',
253
- inputPath: './tmp.js',
254
- input: `
255
- function Foo(props) {
256
- return identity(props);
257
- }
258
- `,
259
- snapshot: null,
260
- },
261
- 0,
262
- false,
263
- false,
264
- );
265
- }
266
- }
267
- } else {
268
- // Non-watch mode. For simplicity we re-use the same watchSrc() function.
269
- // After the first build completes run tests and exit
270
- const tsWatch: ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> =
271
- watchSrc(
272
- () => {},
273
- async (isTypecheckSuccess: boolean) => {
274
- let isSuccess = false;
275
- if (!isTypecheckSuccess) {
276
- console.error(
277
- 'Found typescript errors in Forget source code, skipping test fixtures.',
278
- );
279
- } else {
280
- try {
281
- execSync('yarn build', {cwd: PROJECT_ROOT});
282
- console.log('Built compiler successfully with tsup');
283
-
284
- // Determine which filter to use
285
- let testFilter: TestFilter | null = null;
286
- if (opts.pattern) {
287
- testFilter = {
288
- paths: [opts.pattern],
289
- };
290
- }
291
-
292
- const results = await runFixtures(
293
- worker,
294
- testFilter,
295
- 0,
296
- opts.debug,
297
- false, // no requireSingleFixture in non-watch mode
298
- opts.sync,
299
- );
300
- if (opts.update) {
301
- update(results);
302
- isSuccess = true;
303
- } else {
304
- isSuccess = report(results);
305
- }
306
- } catch (e) {
307
- console.warn('Failed to build compiler with tsup:', e);
308
- }
309
- }
310
- tsWatch?.close();
311
- await worker.end();
312
- process.exit(isSuccess ? 0 : 1);
313
- },
314
- );
315
- }
316
-}