| // 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]); |