scripts/updateBabelTraverseTypes.js (86 lines of code) (raw):
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const virtualTypes = require('@babel/traverse/lib/path/lib/virtual-types');
const t = require('@babel/types');
const fs = require('fs');
const process = require('process');
const NODE_PREFIX = 'BabelNode';
const VISITOR_METHODS_MARKER_NAME = 'VISITOR METHODS';
const NODE_PATH_METHOD_MARKER_NAME = 'NODE PATH METHODS';
const declarationFileName = process.argv[2];
if (declarationFileName == null) {
throw new Error(
'Expected the path to the babel-traverse.js library definition as an argument.',
);
}
if (!fs.existsSync(declarationFileName)) {
throw new Error(`The file ${declarationFileName} does not exist.`);
}
if (!fs.statSync(declarationFileName).isFile()) {
throw new Error(`${declarationFileName} is a directory, expected a file.`);
}
let content = fs.readFileSync(declarationFileName).toString('utf-8');
content = replaceGeneratedBlock(
content,
VISITOR_METHODS_MARKER_NAME,
generateVisitorMethods(),
);
content = replaceGeneratedBlock(
content,
NODE_PATH_METHOD_MARKER_NAME,
generateNodePathMethods(),
);
fs.writeFileSync(declarationFileName, content);
function generateVisitorMethods() {
const uniqueTypes = new Set([
...t.TYPES,
...Object.keys(t.FLIPPED_ALIAS_KEYS),
]);
const types = [...uniqueTypes].filter(type => {
if (type === 'File') {
// The file node can not be visited using a visitor because traverse(node) only visits the
// children of the passed in node and File has no parent node.
return false;
}
return true;
});
types.sort();
const lines = types.map(type => {
const nodeType =
(t.NODE_FIELDS[type] || t.FLIPPED_ALIAS_KEYS[type]) != null ? type : '';
return ` ${type}?: VisitNode<${NODE_PREFIX}${nodeType}, TState>,`;
});
return lines.join('\n');
}
function generateNodePathMethods() {
const isTypes = [
...new Set([
...t.TYPES,
...Object.keys(virtualTypes).filter(type => !type.startsWith('_')),
]),
].sort();
const is = isTypes.map(type => ` is${type}(opts?: Opts): boolean;`);
const asserts = t.TYPES.map(
type => ` assert${type}(opts?: Opts): void;`,
).sort();
return `${is.join('\n')}\n${asserts.join('\n')}`;
}
function replaceGeneratedBlock(content, markerName, code) {
const insertPosition = getGeneratedCodeInsertPosition(content, markerName);
const prelude = content.substring(0, insertPosition.start);
const postlude = content.substring(insertPosition.end);
return `${prelude}\n${code}${postlude}`;
}
function getGeneratedCodeInsertPosition(content, markerName) {
const beginMarker = `BEGIN GENERATED ${markerName}`;
const endMarker = `END GENERATED ${markerName}`;
const beginIndex = content.indexOf(beginMarker);
const endIndex = content.indexOf(endMarker);
if (beginIndex === -1) {
throw new Error(`Did not found ${beginMarker} in the provided file`);
}
if (endIndex === -1) {
throw new Error(`Did not found ${endMarker} in the provided file`);
}
return {
start: beginIndex + beginMarker.length,
end: content.lastIndexOf('\n', endIndex),
};
}