Last active
December 23, 2022 11:38
-
-
Save dagingaa/11ddf0853c41f9facb88 to your computer and use it in GitHub Desktop.
Codemod to transform well-written function prototype style classes into the new ES2015 syntax. Requires jscodeshift.
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
module.exports = (file, api, options) => { | |
const j = api.jscodeshift; | |
const root = j(file.source); | |
// We have to add "use strict" for node to play nice | |
// Taken from https://github.com/cpojer/js-codemod/blob/master/transforms/use-strict.js | |
const hasStrictMode = body => | |
body.some( | |
statement => j.match(statement, { | |
type: 'ExpressionStatement', | |
expression: { | |
type: 'Literal', | |
value: 'use strict', | |
}, | |
} | |
) | |
); | |
const withComments = (to, from) => { | |
to.comments = from.comments; | |
return to; | |
}; | |
const createUseStrictExpression = () => ( | |
j.expressionStatement( | |
j.literal('use strict') | |
) | |
); | |
// Convert prototype style function classes into new es6 syntax | |
const paths = {}; | |
const createMethodDefinition = (identifier, fn) => { | |
return j.methodDefinition( | |
"method", | |
identifier, | |
j.functionExpression( | |
null, fn.params, fn.body | |
) | |
) | |
}; | |
const hasModifiedFile = root | |
// Find all function declarations in the document | |
.find(j.FunctionDeclaration) | |
// Filter to only contain methods with names | |
.filter(path => ( | |
path.value.id && | |
path.value.id.type === "Identifier" | |
)) | |
// Only select methods that have upper case leading character | |
// This is usually a sign that this is a class | |
.filter(path => { | |
const char = path.value.id.name[0]; | |
return char === char.toUpperCase(); | |
}) | |
// For each identified function, | |
// transform into a class definition | |
// and transform the named function into the constructor | |
// and add it to our class definition paths | |
.forEach(path => { | |
const className = path.value.id.name; | |
paths[className] = paths[className] || {}; | |
j(path).replaceWith( | |
withComments(j.classDeclaration( | |
path.value.id, | |
j.classBody([ | |
createMethodDefinition( | |
j.identifier('constructor'), | |
path.value | |
) | |
]) | |
), path.value) | |
) | |
paths[className] = path; | |
}) | |
.size() > 0; | |
root | |
// Find all assignments in the document | |
.find(j.AssignmentExpression, { | |
left: { | |
// that assigns to a member | |
type: "MemberExpression" | |
} | |
}) | |
// right-hand assignment can be either a CallExpression or a FunctionExpression | |
.filter(path => ( | |
["CallExpression", "FunctionExpression"].includes(path.value.right.type) | |
)) | |
// Filter to only contain members that assign to the prototype | |
.filter(path => ( | |
path.value.left.object && | |
path.value.left.object.property && | |
path.value.left.object.property.name === "prototype" | |
)) | |
// For all identified assignments | |
// transform into a method on the relevant class definition | |
// and remove the function from the current path | |
.forEach(path => { | |
const className = path.value.left.object.object.name; | |
const classPath = paths[className]; | |
const classBody = classPath.value.body.body; | |
const classMethodName = path.value.left.property; | |
const classMethodFunction = path.value.right.type === "CallExpression" ? | |
path.value.right.callee.object | |
: path.value.right; | |
classBody.push( | |
withComments(createMethodDefinition( | |
classMethodName, | |
classMethodFunction | |
), path.parentPath.value) | |
); | |
j(path).remove(); | |
}) | |
if (hasModifiedFile) { | |
const body = root.get().value.program.body; | |
if (!body.length || hasStrictMode(body)) { | |
return null; | |
} | |
body.unshift(withComments(createUseStrictExpression(), body[0])); | |
body[0].comments = body[1].comments; | |
delete body[1].comments; | |
} | |
return root.toSource(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment