[compiler][snap] Fixes to relative path resolution; compile subcommand (#35688)

More snap improvements for use with agents:
* `yarn snap compile [--debug] <path>` for compiling any file,
optionally with debug logs
* `yarn snap minimize <path>` now accepts path as a positional param for
consistency w 'compile' command
* Both compile/minimize commands properly handle paths relative to the
compiler/ directory. When using `yarn snap` the current working
directory is compiler/packages/snap, but you're generally running it
from the compiler directory so this matches expectations of callers
better.
This commit is contained in:
Joseph Savona
2026-02-03 22:12:21 -05:00
committed by GitHub
parent cd0c4879a2
commit 3ce1316b05
9 changed files with 238 additions and 24 deletions

View File

@@ -294,6 +294,15 @@ yarn snap -p <fixture-name>
# Run with debug output (shows all passes)
yarn snap -p <fixture-name> -d
# Compile any file (not just fixtures) and see output
yarn snap compile <path>
# Compile any file with debug output (alternative to yarn snap -d -p when you don't have a fixture)
yarn snap compile --debug <path>
# Minimize a failing test case to its minimal reproduction
yarn snap minimize <path>
# Update expected outputs
yarn snap -u
```

View File

@@ -7,19 +7,21 @@
import path from 'path';
export const PROJECT_ROOT = path.join(process.cwd(), '..', '..');
// We assume this is run from `babel-plugin-react-compiler`
export const PROJECT_ROOT = path.normalize(
path.join(process.cwd(), '..', 'babel-plugin-react-compiler'),
export const BABEL_PLUGIN_ROOT = path.normalize(
path.join(PROJECT_ROOT, 'packages', 'babel-plugin-react-compiler'),
);
export const PROJECT_SRC = path.normalize(
path.join(PROJECT_ROOT, 'dist', 'index.js'),
export const BABEL_PLUGIN_SRC = path.normalize(
path.join(BABEL_PLUGIN_ROOT, 'dist', 'index.js'),
);
export const PRINT_HIR_IMPORT = 'printFunctionWithOutlined';
export const PRINT_REACTIVE_IR_IMPORT = 'printReactiveFunction';
export const PARSE_CONFIG_PRAGMA_IMPORT = 'parseConfigPragmaForTests';
export const FIXTURES_PATH = path.join(
PROJECT_ROOT,
BABEL_PLUGIN_ROOT,
'src',
'__tests__',
'fixtures',

View File

@@ -12,7 +12,7 @@ import traverse from '@babel/traverse';
import * as t from '@babel/types';
import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
import {parseInput} from './compiler.js';
import {PARSE_CONFIG_PRAGMA_IMPORT, PROJECT_SRC} from './constants.js';
import {PARSE_CONFIG_PRAGMA_IMPORT, BABEL_PLUGIN_SRC} from './constants.js';
type CompileSuccess = {kind: 'success'};
type CompileParseError = {kind: 'parse_error'; message: string};
@@ -1919,7 +1919,7 @@ export function minimize(
sourceType: 'module' | 'script',
): MinimizeResult {
// Load the compiler plugin
const importedCompilerPlugin = require(PROJECT_SRC) as Record<
const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record<
string,
unknown
>;

View File

@@ -8,7 +8,7 @@
import watcher from '@parcel/watcher';
import path from 'path';
import ts from 'typescript';
import {FIXTURES_PATH, PROJECT_ROOT} from './constants';
import {FIXTURES_PATH, BABEL_PLUGIN_ROOT} from './constants';
import {TestFilter, getFixtures} from './fixture-utils';
import {execSync} from 'child_process';
@@ -17,7 +17,7 @@ export function watchSrc(
onComplete: (isSuccess: boolean) => void,
): ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> {
const configPath = ts.findConfigFile(
/*searchPath*/ PROJECT_ROOT,
/*searchPath*/ BABEL_PLUGIN_ROOT,
ts.sys.fileExists,
'tsconfig.json',
);
@@ -166,7 +166,7 @@ function subscribeTsc(
let isCompilerBuildValid = false;
if (isTypecheckSuccess) {
try {
execSync('yarn build', {cwd: PROJECT_ROOT});
execSync('yarn build', {cwd: BABEL_PLUGIN_ROOT});
console.log('Built compiler successfully with tsup');
isCompilerBuildValid = true;
} catch (e) {

View File

@@ -5,7 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
import {codeFrameColumns} from '@babel/code-frame';
import type {PluginObj} from '@babel/core';
import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
@@ -15,7 +14,7 @@ import {
PARSE_CONFIG_PRAGMA_IMPORT,
PRINT_HIR_IMPORT,
PRINT_REACTIVE_IR_IMPORT,
PROJECT_SRC,
BABEL_PLUGIN_SRC,
} from './constants';
import {TestFixture, getBasename, isExpectError} from './fixture-utils';
import {TestResult, writeOutputToString} from './reporter';
@@ -65,7 +64,7 @@ async function compile(
let compileResult: TransformResult | null = null;
let error: string | null = null;
try {
const importedCompilerPlugin = require(PROJECT_SRC) as Record<
const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record<
string,
unknown
>;

View File

@@ -12,7 +12,7 @@ import * as readline from 'readline';
import ts from 'typescript';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers';
import {PROJECT_ROOT} from './constants';
import {BABEL_PLUGIN_ROOT, PROJECT_ROOT} from './constants';
import {TestFilter, getFixtures} from './fixture-utils';
import {TestResult, TestResults, report, update} from './reporter';
import {
@@ -26,7 +26,14 @@ import {execSync} from 'child_process';
import fs from 'fs';
import path from 'path';
import {minimize} from './minimize';
import {parseLanguage, parseSourceType} from './compiler';
import {parseInput, parseLanguage, parseSourceType} from './compiler';
import {
PARSE_CONFIG_PRAGMA_IMPORT,
PRINT_HIR_IMPORT,
PRINT_REACTIVE_IR_IMPORT,
BABEL_PLUGIN_SRC,
} from './constants';
import chalk from 'chalk';
const WORKER_PATH = require.resolve('./runner-worker.js');
const NUM_WORKERS = cpus().length - 1;
@@ -48,6 +55,11 @@ type MinimizeOptions = {
update: boolean;
};
type CompileOptions = {
path: string;
debug: boolean;
};
async function runTestCommand(opts: TestOptions): Promise<void> {
const worker: Worker & typeof runnerWorker = new Worker(WORKER_PATH, {
enableWorkerThreads: opts.workerThreads,
@@ -106,7 +118,7 @@ async function runTestCommand(opts: TestOptions): Promise<void> {
);
} else {
try {
execSync('yarn build', {cwd: PROJECT_ROOT});
execSync('yarn build', {cwd: BABEL_PLUGIN_ROOT});
console.log('Built compiler successfully with tsup');
// Determine which filter to use
@@ -147,7 +159,7 @@ async function runMinimizeCommand(opts: MinimizeOptions): Promise<void> {
// Resolve the input path
const inputPath = path.isAbsolute(opts.path)
? opts.path
: path.resolve(process.cwd(), opts.path);
: path.resolve(PROJECT_ROOT, opts.path);
// Check if file exists
if (!fs.existsSync(inputPath)) {
@@ -196,6 +208,128 @@ async function runMinimizeCommand(opts: MinimizeOptions): Promise<void> {
}
}
async function runCompileCommand(opts: CompileOptions): Promise<void> {
// Resolve the input path
const inputPath = path.isAbsolute(opts.path)
? opts.path
: path.resolve(PROJECT_ROOT, opts.path);
// Check if file exists
if (!fs.existsSync(inputPath)) {
console.error(`Error: File not found: ${inputPath}`);
process.exit(1);
}
// Read the input file
const input = fs.readFileSync(inputPath, 'utf-8');
const filename = path.basename(inputPath);
const firstLine = input.substring(0, input.indexOf('\n'));
const language = parseLanguage(firstLine);
const sourceType = parseSourceType(firstLine);
// Import the compiler
const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record<
string,
any
>;
const BabelPluginReactCompiler = importedCompilerPlugin['default'];
const parseConfigPragmaForTests =
importedCompilerPlugin[PARSE_CONFIG_PRAGMA_IMPORT];
const printFunctionWithOutlined = importedCompilerPlugin[PRINT_HIR_IMPORT];
const printReactiveFunctionWithOutlined =
importedCompilerPlugin[PRINT_REACTIVE_IR_IMPORT];
const EffectEnum = importedCompilerPlugin['Effect'];
const ValueKindEnum = importedCompilerPlugin['ValueKind'];
const ValueReasonEnum = importedCompilerPlugin['ValueReason'];
// Setup debug logger
let lastLogged: string | null = null;
const debugIRLogger = opts.debug
? (value: any) => {
let printed: string;
switch (value.kind) {
case 'hir':
printed = printFunctionWithOutlined(value.value);
break;
case 'reactive':
printed = printReactiveFunctionWithOutlined(value.value);
break;
case 'debug':
printed = value.value;
break;
case 'ast':
printed = '(ast)';
break;
default:
printed = String(value);
}
if (printed !== lastLogged) {
lastLogged = printed;
console.log(`${chalk.green(value.name)}:\n${printed}\n`);
} else {
console.log(`${chalk.blue(value.name)}: (no change)\n`);
}
}
: () => {};
// Parse the input
let ast;
try {
ast = parseInput(input, filename, language, sourceType);
} catch (e: any) {
console.error(`Parse error: ${e.message}`);
process.exit(1);
}
// Build plugin options
const config = parseConfigPragmaForTests(firstLine, {compilationMode: 'all'});
const options = {
...config,
environment: {
...config.environment,
},
logger: {
logEvent: () => {},
debugLogIRs: debugIRLogger,
},
enableReanimatedCheck: false,
};
// Compile
const {transformFromAstSync} = require('@babel/core');
try {
const result = transformFromAstSync(ast, input, {
filename: '/' + filename,
highlightCode: false,
retainLines: true,
compact: true,
plugins: [[BabelPluginReactCompiler, options]],
sourceType: 'module',
ast: false,
cloneInputAst: true,
configFile: false,
babelrc: false,
});
if (result?.code != null) {
// Format the output
const prettier = require('prettier');
const formatted = await prettier.format(result.code, {
semi: true,
parser: language === 'typescript' ? 'babel-ts' : 'flow',
});
console.log(formatted);
} else {
console.error('Error: No code emitted from compiler');
process.exit(1);
}
} catch (e: any) {
console.error(e.message);
process.exit(1);
}
}
yargs(hideBin(process.argv))
.command(
['test', '$0'],
@@ -245,14 +379,15 @@ yargs(hideBin(process.argv))
},
)
.command(
'minimize',
'minimize <path>',
'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')
.positional('path', {
describe: 'Path to the file to minimize',
type: 'string',
demandOption: true,
})
.boolean('update')
.alias('u', 'update')
.describe(
@@ -265,6 +400,25 @@ yargs(hideBin(process.argv))
await runMinimizeCommand(argv as unknown as MinimizeOptions);
},
)
.command(
'compile <path>',
'Compile a file with the React Compiler',
yargs => {
return yargs
.positional('path', {
describe: 'Path to the file to compile',
type: 'string',
demandOption: true,
})
.boolean('debug')
.alias('d', 'debug')
.describe('debug', 'Enable debug logging to print HIR for each pass')
.default('debug', false);
},
async argv => {
await runCompileCommand(argv as unknown as CompileOptions);
},
)
.help('help')
.strict()
.demandCommand()