Skip to content

Instantly share code, notes, and snippets.

@ForbesLindesay
Last active January 9, 2017 02:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ForbesLindesay/778493a4b4ab796906db6ef06125a9a5 to your computer and use it in GitHub Desktop.
Save ForbesLindesay/778493a4b4ab796906db6ef06125a9a5 to your computer and use it in GitHub Desktop.
// Press ctrl+space for code completion
export default function transformer(file, api) {
const j = api.jscodeshift;
let source = file.source;
let root = j(source);
// remove "use strict"
root.find(j.ExpressionStatement).filter(path => (
path.value.expression.type === 'Literal' &&
path.value.expression.value === 'use strict'
)).forEach(path => j(path).remove());
// remove calls to `type` function
root.find(j.CallExpression).filter(path => (
(
path.value.callee.type === 'Identifier' &&
path.value.callee.name === 'type'
) ||
(
path.value.callee.type === 'MemberExpression' &&
path.value.callee.object.type === 'Identifier' &&
path.value.callee.object.name === 'util' &&
path.value.callee.property.name === 'inherits'
)
)).forEach(path => j(path).remove());
// remove _ before _driver and _requestJSON
root.find(j.MemberExpression).filter(path => (
path.value.property.type === 'Identifier' &&
(
path.value.property.name === '_driver' ||
path.value.property.name === '_requestJSON'
)
)).forEach(path => {
path.node.property.name = path.node.property.name.replace(/^_/, '');
});
function getType(str) {
if (str === '*') {
return j.anyTypeAnnotation();
}
if (str === 'void') {
return j.voidTypeAnnotation();
}
if (str === 'String') {
return j.stringTypeAnnotation();
}
if (str === 'boolean') {
return j.booleanTypeAnnotation();
}
if (str === 'number') {
return j.numericTypeAnnotation();
}
if (/^Array\.<.*>$/.test(str)) {
return j.genericTypeAnnotation(
j.identifier('Array'),
j.typeParameterInstantiation(
[getType(/^Array\.<(.*)>$/.exec(str)[1])],
),
);
}
if (str === 'Array') {
return j.genericTypeAnnotation(
j.identifier('Array'),
j.typeParameterInstantiation(
[getType('*')],
),
);
}
if (str.indexOf('|') !== -1) {
return j.unionTypeAnnotation(str.split('|').map(s => getType(s.trim())));
}
console.log(str);
return j.genericTypeAnnotation(
j.identifier(str),
null,
);
}
function convertFn(path, value) {
const comment = path.value.comments[path.value.comments.length - 1];
const returnType = /@return *\{([^\}]*)\} *[^\{]*(\{.*\})?/.exec(comment.value);
let returnTypeAnnotation = getType(
returnType
? returnType[1].toLowerCase() === 'object' && returnType[2]
? returnType[2]
: returnType[1]
: 'void'
);
const isVoid = !returnType;
const paramTypes = {};
const optionalParams = {};
const defaultValues = {};
comment.value.replace(/@param *{(.*)} *(\[?)([a-zA-Z0-9]*)(?: *\= *(.*)\])?/g, (_, type, isOptional, name, defaultValue) => {
paramTypes[name] = getType(type);
if (isOptional && !defaultValue) {
optionalParams[name] = true;
}
if (defaultValue) {
if (/^[a-z]+\.[a-z]+$/.test(defaultValue)) {
defaultValues[name] = j.identifier(defaultValue);
} else {
const value = eval(defaultValue);
if (typeof value === 'string') {
defaultValues[name] = j.stringLiteral(value);
} else if (typeof value === 'number') {
defaultValues[name] = j.numericLiteral(value);
} else if (typeof value === 'boolean') {
defaultValues[name] = j.booleanLiteral(value);
} else {
throw new Error('Unexpected default value type: ' + defaultValue);
}
}
}
});
value.params = value.params.map(param => {
if (paramTypes[param.name]) {
param.typeAnnotation = j.typeAnnotation(paramTypes[param.name]);
} else {
console.dir(param);
console.dir(paramTypes);
param.typeAnnotation = j.typeAnnotation(j.genericTypeAnnotation(
j.identifier('$FlowFixMe'),
null,
));
}
if (optionalParams[param.name] === true) {
param.name += '?';
//param.optional = true;
}
if (defaultValues[param.name]) {
return j.assignmentPattern(param, defaultValues[param.name]);
} else {
return param;
}
});
const isAsync = j(path).find(j.CallExpression).size();
if (isAsync) {
value.async = true;
returnTypeAnnotation = j.genericTypeAnnotation(
j.identifier('Promise'),
j.typeParameterInstantiation([returnTypeAnnotation]),
);
}
value.returnType = j.typeAnnotation(returnTypeAnnotation);
comment.value = comment.value
.replace(/\n *\* *@return *\{(.*)\}/, '')
.replace(/\n *\* *@param *{(.*)} *(\[?)([a-zA-Z0-9]*)(?: *\= *(.*)\])?/g, '')
.replace(/\n *\* *@method .*/g, '')
.replace(/\n *\* *Direct-access. No need to wait./g, '')
.replace(/^ */gm, ' ')
.replace(/\*\s*$/, '')
.trim() + '\n ';
if (isVoid) {
value.body.body = value.body.body.map(statement => {
if (statement && statement.type === 'ReturnStatement') {
return j.expressionStatement(statement.argument);
}
return statement;
});
}
}
// Convert when to `await`
let updated = true;
while (updated) {
updated = false;
root.find(j.ReturnStatement).filter(path => (
path.value.argument.type === 'CallExpression' &&
path.value.argument.callee.type === 'Identifier' &&
path.value.argument.callee.name === 'when'
)).forEach(path => {
if (path.value.argument.arguments.length === 2) {
const value = path.value.argument.arguments[0];
let handler = path.value.argument.arguments[1];
if (
handler.type === 'CallExpression' &&
handler.arguments.length === 1 &&
handler.arguments[0].type === 'ThisExpression' &&
handler.callee.type === 'MemberExpression' &&
handler.callee.property.name === 'bind'
) {
handler = handler.callee.object;
}
if (handler.type !== 'FunctionExpression') {
return;
}
let definition;
if (handler.params.length) {
const name = handler.params[0];
definition = j.variableDeclaration('const', [
j.variableDeclarator(name, j.awaitExpression(value)),
]);
} else {
definition = j.expressionStatement(j.awaitExpression(value));
}
updated = true;
j(path).replaceWith([definition].concat(handler.body.body));
}
});
}
// convert MethodDefinition
root.find(j.MethodDefinition).filter(path => (
path.value.comments && path.value.comments.length > 0
)).forEach(path => {
convertFn(path, path.value.value);
});
// convert FunctionDeclaration
root.find(j.FunctionDeclaration).filter(path => (
path.value.comments && path.value.comments.length > 0
)).forEach(path => {
convertFn(path, path.value);
});
// convert to import statements
root.find(j.VariableDeclaration).filter(path => (
path.value.declarations.length === 1 &&
path.value.declarations[0].init &&
path.value.declarations[0].init.type === 'CallExpression' &&
path.value.declarations[0].init.callee.type === 'Identifier' &&
path.value.declarations[0].init.callee.name === 'require'
)).forEach(path => {
const declaration = path.value.declarations[0];
j(path).replaceWith(
j.importDeclaration(
[j.importDefaultSpecifier(declaration.id)],
j.stringLiteral(declaration.init.arguments[0].value),
),
);
});
// gather enums
const enums = {};
root.find(j.ExpressionStatement).filter(path => (
path.value.expression.type === 'AssignmentExpression' &&
path.value.expression.left.type === 'MemberExpression' &&
path.value.expression.left.object.type === 'Identifier' &&
path.value.expression.operator === '=' &&
path.value.expression.right.type === 'Literal'
)).forEach(path => {
const expression = path.value.expression;
const enumName = expression.left.object.name;
const valueName = expression.left.property;
const value = expression.right;
const prop = j.property('init', valueName, value);
prop.comments = path.value.comments;
enums[enumName] = enums[enumName] || [];
enums[enumName].push(prop);
j(path).remove();
});
// output enums
const body = root.get().value.program.body;
Object.keys(enums).forEach(key => {
const init = j.objectExpression(enums[key]);
body.push(
j.variableDeclaration(
'const',
[j.variableDeclarator(j.identifier(key + 'Enum'), init)],
),
);
});
// add await to this.requestJSON
root.find(j.CallExpression).filter(path => (
path.value.callee.type === 'MemberExpression' &&
path.value.callee.property.name === 'requestJSON' &&
path.parentPath.value.type !== 'AwaitExpression'
)).forEach(path => {
j(path).replaceWith(j.awaitExpression(path.value));
});
source = root.toSource();
return source;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment