Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update vue/return-in-emits-validator rule to support <script setup> #1542

Merged
merged 1 commit into from Jul 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}
})
}
}