blob: 802e57c23640b05c968895325b6d3f2108afa70e [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 { parse, print, types, visit } from 'recast';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { getMappings } from './get-mappings.js';
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
async function rewriteSource(pathName: string, srcFile: string, mappings:Map<string, any>, useExternalRefs = false) {
const filePath = path.join(pathName, srcFile);
const srcFileContents = await readFile(filePath, { encoding: 'utf-8' });
const ast = parse(srcFileContents);
const importsRequired = new Set<{file: string, replacement: string, sameFolderReplacement: string}>();
visit(ast, {
visitComment(path) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const comments = (path.node as any).comments;
for (const comment of comments) {
if (comment.loc) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(comment.loc as any).indent = 0;
}
for (const [str, value] of mappings.entries()) {
const containsString = new RegExp(`${str}([^\\.\\w])`, 'g');
const stringMatches = containsString.exec(comment.value);
if (!stringMatches) {
continue;
}
const replacement = useExternalRefs ? value.replacement : value.sameFolderReplacement;
importsRequired.add(value);
comment.value = comment.value.replace(stringMatches[0], replacement + stringMatches[0].slice(-1));
}
}
this.traverse(path);
},
visitMemberExpression(path) {
const node = path.node;
const nodeCopy = b.memberExpression.from({...node, comments: []});
const nodeAsCode = print(nodeCopy).code;
for (const [str, value] of mappings.entries()) {
if (nodeAsCode !== str) {
continue;
}
const name = useExternalRefs ? value.replacement : value.sameFolderReplacement;
importsRequired.add(value);
return b.identifier.from({ name, comments: node.comments || [] });
}
this.traverse(path);
},
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const importMap = new Map<string, any[]>();
for (const { file, sameFolderReplacement, replacement } of importsRequired) {
if (filePath === file) {
continue;
}
const src = path.dirname(filePath);
const dst = path.dirname(file);
let replacementIdentifier = '';
let relativePath = path.relative(src, dst);
const isSameFolder = relativePath === '';
if (isSameFolder) {
relativePath = './';
replacementIdentifier = sameFolderReplacement;
} else {
relativePath += '/';
replacementIdentifier = replacement;
}
const targetImportFile = relativePath + path.basename(file);
if (!importMap.has(targetImportFile)) {
importMap.set(targetImportFile, []);
}
const imports = importMap.get(targetImportFile);
if (!imports) {
throw new Error(`Expected to find imports for ${targetImportFile} but found none.`);
}
if (useExternalRefs) {
if (imports.length === 0) {
// We are creating statements like import * as Foo from '../foo/foo.js' so
// here we take the first part of the identifier, e.g. Foo.Bar.Bar gives us
// Foo so we can make import * as Foo from that.
const namespaceIdentifier = replacementIdentifier.split('.')[0];
imports.push(b.importNamespaceSpecifier(b.identifier(namespaceIdentifier)));
}
// Make sure there is only one import * from Foo import added.
continue;
}
imports.push(b.importSpecifier(b.identifier(replacementIdentifier)));
}
// Add missing imports.
for (const [targetImportFile, specifiers] of importMap) {
const newImport = b.importDeclaration.from({
specifiers,
comments: ast.program.body[0].comments,
source: b.literal(targetImportFile),
});
// Remove any file comments.
ast.program.body[0].comments = [];
// Add the import statements.
ast.program.body.unshift(newImport);
}
return print(ast).code;
}
async function main(folder: string, namespaces?: string[]) {
const pathName = path.join(FRONT_END_FOLDER, folder);
const srcDir = await readDir(pathName);
const useExternalRefs = namespaces !== undefined && (namespaces[0] !== folder);
let mappings = new Map();
if (namespaces && namespaces.length) {
for (const namespace of namespaces) {
mappings = await getMappings(namespace, mappings, useExternalRefs);
}
} else {
mappings = await getMappings(folder, mappings, useExternalRefs);
}
for (const srcFile of srcDir) {
if (srcFile === `${folder}.js` || srcFile === `${folder}-legacy.js` || !srcFile.endsWith('.js')) {
continue;
}
const distFileContents = await rewriteSource(pathName, srcFile, mappings, useExternalRefs);
await writeFile(path.join(pathName, `${srcFile}`), distFileContents);
}
}
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], process.argv[3] && process.argv[3].split(',') || undefined);