no-throw-in-callback.js 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. const CALLBACK_PARAM_NAMES = new Set(['cb', 'callback', 'done', 'next'])
  2. function isCallbackParam(param) {
  3. return (
  4. param && param.type === 'Identifier' && CALLBACK_PARAM_NAMES.has(param.name)
  5. )
  6. }
  7. module.exports = {
  8. meta: {
  9. type: 'error',
  10. docs: {
  11. description: 'Disallow throw statements inside callback-based functions',
  12. },
  13. messages: {
  14. noThrowInCallback:
  15. 'Pass the error to the callback instead of throwing in callback-based code.',
  16. },
  17. },
  18. create(context) {
  19. // Stack tracks whether each enclosing function is a callback-style function.
  20. // A callback-style function is non-async and has a last param named cb/callback/done/next.
  21. const stack = []
  22. function enterFunction(node) {
  23. const params = node.params
  24. const isCallback =
  25. !node.async &&
  26. params.length > 0 &&
  27. isCallbackParam(params[params.length - 1])
  28. stack.push(isCallback)
  29. }
  30. function exitFunction() {
  31. stack.pop()
  32. }
  33. return {
  34. FunctionDeclaration: enterFunction,
  35. 'FunctionDeclaration:exit': exitFunction,
  36. FunctionExpression: enterFunction,
  37. 'FunctionExpression:exit': exitFunction,
  38. ArrowFunctionExpression: enterFunction,
  39. 'ArrowFunctionExpression:exit': exitFunction,
  40. ThrowStatement(node) {
  41. if (stack[stack.length - 1]) {
  42. context.report({ node, messageId: 'noThrowInCallback' })
  43. }
  44. },
  45. }
  46. },
  47. }