mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-02 18:11:04 +08:00
R=aerotwist@chromium.org, liviurau@chromium.org, machenbach@chromium.org Bug: 1018122 Change-Id: I7cc3a1a9ab01c649b3c7fe8ba5d5254adc631634 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/1880033 Commit-Queue: Yang Guo <yangguo@chromium.org> Reviewed-by: Liviu Rau <liviurau@chromium.org> Reviewed-by: Michael Achenbach <machenbach@chromium.org>
177 lines
7.2 KiB
TypeScript
177 lines
7.2 KiB
TypeScript
// 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 } from 'recast';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { promisify } from 'util';
|
|
import { IdentifierKind, MemberExpressionKind, ExpressionKind, CommentKind } from 'ast-types/gen/kinds';
|
|
|
|
const readFile = promisify(fs.readFile);
|
|
const writeFile = promisify(fs.writeFile);
|
|
|
|
const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end')
|
|
|
|
function capitalizeFirstLetter(string: string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
|
|
const b = types.builders;
|
|
|
|
function createReplacementDeclaration(propertyName: IdentifierKind, declaration: any): any {
|
|
// UI.ARIAUtils.Foo = class {} -> export class Foo {}
|
|
if (declaration.type === 'ClassExpression') {
|
|
return b.exportDeclaration(false, b.classDeclaration(propertyName, declaration.body));
|
|
}
|
|
// UI.ARIAUtils.Foo = expression; -> export const Foo = expression;
|
|
if (declaration.type === 'Literal' || declaration.type.endsWith('Expression')) {
|
|
return b.exportNamedDeclaration(b.variableDeclaration("const", [b.variableDeclarator(propertyName, declaration)]));
|
|
}
|
|
console.error(`Unable to refactor declaration of type "${declaration.type}" named "${propertyName.name}"`);
|
|
}
|
|
|
|
function getTopLevelMemberExpression(expression: MemberExpressionKind): any {
|
|
while (expression.object.type === 'MemberExpression') {
|
|
expression = expression.object;
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
function rewriteSource(source: string, refactoringNamespace: string, refactoringFileName: string) {
|
|
const exportedMembers: {prop: IdentifierKind, comments: CommentKind[]}[] = [];
|
|
const ast = parse(source);
|
|
|
|
ast.program.body = ast.program.body.map((expression: any) => {
|
|
// UI.ARIAUtils.Foo = 5;
|
|
if (expression.type === 'ExpressionStatement') {
|
|
// UI.ARIAUtils.Foo = 5
|
|
if (expression.expression.type === 'AssignmentExpression') {
|
|
const assignment = expression.expression;
|
|
|
|
// UI.ARIAUtils.Foo
|
|
if (assignment.left.type === 'MemberExpression') {
|
|
// UI.ARIAUtils.Foo -> UI.ARIAUtils
|
|
// UI.ARIAUtils.Nested.Foo -> UI.ARIAUtils
|
|
const topLevelAssignment = getTopLevelMemberExpression(assignment.left);
|
|
|
|
// If there is a nested export, such as `UI.ARIAUtils.Nested.Field`
|
|
if (topLevelAssignment !== assignment.left.object) {
|
|
// Exports itself. E.g. `UI.ARIAUtils = <...>`
|
|
if (assignment.left.object.name === refactoringNamespace && assignment.left.property.name === refactoringFileName) {
|
|
const {declaration} = createReplacementDeclaration(assignment.left.property, assignment.right);
|
|
// If it is a variable declaration, we need to use the actual variabledeclator. E.g.:
|
|
// UI.ARIAUtils = 5; -> export ARIAUtils = 5; instead of "export const ARIAUtils = 5;"
|
|
const declarationStatement = b.exportDefaultDeclaration(declaration.type === 'VariableDeclaration' ? declaration.declarations[0].init : declaration);
|
|
|
|
declarationStatement.comments = expression.comments;
|
|
|
|
return declarationStatement;
|
|
}
|
|
|
|
console.error(`Nested field "${assignment.left.property.name}" detected! Requires manual changes.`);
|
|
expression.comments = expression.comments || [];
|
|
expression.comments.push(b.commentLine(' TODO(http://crbug.com/1006759): Fix exported symbol'));
|
|
return expression;
|
|
}
|
|
|
|
const propertyName = assignment.left.property;
|
|
const {object, property} = topLevelAssignment;
|
|
|
|
|
|
if (object.type === 'Identifier' && property.type === 'Identifier') {
|
|
// UI
|
|
const namespace = object.name;
|
|
// ARIAUtils
|
|
const fileName = property.name;
|
|
|
|
if (namespace === refactoringNamespace && fileName === refactoringFileName) {
|
|
const declaration = createReplacementDeclaration(propertyName, assignment.right);
|
|
|
|
if (declaration) {
|
|
exportedMembers.push({prop: propertyName, comments: expression.comments || [b.commentLine(' TODO(http://crbug.com/1006759): Add type information if necessary')]});
|
|
declaration.comments = expression.comments;
|
|
return declaration;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return expression;
|
|
});
|
|
|
|
// self.UI = self.UI || {};
|
|
{
|
|
const legacyNamespaceName = b.memberExpression(b.identifier('self'), b.identifier(refactoringNamespace), false);
|
|
const legacyNamespaceOr = b.logicalExpression("||", legacyNamespaceName, b.objectExpression([]));
|
|
ast.program.body.push(b.expressionStatement.from({
|
|
expression: b.assignmentExpression('=', legacyNamespaceName, legacyNamespaceOr),
|
|
comments: [b.commentBlock(' Legacy exported object ', true, false)]
|
|
}));
|
|
}
|
|
|
|
// UI = UI || {};
|
|
const legacyNamespaceName = b.identifier(refactoringNamespace)
|
|
{
|
|
const legacyNamespaceOr = b.logicalExpression("||", legacyNamespaceName, b.objectExpression([]));
|
|
ast.program.body.push(b.expressionStatement.from({
|
|
expression: b.assignmentExpression('=', legacyNamespaceName, legacyNamespaceOr),
|
|
comments: [b.commentBlock(' Legacy exported object ', true, false)]
|
|
}));
|
|
}
|
|
|
|
// UI.ARIAUtils = ARIAUtils;
|
|
const legacyNamespaceExport = b.memberExpression(b.identifier(refactoringNamespace), b.identifier(refactoringFileName), false);
|
|
|
|
ast.program.body.push(b.expressionStatement(b.assignmentExpression.from({
|
|
operator: '=',
|
|
left: legacyNamespaceExport,
|
|
right: b.identifier(refactoringFileName),
|
|
comments: [b.commentLine(' TODO(http://crbug.com/1006759): Add type information if necessary')]
|
|
})));
|
|
|
|
// UI.ARIAUtils.Foo = Foo;
|
|
exportedMembers.forEach(({prop, comments}) => {
|
|
const legacyExportedProperty = b.memberExpression(legacyNamespaceExport, b.identifier(prop.name), false);
|
|
|
|
ast.program.body.push(b.expressionStatement(b.assignmentExpression.from({
|
|
operator: '=',
|
|
left: legacyExportedProperty,
|
|
right: b.identifier(prop.name),
|
|
comments,
|
|
})));
|
|
});
|
|
|
|
return print(ast).code;
|
|
}
|
|
|
|
const FOLDER_MAPPING: {[name: string]: string} = require(path.join('..', 'build', 'special_case_namespaces.json'));
|
|
|
|
function computeNamespaceName(folderName: string): string {
|
|
if (folderName in FOLDER_MAPPING) {
|
|
return FOLDER_MAPPING[folderName];
|
|
}
|
|
return capitalizeFirstLetter(folderName);
|
|
}
|
|
|
|
async function main(refactoringNamespace: string, refactoringFileName: string) {
|
|
const pathName = path.join(FRONT_END_FOLDER, refactoringNamespace, `${refactoringFileName}.js`);
|
|
const source = await readFile(pathName, {encoding: 'utf-8'});
|
|
|
|
const rewrittenSource = rewriteSource(source, computeNamespaceName(process.argv[2]), refactoringFileName);
|
|
|
|
await writeFile(pathName, rewrittenSource);
|
|
|
|
// console.log(`Succesfully written source to "${pathName}". Make sure that no other errors are reported before submitting!`);
|
|
}
|
|
|
|
if (!process.argv[2]) {
|
|
console.error(`No arguments specified. Run this script with "<folder-name> <filename>". For example: "common Color"`);
|
|
process.exit(1);
|
|
}
|
|
|
|
main(process.argv[2], process.argv[3]);
|