/** * @author Yosuke Ota * See LICENSE file in root directory for full license. */ 'use strict' const utils = require('../utils') /** * @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit * @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit */ /** * Checks if the given node value is falsy. * @param {Expression} node The node to check * @returns {boolean} If `true`, the given node value is falsy. */ function isFalsy(node) { if (node.type === 'Literal') { if (node.bigint) { return node.bigint === '0' } else if (!node.value) { return true } } else if (node.type === 'Identifier') { if (node.name === 'undefined' || node.name === 'NaN') { return true } } return false } // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ module.exports = { meta: { type: 'problem', docs: { description: 'enforce that a return statement is present in emits validator', categories: ['vue3-essential'], url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html' }, fixable: null, // or "code" or "whitespace" schema: [] }, /** @param {RuleContext} context */ create(context) { /** @type {ComponentObjectEmit[]} */ const emitsValidators = [] // ---------------------------------------------------------------------- // Public // ---------------------------------------------------------------------- /** * @typedef {object} ScopeStack * @property {ScopeStack | null} upper * @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} functionNode * @property {boolean} hasReturnValue * @property {boolean} possibleOfReturnTrue */ /** * @type {ScopeStack | null} */ let scopeStack = null return Object.assign( {}, utils.defineVueVisitor(context, { /** @param {ObjectExpression} obj */ onVueObjectEnter(obj) { for (const emits of utils.getComponentEmits(obj)) { if (!emits.value) { continue } const emitsValue = emits.value if ( emitsValue.type !== 'FunctionExpression' && emitsValue.type !== 'ArrowFunctionExpression' ) { continue } emitsValidators.push(emits) } }, /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */ ':function'(node) { scopeStack = { upper: scopeStack, functionNode: node, hasReturnValue: false, possibleOfReturnTrue: false } if (node.type === 'ArrowFunctionExpression' && node.expression) { scopeStack.hasReturnValue = true if (!isFalsy(node.body)) { scopeStack.possibleOfReturnTrue = true } } }, /** @param {ReturnStatement} node */ ReturnStatement(node) { if (!scopeStack) { return } if (node.argument) { scopeStack.hasReturnValue = true if (!isFalsy(node.argument)) { scopeStack.possibleOfReturnTrue = true } } }, /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */ ':function:exit'(node) { if (scopeStack && !scopeStack.possibleOfReturnTrue) { const emits = emitsValidators.find((e) => e.value === node) if (emits) { context.report({ node, message: scopeStack.hasReturnValue ? 'Expected to return a true value in "{{name}}" emits validator.' : 'Expected to return a boolean value in "{{name}}" emits validator.', data: { name: emits.emitName || 'Unknown' } }) } } scopeStack = scopeStack && scopeStack.upper } }) ) } }