transform.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. function functionArgsFilter(j, path) {
  2. if (path.get('params') && path.get('params').value[0]) {
  3. return ['err', 'error'].includes(path.get('params').value[0].name)
  4. } else {
  5. return false
  6. }
  7. }
  8. function isReturningFunctionCallWithError(path, errorVarName) {
  9. return (
  10. path.value.argument &&
  11. path.value.argument.arguments &&
  12. path.value.argument.arguments[0] &&
  13. path.value.argument.arguments[0].name === errorVarName
  14. )
  15. }
  16. function expressionIsLoggingError(path) {
  17. return ['warn', 'error', 'err'].includes(
  18. path.get('callee').get('property').value.name
  19. )
  20. }
  21. function createTagErrorExpression(j, path, errorVarName) {
  22. let message = 'error'
  23. if (path.value.arguments.length >= 2) {
  24. message = path.value.arguments[1].value || message
  25. }
  26. let info
  27. try {
  28. info = j.objectExpression(
  29. // add properties from original logger info object to the
  30. // OError info object, filtering out the err object itself,
  31. // which is typically one of the args when doing intermediate
  32. // error logging
  33. // TODO: this can fail when the property name does not match
  34. // the variable name. e.g. { err: error } so need to check
  35. // both in the filter
  36. path
  37. .get('arguments')
  38. .value[0].properties.filter(
  39. property => property.key.name !== errorVarName
  40. )
  41. )
  42. } catch (error) {
  43. // if info retrieval fails it remains empty
  44. }
  45. const args = [j.identifier(errorVarName), j.literal(message)]
  46. if (info) {
  47. args.push(info)
  48. }
  49. return j.callExpression(
  50. j.memberExpression(j.identifier('OError'), j.identifier('tag')),
  51. args
  52. )
  53. }
  54. function functionBodyProcessor(j, path) {
  55. // the error variable should be the first parameter to the function
  56. const errorVarName = path.get('params').value[0].name
  57. j(path)
  58. .find(j.IfStatement) // look for if statements
  59. .filter(path =>
  60. j(path)
  61. // find returns inside the if statement where the error from
  62. // the args is explicitly returned
  63. .find(j.ReturnStatement)
  64. .some(path => isReturningFunctionCallWithError(path, errorVarName))
  65. )
  66. .forEach(path => {
  67. j(path)
  68. .find(j.CallExpression, {
  69. callee: {
  70. object: { name: 'logger' },
  71. },
  72. })
  73. .filter(path => expressionIsLoggingError(path))
  74. .replaceWith(path => {
  75. return createTagErrorExpression(j, path, errorVarName)
  76. })
  77. })
  78. }
  79. export default function transformer(file, api) {
  80. const j = api.jscodeshift
  81. let source = file.source
  82. // apply transformer to declared functions
  83. source = j(source)
  84. .find(j.FunctionDeclaration)
  85. .filter(path => functionArgsFilter(j, path))
  86. .forEach(path => functionBodyProcessor(j, path))
  87. .toSource()
  88. // apply transformer to inline-functions
  89. source = j(source)
  90. .find(j.FunctionExpression)
  91. .filter(path => functionArgsFilter(j, path))
  92. .forEach(path => functionBodyProcessor(j, path))
  93. .toSource()
  94. // apply transformer to inline-arrow-functions
  95. source = j(source)
  96. .find(j.ArrowFunctionExpression)
  97. .filter(path => functionArgsFilter(j, path))
  98. .forEach(path => functionBodyProcessor(j, path))
  99. .toSource()
  100. // do a plain text search to see if OError is used but not imported
  101. if (source.includes('OError') && !source.includes('@overleaf/o-error')) {
  102. const root = j(source)
  103. // assume the first variable declaration is an import
  104. // TODO: this should check that there is actually a require/import here
  105. // but in most cases it will be
  106. const imports = root.find(j.VariableDeclaration)
  107. const importOError = "const OError = require('@overleaf/o-error')\n"
  108. // if there were imports insert into list, format can re-order
  109. if (imports.length) {
  110. j(imports.at(0).get()).insertAfter(importOError)
  111. }
  112. // otherwise insert at beginning
  113. else {
  114. root.get().node.program.body.unshift(importOError)
  115. }
  116. source = root.toSource()
  117. }
  118. return source
  119. }