| // 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 stat = promisify(fs.stat); |
| const b = types.builders; |
| |
| const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end') |
| |
| 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) { |
| const comments = (path.node as any).comments; |
| for (const comment of comments) { |
| |
| if (comment.loc) { |
| (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); |
| } |
| }); |
| |
| 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 (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, namespace?: string) { |
| const pathName = path.join(FRONT_END_FOLDER, folder); |
| const srcDir = await readDir(pathName); |
| const targetNamespace = namespace ? namespace : folder; |
| const useExternalRefs = namespace !== undefined && (namespace !== folder); |
| const mappings = await getMappings(targetNamespace, new Map(), 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]); |