Files
react-native-devtools-frontend/scripts/migration/replace-internal-references.ts
T
Jack Franklin fc495906c3 Enforce no TS non-null bang assertions
This CL bans the TS `!` operator which is used to assert not null.

Typically when we use this we are making errors harder to track down
should the value actually end up being null. Checking it takes more work
but will give nicer errors.

Fixed: 1154182
Change-Id: I11599541c199204e4ca21c9fd81a256614d70389
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2566804
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Paul Lewis <aerotwist@chromium.org>
2020-12-02 14:46:50 +00:00

169 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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);