From f58b4d9c61f26da9f8aa79407ce0b180d344dbfa Mon Sep 17 00:00:00 2001 From: Joe Ellis Date: Tue, 19 Nov 2024 12:36:31 +0000 Subject: [PATCH 1/3] fix(require-explicit-slots): add support for type references --- lib/rules/require-explicit-slots.js | 40 +- lib/utils/index.js | 106 +++- lib/utils/ts-utils/index.js | 39 +- lib/utils/ts-utils/ts-ast.js | 37 +- lib/utils/ts-utils/ts-types.js | 48 ++ tests/lib/rules/require-explicit-slots.js | 468 ++++++++++++++++++ typings/eslint-plugin-vue/util-types/utils.ts | 65 ++- 7 files changed, 772 insertions(+), 31 deletions(-) diff --git a/lib/rules/require-explicit-slots.js b/lib/rules/require-explicit-slots.js index f87503bb7..5298e598c 100644 --- a/lib/rules/require-explicit-slots.js +++ b/lib/rules/require-explicit-slots.js @@ -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) } } } diff --git a/lib/utils/index.js b/lib/utils/index.js index 58cd32689..e6650d275 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -26,6 +26,12 @@ 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').ComponentArraySlot} ComponentArraySlot + * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectSlot} ComponentObjectSlot + * @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 */ @@ -70,6 +76,7 @@ const { const { getComponentPropsFromTypeDefine, getComponentEmitsFromTypeDefine, + getComponentSlotsFromTypeDefine, isTypeNode } = require('./ts-utils') @@ -1424,7 +1431,7 @@ module.exports = { 'onDefineSlotsEnter', 'onDefineSlotsExit', (candidateMacro, node) => candidateMacro === node, - () => undefined + getComponentSlotsFromDefineSlots ), new MacroListener( 'defineExpose', @@ -3361,6 +3368,41 @@ 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) { + if (node.arguments.length > 0) { + const defNode = getObjectOrArray(context, node.arguments[0]) + if (defNode) { + return getComponentSlotsFromDefine(defNode) + } + return [ + { + type: 'unknown', + slotName: null, + node: node.arguments[0] + } + ] + } + 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. @@ -3403,6 +3445,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 @@ -3524,6 +3567,67 @@ function getComponentEmitsFromDefine(emitsNode) { }) } +/** + * Get all slots by looking at all component's properties + * @param {ObjectExpression|ArrayExpression} slotsNode Object with slots definition + * @return {(ComponentArraySlot | ComponentObjectSlot | ComponentUnknownSlot)[]} Array of component slots + */ +function getComponentSlotsFromDefine(slotsNode) { + if (slotsNode.type === 'ObjectExpression') { + return slotsNode.properties.map( + /** @returns {ComponentArraySlot | ComponentObjectSlot | ComponentUnknownSlot} */ + (slot) => { + if (!isProperty(slot)) { + return { + type: 'unknown', + slotName: null, + node: slot + } + } + const slotName = getStaticPropertyName(slot) + if (slotName != null) { + return { + type: 'object', + key: slot.key, + slotName, + value: skipTSAsExpression(slot.value), + node: slot + } + } + return { + type: 'object', + key: null, + slotName: null, + value: skipTSAsExpression(slot.value), + node: slot + } + } + ) + } + + return slotsNode.elements.filter(isDef).map((slot) => { + if (slot.type === 'Literal' || slot.type === 'TemplateLiteral') { + const slotName = getStringLiteralValue(slot) + if (slotName != null) { + return { + type: 'array', + key: slot, + slotName, + value: null, + node: slot + } + } + } + return { + type: 'array', + key: null, + slotName: null, + value: null, + node: slot + } + }) +} + /** * @param {RuleContext} context The rule context object. * @param {ESNode} node diff --git a/lib/utils/ts-utils/index.js b/lib/utils/ts-utils/index.js index 8b6c53b26..3db610d1c 100644 --- a/lib/utils/ts-utils/index.js +++ b/lib/utils/ts-utils/index.js @@ -5,11 +5,13 @@ const { isTSTypeLiteralOrTSFunctionType, extractRuntimeEmits, flattenTypeNodes, - isTSInterfaceBody + isTSInterfaceBody, + extractRuntimeSlots } = require('./ts-ast') const { getComponentPropsFromTypeDefineTypes, - getComponentEmitsFromTypeDefineTypes + getComponentEmitsFromTypeDefineTypes, + getComponentSlotsFromTypeDefineTypes } = require('./ts-types') /** @@ -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 } /** @@ -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 +} diff --git a/lib/utils/ts-utils/ts-ast.js b/lib/utils/ts-utils/ts-ast.js index ddbb9de05..1021b4baf 100644 --- a/lib/utils/ts-utils/ts-ast.js +++ b/lib/utils/ts-utils/ts-ast.js @@ -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 @@ -26,7 +28,8 @@ module.exports = { isTSTypeLiteral, isTSTypeLiteralOrTSFunctionType, extractRuntimeProps, - extractRuntimeEmits + extractRuntimeEmits, + extractRuntimeSlots } /** @@ -209,6 +212,38 @@ function* extractRuntimeEmits(node) { } } +/** + * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node + * @returns {IterableIterator} + */ +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 diff --git a/lib/utils/ts-utils/ts-types.js b/lib/utils/ts-utils/ts-types.js index abb303862..2fe354c2c 100644 --- a/lib/utils/ts-utils/ts-types.js +++ b/lib/utils/ts-utils/ts-types.js @@ -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 } @@ -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 @@ -259,6 +290,23 @@ function* extractRuntimeEmits(type, tsNode, emitsNode, services) { } } +/** + * @param {Type} type + * @param {TypeNode} slotsNode Type with slots definition + * @returns {IterableIterator} + */ +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} diff --git a/tests/lib/rules/require-explicit-slots.js b/tests/lib/rules/require-explicit-slots.js index 92d1a1334..f99614119 100644 --- a/tests/lib/rules/require-explicit-slots.js +++ b/tests/lib/rules/require-explicit-slots.js @@ -34,6 +34,36 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -48,6 +78,36 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -62,6 +122,36 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -76,6 +166,36 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -90,6 +210,36 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -178,6 +328,40 @@ tester.run('require-explicit-slots', rule, { }>() ` }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` @@ -191,6 +375,36 @@ tester.run('require-explicit-slots', rule, { default(props: { msg: string }): any }>() ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` } ], invalid: [ @@ -261,6 +475,46 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, { filename: 'test.vue', code: ` @@ -280,6 +534,46 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, { filename: 'test.vue', code: ` @@ -299,6 +593,46 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, { filename: 'test.vue', code: ` @@ -342,6 +676,48 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + // ignore attribute binding except string literal + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, + { + // ignore attribute binding except string literal + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slots must be explicitly defined.' + } + ] + }, { filename: 'test.vue', code: ` @@ -362,6 +738,48 @@ tester.run('require-explicit-slots', rule, { } ] }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slot foo is already defined.' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slot foo is already defined.' + } + ] + }, { filename: 'test.vue', code: ` @@ -384,6 +802,56 @@ tester.run('require-explicit-slots', rule, { message: 'Slot foo is already defined.' } ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slot foo is already defined.' + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Slot foo is already defined.' + } + ] } ] }) diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts index 3e9184262..a9e8aaae9 100644 --- a/typings/eslint-plugin-vue/util-types/utils.ts +++ b/typings/eslint-plugin-vue/util-types/utils.ts @@ -42,8 +42,8 @@ export interface ScriptSetupVisitor extends ScriptSetupVisitorBase { onDefineEmitsExit?(node: CallExpression, emits: ComponentEmit[]): void onDefineOptionsEnter?(node: CallExpression): void onDefineOptionsExit?(node: CallExpression): void - onDefineSlotsEnter?(node: CallExpression): void - onDefineSlotsExit?(node: CallExpression): void + onDefineSlotsEnter?(node: CallExpression, slots: ComponentSlot[]): void + onDefineSlotsExit?(node: CallExpression, slots: ComponentSlot[]): void onDefineExposeEnter?(node: CallExpression): void onDefineExposeExit?(node: CallExpression): void onDefineModelEnter?(node: CallExpression, model: ComponentModel): void @@ -52,6 +52,7 @@ export interface ScriptSetupVisitor extends ScriptSetupVisitorBase { | ((node: VAST.ParamNode) => void) | ((node: CallExpression, props: ComponentProp[]) => void) | ((node: CallExpression, emits: ComponentEmit[]) => void) + | ((node: CallExpression, slots: ComponentSlot[]) => void) | ((node: CallExpression, model: ComponentModel) => void) | undefined } @@ -191,6 +192,66 @@ export type ComponentEmit = | ComponentInferTypeEmit | ComponentUnknownEmit +type ComponentArraySlotDetectName = { + type: 'array' + key: Literal | TemplateLiteral + slotName: string + node: Expression | SpreadElement +} +type ComponentArraySlotUnknownName = { + type: 'array' + key: null + slotName: null + node: Expression | SpreadElement +} +export type ComponentArraySlot = + | ComponentArraySlotDetectName + | ComponentArraySlotUnknownName + +type ComponentObjectSlotDetectName = { + type: 'object' + key: Expression + slotName: string + value: Expression + node: Property +} +type ComponentObjectSlotUnknownName = { + type: 'object' + key: null + slotName: null + value: Expression + node: Property +} +export type ComponentObjectSlot = + | ComponentObjectSlotDetectName + | ComponentObjectSlotUnknownName + +export type ComponentUnknownSlot = { + type: 'unknown' + slotName: null + node: Expression | SpreadElement | TypeNode | null +} + +export type ComponentTypeSlot = { + type: 'type' + key: Identifier | Literal + slotName: string + node: TSPropertySignature | TSMethodSignature +} + +export type ComponentInferTypeSlot = { + type: 'infer-type' + slotName: string + node: TypeNode +} + +export type ComponentSlot = + | ComponentArraySlot + | ComponentObjectSlot + | ComponentTypeSlot + | ComponentInferTypeSlot + | ComponentUnknownSlot + export type ComponentModelName = { modelName: string node: Literal | null From d4ce89d0a8157609cd83aaf11bf71a5f41ef877b Mon Sep 17 00:00:00 2001 From: Joe Ellis Date: Tue, 19 Nov 2024 14:22:32 +0000 Subject: [PATCH 2/3] test(getComponentSlotsFromTypeDefineTypes): add basic tests --- .../ts-utils/index/get-component-slots.js | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/lib/utils/ts-utils/index/get-component-slots.js diff --git a/tests/lib/utils/ts-utils/index/get-component-slots.js b/tests/lib/utils/ts-utils/index/get-component-slots.js new file mode 100644 index 000000000..410021b93 --- /dev/null +++ b/tests/lib/utils/ts-utils/index/get-component-slots.js @@ -0,0 +1,115 @@ +/** + * Test for getComponentSlotsFromTypeDefineTypes + */ +'use strict' + +const path = require('path') +const fs = require('fs') +const Linter = require('../../../../eslint-compat').Linter +const parser = require('vue-eslint-parser') +const tsParser = require('@typescript-eslint/parser') +const utils = require('../../../../../lib/utils/index') +const assert = require('assert') + +const FIXTURES_ROOT = path.resolve( + __dirname, + '../../../../fixtures/utils/ts-utils' +) +const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json') +const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts') + +function extractComponentSlots(code, tsFileCode) { + const linter = new Linter() + const result = [] + const config = { + files: ['**/*.vue'], + languageOptions: { + parser, + ecmaVersion: 2020, + parserOptions: { + parser: tsParser, + project: [TSCONFIG_PATH], + extraFileExtensions: ['.vue'] + } + }, + plugins: { + test: { + rules: { + test: { + create(context) { + return utils.defineScriptSetupVisitor(context, { + onDefineSlotsEnter(_node, slots) { + result.push( + ...slots.map((prop) => ({ + type: prop.type, + name: prop.slotName + })) + ) + } + }) + } + } + } + } + }, + rules: { + 'test/test': 'error' + } + } + fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8') + // clean './src/test.ts' cache + tsParser.clearCaches() + assert.deepStrictEqual( + linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')), + [] + ) + // reset + fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8') + return result +} + +describe('getComponentSlotsFromTypeDefineTypes', () => { + for (const { scriptCode, tsFileCode, slots: expected } of [ + { + scriptCode: ` + defineSlots<{ + default(props: { msg: string }): any + }>() + `, + slots: [{ type: 'type', name: 'default' }] + }, + { + scriptCode: ` + interface Slots { + default(props: { msg: string }): any + } + defineSlots() + `, + slots: [{ type: 'type', name: 'default' }] + }, + { + scriptCode: ` + type Slots = { + default(props: { msg: string }): any + } + defineSlots() + `, + slots: [{ type: 'type', name: 'default' }] + } + ]) { + const code = ` + + ` + it(`should return expected slots with :${code}`, () => { + const slots = extractComponentSlots(code, tsFileCode) + + assert.deepStrictEqual( + slots, + expected, + `\n${JSON.stringify(slots)}\n === \n${JSON.stringify(expected)}` + ) + }) + } +}) From 3b9ee761cd174e84ce196f4d8efd8ae3be30d9dc Mon Sep 17 00:00:00 2001 From: Joe Ellis Date: Wed, 27 Nov 2024 10:18:55 +0000 Subject: [PATCH 3/3] chore(require-explicit-slots): remove redundant `getComponentSlotsFromDefine` utility --- lib/utils/index.js | 76 ------------------- typings/eslint-plugin-vue/util-types/utils.ts | 36 --------- 2 files changed, 112 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index cf034b24b..167edf208 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -26,8 +26,6 @@ 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').ComponentArraySlot} ComponentArraySlot - * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectSlot} ComponentObjectSlot * @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 @@ -3387,19 +3385,6 @@ function getComponentEmitsFromDefineEmits(context, node) { * @return {ComponentSlot[]} Array of component slots */ function getComponentSlotsFromDefineSlots(context, node) { - if (node.arguments.length > 0) { - const defNode = getObjectOrArray(context, node.arguments[0]) - if (defNode) { - return getComponentSlotsFromDefine(defNode) - } - return [ - { - type: 'unknown', - slotName: null, - node: node.arguments[0] - } - ] - } const typeArguments = 'typeArguments' in node ? node.typeArguments : node.typeParameters if (typeArguments && typeArguments.params.length > 0) { @@ -3578,67 +3563,6 @@ function getComponentEmitsFromDefine(emitsNode) { }) } -/** - * Get all slots by looking at all component's properties - * @param {ObjectExpression|ArrayExpression} slotsNode Object with slots definition - * @return {(ComponentArraySlot | ComponentObjectSlot | ComponentUnknownSlot)[]} Array of component slots - */ -function getComponentSlotsFromDefine(slotsNode) { - if (slotsNode.type === 'ObjectExpression') { - return slotsNode.properties.map( - /** @returns {ComponentArraySlot | ComponentObjectSlot | ComponentUnknownSlot} */ - (slot) => { - if (!isProperty(slot)) { - return { - type: 'unknown', - slotName: null, - node: slot - } - } - const slotName = getStaticPropertyName(slot) - if (slotName != null) { - return { - type: 'object', - key: slot.key, - slotName, - value: skipTSAsExpression(slot.value), - node: slot - } - } - return { - type: 'object', - key: null, - slotName: null, - value: skipTSAsExpression(slot.value), - node: slot - } - } - ) - } - - return slotsNode.elements.filter(isDef).map((slot) => { - if (slot.type === 'Literal' || slot.type === 'TemplateLiteral') { - const slotName = getStringLiteralValue(slot) - if (slotName != null) { - return { - type: 'array', - key: slot, - slotName, - value: null, - node: slot - } - } - } - return { - type: 'array', - key: null, - slotName: null, - value: null, - node: slot - } - }) -} - /** * @param {RuleContext} context The rule context object. * @param {ESNode} node diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts index a9e8aaae9..ebe9933d3 100644 --- a/typings/eslint-plugin-vue/util-types/utils.ts +++ b/typings/eslint-plugin-vue/util-types/utils.ts @@ -192,40 +192,6 @@ export type ComponentEmit = | ComponentInferTypeEmit | ComponentUnknownEmit -type ComponentArraySlotDetectName = { - type: 'array' - key: Literal | TemplateLiteral - slotName: string - node: Expression | SpreadElement -} -type ComponentArraySlotUnknownName = { - type: 'array' - key: null - slotName: null - node: Expression | SpreadElement -} -export type ComponentArraySlot = - | ComponentArraySlotDetectName - | ComponentArraySlotUnknownName - -type ComponentObjectSlotDetectName = { - type: 'object' - key: Expression - slotName: string - value: Expression - node: Property -} -type ComponentObjectSlotUnknownName = { - type: 'object' - key: null - slotName: null - value: Expression - node: Property -} -export type ComponentObjectSlot = - | ComponentObjectSlotDetectName - | ComponentObjectSlotUnknownName - export type ComponentUnknownSlot = { type: 'unknown' slotName: null @@ -246,8 +212,6 @@ export type ComponentInferTypeSlot = { } export type ComponentSlot = - | ComponentArraySlot - | ComponentObjectSlot | ComponentTypeSlot | ComponentInferTypeSlot | ComponentUnknownSlot