Last active
August 6, 2017 16:33
-
-
Save timofei-iatsenko/d49023d8de64eb8404d489d0219a7fac to your computer and use it in GitHub Desktop.
Codemod: Upgrade old Angular services to ES6 classes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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