mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[compiler][snap] Support pattern of files to test as CLI argument (#35148)
I've been trying out LLM agents for compiler development, and one thing
i found is that the agent naturally wants to run `yarn snap <pattern>`
to test a specific fixture, and I want to be able to tell it (directly
or in rules/skills) to do this in order to get the debug output from all
the compiler passes. Agents can figure out our current testfilter.txt
file system but that's just tedious. So here we add support for `yarn
snap -p <pattern>`. If you pass in a pattern with an extension, we
target that extension specifically. If you pass in a .expect.md file, we
look at that specific fixture. And if the pattern doesn't have
extensions, we search for `<pattern>{.js,.jsx,.ts,.tsx}`. When patterns
are enabled we automatically log as in debug mode (if there is a single
match), and disable watch mode.
Open to feedback!
This commit is contained in:
@@ -44,6 +44,21 @@ function stripExtension(filename: string, extensions: Array<string>): string {
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip all extensions from a filename
|
||||
* e.g., "foo.expect.md" -> "foo"
|
||||
*/
|
||||
function stripAllExtensions(filename: string): string {
|
||||
let result = filename;
|
||||
while (true) {
|
||||
const extension = path.extname(result);
|
||||
if (extension === '') {
|
||||
return result;
|
||||
}
|
||||
result = path.basename(result, extension);
|
||||
}
|
||||
}
|
||||
|
||||
export async function readTestFilter(): Promise<TestFilter | null> {
|
||||
if (!(await exists(FILTER_PATH))) {
|
||||
throw new Error(`testfilter file not found at \`${FILTER_PATH}\``);
|
||||
@@ -111,11 +126,25 @@ async function readInputFixtures(
|
||||
} else {
|
||||
inputFiles = (
|
||||
await Promise.all(
|
||||
filter.paths.map(pattern =>
|
||||
glob.glob(`${pattern}{${INPUT_EXTENSIONS.join(',')}}`, {
|
||||
filter.paths.map(pattern => {
|
||||
// If the pattern already has an extension other than .expect.md,
|
||||
// search for the pattern directly. Otherwise, search for the
|
||||
// pattern with the expected input extensions added.
|
||||
// Eg
|
||||
// `alias-while` => search for `alias-while{.js,.jsx,.ts,.tsx}`
|
||||
// `alias-while.js` => search as-is
|
||||
// `alias-while.expect.md` => search for `alias-while{.js,.jsx,.ts,.tsx}`
|
||||
const basename = path.basename(pattern);
|
||||
const basenameWithoutExt = stripAllExtensions(basename);
|
||||
const hasExtension = basename !== basenameWithoutExt;
|
||||
const globPattern =
|
||||
hasExtension && !pattern.endsWith(SNAPSHOT_EXTENSION)
|
||||
? pattern
|
||||
: `${basenameWithoutExt}{${INPUT_EXTENSIONS.join(',')}}`;
|
||||
return glob.glob(globPattern, {
|
||||
cwd: rootDir,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}),
|
||||
)
|
||||
).flat();
|
||||
}
|
||||
@@ -150,11 +179,13 @@ async function readOutputFixtures(
|
||||
} else {
|
||||
outputFiles = (
|
||||
await Promise.all(
|
||||
filter.paths.map(pattern =>
|
||||
glob.glob(`${pattern}${SNAPSHOT_EXTENSION}`, {
|
||||
filter.paths.map(pattern => {
|
||||
// Strip all extensions and find matching .expect.md files
|
||||
const basenameWithoutExt = stripAllExtensions(pattern);
|
||||
return glob.glob(`${basenameWithoutExt}${SNAPSHOT_EXTENSION}`, {
|
||||
cwd: rootDir,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}),
|
||||
)
|
||||
).flat();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ type RunnerOptions = {
|
||||
watch: boolean;
|
||||
filter: boolean;
|
||||
update: boolean;
|
||||
pattern?: string;
|
||||
};
|
||||
|
||||
const opts: RunnerOptions = yargs
|
||||
@@ -62,9 +63,15 @@ const opts: RunnerOptions = yargs
|
||||
'Only run fixtures which match the contents of testfilter.txt',
|
||||
)
|
||||
.default('filter', false)
|
||||
.string('pattern')
|
||||
.alias('p', 'pattern')
|
||||
.describe(
|
||||
'pattern',
|
||||
'Optional glob pattern to filter fixtures (e.g., "error.*", "use-memo")',
|
||||
)
|
||||
.help('help')
|
||||
.strict()
|
||||
.parseSync(hideBin(process.argv));
|
||||
.parseSync(hideBin(process.argv)) as RunnerOptions;
|
||||
|
||||
/**
|
||||
* Do a test run and return the test results
|
||||
@@ -171,7 +178,13 @@ export async function main(opts: RunnerOptions): Promise<void> {
|
||||
worker.getStderr().pipe(process.stderr);
|
||||
worker.getStdout().pipe(process.stdout);
|
||||
|
||||
if (opts.watch) {
|
||||
// If pattern is provided, force watch mode off and use pattern filter
|
||||
const shouldWatch = opts.watch && opts.pattern == null;
|
||||
if (opts.watch && opts.pattern != null) {
|
||||
console.warn('NOTE: --watch is ignored when a --pattern is supplied');
|
||||
}
|
||||
|
||||
if (shouldWatch) {
|
||||
makeWatchRunner(state => onChange(worker, state), opts.filter);
|
||||
if (opts.filter) {
|
||||
/**
|
||||
@@ -216,7 +229,18 @@ export async function main(opts: RunnerOptions): Promise<void> {
|
||||
try {
|
||||
execSync('yarn build', {cwd: PROJECT_ROOT});
|
||||
console.log('Built compiler successfully with tsup');
|
||||
const testFilter = opts.filter ? await readTestFilter() : null;
|
||||
|
||||
// Determine which filter to use
|
||||
let testFilter: TestFilter | null = null;
|
||||
if (opts.pattern) {
|
||||
testFilter = {
|
||||
debug: true,
|
||||
paths: [opts.pattern],
|
||||
};
|
||||
} else if (opts.filter) {
|
||||
testFilter = await readTestFilter();
|
||||
}
|
||||
|
||||
const results = await runFixtures(worker, testFilter, 0);
|
||||
if (opts.update) {
|
||||
update(results);
|
||||
|
||||
Reference in New Issue
Block a user