PHP WebShell
Текущая директория: /opt/BitGoJS/scripts/transform
Просмотр файла: bluebird.js
/**
* jscodeshift compatible transform to convert legacy bluebird coroutines/yield syntax
* into equivalent async/await syntax.
*
* Currently handled cases:
* * coroutine assignments to const vars
* * coroutine assignments to member expressions
* * yield keywords in coroutine bodies
* * redundant imports of `coroutine` from bluebird
* * Bluebird.delay() to native Promise with setTimeout
* * import * as Promise from 'bluebird'
* * import Bluebird from 'bluebird'
* * Bluebird.resolve() to Promise.resolve()
* * Bluebird.reject() to Promise.reject()
* * .nodeify(callback) and .asCallback(callback) to .then(callback).catch(callback)
* * Bluebird.all() to Promise.all()
* * Bluebird<T> type references to Promise<T>
* * Bluebird.coroutine(function* () {}) to async function() {}
*
* Opportunities for improvement:
* * co() IIFE's inside class members
*/
module.exports = function (fileInfo, api) {
const j = api.jscodeshift;
const root = j(fileInfo.source);
function filterCoroutines(path) {
const args = path.value.arguments;
if (args.length !== 1) {
return false;
}
const arg = args[0];
return arg.type === 'FunctionExpression' && arg.generator;
}
function replaceCoroutines(coroutine) {
const { body, params, id = null } = coroutine.value.arguments[0];
// build a new async function to replace the coroutine
const asyncFn = j.functionExpression(id, params, body);
asyncFn.async = true;
return asyncFn;
}
function filterBluebirdCoroutines(path) {
// Check if it's a call to Bluebird.coroutine
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { object, property } = path.value.callee;
if (object.name !== 'Bluebird' || property.name !== 'coroutine') {
return false;
}
const args = path.value.arguments;
if (args.length !== 1) {
return false;
}
const arg = args[0];
return arg.type === 'FunctionExpression' && arg.generator;
}
function replaceBluebirdCoroutines(coroutine) {
const { body, params, id = null } = coroutine.value.arguments[0];
// build a new async function to replace the coroutine
const asyncFn = j.functionExpression(id, params, body);
asyncFn.async = true;
return asyncFn;
}
function filterConstVarAsyncFunctions(path) {
if (path.value.declarations.length !== 1) {
return false;
}
const [decl] = path.value.declarations;
if (decl.init === null) {
return false;
}
// only async functions
if (decl.init.type !== 'FunctionExpression' || !decl.init.async) {
return false;
}
if (!decl.init.id) {
return true;
}
// check for co prefix on initializer function, or no name at all
return decl.init.id.name.toLowerCase() === `co${decl.id.name}`.toLowerCase();
}
function replaceConstVarAsyncFunctions(varDecl) {
// variable declaration identifier becomes async function identifier
const asyncFnName = varDecl.value.declarations[0].id.name;
const { body, params } = varDecl.value.declarations[0].init;
const replacementFn = j.functionDeclaration(j.identifier(asyncFnName), params, body);
replacementFn.async = true;
replacementFn.comments = varDecl.value.comments;
return replacementFn;
}
function filterMemberAssignments(path) {
const { left, right } = path.value;
// assignment lhs must be a member expression
if (left.type !== 'MemberExpression') {
return false;
}
// assignment rhs must be a named async function expression
if (right.type !== 'FunctionExpression' || !right.async || !right.id) {
return false;
}
// rhs function name must be `coSomeFn` if lhs member name is `someFn`
return `co${left.property.name.toLowerCase()}` === right.id.name.toLowerCase();
}
function replaceMemberAssignments(assignment) {
const { right } = assignment.value;
const replacementFn = j.functionDeclaration(null, right.params, right.body);
replacementFn.async = true;
replacementFn.comments = right.comments;
return {
...assignment.value,
right: replacementFn,
};
}
function removeImports(bbImport) {
const newSpecifiers = bbImport.value.specifiers.filter(({ local, imported }) => {
return !(local.name === 'co' && imported.name === 'coroutine');
});
if (newSpecifiers.length) {
bbImport.value.specifiers = newSpecifiers;
return bbImport.value;
}
}
function filterBluebirdDelay(path) {
// Check if it's a call to Bluebird.delay
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { object, property } = path.value.callee;
return (object.name === 'Bluebird' || object.name === 'Promise') && property.name === 'delay';
}
function replaceBluebirdDelay(path) {
// Get the delay time argument
const delayTime = path.value.arguments[0];
// Create a new Promise with setTimeout
return j.newExpression(j.identifier('Promise'), [
j.arrowFunctionExpression(
[j.identifier('resolve')],
j.callExpression(j.identifier('setTimeout'), [j.identifier('resolve'), delayTime])
),
]);
}
function filterBluebirdResolve(path) {
// Check if it's a call to Bluebird.resolve
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { object, property } = path.value.callee;
return object.name === 'Bluebird' && property.name === 'resolve';
}
function replaceBluebirdResolve(path) {
// Replace Bluebird.resolve with Promise.resolve
return j.callExpression(j.memberExpression(j.identifier('Promise'), j.identifier('resolve')), path.value.arguments);
}
function filterBluebirdReject(path) {
// Check if it's a call to Bluebird.reject
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { object, property } = path.value.callee;
return object.name === 'Bluebird' && property.name === 'reject';
}
function replaceBluebirdReject(path) {
// Replace Bluebird.reject with Promise.reject
return j.callExpression(j.memberExpression(j.identifier('Promise'), j.identifier('reject')), path.value.arguments);
}
function filterNodeifyOrAsCallback(path) {
// Check if it's a call to .nodeify() or .asCallback()
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { property } = path.value.callee;
return property.name === 'nodeify' || property.name === 'asCallback';
}
function replaceNodeifyOrAsCallback(path) {
// Get the callback argument
const callback = path.value.arguments[0];
// Get the object that nodeify/asCallback was called on
const promiseChain = path.value.callee.object;
// Replace .nodeify(callback) or .asCallback(callback) with .then(callback).catch(callback)
return j.callExpression(
j.memberExpression(
j.callExpression(j.memberExpression(promiseChain, j.identifier('then')), [callback]),
j.identifier('catch')
),
[callback]
);
}
function filterBluebirdAll(path) {
// Check if it's a call to Bluebird.all
if (path.value.callee.type !== 'MemberExpression') {
return false;
}
const { object, property } = path.value.callee;
return object.name === 'Bluebird' && property.name === 'all';
}
function replaceBluebirdAll(path) {
// Replace Bluebird.all with Promise.all
return j.callExpression(j.memberExpression(j.identifier('Promise'), j.identifier('all')), path.value.arguments);
}
function filterBluebirdTypeReferences(path) {
// Check if it's a TypeReference to Bluebird
if (path.value.typeName.type !== 'Identifier') {
return false;
}
return path.value.typeName.name === 'Bluebird';
}
function replaceBluebirdTypeReferences(path) {
// Replace Bluebird<T> with Promise<T>
return j.tsTypeReference(j.identifier('Promise'), path.value.typeParameters);
}
// replace coroutines with async functions
let didTransform = root
.find(j.CallExpression, { callee: { name: 'co' } })
.filter(filterCoroutines)
.replaceWith(replaceCoroutines)
.size();
// replace Bluebird.coroutine calls with async functions
didTransform += root
.find(j.CallExpression)
.filter(filterBluebirdCoroutines)
.replaceWith(replaceBluebirdCoroutines)
.size();
// replace appearances of `const someFn = async function(...) {` with `async function someFn(...) {`
didTransform += root
.find(j.VariableDeclaration)
.filter(filterConstVarAsyncFunctions)
.replaceWith(replaceConstVarAsyncFunctions)
.size();
// replace appearances of `someObj.someFn = async function coSomeFn(...)` with `someObj.someFn = async function(...) {`
didTransform += root
.find(j.AssignmentExpression)
.filter(filterMemberAssignments)
.replaceWith(replaceMemberAssignments)
.size();
// replace yield expressions with await expressions
didTransform += root
.find(j.YieldExpression)
.replaceWith((expr) => {
return j.awaitExpression(expr.value.argument);
})
.size();
// replace Bluebird.delay() with native Promise + setTimeout
didTransform += root.find(j.CallExpression).filter(filterBluebirdDelay).replaceWith(replaceBluebirdDelay).size();
// replace Bluebird.resolve() with Promise.resolve()
didTransform += root.find(j.CallExpression).filter(filterBluebirdResolve).replaceWith(replaceBluebirdResolve).size();
// replace Bluebird.reject() with Promise.reject()
didTransform += root.find(j.CallExpression).filter(filterBluebirdReject).replaceWith(replaceBluebirdReject).size();
// replace .nodeify(callback) and .asCallback(callback) with .then(callback).catch(callback)
didTransform += root
.find(j.CallExpression)
.filter(filterNodeifyOrAsCallback)
.replaceWith(replaceNodeifyOrAsCallback)
.size();
// replace Bluebird.all() with Promise.all()
didTransform += root.find(j.CallExpression).filter(filterBluebirdAll).replaceWith(replaceBluebirdAll).size();
// replace Bluebird<T> type references with Promise<T>
didTransform += root
.find(j.TSTypeReference)
.filter(filterBluebirdTypeReferences)
.replaceWith(replaceBluebirdTypeReferences)
.size();
// Fix: Instead of removing the import declaration entirely, replace it with an empty statement
// This preserves any comments attached to the import declaration
didTransform += root
.find(j.ImportDeclaration, { source: { value: 'bluebird' } })
.filter((path) => {
return path.value.specifiers.some(
(specifier) => specifier.type === 'ImportDefaultSpecifier' && specifier.local.name === 'Bluebird'
);
})
.replaceWith((path) => {
// Create an empty statement to preserve comments
const emptyStatement = j.emptyStatement();
emptyStatement.comments = path.value.comments;
return emptyStatement;
})
.size();
// remove coroutine imports and requires
didTransform += root
.find(j.ImportDeclaration, { source: { value: 'bluebird' } })
.replaceWith(removeImports)
.size();
// Fix: Instead of removing namespace imports, replace with empty statement to preserve comments
didTransform += root
.find(j.ImportDeclaration, { source: { value: 'bluebird' } })
.filter((path) => {
return path.value.specifiers.some((specifier) => specifier.type === 'ImportNamespaceSpecifier');
})
.replaceWith((path) => {
// Create an empty statement to preserve comments
const emptyStatement = j.emptyStatement();
emptyStatement.comments = path.value.comments;
return emptyStatement;
})
.size();
// remove declarations of co
didTransform += root
.find(j.VariableDeclaration)
.replaceWith((path) => {
const decls = path.value.declarations;
const newDecls = decls.filter((decl) => decl.id.name !== 'co');
if (newDecls.length) {
path.value.declarations = newDecls;
return path.value;
}
})
.size();
return didTransform ? root.toSource({ quote: 'single' }) : null;
};
module.exports.parser = 'ts';
Выполнить команду
Для локальной разработки. Не используйте в интернете!