mirror of
https://github.com/zotero/zotero.git
synced 2026-09-29 01:41:24 +00:00
This seems to produce decent output on many of our files, but it totally misses some import statements. Not sure why that is yet.
208 lines
5.3 KiB
JavaScript
Executable file
208 lines
5.3 KiB
JavaScript
Executable file
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// jscodeshift rule to replace EXPORTED_SYMBOLS with export declarations,
|
|
// and also convert existing ChromeUtils.importESModule to static import.
|
|
|
|
/* eslint-env node */
|
|
|
|
const _path = require("path");
|
|
const {
|
|
warnForPath,
|
|
getPrevStatement,
|
|
getNextStatement,
|
|
} = require(_path.resolve(__dirname, "./utils.js"));
|
|
const {
|
|
isImportESModuleCall,
|
|
replaceImportESModuleCall,
|
|
} = require(_path.resolve(__dirname, "./static-import.js"));
|
|
|
|
module.exports = function (fileInfo, api) {
|
|
const { jscodeshift } = api;
|
|
const root = jscodeshift(fileInfo.source);
|
|
doTranslate(fileInfo.path, jscodeshift, root);
|
|
return root.toSource({ lineTerminator: "\n" });
|
|
};
|
|
|
|
module.exports.doTranslate = doTranslate;
|
|
|
|
// Move the comment for `path.node` to adjacent statement, keeping the position
|
|
// as much as possible.
|
|
function moveComments(inputFile, path) {
|
|
const next = getNextStatement(path);
|
|
if (next) {
|
|
if (next.comments) {
|
|
next.comments = [...path.node.comments, ...next.comments];
|
|
} else {
|
|
next.comments = path.node.comments;
|
|
}
|
|
path.node.comments = [];
|
|
|
|
return;
|
|
}
|
|
|
|
const prev = getPrevStatement(path);
|
|
if (prev) {
|
|
path.node.comments.forEach(c => {
|
|
c.leading = false;
|
|
c.trailing = true;
|
|
});
|
|
|
|
if (prev.comments) {
|
|
prev.comments = [...prev.comments, ...path.node.comments];
|
|
} else {
|
|
prev.comments = path.node.comments;
|
|
}
|
|
path.node.comments = [];
|
|
|
|
return;
|
|
}
|
|
|
|
warnForPath(
|
|
inputFile,
|
|
path,
|
|
`EXPORTED_SYMBOLS has comments and it cannot be preserved`
|
|
);
|
|
}
|
|
|
|
function collectAndRemoveExportedSymbols(inputFile, root) {
|
|
const nodes = root.findVariableDeclarators("EXPORTED_SYMBOLS");
|
|
if (!nodes.length) {
|
|
throw Error(`EXPORTED_SYMBOLS not found`);
|
|
}
|
|
|
|
let path = nodes.get(0);
|
|
const obj = nodes.get(0).node.init;
|
|
if (!obj) {
|
|
throw Error(`EXPORTED_SYMBOLS is not statically known`);
|
|
}
|
|
|
|
if (path.parent.node.declarations.length !== 1) {
|
|
throw Error(`EXPORTED_SYMBOLS shouldn't be declared with other variables`);
|
|
}
|
|
|
|
if (path.parent.node.comments && path.parent.node.comments.length) {
|
|
moveComments(inputFile, path.parent);
|
|
}
|
|
|
|
path.parent.prune();
|
|
|
|
const EXPORTED_SYMBOLS = new Set();
|
|
if (obj.type !== "ArrayExpression") {
|
|
throw Error(`EXPORTED_SYMBOLS is not statically known`);
|
|
}
|
|
|
|
for (const elem of obj.elements) {
|
|
if (elem.type !== "Literal") {
|
|
throw Error(`EXPORTED_SYMBOLS is not statically known`);
|
|
}
|
|
var name = elem.value;
|
|
if (typeof name !== "string") {
|
|
throw Error(`EXPORTED_SYMBOLS item must be a string`);
|
|
}
|
|
EXPORTED_SYMBOLS.add(name);
|
|
}
|
|
|
|
return EXPORTED_SYMBOLS;
|
|
}
|
|
|
|
function isTopLevel(path) {
|
|
return path.parent.node.type === "Program";
|
|
}
|
|
|
|
function convertToExport(jscodeshift, path, name) {
|
|
const e = jscodeshift.exportNamedDeclaration(path.node);
|
|
e.comments = [];
|
|
e.comments = path.node.comments;
|
|
path.node.comments = [];
|
|
|
|
path.replace(e);
|
|
}
|
|
|
|
function doTranslate(inputFile, jscodeshift, root) {
|
|
const EXPORTED_SYMBOLS = collectAndRemoveExportedSymbols(inputFile, root);
|
|
|
|
root.find(jscodeshift.FunctionDeclaration).forEach(path => {
|
|
if (!isTopLevel(path)) {
|
|
return;
|
|
}
|
|
const name = path.node.id.name;
|
|
if (!EXPORTED_SYMBOLS.has(name)) {
|
|
return;
|
|
}
|
|
EXPORTED_SYMBOLS.delete(name);
|
|
convertToExport(jscodeshift, path, name);
|
|
});
|
|
|
|
root.find(jscodeshift.ClassDeclaration).forEach(path => {
|
|
if (!isTopLevel(path)) {
|
|
return;
|
|
}
|
|
const name = path.node.id.name;
|
|
if (!EXPORTED_SYMBOLS.has(name)) {
|
|
return;
|
|
}
|
|
EXPORTED_SYMBOLS.delete(name);
|
|
convertToExport(jscodeshift, path, name);
|
|
});
|
|
|
|
root.find(jscodeshift.VariableDeclaration).forEach(path => {
|
|
if (!isTopLevel(path)) {
|
|
return;
|
|
}
|
|
|
|
let exists = false;
|
|
let name;
|
|
for (const decl of path.node.declarations) {
|
|
if (decl.id.type === "Identifier") {
|
|
name = decl.id.name;
|
|
if (EXPORTED_SYMBOLS.has(name)) {
|
|
exists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (decl.id.type === "ObjectPattern") {
|
|
if (decl.id.properties.length === 1) {
|
|
const prop = decl.id.properties[0];
|
|
if (prop.shorthand) {
|
|
if (prop.key.type === "Identifier") {
|
|
name = prop.key.name;
|
|
if (EXPORTED_SYMBOLS.has(name)) {
|
|
exists = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!exists) {
|
|
return;
|
|
}
|
|
|
|
if (path.node.declarations.length !== 1) {
|
|
throw Error(
|
|
`exported variable shouldn't be declared with other variables`
|
|
);
|
|
}
|
|
|
|
EXPORTED_SYMBOLS.delete(name);
|
|
convertToExport(jscodeshift, path, name);
|
|
});
|
|
|
|
if (EXPORTED_SYMBOLS.size !== 0) {
|
|
throw Error(
|
|
`exported symbols ${[...EXPORTED_SYMBOLS].join(", ")} not found`
|
|
);
|
|
}
|
|
|
|
root.find(jscodeshift.CallExpression).forEach(path => {
|
|
if (isImportESModuleCall(path.node)) {
|
|
// This file is not yet renamed. Skip the extension check.
|
|
// Also skip the isTargetESM.
|
|
replaceImportESModuleCall(inputFile, jscodeshift, path, true);
|
|
}
|
|
});
|
|
}
|