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

fix(require-explicit-slots): add support for type references #2617

Merged
merged 4 commits into from
Nov 27, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 16 additions & 24 deletions lib/rules/require-explicit-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,30 +98,22 @@ module.exports = {

return utils.compositingVisitors(
utils.defineScriptSetupVisitor(context, {
onDefineSlotsEnter(node) {
const typeArguments =
'typeArguments' in node ? node.typeArguments : node.typeParameters
const param = /** @type {TypeNode|undefined} */ (
typeArguments?.params[0]
)
if (!param) return

if (param.type === 'TSTypeLiteral') {
for (const memberNode of param.members) {
const slotName = getSlotsName(memberNode)
if (!slotName) continue

if (slotsDefined.has(slotName)) {
context.report({
node: memberNode,
messageId: 'alreadyDefinedSlot',
data: {
slotName
}
})
} else {
slotsDefined.add(slotName)
}
onDefineSlotsEnter(_node, slots) {
for (const slot of slots) {
if (!slot.slotName) {
continue
}

if (slotsDefined.has(slot.slotName)) {
context.report({
node: slot.node,
messageId: 'alreadyDefinedSlot',
data: {
slotName: slot.slotName
}
})
} else {
slotsDefined.add(slot.slotName)
}
}
}
Expand Down
30 changes: 29 additions & 1 deletion lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const { getScope } = require('./scope')
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeEmit} ComponentInferTypeEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeSlot} ComponentTypeSlot
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeSlot} ComponentInferTypeSlot
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownSlot} ComponentUnknownSlot
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentSlot} ComponentSlot
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModelName} ComponentModelName
* @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModel} ComponentModel
*/
Expand Down Expand Up @@ -70,6 +74,7 @@ const {
const {
getComponentPropsFromTypeDefine,
getComponentEmitsFromTypeDefine,
getComponentSlotsFromTypeDefine,
isTypeNode
} = require('./ts-utils')

Expand Down Expand Up @@ -1435,7 +1440,7 @@ module.exports = {
'onDefineSlotsEnter',
'onDefineSlotsExit',
(candidateMacro, node) => candidateMacro === node,
() => undefined
getComponentSlotsFromDefineSlots
),
new MacroListener(
'defineExpose',
Expand Down Expand Up @@ -3372,6 +3377,28 @@ function getComponentEmitsFromDefineEmits(context, node) {
}
]
}

/**
* Get all slots from `defineSlots` call expression.
* @param {RuleContext} context The rule context object.
* @param {CallExpression} node `defineSlots` call expression
* @return {ComponentSlot[]} Array of component slots
*/
function getComponentSlotsFromDefineSlots(context, node) {
const typeArguments =
'typeArguments' in node ? node.typeArguments : node.typeParameters
if (typeArguments && typeArguments.params.length > 0) {
return getComponentSlotsFromTypeDefine(context, typeArguments.params[0])
}
return [
{
type: 'unknown',
slotName: null,
node: null
}
]
}

/**
* Get model info from `defineModel` call expression.
* @param {RuleContext} _context The rule context object.
Expand Down Expand Up @@ -3414,6 +3441,7 @@ function getComponentModelFromDefineModel(_context, node) {
typeNode: null
}
}

/**
* Get all props by looking at all component's properties
* @param {ObjectExpression|ArrayExpression} propsNode Object with props definition
Expand Down
39 changes: 36 additions & 3 deletions lib/utils/ts-utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ const {
isTSTypeLiteralOrTSFunctionType,
extractRuntimeEmits,
flattenTypeNodes,
isTSInterfaceBody
isTSInterfaceBody,
extractRuntimeSlots
} = require('./ts-ast')
const {
getComponentPropsFromTypeDefineTypes,
getComponentEmitsFromTypeDefineTypes
getComponentEmitsFromTypeDefineTypes,
getComponentSlotsFromTypeDefineTypes
} = require('./ts-types')

/**
Expand All @@ -22,12 +24,16 @@ const {
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/

module.exports = {
isTypeNode,
getComponentPropsFromTypeDefine,
getComponentEmitsFromTypeDefine
getComponentEmitsFromTypeDefine,
getComponentSlotsFromTypeDefine
}

/**
Expand Down Expand Up @@ -86,3 +92,30 @@ function getComponentEmitsFromTypeDefine(context, emitsNode) {
}
return result
}

/**
* Get all slots by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} slotsNode Type with slots definition
* @return {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
*/
function getComponentSlotsFromTypeDefine(context, slotsNode) {
/** @type {(ComponentTypeSlot|ComponentInferTypeSlot|ComponentUnknownSlot)[]} */
const result = []
for (const defNode of flattenTypeNodes(
context,
/** @type {TSESTreeTypeNode} */ (slotsNode)
)) {
if (isTSInterfaceBody(defNode) || isTSTypeLiteral(defNode)) {
result.push(...extractRuntimeSlots(defNode))
} else {
result.push(
...getComponentSlotsFromTypeDefineTypes(
context,
/** @type {TypeNode} */ (defNode)
)
)
}
}
return result
}
37 changes: 36 additions & 1 deletion lib/utils/ts-utils/ts-ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const { inferRuntimeTypeFromTypeNode } = require('./ts-types')
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/

const noop = Function.prototype
Expand All @@ -26,7 +28,8 @@ module.exports = {
isTSTypeLiteral,
isTSTypeLiteralOrTSFunctionType,
extractRuntimeProps,
extractRuntimeEmits
extractRuntimeEmits,
extractRuntimeSlots
}

/**
Expand Down Expand Up @@ -209,6 +212,38 @@ function* extractRuntimeEmits(node) {
}
}

/**
* @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
* @returns {IterableIterator<ComponentTypeSlot | ComponentUnknownSlot>}
*/
function* extractRuntimeSlots(node) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const member of members) {
if (
member.type === 'TSPropertySignature' ||
member.type === 'TSMethodSignature'
) {
if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
yield {
type: 'unknown',
slotName: null,
node: /** @type {Expression} */ (member.key)
}
continue
}
yield {
type: 'type',
key: /** @type {Identifier | Literal} */ (member.key),
slotName:
member.key.type === 'Identifier'
? member.key.name
: `${member.key.value}`,
node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
}
}
}
}

/**
* @param {TSESTreeParameter} eventName
* @param {TSCallSignatureDeclaration | TSFunctionType} member
Expand Down
48 changes: 48 additions & 0 deletions lib/utils/ts-utils/ts-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ const {
* @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
* @typedef {import('../index').ComponentInferTypeEmit} ComponentInferTypeEmit
* @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
* @typedef {import('../index').ComponentInferTypeSlot} ComponentInferTypeSlot
* @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
*/

module.exports = {
getComponentPropsFromTypeDefineTypes,
getComponentEmitsFromTypeDefineTypes,
getComponentSlotsFromTypeDefineTypes,
inferRuntimeTypeFromTypeNode
}

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

/**
* Get all slots by looking at all component's properties
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode} slotsNode Type with slots definition
* @return {(ComponentInferTypeSlot|ComponentUnknownSlot)[]} Array of component slots
*/
function getComponentSlotsFromTypeDefineTypes(context, slotsNode) {
const services = getTSParserServices(context)
const tsNode = services && services.tsNodeMap.get(slotsNode)
const type = tsNode && services.checker.getTypeAtLocation(tsNode)
if (
!type ||
isAny(type) ||
isUnknown(type) ||
isNever(type) ||
isNull(type)
) {
return [
{
type: 'unknown',
slotName: null,
node: slotsNode
}
]
}
return [...extractRuntimeSlots(type, slotsNode)]
}

/**
* @param {RuleContext} context The ESLint rule context object.
* @param {TypeNode|Expression} node
Expand Down Expand Up @@ -259,6 +290,23 @@ function* extractRuntimeEmits(type, tsNode, emitsNode, services) {
}
}

/**
* @param {Type} type
* @param {TypeNode} slotsNode Type with slots definition
* @returns {IterableIterator<ComponentInferTypeSlot>}
*/
function* extractRuntimeSlots(type, slotsNode) {
for (const property of type.getProperties()) {
const name = property.getName()

yield {
type: 'infer-type',
slotName: name,
node: slotsNode
}
}
}

/**
* @param {Type} type
* @returns {Iterable<Type>}
Expand Down
Loading
Loading