import { codeFrameColumns } from "@babel/code-frame";
import type { PluginObj } from "@babel/core";
import type { parseConfigPragma as ParseConfigPragma } from "babel-plugin-react-compiler/src/HIR/Environment";
import { TransformResult, transformFixtureInput } from "./compiler";
import {
COMPILER_PATH,
LOGGER_PATH,
PARSE_CONFIG_PRAGMA_PATH,
} from "./constants";
import { TestFixture, getBasename, isExpectError } from "./fixture-utils";
import { TestResult, writeOutputToString } from "./reporter";
import { runSprout } from "./sprout";
const originalConsoleError = console.error;
const invalidationSubpath = "packages/babel-plugin-react-compiler/dist";
let version: number | null = null;
export function clearRequireCache() {
Object.keys(require.cache).forEach(function (path) {
if (path.includes(invalidationSubpath)) {
delete require.cache[path];
}
});
}
async function compile(
input: string,
fixturePath: string,
compilerVersion: number,
shouldLog: boolean,
includeEvaluator: boolean
): Promise<{
error: string | null;
compileResult: TransformResult | null;
}> {
const seenConsoleErrors: Array<string> = [];
console.error = (...messages: Array<string>) => {
seenConsoleErrors.push(...messages);
};
if (version !== null && compilerVersion !== version) {
clearRequireCache();
}
version = compilerVersion;
let compileResult: TransformResult | null = null;
let error: string | null = null;
try {
const { default: BabelPluginReactCompiler } = require(COMPILER_PATH) as {
default: PluginObj;
};
const { toggleLogging } = require(LOGGER_PATH);
const { parseConfigPragma } = require(PARSE_CONFIG_PRAGMA_PATH) as {
parseConfigPragma: typeof ParseConfigPragma;
};
toggleLogging(shouldLog);
const result = await transformFixtureInput(
input,
fixturePath,
parseConfigPragma,
BabelPluginReactCompiler,
includeEvaluator
);
if (result.kind === "err") {
error = result.msg;
} else {
compileResult = result.value;
}
} catch (e) {
if (shouldLog) {
console.error(e.stack);
}
error = e.message.replace(/\u001b[^m]*m/g, "");
const loc = e.details?.[0]?.loc;
if (loc != null) {
try {
error = codeFrameColumns(
input,
{
start: {
line: loc.start.line,
column: loc.start.column + 1,
},
end: {
line: loc.end.line,
column: loc.end.column + 1,
},
},
{
message: e.message,
}
);
} catch {
}
}
}
for (const consoleError of seenConsoleErrors) {
if (error != null) {
error = `${error}\n\n${consoleError}`;
} else {
error = `ConsoleError: ${consoleError}`;
}
}
console.error = originalConsoleError;
return {
error,
compileResult,
};
}
export async function transformFixture(
fixture: TestFixture,
compilerVersion: number,
shouldLog: boolean,
includeEvaluator: boolean
): Promise<TestResult> {
const { input, snapshot: expected, snapshotPath: outputPath } = fixture;
const basename = getBasename(fixture);
const expectError = isExpectError(fixture);
if (input === null) {
return {
outputPath,
actual: null,
expected,
unexpectedError: null,
};
}
const { compileResult, error } = await compile(
input,
fixture.fixturePath,
compilerVersion,
shouldLog,
includeEvaluator
);
let unexpectedError: string | null = null;
if (expectError) {
if (error === null) {
unexpectedError = `Expected an error to be thrown for fixture: \`${basename}\`, remove the 'error.' prefix if an error is not expected.`;
}
} else {
if (error !== null) {
unexpectedError = `Expected fixture \`${basename}\` to succeed but it failed with error:\n\n${error}`;
} else if (compileResult == null) {
unexpectedError = `Expected output for fixture \`${basename}\`.`;
}
}
const snapOutput: string | null = compileResult?.forgetOutput ?? null;
let sproutOutput: string | null = null;
if (compileResult?.evaluatorCode != null) {
const sproutResult = runSprout(
compileResult.evaluatorCode.original,
compileResult.evaluatorCode.forget
);
if (sproutResult.kind === "invalid") {
unexpectedError ??= "";
unexpectedError += `\n\n${sproutResult.value}`;
} else {
sproutOutput = sproutResult.value;
}
} else if (!includeEvaluator && expected != null) {
sproutOutput = expected.split("\n### Eval output\n")[1];
}
const actualOutput = writeOutputToString(
input,
snapOutput,
sproutOutput,
compileResult?.logs ?? null,
error
);
return {
outputPath,
actual: actualOutput,
expected,
unexpectedError,
};
}