|
|
@@ -0,0 +1,135 @@
|
|
|
+function functionArgsFilter(j, path) {
|
|
|
+ return ['err', 'error'].includes(path.get('params').value[0].name)
|
|
|
+}
|
|
|
+
|
|
|
+function functionBodyProcessor(j, path) {
|
|
|
+ // the error variable should be the first parameter to the function
|
|
|
+ const errorVarName = path.get('params').value[0].name
|
|
|
+ j(path)
|
|
|
+ // look for if statements
|
|
|
+ .find(j.IfStatement)
|
|
|
+ .filter(path => {
|
|
|
+ let hasReturnError = false
|
|
|
+ j(path)
|
|
|
+ // find returns inside the if statement where the error from
|
|
|
+ // the args is explicitly returned
|
|
|
+ .find(j.ReturnStatement)
|
|
|
+ .forEach(
|
|
|
+ path =>
|
|
|
+ (hasReturnError =
|
|
|
+ path.value.argument.arguments[0].name === errorVarName)
|
|
|
+ )
|
|
|
+ return hasReturnError
|
|
|
+ })
|
|
|
+ .forEach(path => {
|
|
|
+ j(path)
|
|
|
+ // within the selected if blocks find calls to logger
|
|
|
+ .find(j.CallExpression, {
|
|
|
+ callee: {
|
|
|
+ object: { name: 'logger' }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // handle logger.warn, logger.error and logger.err
|
|
|
+ .filter(path =>
|
|
|
+ ['warn', 'error', 'err'].includes(
|
|
|
+ path.get('callee').get('property').value.name
|
|
|
+ )
|
|
|
+ )
|
|
|
+ // replace the logger call with the constructed OError wrapper
|
|
|
+ .replaceWith(path => {
|
|
|
+ // extract the error message which is the second arg for logger
|
|
|
+ const message =
|
|
|
+ path.value.arguments.length >= 2
|
|
|
+ ? path.value.arguments[1].value
|
|
|
+ : 'Error'
|
|
|
+ // create: err = new OError(...)
|
|
|
+ return j.assignmentExpression(
|
|
|
+ '=',
|
|
|
+ // assign over the existing error var
|
|
|
+ j.identifier(errorVarName),
|
|
|
+ j.callExpression(
|
|
|
+ j.memberExpression(
|
|
|
+ // create: new OError
|
|
|
+ j.newExpression(j.identifier('OError'), [
|
|
|
+ // create: { ... } args for new OError()
|
|
|
+ j.objectExpression([
|
|
|
+ // set message property with original error message
|
|
|
+ j.property(
|
|
|
+ 'init',
|
|
|
+ j.identifier('message'),
|
|
|
+ j.literal(message)
|
|
|
+ ),
|
|
|
+ j.property(
|
|
|
+ 'init',
|
|
|
+ // set info property with object { info: {} }
|
|
|
+ j.identifier('info'),
|
|
|
+ j.objectExpression(
|
|
|
+ // add properties from original logger info object to the
|
|
|
+ // OError info object, filtering out the err object itself,
|
|
|
+ // which is typically one of the args when doing intermediate
|
|
|
+ // error logging
|
|
|
+ // TODO: this can fail when the property name does not match
|
|
|
+ // the variable name. e.g. { err: error } so need to check
|
|
|
+ // both in the filter
|
|
|
+ path
|
|
|
+ .get('arguments')
|
|
|
+ .value[0].properties.filter(
|
|
|
+ property => property.key.name !== errorVarName
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ // add: .withCause( ) to OError
|
|
|
+ j.identifier('withCause')
|
|
|
+ ),
|
|
|
+ // add original error var as argument: .withCause(err)
|
|
|
+ [j.identifier(errorVarName)]
|
|
|
+ )
|
|
|
+ )
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export default function transformer(file, api) {
|
|
|
+ const j = api.jscodeshift
|
|
|
+ let source = file.source
|
|
|
+ // apply transformer to declared functions
|
|
|
+ source = j(source)
|
|
|
+ .find(j.FunctionDeclaration)
|
|
|
+ .filter(path => functionArgsFilter(j, path))
|
|
|
+ .forEach(path => functionBodyProcessor(j, path))
|
|
|
+ .toSource()
|
|
|
+ // apply transformer to inline-functions
|
|
|
+ source = j(source)
|
|
|
+ .find(j.FunctionExpression)
|
|
|
+ .filter(path => functionArgsFilter(j, path))
|
|
|
+ .forEach(path => functionBodyProcessor(j, path))
|
|
|
+ .toSource()
|
|
|
+ // apply transformer to inline-arrow-functions
|
|
|
+ source = j(source)
|
|
|
+ .find(j.ArrowFunctionExpression)
|
|
|
+ .filter(path => functionArgsFilter(j, path))
|
|
|
+ .forEach(path => functionBodyProcessor(j, path))
|
|
|
+ .toSource()
|
|
|
+ // do a plain text search to see if OError is used but not imported
|
|
|
+ if (source.includes('OError') && !source.includes('@overleaf/o-error')) {
|
|
|
+ const root = j(source)
|
|
|
+ // assume the first variable declaration is an import
|
|
|
+ // TODO: this should check that there is actually a require/import here
|
|
|
+ // but in most cases it will be
|
|
|
+ const imports = root.find(j.VariableDeclaration)
|
|
|
+ const importOError = "const OError = require('@overleaf/o-error')\n"
|
|
|
+ // if there were imports insert into list, format can re-order
|
|
|
+ if (imports.length) {
|
|
|
+ j(imports.at(0).get()).insertAfter(importOError)
|
|
|
+ }
|
|
|
+ // otherwise insert at beginning
|
|
|
+ else {
|
|
|
+ root.get().node.program.body.unshift(importOError)
|
|
|
+ }
|
|
|
+ source = root.toSource()
|
|
|
+ }
|
|
|
+
|
|
|
+ return source
|
|
|
+}
|