Skip to content

Commit 984ce20

Browse files
committed
Update vue/return-in-emits-validator rule to support <script setup>
1 parent ee5ea4b commit 984ce20

File tree

7 files changed

+342
-117
lines changed

7 files changed

+342
-117
lines changed

lib/rules/return-in-emits-validator.js

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const utils = require('../utils')
99
/**
1010
* @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit
1111
* @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit
12+
* @typedef {import('../utils').ComponentTypeEmit} ComponentTypeEmit
1213
*/
1314

1415
/**
@@ -67,25 +68,36 @@ module.exports = {
6768
*/
6869
let scopeStack = null
6970

70-
return Object.assign(
71-
{},
71+
/**
72+
* @param {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits
73+
*/
74+
function processEmits(emits) {
75+
for (const emit of emits) {
76+
if (!emit.value) {
77+
continue
78+
}
79+
if (
80+
emit.value.type !== 'FunctionExpression' &&
81+
emit.value.type !== 'ArrowFunctionExpression'
82+
) {
83+
continue
84+
}
85+
emitsValidators.push(emit)
86+
}
87+
}
88+
return utils.compositingVisitors(
89+
utils.defineScriptSetupVisitor(context, {
90+
onDefineEmitsEnter(_node, emits) {
91+
processEmits(emits)
92+
}
93+
}),
7294
utils.defineVueVisitor(context, {
7395
/** @param {ObjectExpression} obj */
7496
onVueObjectEnter(obj) {
75-
for (const emits of utils.getComponentEmits(obj)) {
76-
if (!emits.value) {
77-
continue
78-
}
79-
const emitsValue = emits.value
80-
if (
81-
emitsValue.type !== 'FunctionExpression' &&
82-
emitsValue.type !== 'ArrowFunctionExpression'
83-
) {
84-
continue
85-
}
86-
emitsValidators.push(emits)
87-
}
88-
},
97+
processEmits(utils.getComponentEmits(obj))
98+
}
99+
}),
100+
{
89101
/** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
90102
':function'(node) {
91103
scopeStack = {
@@ -135,7 +147,7 @@ module.exports = {
135147

136148
scopeStack = scopeStack && scopeStack.upper
137149
}
138-
})
150+
}
139151
)
140152
}
141153
}

lib/utils/index.js

Lines changed: 109 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,9 @@
1515
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayProp} ComponentArrayProp
1616
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectProp} ComponentObjectProp
1717
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
18-
*/
19-
/**
20-
* @typedef {object} ComponentArrayEmitDetectName
21-
* @property {'array'} type
22-
* @property {Literal | TemplateLiteral} key
23-
* @property {string} emitName
24-
* @property {null} value
25-
* @property {Expression | SpreadElement} node
26-
*
27-
* @typedef {object} ComponentArrayEmitUnknownName
28-
* @property {'array'} type
29-
* @property {null} key
30-
* @property {null} emitName
31-
* @property {null} value
32-
* @property {Expression | SpreadElement} node
33-
*
34-
* @typedef {ComponentArrayEmitDetectName | ComponentArrayEmitUnknownName} ComponentArrayEmit
35-
*
36-
* @typedef {object} ComponentObjectEmitDetectName
37-
* @property {'object'} type
38-
* @property {Expression} key
39-
* @property {string} emitName
40-
* @property {Expression} value
41-
* @property {Property} node
42-
*
43-
* @typedef {object} ComponentObjectEmitUnknownName
44-
* @property {'object'} type
45-
* @property {null} key
46-
* @property {null} emitName
47-
* @property {Expression} value
48-
* @property {Property} node
49-
*
50-
* @typedef {ComponentObjectEmitDetectName | ComponentObjectEmitUnknownName} ComponentObjectEmit
18+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayEmit} ComponentArrayEmit
19+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectEmit} ComponentObjectEmit
20+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
5121
*/
5222
/**
5323
* @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty
@@ -82,7 +52,10 @@ const path = require('path')
8252
const vueEslintParser = require('vue-eslint-parser')
8353
const traverseNodes = vueEslintParser.AST.traverseNodes
8454
const { findVariable } = require('eslint-utils')
85-
const { getComponentPropsFromTypeDefine } = require('./ts-ast-utils')
55+
const {
56+
getComponentPropsFromTypeDefine,
57+
getComponentEmitsFromTypeDefine
58+
} = require('./ts-ast-utils')
8659

8760
/**
8861
* @type { WeakMap<RuleContext, Token[]> }
@@ -769,49 +742,7 @@ module.exports = {
769742
return []
770743
}
771744

772-
if (emitsNode.value.type === 'ObjectExpression') {
773-
return emitsNode.value.properties.filter(isProperty).map((prop) => {
774-
const emitName = getStaticPropertyName(prop)
775-
if (emitName != null) {
776-
return {
777-
type: 'object',
778-
key: prop.key,
779-
emitName,
780-
value: skipTSAsExpression(prop.value),
781-
node: prop
782-
}
783-
}
784-
return {
785-
type: 'object',
786-
key: null,
787-
emitName: null,
788-
value: skipTSAsExpression(prop.value),
789-
node: prop
790-
}
791-
})
792-
} else {
793-
return emitsNode.value.elements.filter(isDef).map((prop) => {
794-
if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
795-
const emitName = getStringLiteralValue(prop)
796-
if (emitName != null) {
797-
return {
798-
type: 'array',
799-
key: prop,
800-
emitName,
801-
value: null,
802-
node: prop
803-
}
804-
}
805-
}
806-
return {
807-
type: 'array',
808-
key: null,
809-
emitName: null,
810-
value: null,
811-
node: prop
812-
}
813-
})
814-
}
745+
return getComponentEmitsFromDefine(emitsNode.value)
815746
},
816747

817748
/**
@@ -1051,6 +982,8 @@ module.exports = {
1051982
*
1052983
* - `onDefinePropsEnter` ... Event when defineProps is found.
1053984
* - `onDefinePropsExit` ... Event when defineProps visit ends.
985+
* - `onDefineEmitsEnter` ... Event when defineEmits is found.
986+
* - `onDefineEmitsExit` ... Event when defineEmits visit ends.
1054987
*
1055988
* @param {RuleContext} context The ESLint rule context object.
1056989
* @param {ScriptSetupVisitor} visitor The visitor to traverse the AST nodes.
@@ -1120,34 +1053,57 @@ module.exports = {
11201053
return null
11211054
}
11221055

1123-
if (visitor.onDefinePropsEnter || visitor.onDefinePropsExit) {
1056+
const hasPropsEvent =
1057+
visitor.onDefinePropsEnter || visitor.onDefinePropsExit
1058+
const hasEmitsEvent =
1059+
visitor.onDefineEmitsEnter || visitor.onDefineEmitsExit
1060+
if (hasPropsEvent || hasEmitsEvent) {
11241061
const definePropsMap = new Map()
1062+
const defineEmitsMap = new Map()
11251063
/**
11261064
* @param {CallExpression} node
11271065
*/
11281066
scriptSetupVisitor.CallExpression = (node) => {
1129-
if (
1130-
inScriptSetup(node) &&
1131-
node.callee.type === 'Identifier' &&
1132-
node.callee.name === 'defineProps'
1133-
) {
1134-
/** @type {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} */
1135-
let props = []
1136-
if (node.arguments.length >= 1) {
1137-
const defNode = getObjectOrArray(node.arguments[0])
1138-
if (defNode) {
1139-
props = getComponentPropsFromDefine(defNode)
1067+
if (inScriptSetup(node) && node.callee.type === 'Identifier') {
1068+
if (hasPropsEvent && node.callee.name === 'defineProps') {
1069+
/** @type {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} */
1070+
let props = []
1071+
if (node.arguments.length >= 1) {
1072+
const defNode = getObjectOrArray(node.arguments[0])
1073+
if (defNode) {
1074+
props = getComponentPropsFromDefine(defNode)
1075+
}
1076+
} else if (
1077+
node.typeParameters &&
1078+
node.typeParameters.params.length >= 1
1079+
) {
1080+
props = getComponentPropsFromTypeDefine(
1081+
context,
1082+
node.typeParameters.params[0]
1083+
)
11401084
}
1141-
} else if (
1142-
node.typeParameters &&
1143-
node.typeParameters.params.length >= 1
1144-
) {
1145-
props = getComponentPropsFromTypeDefine(
1146-
context,
1147-
node.typeParameters.params[0]
1148-
)
1085+
callVisitor('onDefinePropsEnter', node, props)
1086+
definePropsMap.set(node, props)
1087+
} else if (hasEmitsEvent) {
1088+
/** @type {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} */
1089+
let emits = []
1090+
if (node.arguments.length >= 1) {
1091+
const defNode = getObjectOrArray(node.arguments[0])
1092+
if (defNode) {
1093+
emits = getComponentEmitsFromDefine(defNode)
1094+
}
1095+
} else if (
1096+
node.typeParameters &&
1097+
node.typeParameters.params.length >= 1
1098+
) {
1099+
emits = getComponentEmitsFromTypeDefine(
1100+
context,
1101+
node.typeParameters.params[0]
1102+
)
1103+
}
1104+
callVisitor('onDefineEmitsEnter', node, emits)
1105+
defineEmitsMap.set(node, emits)
11491106
}
1150-
callVisitor('onDefinePropsEnter', node, props)
11511107
}
11521108
callVisitor('CallExpression', node)
11531109
}
@@ -1157,6 +1113,10 @@ module.exports = {
11571113
callVisitor('onDefinePropsExit', node, definePropsMap.get(node))
11581114
definePropsMap.delete(node)
11591115
}
1116+
if (defineEmitsMap.has(node)) {
1117+
callVisitor('onDefineEmitsExit', node, defineEmitsMap.get(node))
1118+
defineEmitsMap.delete(node)
1119+
}
11601120
}
11611121
}
11621122

@@ -2422,3 +2382,54 @@ function getComponentPropsFromDefine(propsNode) {
24222382
})
24232383
}
24242384
}
2385+
2386+
/**
2387+
* Get all emits by looking at all component's properties
2388+
* @param {ObjectExpression|ArrayExpression} emitsNode Object with emits definition
2389+
* @return {(ComponentArrayEmit | ComponentObjectEmit)[]} Array of component emits
2390+
*/
2391+
function getComponentEmitsFromDefine(emitsNode) {
2392+
if (emitsNode.type === 'ObjectExpression') {
2393+
return emitsNode.properties.filter(isProperty).map((prop) => {
2394+
const emitName = getStaticPropertyName(prop)
2395+
if (emitName != null) {
2396+
return {
2397+
type: 'object',
2398+
key: prop.key,
2399+
emitName,
2400+
value: skipTSAsExpression(prop.value),
2401+
node: prop
2402+
}
2403+
}
2404+
return {
2405+
type: 'object',
2406+
key: null,
2407+
emitName: null,
2408+
value: skipTSAsExpression(prop.value),
2409+
node: prop
2410+
}
2411+
})
2412+
} else {
2413+
return emitsNode.elements.filter(isDef).map((emit) => {
2414+
if (emit.type === 'Literal' || emit.type === 'TemplateLiteral') {
2415+
const emitName = getStringLiteralValue(emit)
2416+
if (emitName != null) {
2417+
return {
2418+
type: 'array',
2419+
key: emit,
2420+
emitName,
2421+
value: null,
2422+
node: emit
2423+
}
2424+
}
2425+
}
2426+
return {
2427+
type: 'array',
2428+
key: null,
2429+
emitName: null,
2430+
value: null,
2431+
node: emit
2432+
}
2433+
})
2434+
}
2435+
}

0 commit comments

Comments
 (0)