oxc/tasks/transform_conformance/update_fixtures.js
overlookmotel d14d360061 test(transformer): remove dead code from fixtures update script (#7808)
Follow-up after #7771 and #7779. Remove dead code from transformer fixtures updater script.
2024-12-12 09:27:54 +00:00

192 lines
5.8 KiB
JavaScript

// Script to amend test fixtures for transforms.
//
// Babel's test fixtures for some plugins are not usable for us because they either use
// `@babel/preset-env` or transforms which we haven't implemented yet (e.g. `@babel/transform-classes`).
//
// This script:
// 1. Removes unsupported options from `options.json` files.
// 2. Transforms the input code with Babel using the updated options, and saves as `output.js`.
// TODO: We follow Babel 8 not Babel 7. So need to:
// 1. Use Babel 8 to transform code.
// 2. Skip the fixtures which are marked "SKIP_babel7plugins_babel8core"
// (or maybe don't - what does this option mean?)
import { transformFileAsync } from '@babel/core';
import { readdir, readFile, rename, writeFile } from 'fs/promises';
import { extname, join as pathJoin } from 'path';
const PACKAGES = [
'babel-plugin-transform-class-properties',
'babel-plugin-transform-logical-assignment-operators',
];
const FILTER_OUT_PRESETS = ['env'];
const FILTER_OUT_PLUGINS = [
'transform-classes',
'transform-block-scoping',
'transform-destructuring',
];
const PACKAGES_PATH = pathJoin(import.meta.dirname, '../coverage/babel/packages');
// Copied from `@babel/helper-transform-fixture-test-runner`
const EXTERNAL_HELPERS_VERSION = '7.100.0';
for (const packageName of PACKAGES) {
const dirPath = pathJoin(PACKAGES_PATH, packageName, 'test/fixtures');
await updateDir(dirPath, {}, false);
}
/**
* Update fixtures in directory, and its sub-directories.
* @param {string} dirPath - Path to directory containing fixtures
* @param {Object} options - Transform options from parent directory
* @param {boolean} hasChangedOptions - `true` if transform options from parent directory have changed
* @returns {undefined}
*/
async function updateDir(dirPath, options, hasChangedOptions) {
const files = await readdir(dirPath, { withFileTypes: true });
const dirFiles = [],
filenames = { options: null, input: null, output: null };
// Find files in dir
for (const file of files) {
const filename = file.name;
if (file.isDirectory()) {
dirFiles.push(filename);
} else {
const ext = extname(filename),
type = ext === '' ? filename : filename.slice(0, -ext.length);
if (Object.hasOwn(filenames, type)) filenames[type] = filename;
}
}
// Update options, save to file, and merge options with parent
if (filenames.options) {
const path = pathJoin(dirPath, filenames.options);
const localOptions = JSON.parse(await readFile(path, 'utf8'));
if (updateOptions(localOptions)) {
hasChangedOptions = true;
await backupFile(path);
await writeFile(path, JSON.stringify(localOptions, null, 2) + '\n');
}
options = { ...options, ...localOptions };
}
// Run Babel with updated options/input
if (filenames.output && hasChangedOptions) {
const inputPath = pathJoin(dirPath, filenames.input),
outputPath = pathJoin(dirPath, filenames.output);
const transformedCode = await transform(inputPath, options);
const originalTransformedCode = await readFile(outputPath, 'utf8');
if (transformedCode.trim() !== originalTransformedCode.trim()) {
await backupFile(outputPath);
await writeFile(outputPath, transformedCode);
}
}
// Process subfolders
for (const filename of dirFiles) {
const path = pathJoin(dirPath, filename);
await updateDir(path, options, hasChangedOptions);
}
}
/**
* Remove unsupported presets + plugins from `options`.
* @param {Object} options - Options object
* @returns {boolean} - `true` if `options` has been altered.
*/
function updateOptions(options) {
let hasChangedOptions = false;
function filter(key, filterOut) {
if (!options[key]) return;
options[key] = options[key].filter((plugin) => {
if (filterOut.includes(getName(plugin))) {
hasChangedOptions = true;
return false;
}
return true;
});
if (options[key].length === 0) delete options[key];
}
filter('presets', FILTER_OUT_PRESETS);
filter('plugins', FILTER_OUT_PLUGINS);
return hasChangedOptions;
}
/**
* Transform input with Babel and save to output file.
* @param {string} inputPath - Path of input file
* @param {Object} options - Transform options
* @returns {undefined}
*/
async function transform(inputPath, options) {
options = {
...options,
configFile: false,
babelrc: false,
cwd: import.meta.dirname,
};
delete options.SKIP_babel7plugins_babel8core;
delete options.minNodeVersion;
function prefixName(plugin, type) {
if (Array.isArray(plugin)) {
plugin = [...plugin];
plugin[0] = `@babel/${type}-${plugin[0]}`;
} else {
plugin = `@babel/${type}-${plugin}`;
}
return plugin;
}
if (options.presets) {
options.presets = options.presets.map((preset) => prefixName(preset, 'preset'));
}
options.plugins = (options.plugins || []).map((plugin) => prefixName(plugin, 'plugin'));
let addExternalHelpersPlugin = true;
if (Object.hasOwn(options, 'externalHelpers')) {
if (!options.externalHelpers) addExternalHelpersPlugin = false;
delete options.externalHelpers;
}
if (addExternalHelpersPlugin) {
options.plugins.push([
'@babel/plugin-external-helpers',
{ helperVersion: EXTERNAL_HELPERS_VERSION },
]);
}
const { code } = await transformFileAsync(inputPath, options);
return code;
}
/**
* Get name of plugin/preset.
* @param {string|Array} stringOrArray - Input
* @returns {string} - Name of plugin/preset
*/
function getName(stringOrArray) {
if (Array.isArray(stringOrArray)) return stringOrArray[0];
return stringOrArray;
}
/**
* Backup file.
* @param {string} path - Original path
* @returns {undefined}
*/
async function backupFile(path) {
const ext = extname(path),
backupPath = `${path.slice(0, -ext.length)}.original${ext}`;
await rename(path, backupPath);
}