eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
152 lines (141 loc) • 4.15 kB
JavaScript
/**
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentEmit} ComponentEmit
* @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'
}
if (!node.value) {
return true
}
}
return (
node.type === 'Identifier' &&
(node.name === 'undefined' || node.name === 'NaN')
)
}
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'enforce that a return statement is present in emits validator',
categories: ['vue3-essential', 'vue2-essential'],
url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
},
fixable: null,
schema: [],
messages: {
expectedTrue:
'Expected to return a true value in "{{name}}" emits validator.',
expectedBoolean:
'Expected to return a boolean value in "{{name}}" emits validator.'
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {ComponentObjectEmit[]} */
const emitsValidators = []
/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} functionNode
* @property {boolean} hasReturnValue
* @property {boolean} possibleOfReturnTrue
*/
/**
* @type {ScopeStack | null}
*/
let scopeStack = null
/**
* @param {ComponentEmit[]} emits
*/
function processEmits(emits) {
for (const emit of emits) {
if (emit.type !== 'object' || !emit.value) {
continue
}
if (
emit.value.type !== 'FunctionExpression' &&
emit.value.type !== 'ArrowFunctionExpression'
) {
continue
}
emitsValidators.push(emit)
}
}
return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefineEmitsEnter(_node, emits) {
processEmits(emits)
}
}),
utils.defineVueVisitor(context, {
/** @param {ObjectExpression} obj */
onVueObjectEnter(obj) {
processEmits(utils.getComponentEmitsFromOptions(obj))
}
}),
{
/** @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,
messageId: scopeStack.hasReturnValue
? 'expectedTrue'
: 'expectedBoolean',
data: {
name: emits.emitName || 'Unknown'
}
})
}
}
scopeStack = scopeStack && scopeStack.upper
}
}
)
}
}