Skip to content

Commit

Permalink
Update vue/return-in-emits-validator rule to support `<script setup…
Browse files Browse the repository at this point in the history
…>` (#1542)
  • Loading branch information
ota-meshi committed Jul 3, 2021
1 parent edd2248 commit 8dc37de
Show file tree
Hide file tree
Showing 7 changed files with 343 additions and 116 deletions.
46 changes: 29 additions & 17 deletions lib/rules/return-in-emits-validator.js
Expand Up @@ -9,6 +9,7 @@ const utils = require('../utils')
/**
* @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit
* @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit
* @typedef {import('../utils').ComponentTypeEmit} ComponentTypeEmit
*/

/**
Expand Down Expand Up @@ -67,25 +68,36 @@ module.exports = {
*/
let scopeStack = null

return Object.assign(
{},
/**
* @param {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits
*/
function processEmits(emits) {
for (const emit of emits) {
if (!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) {
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)
}
},
processEmits(utils.getComponentEmits(obj))
}
}),
{
/** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
':function'(node) {
scopeStack = {
Expand Down Expand Up @@ -135,7 +147,7 @@ module.exports = {

scopeStack = scopeStack && scopeStack.upper
}
})
}
)
}
}
207 changes: 110 additions & 97 deletions lib/utils/index.js
Expand Up @@ -15,39 +15,9 @@
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayProp} ComponentArrayProp
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectProp} ComponentObjectProp
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
*/
/**
* @typedef {object} ComponentArrayEmitDetectName
* @property {'array'} type
* @property {Literal | TemplateLiteral} key
* @property {string} emitName
* @property {null} value
* @property {Expression | SpreadElement} node
*
* @typedef {object} ComponentArrayEmitUnknownName
* @property {'array'} type
* @property {null} key
* @property {null} emitName
* @property {null} value
* @property {Expression | SpreadElement} node
*
* @typedef {ComponentArrayEmitDetectName | ComponentArrayEmitUnknownName} ComponentArrayEmit
*
* @typedef {object} ComponentObjectEmitDetectName
* @property {'object'} type
* @property {Expression} key
* @property {string} emitName
* @property {Expression} value
* @property {Property} node
*
* @typedef {object} ComponentObjectEmitUnknownName
* @property {'object'} type
* @property {null} key
* @property {null} emitName
* @property {Expression} value
* @property {Property} node
*
* @typedef {ComponentObjectEmitDetectName | ComponentObjectEmitUnknownName} ComponentObjectEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayEmit} ComponentArrayEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectEmit} ComponentObjectEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
*/
/**
* @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty
Expand Down Expand Up @@ -82,7 +52,10 @@ const path = require('path')
const vueEslintParser = require('vue-eslint-parser')
const traverseNodes = vueEslintParser.AST.traverseNodes
const { findVariable } = require('eslint-utils')
const { getComponentPropsFromTypeDefine } = require('./ts-ast-utils')
const {
getComponentPropsFromTypeDefine,
getComponentEmitsFromTypeDefine
} = require('./ts-ast-utils')

/**
* @type { WeakMap<RuleContext, Token[]> }
Expand Down Expand Up @@ -769,49 +742,7 @@ module.exports = {
return []
}

if (emitsNode.value.type === 'ObjectExpression') {
return emitsNode.value.properties.filter(isProperty).map((prop) => {
const emitName = getStaticPropertyName(prop)
if (emitName != null) {
return {
type: 'object',
key: prop.key,
emitName,
value: skipTSAsExpression(prop.value),
node: prop
}
}
return {
type: 'object',
key: null,
emitName: null,
value: skipTSAsExpression(prop.value),
node: prop
}
})
} else {
return emitsNode.value.elements.filter(isDef).map((prop) => {
if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
const emitName = getStringLiteralValue(prop)
if (emitName != null) {
return {
type: 'array',
key: prop,
emitName,
value: null,
node: prop
}
}
}
return {
type: 'array',
key: null,
emitName: null,
value: null,
node: prop
}
})
}
return getComponentEmitsFromDefine(emitsNode.value)
},

/**
Expand Down Expand Up @@ -1051,6 +982,8 @@ module.exports = {
*
* - `onDefinePropsEnter` ... Event when defineProps is found.
* - `onDefinePropsExit` ... Event when defineProps visit ends.
* - `onDefineEmitsEnter` ... Event when defineEmits is found.
* - `onDefineEmitsExit` ... Event when defineEmits visit ends.
*
* @param {RuleContext} context The ESLint rule context object.
* @param {ScriptSetupVisitor} visitor The visitor to traverse the AST nodes.
Expand Down Expand Up @@ -1120,9 +1053,11 @@ module.exports = {
return null
}

if (visitor.onDefinePropsEnter || visitor.onDefinePropsExit) {
const definePropsMap = new Map()

const hasPropsEvent =
visitor.onDefinePropsEnter || visitor.onDefinePropsExit
const hasEmitsEvent =
visitor.onDefineEmitsEnter || visitor.onDefineEmitsExit
if (hasPropsEvent || hasEmitsEvent) {
/** @type {ESNode | null} */
let nested = null
scriptSetupVisitor[':function, BlockStatement'] = (node) => {
Expand All @@ -1135,33 +1070,56 @@ module.exports = {
nested = null
}
}
const definePropsMap = new Map()
const defineEmitsMap = new Map()
/**
* @param {CallExpression} node
*/
scriptSetupVisitor.CallExpression = (node) => {
if (
!nested &&
inScriptSetup(node) &&
node.callee.type === 'Identifier' &&
node.callee.name === 'defineProps'
node.callee.type === 'Identifier'
) {
/** @type {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} */
let props = []
if (node.arguments.length >= 1) {
const defNode = getObjectOrArray(node.arguments[0])
if (defNode) {
props = getComponentPropsFromDefine(defNode)
if (hasPropsEvent && node.callee.name === 'defineProps') {
/** @type {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} */
let props = []
if (node.arguments.length >= 1) {
const defNode = getObjectOrArray(node.arguments[0])
if (defNode) {
props = getComponentPropsFromDefine(defNode)
}
} else if (
node.typeParameters &&
node.typeParameters.params.length >= 1
) {
props = getComponentPropsFromTypeDefine(
context,
node.typeParameters.params[0]
)
}
} else if (
node.typeParameters &&
node.typeParameters.params.length >= 1
) {
props = getComponentPropsFromTypeDefine(
context,
node.typeParameters.params[0]
)
callVisitor('onDefinePropsEnter', node, props)
definePropsMap.set(node, props)
} else if (hasEmitsEvent && node.callee.name === 'defineEmits') {
/** @type {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} */
let emits = []
if (node.arguments.length >= 1) {
const defNode = getObjectOrArray(node.arguments[0])
if (defNode) {
emits = getComponentEmitsFromDefine(defNode)
}
} else if (
node.typeParameters &&
node.typeParameters.params.length >= 1
) {
emits = getComponentEmitsFromTypeDefine(
context,
node.typeParameters.params[0]
)
}
callVisitor('onDefineEmitsEnter', node, emits)
defineEmitsMap.set(node, emits)
}
callVisitor('onDefinePropsEnter', node, props)
}
callVisitor('CallExpression', node)
}
Expand All @@ -1171,6 +1129,10 @@ module.exports = {
callVisitor('onDefinePropsExit', node, definePropsMap.get(node))
definePropsMap.delete(node)
}
if (defineEmitsMap.has(node)) {
callVisitor('onDefineEmitsExit', node, defineEmitsMap.get(node))
defineEmitsMap.delete(node)
}
}
}

Expand Down Expand Up @@ -2472,3 +2434,54 @@ function getComponentPropsFromDefine(propsNode) {
})
}
}

/**
* Get all emits by looking at all component's properties
* @param {ObjectExpression|ArrayExpression} emitsNode Object with emits definition
* @return {(ComponentArrayEmit | ComponentObjectEmit)[]} Array of component emits
*/
function getComponentEmitsFromDefine(emitsNode) {
if (emitsNode.type === 'ObjectExpression') {
return emitsNode.properties.filter(isProperty).map((prop) => {
const emitName = getStaticPropertyName(prop)
if (emitName != null) {
return {
type: 'object',
key: prop.key,
emitName,
value: skipTSAsExpression(prop.value),
node: prop
}
}
return {
type: 'object',
key: null,
emitName: null,
value: skipTSAsExpression(prop.value),
node: prop
}
})
} else {
return emitsNode.elements.filter(isDef).map((emit) => {
if (emit.type === 'Literal' || emit.type === 'TemplateLiteral') {
const emitName = getStringLiteralValue(emit)
if (emitName != null) {
return {
type: 'array',
key: emit,
emitName,
value: null,
node: emit
}
}
}
return {
type: 'array',
key: null,
emitName: null,
value: null,
node: emit
}
})
}
}

0 comments on commit 8dc37de

Please sign in to comment.