blob: 0c942b20367dbdf88a196b19ff3946e95351f9b6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import fs from 'fs';
import path from 'path';
import {parse, print, types} from 'recast';
import {promisify} from 'util';
const readDir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const b = types.builders;
const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let legacyStatements: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let legacyTypeDefs: any[] = [];
let targetNamespace = '';
function rewriteSource(source: string, fileName: string) {
const ast = parse(source);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const statements: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const typeDefs: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ast.program.body = ast.program.body.map((statement: any) => {
try {
switch (statement.type) {
case 'ExpressionStatement':
if (statement.expression.type === 'CallExpression') {
break;
}
// Remove Foo = Foo || {}
if (statement.expression.type === 'AssignmentExpression') {
if (statement.expression.left.name === targetNamespace) {
return b.emptyStatement();
}
}
// Remove typedefs of the type Foo.ThingName;
if (statement.expression.type === 'MemberExpression') {
// Keep going to the left of namespaces to check the leftmost,
// and if it is the target namespace, pull it.
let current = statement.expression.object;
while (current.object) {
current = current.object;
}
if (current.name === targetNamespace) {
typeDefs.push(statement);
return b.emptyStatement();
}
}
if (statement.expression.left.type === 'MemberExpression') {
// Remove self.Foo = self.Foo || {}
if (statement.expression.left.object.name === 'self') {
// Make sure to only grab the statement if it follows the self.X = self.X || {} pattern.
if (statement.expression.right.type !== 'LogicalExpression' || statement.expression.right.operator !== '||') {
break;
}
// Grab the namespace from the RHS of self.[Namespace]
if (statement.expression.right.type === 'LogicalExpression') {
targetNamespace = statement.expression.right.left.property.name;
}
return b.emptyStatement();
}
// Keep going to the left of namespaces to check the leftmost,
// and if it is the target namespace, pull it.
let current = statement.expression.left.object;
while (current.object) {
current = current.object;
}
if (current.name === targetNamespace) {
statements.push(statement);
return b.emptyStatement();
}
}
break;
}
} catch (e) {
console.warn(`Unexpected expression in ${fileName}:`);
console.warn(print(statement).code);
console.log(statement);
process.exit(1);
}
return statement;
});
// Rewrite legacy RHS to use module name.
const remappedStatements = statements.map(statement => {
if (statement.expression.type === 'AssignmentExpression') {
const { name } = statement.expression.right;
const innerNamespace = capitalizeFirstLetter(fileName).replace(/.js$/, '');
statement.expression.right.name = `${targetNamespace}Module.${innerNamespace}.${name}`;
}
return statement;
});
legacyStatements = [...legacyStatements, ...remappedStatements];
legacyTypeDefs = [...legacyTypeDefs, ...typeDefs];
return print(ast).code;
}
function createLegacy() {
const ast = parse('');
ast.program.body = legacyStatements.concat(legacyTypeDefs);
return print(ast).code;
}
function capitalizeFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
async function main(folder: string) {
const pathName = path.join(FRONT_END_FOLDER, folder);
const srcDir = await readDir(pathName);
for (const srcFile of srcDir) {
if (srcFile !== 'ARIAUtils.js' && !srcFile.endsWith('.js')) {
continue;
}
const filePath = path.join(pathName, srcFile);
const srcFileContents = await readFile(filePath, { encoding: 'utf-8' });
const dstFileContents = rewriteSource(srcFileContents, srcFile);
await writeFile(filePath, dstFileContents);
}
// Create a foo-legacy.js file
const dstLegacy = path.join(pathName, `${folder}-legacy.js`);
const legacyContents = `// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as ${targetNamespace}Module from './${folder}.js';
self.${targetNamespace} = self.${targetNamespace} || {};
${targetNamespace} = ${targetNamespace} || {};
${createLegacy()}
`;
await writeFile(dstLegacy, legacyContents);
}
if (!process.argv[2]) {
console.error('No arguments specified. Run this script with "<folder-name>". For example: "ui"');
process.exit(1);
}
main(process.argv[2]);