Commit 6d48f36b authored by Tim van der Lippe's avatar Tim van der Lippe Committed by Commit Bot

Introduce migration script for JavaScript modules

Example invocation of the script:

./refactor-folder-to-es-modules.sh ui

It will consequently refactor all files in the ui folder to JavaScript
modules. It will also update the BUILD.gn variables to remove the
references from the old all_devtools_files into the new modules
variables.

Change-Id: I35ad6360b15d1aff250afffc8a28774c4e8cc1ae
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1808867Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Tim van der Lippe <tvanderlippe@google.com>
Cr-Commit-Position: refs/heads/master@{#697602}
parent 370c916f
refactor-to-es-module.js
\ No newline at end of file
{
"devDependencies": {
"@types/node": "^12.7.1",
"recast": "^0.18.2",
"typescript": "^3.6.3"
},
"scripts": {
"build": "tsc",
"migrate": "node refactor-to-es-module.js"
}
}
#!/bin/bash
if [ -z "$1" ]; then
echo "Must supply folder name"
exit
fi
MIGRATION_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
ROOT_PATH="$MIGRATION_SCRIPT_DIR/../.."
FRONT_END_PATH="$ROOT_PATH/front_end"
BUILD_GN_PATH="$ROOT_PATH/BUILD.gn"
FOLDER_PATH="$FRONT_END_PATH/$1"
if [ ! -d "$FOLDER_PATH" ]; then
echo "Folder on location $FOLDER_PATH does not exist"
exit
fi
FILES=$(find $FOLDER_PATH/*.js | xargs -n 1 basename -s .js)
npm run build
for FILE in $FILES
do
npm run migrate -- $1 $FILE
# Remove old reference in all_devtools_files variable
# The start of the substitution reads the whole file, which is necessary to remove the newline characters
sed -i -e ":a;N;\$!ba;s/\"front\_end\/$1\/$FILE.js\"\,\n//g" "$BUILD_GN_PATH"
# Add to all_devtools_modules
sed -i -e "s/all\_devtools\_modules = \[/all\_devtools\_modules = \[ \"front\_end\/$1\/$FILE.js\"\,/" "$BUILD_GN_PATH"
# Add to copied_devtools_modules
sed -i -e "s/copied\_devtools\_modules = \[/copied\_devtools\_modules = \[ \"\$resources\_out\_dir\/$1\/$FILE.js\"\,/" "$BUILD_GN_PATH"
done
git cl format
import { parse, print, types } from 'recast';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { IdentifierKind, MemberExpressionKind, ExpressionKind } from 'ast-types/gen/kinds';
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end')
function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const b = types.builders;
function createReplacementDeclaration(propertyName: IdentifierKind, declaration: any): any {
if (declaration.type === 'ClassExpression') {
return b.exportDeclaration(false, b.classDeclaration(propertyName, declaration.body));
}
if (declaration.type === 'Literal' || declaration.type.endsWith('Expression')) {
return b.exportNamedDeclaration(b.variableDeclaration("const", [b.variableDeclarator(propertyName, declaration)]));
}
console.error(`Unable to refactor declaration of type "${declaration.type}" named "${propertyName.name}"`);
}
function getTopLevelMemberExpression(expression: MemberExpressionKind): any {
while (expression.object.type === 'MemberExpression') {
expression = expression.object;
}
return expression;
}
function rewriteSource(source: string, refactoringNamespace: string, refactoringFileName: string) {
const exportedMembers: IdentifierKind[] = [];
let needToObjectAssign = false;
const ast = parse(source);
ast.program.body = ast.program.body.map((expression: any) => {
if (expression.type === 'ExpressionStatement') {
if (expression.expression.type === 'AssignmentExpression') {
const assignment = expression.expression;
if (assignment.left.type === 'MemberExpression') {
const topLevelAssignment = getTopLevelMemberExpression(assignment.left);
// If there is a nested export, such as `UI.ARIAUtils.Nested.Field`
if (topLevelAssignment !== assignment.left.object) {
// Exports itself. E.g. `UI.ARIAUtils = <...>`
if (assignment.left.object.name === refactoringNamespace && assignment.left.property.name === refactoringFileName) {
const {declaration} = createReplacementDeclaration(assignment.left.property, assignment.right);
const declarationStatement = b.exportDefaultDeclaration(declaration.type === 'VariableDeclaration' ? declaration.declarations[0].init : declaration);
declarationStatement.comments = expression.comments;
if (needToObjectAssign) {
console.error(`Multiple exports with the same name is invalid!`);
}
needToObjectAssign = true;
return declarationStatement;
}
console.error(`Nested field "${assignment.left.property.name}" detected! Requires manual changes.`);
return expression;
}
const propertyName = assignment.left.property;
const {object, property} = topLevelAssignment;
if (object.type === 'Identifier' && property.type === 'Identifier') {
const namespace = object.name;
const fileName = property.name;
if (namespace === refactoringNamespace && fileName === refactoringFileName) {
const declaration = createReplacementDeclaration(propertyName, assignment.right);
if (declaration) {
exportedMembers.push(propertyName);
declaration.comments = expression.comments;
return declaration;
}
}
}
}
}
}
return expression;
});
// self.UI = self.UI || {};
const legacyNamespaceName = b.memberExpression(b.identifier('self'), b.identifier(refactoringNamespace), false);
const legacyNamespaceOr = b.logicalExpression("||", legacyNamespaceName, b.objectExpression([]));
ast.program.body.push(b.expressionStatement.from({expression: b.assignmentExpression('=', legacyNamespaceName, legacyNamespaceOr), comments: [b.commentBlock('Legacy exported object', true, false)]}));
// self.UI.ARIAUtils = {properties};
const legacyNamespaceExport = b.memberExpression(b.identifier('self'), b.memberExpression(b.identifier(refactoringNamespace), b.identifier(refactoringFileName), false), false);
let exportedObjectProperties: ExpressionKind = b.objectExpression(exportedMembers.map(prop => b.objectProperty.from({key: prop, value: prop, shorthand: true })));
// self.UI.ARIAUtils = Object.assign(ARIAUtils, {properties})
if (needToObjectAssign) {
exportedObjectProperties = b.callExpression(b.memberExpression(b.identifier('Object'), b.identifier('assign'), false), [b.identifier(refactoringFileName), exportedObjectProperties]);
}
ast.program.body.push(b.expressionStatement(b.assignmentExpression('=', legacyNamespaceExport, exportedObjectProperties)));
return print(ast).code;
}
const FOLDER_MAPPING: {[name: string]: string} = require(path.join('..', 'special_case_namespaces.json'));
function computeNamespaceName(folderName: string): string {
if (folderName in FOLDER_MAPPING) {
return FOLDER_MAPPING[folderName];
}
return capitalizeFirstLetter(folderName);
}
async function main(refactoringNamespace: string, refactoringFileName: string) {
const pathName = path.join(FRONT_END_FOLDER, refactoringNamespace, `${refactoringFileName}.js`);
const source = await readFile(pathName, {encoding: 'utf-8'});
const rewrittenSource = rewriteSource(source, computeNamespaceName(process.argv[2]), refactoringFileName);
await writeFile(pathName, rewrittenSource);
// console.log(`Succesfully written source to "${pathName}". Make sure that no other errors are reported before submitting!`);
}
if (!process.argv[2]) {
console.error(`No arguments specified. Run this script with "<folder-name> <filename>". For example: "common Color"`);
process.exit(1);
}
main(process.argv[2], process.argv[3]);
\ No newline at end of file
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"declaration": false
}
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment