mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
[compiler] Add snap subcommand to minimize a test input (#35663)
Snap now supports subcommands 'test' (default) and 'minimize`. The
minimize subcommand attempts to minimize a single failing input fixture
by incrementally simplifying the ast so long as the same error occurs. I
spot-checked it and it seemed to work pretty well. This is intended for
use in a new subagent designed for investigating bugs — fixture
simplification is an important part of the process and we can automate
this rather than light tokens on fire.
Example Input:
```js
function Component(props) {
const x = [];
let result;
for (let i = 0; i < 10; i++) {
if (cond) {
try {
result = {key: bar([props.cond && props.foo])};
} catch (e) {
console.log(e);
}
}
}
x.push(result);
return <Stringify x={x} />;
}
```
Command output:
```
$ yarn snap minimize --path .../input.js
Minimizing: .../input.js
Minimizing................
--- Minimized Code ---
function Component(props) {
try {
props && props;
} catch (e) {}
}
Reduced from 16 lines to 5 lines
```
This demonstrates things like:
* Removing one statement at at time
* Replacing if/else with the test, consequent, or alternate. Similar for
other control-flow statements including try/catch
* Removing individual array/object expression properties
* Replacing single-value array/object with the value
* Replacing control-flow expression (logical, consequent) w the test or
left/right values
* Removing call arguments
* Replacing calls with a single argument with the argument
* Replacing calls with multiple arguments with an array of the arguments
* Replacing optional member/call with non-optional versions
* Replacing member expression with the object. If computed, also try
replacing w the key
* And a bunch more strategies, see the code
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.19.1",
|
||||
"@babel/plugin-syntax-jsx": "^7.18.6",
|
||||
"@babel/preset-flow": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
@@ -58,6 +59,7 @@
|
||||
"resolutions": {
|
||||
"./**/@babel/parser": "7.7.4",
|
||||
"./**/@babel/types": "7.7.4",
|
||||
"@babel/generator": "7.2.0",
|
||||
"@babel/preset-flow": "7.22.5"
|
||||
}
|
||||
}
|
||||
|
||||
2070
compiler/packages/snap/src/minimize.ts
Normal file
2070
compiler/packages/snap/src/minimize.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ import {
|
||||
} from './runner-watch';
|
||||
import * as runnerWorker from './runner-worker';
|
||||
import {execSync} from 'child_process';
|
||||
import {runMinimize} from './minimize';
|
||||
|
||||
const WORKER_PATH = require.resolve('./runner-worker.js');
|
||||
const NUM_WORKERS = cpus().length - 1;
|
||||
@@ -38,40 +39,76 @@ type RunnerOptions = {
|
||||
debug: boolean;
|
||||
};
|
||||
|
||||
const opts: RunnerOptions = yargs
|
||||
.boolean('sync')
|
||||
.describe(
|
||||
'sync',
|
||||
'Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false.',
|
||||
async function runTestCommand(opts: RunnerOptions): Promise<void> {
|
||||
await main(opts);
|
||||
}
|
||||
|
||||
async function runMinimizeCommand(path: string): Promise<void> {
|
||||
await runMinimize({path});
|
||||
}
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command(
|
||||
['test', '$0'],
|
||||
'Run compiler tests',
|
||||
yargs => {
|
||||
return yargs
|
||||
.boolean('sync')
|
||||
.describe(
|
||||
'sync',
|
||||
'Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false.',
|
||||
)
|
||||
.default('sync', false)
|
||||
.boolean('worker-threads')
|
||||
.describe(
|
||||
'worker-threads',
|
||||
'Run compiler in worker threads (instead of subprocesses). Defaults to true.',
|
||||
)
|
||||
.default('worker-threads', true)
|
||||
.boolean('watch')
|
||||
.describe(
|
||||
'watch',
|
||||
'Run compiler in watch mode, re-running after changes',
|
||||
)
|
||||
.alias('w', 'watch')
|
||||
.default('watch', false)
|
||||
.boolean('update')
|
||||
.alias('u', 'update')
|
||||
.describe('update', 'Update fixtures')
|
||||
.default('update', false)
|
||||
.string('pattern')
|
||||
.alias('p', 'pattern')
|
||||
.describe(
|
||||
'pattern',
|
||||
'Optional glob pattern to filter fixtures (e.g., "error.*", "use-memo")',
|
||||
)
|
||||
.boolean('debug')
|
||||
.alias('d', 'debug')
|
||||
.describe('debug', 'Enable debug logging to print HIR for each pass')
|
||||
.default('debug', false);
|
||||
},
|
||||
async argv => {
|
||||
await runTestCommand(argv as RunnerOptions);
|
||||
},
|
||||
)
|
||||
.default('sync', false)
|
||||
.boolean('worker-threads')
|
||||
.describe(
|
||||
'worker-threads',
|
||||
'Run compiler in worker threads (instead of subprocesses). Defaults to true.',
|
||||
.command(
|
||||
'minimize',
|
||||
'Minimize a test case to reproduce a compiler error',
|
||||
yargs => {
|
||||
return yargs
|
||||
.string('path')
|
||||
.alias('p', 'path')
|
||||
.describe('path', 'Path to the file to minimize')
|
||||
.demandOption('path');
|
||||
},
|
||||
async argv => {
|
||||
await runMinimizeCommand(argv.path as string);
|
||||
},
|
||||
)
|
||||
.default('worker-threads', true)
|
||||
.boolean('watch')
|
||||
.describe('watch', 'Run compiler in watch mode, re-running after changes')
|
||||
.alias('w', 'watch')
|
||||
.default('watch', false)
|
||||
.boolean('update')
|
||||
.alias('u', 'update')
|
||||
.describe('update', 'Update fixtures')
|
||||
.default('update', false)
|
||||
.string('pattern')
|
||||
.alias('p', 'pattern')
|
||||
.describe(
|
||||
'pattern',
|
||||
'Optional glob pattern to filter fixtures (e.g., "error.*", "use-memo")',
|
||||
)
|
||||
.boolean('debug')
|
||||
.alias('d', 'debug')
|
||||
.describe('debug', 'Enable debug logging to print HIR for each pass')
|
||||
.default('debug', false)
|
||||
.help('help')
|
||||
.strict()
|
||||
.parseSync(hideBin(process.argv)) as RunnerOptions;
|
||||
.demandCommand()
|
||||
.parse();
|
||||
|
||||
/**
|
||||
* Do a test run and return the test results
|
||||
@@ -82,6 +119,7 @@ async function runFixtures(
|
||||
compilerVersion: number,
|
||||
debug: boolean,
|
||||
requireSingleFixture: boolean,
|
||||
sync: boolean,
|
||||
): Promise<TestResults> {
|
||||
// We could in theory be fancy about tracking the contents of the fixtures
|
||||
// directory via our file subscription, but it's simpler to just re-read
|
||||
@@ -91,7 +129,7 @@ async function runFixtures(
|
||||
const shouldLog = debug && (!requireSingleFixture || isOnlyFixture);
|
||||
|
||||
let entries: Array<[string, TestResult]>;
|
||||
if (!opts.sync) {
|
||||
if (!sync) {
|
||||
// Note: promise.all to ensure parallelism when enabled
|
||||
const work: Array<Promise<[string, TestResult]>> = [];
|
||||
for (const [fixtureName, fixture] of fixtures) {
|
||||
@@ -123,6 +161,7 @@ async function runFixtures(
|
||||
async function onChange(
|
||||
worker: Worker & typeof runnerWorker,
|
||||
state: RunnerState,
|
||||
sync: boolean,
|
||||
) {
|
||||
const {compilerVersion, isCompilerBuildValid, mode, filter, debug} = state;
|
||||
if (isCompilerBuildValid) {
|
||||
@@ -140,6 +179,7 @@ async function onChange(
|
||||
compilerVersion,
|
||||
debug,
|
||||
true, // requireSingleFixture in watch mode
|
||||
sync,
|
||||
);
|
||||
const end = performance.now();
|
||||
|
||||
@@ -192,7 +232,11 @@ export async function main(opts: RunnerOptions): Promise<void> {
|
||||
const shouldWatch = opts.watch;
|
||||
|
||||
if (shouldWatch) {
|
||||
makeWatchRunner(state => onChange(worker, state), opts.debug, opts.pattern);
|
||||
makeWatchRunner(
|
||||
state => onChange(worker, state, opts.sync),
|
||||
opts.debug,
|
||||
opts.pattern,
|
||||
);
|
||||
if (opts.pattern) {
|
||||
/**
|
||||
* Warm up wormers when in watch mode. Loading the Forget babel plugin
|
||||
@@ -251,6 +295,7 @@ export async function main(opts: RunnerOptions): Promise<void> {
|
||||
0,
|
||||
opts.debug,
|
||||
false, // no requireSingleFixture in non-watch mode
|
||||
opts.sync,
|
||||
);
|
||||
if (opts.update) {
|
||||
update(results);
|
||||
@@ -269,5 +314,3 @@ export async function main(opts: RunnerOptions): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
main(opts).catch(error => console.error(error));
|
||||
|
||||
@@ -268,6 +268,17 @@
|
||||
source-map "^0.5.0"
|
||||
trim-right "^1.0.1"
|
||||
|
||||
"@babel/generator@^7.19.1":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1"
|
||||
integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.28.6"
|
||||
"@babel/types" "^7.28.6"
|
||||
"@jridgewell/gen-mapping" "^0.3.12"
|
||||
"@jridgewell/trace-mapping" "^0.3.28"
|
||||
jsesc "^3.0.2"
|
||||
|
||||
"@babel/generator@^7.2.0", "@babel/generator@^7.26.0", "@babel/generator@^7.26.10", "@babel/generator@^7.26.3", "@babel/generator@^7.27.0", "@babel/generator@^7.7.2", "@babel/generator@^7.7.4":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz"
|
||||
@@ -662,6 +673,13 @@
|
||||
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz"
|
||||
integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==
|
||||
|
||||
"@babel/parser@^7.28.6":
|
||||
version "7.28.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd"
|
||||
integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.28.6"
|
||||
|
||||
"@babel/parser@^7.7.4":
|
||||
version "7.21.4"
|
||||
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz"
|
||||
@@ -1592,7 +1610,7 @@
|
||||
debug "^4.3.1"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
|
||||
"@babel/types@7.26.3", "@babel/types@^7.0.0", "@babel/types@^7.19.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.10", "@babel/types@^7.26.3", "@babel/types@^7.27.0", "@babel/types@^7.27.1", "@babel/types@^7.28.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.4":
|
||||
version "7.26.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0"
|
||||
integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==
|
||||
@@ -2732,6 +2750,14 @@
|
||||
"@types/yargs" "^17.0.8"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.12":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
|
||||
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.2":
|
||||
version "0.3.8"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz"
|
||||
@@ -2765,6 +2791,11 @@
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.5.0":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
|
||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||
|
||||
"@jridgewell/trace-mapping@0.3.9":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
|
||||
@@ -2789,6 +2820,14 @@
|
||||
"@jridgewell/resolve-uri" "3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.28":
|
||||
version "0.3.31"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
|
||||
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@modelcontextprotocol/sdk@^1.9.0":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user