Skip to content

Commit 618f49c

Browse files
fix(require-explicit-slots): add support for type references (#2617)
1 parent a270df8 commit 618f49c

File tree

8 files changed

+775
-31
lines changed

8 files changed

+775
-31
lines changed

lib/rules/require-explicit-slots.js

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -98,30 +98,22 @@ module.exports = {
9898

9999
return utils.compositingVisitors(
100100
utils.defineScriptSetupVisitor(context, {
101-
onDefineSlotsEnter(node) {
102-
const typeArguments =
103-
'typeArguments' in node ? node.typeArguments : node.typeParameters
104-
const param = /** @type {TypeNode|undefined} */ (
105-
typeArguments?.params[0]
106-
)
107-
if (!param) return
108-
109-
if (param.type === 'TSTypeLiteral') {
110-
for (const memberNode of param.members) {
111-
const slotName = getSlotsName(memberNode)
112-
if (!slotName) continue
113-
114-
if (slotsDefined.has(slotName)) {
115-
context.report({
116-
node: memberNode,
117-
messageId: 'alreadyDefinedSlot',
118-
data: {
119-
slotName
120-
}
121-
})
122-
} else {
123-
slotsDefined.add(slotName)
124-
}
101+
onDefineSlotsEnter(_node, slots) {
102+
for (const slot of slots) {
103+
if (!slot.slotName) {
104+
continue
105+
}
106+
107+
if (slotsDefined.has(slot.slotName)) {
108+
context.report({
109+
node: slot.node,
110+
messageId: 'alreadyDefinedSlot',
111+
data: {
112+
slotName: slot.slotName
113+
}
114+
})
115+
} else {
116+
slotsDefined.add(slot.slotName)
125117
}
126118
}
127119
}

lib/utils/index.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ const { getScope } = require('./scope')
2626
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeEmit} ComponentInferTypeEmit
2727
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
2828
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit
29+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeSlot} ComponentTypeSlot
30+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeSlot} ComponentInferTypeSlot
31+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownSlot} ComponentUnknownSlot
32+
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentSlot} ComponentSlot
2933
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModelName} ComponentModelName
3034
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModel} ComponentModel
3135
*/
@@ -70,6 +74,7 @@ const {
7074
const {
7175
getComponentPropsFromTypeDefine,
7276
getComponentEmitsFromTypeDefine,
77+
getComponentSlotsFromTypeDefine,
7378
isTypeNode
7479
} = require('./ts-utils')
7580

@@ -1435,7 +1440,7 @@ module.exports = {
14351440
'onDefineSlotsEnter',
14361441
'onDefineSlotsExit',
14371442
(candidateMacro, node) => candidateMacro === node,
1438-
() => undefined
1443+
getComponentSlotsFromDefineSlots
14391444
),
14401445
new MacroListener(
14411446
'defineExpose',
@@ -3372,6 +3377,28 @@ function getComponentEmitsFromDefineEmits(context, node) {
33723377
}
33733378
]
33743379
}
3380+
3381+
/**
3382+
* Get all slots from `defineSlots` call expression.
3383+
* @param {RuleContext} context The rule context object.
3384+
* @param {CallExpression} node `defineSlots` call expression
3385+
* @return {ComponentSlot[]} Array of component slots
3386+
*/
3387+
function getComponentSlotsFromDefineSlots(context, node) {
3388+
const typeArguments =
3389+
'typeArguments' in node ? node.typeArguments : node.typeParameters
3390+
if (typeArguments && typeArguments.params.length > 0) {
3391+
return getComponentSlotsFromTypeDefine(context, typeArguments.params[0])
3392+
}
3393+
return [
3394+
{
3395+
type: 'unknown',
3396+
slotName: null,
3397+
node: null
3398+
}
3399+
]
3400+
}
3401+
33753402
/**
33763403
* Get model info from `defineModel` call expression.
33773404
* @param {RuleContext} _context The rule context object.
@@ -3414,6 +3441,7 @@ function getComponentModelFromDefineModel(_context, node) {
34143441
typeNode: null
34153442
}
34163443
}
3444+
34173445
/**
34183446
* Get all props by looking at all component's properties
34193447
* @param {ObjectExpression|ArrayExpression} propsNode Object with props definition

lib/utils/ts-utils/index.js

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ const {
55
isTSTypeLiteralOrTSFunctionType,
66
extractRuntimeEmits,
77
flattenTypeNodes,
8-
isTSInterfaceBody
8+
isTSInterfaceBody,
9+
extractRuntimeSlots
910
} = require('./ts-ast')
1011
const {
1112
getComponentPropsFromTypeDefineTypes,
12-
getComponentEmitsFromTypeDefineTypes
13+
getComponentEmitsFromTypeDefineTypes,
14+
getComponentSlotsFromTypeDefineTypes
1315
} = require('./ts-types')
1416

1517
/**
@@ -22,12 +24,16 @@ const {
2224
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
2325
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
2426
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
27+
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
28+
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
29+
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
2530
*/
2631

2732
module.exports = {
2833
isTypeNode,
2934
getComponentPropsFromTypeDefine,
30-
getComponentEmitsFromTypeDefine
35+
getComponentEmitsFromTypeDefine,
36+
getComponentSlotsFromTypeDefine
3137
}
3238

3339
/**
@@ -86,3 +92,30 @@ function getComponentEmitsFromTypeDefine(context, emitsNode) {
8692
}
8793
return result
8894
}
95+
96+
/**
97+
* Get all slots by looking at all component's properties
98+
* @param {RuleContext} context The ESLint rule context object.
99+
* @param {TypeNode} slotsNode Type with slots definition
100+
* @return {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
101+
*/
102+
function getComponentSlotsFromTypeDefine(context, slotsNode) {
103+
/** @type {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} */
104+
const result = []
105+
for (const defNode of flattenTypeNodes(
106+
context,
107+
/** @type {TSESTreeTypeNode} */ (slotsNode)
108+
)) {
109+
if (isTSInterfaceBody(defNode) || isTSTypeLiteral(defNode)) {
110+
result.push(...extractRuntimeSlots(defNode))
111+
} else {
112+
result.push(
113+
...getComponentSlotsFromTypeDefineTypes(
114+
context,
115+
/** @type {TypeNode} */ (defNode)
116+
)
117+
)
118+
}
119+
}
120+
return result
121+
}

lib/utils/ts-utils/ts-ast.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const { inferRuntimeTypeFromTypeNode } = require('./ts-types')
1515
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
1616
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
1717
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
18+
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
19+
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
1820
*/
1921

2022
const noop = Function.prototype
@@ -26,7 +28,8 @@ module.exports = {
2628
isTSTypeLiteral,
2729
isTSTypeLiteralOrTSFunctionType,
2830
extractRuntimeProps,
29-
extractRuntimeEmits
31+
extractRuntimeEmits,
32+
extractRuntimeSlots
3033
}
3134

3235
/**
@@ -209,6 +212,38 @@ function* extractRuntimeEmits(node) {
209212
}
210213
}
211214

215+
/**
216+
* @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
217+
* @returns {IterableIterator<ComponentTypeSlot | ComponentUnknownSlot>}
218+
*/
219+
function* extractRuntimeSlots(node) {
220+
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
221+
for (const member of members) {
222+
if (
223+
member.type === 'TSPropertySignature' ||
224+
member.type === 'TSMethodSignature'
225+
) {
226+
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
227+
yield {
228+
type: 'unknown',
229+
slotName: null,
230+
node: /** @type {Expression} */ (member.key)
231+
}
232+
continue
233+
}
234+
yield {
235+
type: 'type',
236+
key: /** @type {Identifier | Literal} */ (member.key),
237+
slotName:
238+
member.key.type === 'Identifier'
239+
? member.key.name
240+
: `${member.key.value}`,
241+
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
242+
}
243+
}
244+
}
245+
}
246+
212247
/**
213248
* @param {TSESTreeParameter} eventName
214249
* @param {TSCallSignatureDeclaration | TSFunctionType} member

lib/utils/ts-utils/ts-types.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ const {
2424
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
2525
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
2626
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
27+
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
28+
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
2729
*/
2830

2931
module.exports = {
3032
getComponentPropsFromTypeDefineTypes,
3133
getComponentEmitsFromTypeDefineTypes,
34+
getComponentSlotsFromTypeDefineTypes,
3235
inferRuntimeTypeFromTypeNode
3336
}
3437

@@ -122,6 +125,34 @@ function getComponentEmitsFromTypeDefineTypes(context, emitsNode) {
122125
return [...extractRuntimeEmits(type, tsNode, emitsNode, services)]
123126
}
124127

128+
/**
129+
* Get all slots by looking at all component's properties
130+
* @param {RuleContext} context The ESLint rule context object.
131+
* @param {TypeNode} slotsNode Type with slots definition
132+
* @return {(ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
133+
*/
134+
function getComponentSlotsFromTypeDefineTypes(context, slotsNode) {
135+
const services = getTSParserServices(context)
136+
const tsNode = services && services.tsNodeMap.get(slotsNode)
137+
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
138+
if (
139+
!type ||
140+
isAny(type) ||
141+
isUnknown(type) ||
142+
isNever(type) ||
143+
isNull(type)
144+
) {
145+
return [
146+
{
147+
type: 'unknown',
148+
slotName: null,
149+
node: slotsNode
150+
}
151+
]
152+
}
153+
return [...extractRuntimeSlots(type, slotsNode)]
154+
}
155+
125156
/**
126157
* @param {RuleContext} context The ESLint rule context object.
127158
* @param {TypeNode|Expression} node
@@ -259,6 +290,23 @@ function* extractRuntimeEmits(type, tsNode, emitsNode, services) {
259290
}
260291
}
261292

293+
/**
294+
* @param {Type} type
295+
* @param {TypeNode} slotsNode Type with slots definition
296+
* @returns {IterableIterator<ComponentInferTypeSlot>}
297+
*/
298+
function* extractRuntimeSlots(type, slotsNode) {
299+
for (const property of type.getProperties()) {
300+
const name = property.getName()
301+
302+
yield {
303+
type: 'infer-type',
304+
slotName: name,
305+
node: slotsNode
306+
}
307+
}
308+
}
309+
262310
/**
263311
* @param {Type} type
264312
* @returns {Iterable<Type>}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy