d32676d85ec91eff88b628b4fc77636addafba2664cc89dc88715f94a7b1c31839bb6a5f74dbbe3df56b01d76c7c19f92a277b2233bacbd98df09dc0b173d5 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit
  9. * @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit
  10. */
  11. /**
  12. * Checks if the given node value is falsy.
  13. * @param {Expression} node The node to check
  14. * @returns {boolean} If `true`, the given node value is falsy.
  15. */
  16. function isFalsy(node) {
  17. if (node.type === 'Literal') {
  18. if (node.bigint) {
  19. return node.bigint === '0'
  20. } else if (!node.value) {
  21. return true
  22. }
  23. } else if (node.type === 'Identifier') {
  24. if (node.name === 'undefined' || node.name === 'NaN') {
  25. return true
  26. }
  27. }
  28. return false
  29. }
  30. // ------------------------------------------------------------------------------
  31. // Rule Definition
  32. // ------------------------------------------------------------------------------
  33. module.exports = {
  34. meta: {
  35. type: 'problem',
  36. docs: {
  37. description:
  38. 'enforce that a return statement is present in emits validator',
  39. categories: ['vue3-essential'],
  40. url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
  41. },
  42. fixable: null, // or "code" or "whitespace"
  43. schema: []
  44. },
  45. /** @param {RuleContext} context */
  46. create(context) {
  47. /** @type {ComponentObjectEmit[]} */
  48. const emitsValidators = []
  49. // ----------------------------------------------------------------------
  50. // Public
  51. // ----------------------------------------------------------------------
  52. /**
  53. * @typedef {object} ScopeStack
  54. * @property {ScopeStack | null} upper
  55. * @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} functionNode
  56. * @property {boolean} hasReturnValue
  57. * @property {boolean} possibleOfReturnTrue
  58. */
  59. /**
  60. * @type {ScopeStack | null}
  61. */
  62. let scopeStack = null
  63. return Object.assign(
  64. {},
  65. utils.defineVueVisitor(context, {
  66. /** @param {ObjectExpression} obj */
  67. onVueObjectEnter(obj) {
  68. for (const emits of utils.getComponentEmits(obj)) {
  69. if (!emits.value) {
  70. continue
  71. }
  72. const emitsValue = emits.value
  73. if (
  74. emitsValue.type !== 'FunctionExpression' &&
  75. emitsValue.type !== 'ArrowFunctionExpression'
  76. ) {
  77. continue
  78. }
  79. emitsValidators.push(emits)
  80. }
  81. },
  82. /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
  83. ':function'(node) {
  84. scopeStack = {
  85. upper: scopeStack,
  86. functionNode: node,
  87. hasReturnValue: false,
  88. possibleOfReturnTrue: false
  89. }
  90. if (node.type === 'ArrowFunctionExpression' && node.expression) {
  91. scopeStack.hasReturnValue = true
  92. if (!isFalsy(node.body)) {
  93. scopeStack.possibleOfReturnTrue = true
  94. }
  95. }
  96. },
  97. /** @param {ReturnStatement} node */
  98. ReturnStatement(node) {
  99. if (!scopeStack) {
  100. return
  101. }
  102. if (node.argument) {
  103. scopeStack.hasReturnValue = true
  104. if (!isFalsy(node.argument)) {
  105. scopeStack.possibleOfReturnTrue = true
  106. }
  107. }
  108. },
  109. /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
  110. ':function:exit'(node) {
  111. if (scopeStack && !scopeStack.possibleOfReturnTrue) {
  112. const emits = emitsValidators.find((e) => e.value === node)
  113. if (emits) {
  114. context.report({
  115. node,
  116. message: scopeStack.hasReturnValue
  117. ? 'Expected to return a true value in "{{name}}" emits validator.'
  118. : 'Expected to return a boolean value in "{{name}}" emits validator.',
  119. data: {
  120. name: emits.emitName || 'Unknown'
  121. }
  122. })
  123. }
  124. }
  125. scopeStack = scopeStack && scopeStack.upper
  126. }
  127. })
  128. )
  129. }
  130. }