Skip to content

Instantly share code, notes, and snippets.

@timofei-iatsenko
Last active August 6, 2017 16:33
Show Gist options
  • Save timofei-iatsenko/d49023d8de64eb8404d489d0219a7fac to your computer and use it in GitHub Desktop.
Save timofei-iatsenko/d49023d8de64eb8404d489d0219a7fac to your computer and use it in GitHub Desktop.
Codemod: Upgrade old Angular services to ES6 classes
'use strict';
module.exports = function (file, api) {
const j = api.jscodeshift;
const source = j(file.source);
const body = source.find(j.Program).get('body');
delete body.value[0].comments;
let jobDone = false;
const factoryCall = source.find(j.CallExpression, (node) => (
node.callee.object &&
node.callee.object.name === 'module' &&
node.callee.property.name === 'factory'
)).forEach((factoryCallAst) => {
const factory = source.find(j.FunctionDeclaration,
(node) => node.id.name === factoryCallAst.value.arguments[1].name)
.paths()[0];
const returnStatement = factory.get('body', 'body')
.filter((path) => path.value.type === 'ReturnStatement')[0];
if (!returnStatement ||
!['ObjectExpression', 'Identifier'].includes(returnStatement.value.argument.type)) {
return;
}
let statement = returnStatement.get('argument');
let properties = statement.value;
if (statement.value.type === 'Identifier') {
statement = getIdentifierValue(j, source, statement.value.name);
properties = statement.value.init;
j(factory).findVariableDeclarators(statement.value.id.name).renameTo('this');
j(returnStatement).remove();
}
const className = getClassNameFromJsDoc(statement.parent.value);
if (!className) {
return;
}
if (statement.value.type === 'Identifier') {
console.log('Some unsafe changes was performed on this file. Please review it carefully!', file.path);
}
const members = getServiceMembers(j, source, properties);
j(statement.parent).replaceWith(withComments(createClass(className, j, members), statement.parent.value));
j(statement.parent).insertAfter(j.returnStatement(j.newExpression(j.identifier(className), [])));
jobDone = true;
});
if (factoryCall.length === 0 || !jobDone) {
return null;
}
return source.toSource({
arrowParensAlways: true,
quote: 'single',
trailingComma: {
objects: false,
arrays: false,
parameters: false,
},
}).replace('@constructor', '');
};
function getClassNameFromJsDoc(statement) {
if (!statement.comments || !statement.comments.length) {
throw new Error('No JsDoc comment found');
}
let className = null;
statement.comments.forEach((comment) => {
if (comment.type === 'CommentBlock') {
const matches = comment.value.match(/\@name (\w+)/i);
if (matches && matches.length === 2) {
className = matches[1];
return true;
}
}
});
statement.comments.pop(); // remove jsdoc comment
if (!className) {
throw new Error('Failed to parse JsDoc comment, @name directive not found');
}
return className;
}
function getServiceMembers(j, source, objectAst) {
return objectAst.properties.map((prop) => {
if (prop.value.type === 'Identifier') {
const idVal = getIdentifierValue(j, source, prop.value.name);
if (idVal.value.type === 'FunctionDeclaration' && idVal.parent.value.type === 'BlockStatement') {
const func = j.functionExpression(null, idVal.value.params, idVal.value.body);
const method = withComments(j.methodDefinition('method', prop.key, func), idVal.value);
j(idVal).remove();
return method;
}
}
return createClassMember(j, prop);
});
}
function getIdentifierValue(j, source, identifier) {
const func = source.find(j.FunctionDeclaration, (n) => n.id.name === identifier);
if (func.length) {
return func.paths()[0];
}
const varDecl = source.find(j.VariableDeclarator, (n) => n.id.name === identifier);
if (varDecl.length) {
return varDecl.paths()[0];
}
}
function createClass(name, j, members) {
const bodyDef = j.classBody(members.filter((f) => f));
return j.classDeclaration(j.identifier(name), bodyDef);
}
function createClassMember(j, old) {
if (old.kind === 'init' && old.value.type === 'FunctionExpression') {
return withComments(j.methodDefinition('method', old.key, old.value), old);
}
if (old.kind === 'init') {
return withComments(j.classProperty(old.key, old.value, null), old);
}
return withComments(j.methodDefinition(old.kind, old.key, old.value), old);
}
function withComments(to, from) {
to.comments = from.comments;
return to;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment