diff --git a/lib/rules/no-boolean-default.js b/lib/rules/no-boolean-default.js index 6b1f44e74..5f74377e1 100644 --- a/lib/rules/no-boolean-default.js +++ b/lib/rules/no-boolean-default.js @@ -7,9 +7,7 @@ const utils = require('../utils') /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ // ------------------------------------------------------------------------------ @@ -55,7 +53,7 @@ module.exports = { create(context) { const booleanType = context.options[0] || 'no-default' /** - * @param {ComponentArrayProp | ComponentObjectProp | ComponentTypeProp} prop + * @param {ComponentProp} prop * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] */ function processProp(prop, withDefaultsExpressions) { @@ -84,7 +82,7 @@ module.exports = { } } /** - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentProp[]} props * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] */ function processProps(props, withDefaultsExpressions) { @@ -118,7 +116,7 @@ module.exports = { } return utils.compositingVisitors( utils.executeOnVueComponent(context, (obj) => { - processProps(utils.getComponentProps(obj)) + processProps(utils.getComponentPropsFromOptions(obj)) }), utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(node, props) { diff --git a/lib/rules/no-deprecated-props-default-this.js b/lib/rules/no-deprecated-props-default-this.js index 7f9b51a1a..174c6bfae 100644 --- a/lib/rules/no-deprecated-props-default-this.js +++ b/lib/rules/no-deprecated-props-default-this.js @@ -96,7 +96,7 @@ module.exports = { } return utils.defineVueVisitor(context, { onVueObjectEnter(node) { - for (const prop of utils.getComponentProps(node)) { + for (const prop of utils.getComponentPropsFromOptions(node)) { if (prop.type !== 'object') { continue } diff --git a/lib/rules/no-mutating-props.js b/lib/rules/no-mutating-props.js index 93855fd4e..b7ffcd6f7 100644 --- a/lib/rules/no-mutating-props.js +++ b/lib/rules/no-mutating-props.js @@ -297,7 +297,7 @@ module.exports = { node, new Set( utils - .getComponentProps(node) + .getComponentPropsFromOptions(node) .map((p) => p.propName) .filter(utils.isDef) ) diff --git a/lib/rules/no-reserved-keys.js b/lib/rules/no-reserved-keys.js index ea684618f..de2f9453e 100644 --- a/lib/rules/no-reserved-keys.js +++ b/lib/rules/no-reserved-keys.js @@ -67,8 +67,9 @@ module.exports = { return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(_node, props) { - for (const { propName, node } of props) { - if (propName && reservedKeys.has(propName)) { + for (const prop of props) { + if (prop.propName && reservedKeys.has(prop.propName)) { + const { propName, node } = prop context.report({ node, messageId: 'reserved', diff --git a/lib/rules/no-reserved-props.js b/lib/rules/no-reserved-props.js index 8694d58b5..44ba54dfd 100644 --- a/lib/rules/no-reserved-props.js +++ b/lib/rules/no-reserved-props.js @@ -12,9 +12,7 @@ const utils = require('../utils') const casing = require('../utils/casing') /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ // ------------------------------------------------------------------------------ @@ -67,7 +65,7 @@ module.exports = { const reserved = new Set(RESERVED[vueVersion]) /** - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentProp[]} props */ function processProps(props) { for (const prop of props) { @@ -90,7 +88,7 @@ module.exports = { } }), utils.executeOnVue(context, (obj) => { - processProps(utils.getComponentProps(obj)) + processProps(utils.getComponentPropsFromOptions(obj)) }) ) } diff --git a/lib/rules/no-restricted-props.js b/lib/rules/no-restricted-props.js index 6834df55c..7a92b9e51 100644 --- a/lib/rules/no-restricted-props.js +++ b/lib/rules/no-restricted-props.js @@ -8,9 +8,7 @@ const utils = require('../utils') const regexp = require('../utils/regexp') /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ /** @@ -96,7 +94,7 @@ module.exports = { const options = context.options.map(parseOption) /** - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentProp[]} props * @param { { [key: string]: Property | undefined } } [withDefaultsProps] */ function processProps(props, withDefaultsProps) { @@ -133,7 +131,7 @@ module.exports = { }), utils.defineVueVisitor(context, { onVueObjectEnter(node) { - processProps(utils.getComponentProps(node)) + processProps(utils.getComponentPropsFromOptions(node)) } }) ) diff --git a/lib/rules/prop-name-casing.js b/lib/rules/prop-name-casing.js index b21147957..916dff0cd 100644 --- a/lib/rules/prop-name-casing.js +++ b/lib/rules/prop-name-casing.js @@ -9,9 +9,7 @@ const casing = require('../utils/casing') const allowedCaseOptions = ['camelCase', 'snake_case'] /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ // ------------------------------------------------------------------------------ @@ -29,7 +27,7 @@ function create(context) { // ---------------------------------------------------------------------- /** - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentProp[]} props */ function processProps(props) { for (const item of props) { @@ -56,7 +54,7 @@ function create(context) { } }), utils.executeOnVue(context, (obj) => { - processProps(utils.getComponentProps(obj)) + processProps(utils.getComponentPropsFromOptions(obj)) }) ) } diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index e3a8a4076..7cfdc7efa 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -5,9 +5,8 @@ 'use strict' /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp + * @typedef {import('../utils').ComponentProp} ComponentProp * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp * @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject */ @@ -145,7 +144,7 @@ module.exports = { } /** - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentProp[]} props * @param {boolean} [withDefaults] * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] */ @@ -209,7 +208,7 @@ module.exports = { } }), utils.executeOnVue(context, (obj) => { - processProps(utils.getComponentProps(obj)) + processProps(utils.getComponentPropsFromOptions(obj)) }) ) } diff --git a/lib/rules/require-emit-validator.js b/lib/rules/require-emit-validator.js index 603bc728d..24c6a9c55 100644 --- a/lib/rules/require-emit-validator.js +++ b/lib/rules/require-emit-validator.js @@ -7,9 +7,7 @@ const utils = require('../utils') /** - * @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit - * @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit - * @typedef {import('../utils').ComponentTypeEmit} ComponentTypeEmit + * @typedef {import('../utils').ComponentEmit} ComponentEmit */ // ------------------------------------------------------------------------------ @@ -41,9 +39,13 @@ module.exports = { // ---------------------------------------------------------------------- /** - * @param {ComponentArrayEmit|ComponentObjectEmit} emit + * @param {ComponentEmit} emit */ - function checker({ value, node, emitName }) { + function checker(emit) { + if (emit.type !== 'object' && emit.type !== 'array') { + return + } + const { value, node, emitName } = emit const hasType = !!value && (value.type === 'ArrowFunctionExpression' || @@ -87,16 +89,11 @@ module.exports = { return utils.compositingVisitors( utils.executeOnVue(context, (obj) => { - utils.getComponentEmits(obj).forEach(checker) + utils.getComponentEmitsFromOptions(obj).forEach(checker) }), utils.defineScriptSetupVisitor(context, { onDefineEmitsEnter(_node, emits) { - for (const emit of emits) { - if (emit.type === 'type') { - continue - } - checker(emit) - } + emits.forEach(checker) } }) ) diff --git a/lib/rules/require-explicit-emits.js b/lib/rules/require-explicit-emits.js index eecac6dd4..aa83b882f 100644 --- a/lib/rules/require-explicit-emits.js +++ b/lib/rules/require-explicit-emits.js @@ -5,12 +5,8 @@ 'use strict' /** - * @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit - * @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit - * @typedef {import('../utils').ComponentTypeEmit} ComponentTypeEmit - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentEmit} ComponentEmit + * @typedef {import('../utils').ComponentProp} ComponentProp * @typedef {import('../utils').VueObjectData} VueObjectData */ @@ -99,36 +95,36 @@ module.exports = { const allowProps = !!options.allowProps /** @type {Map, emitReferenceIds: Set }>} */ const setupContexts = new Map() - /** @type {Map} */ + /** @type {Map} */ const vueEmitsDeclarations = new Map() - /** @type {Map} */ + /** @type {Map} */ const vuePropsDeclarations = new Map() /** * @typedef {object} VueTemplateDefineData * @property {'export' | 'mark' | 'definition' | 'setup'} type * @property {ObjectExpression | Program} define - * @property {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits - * @property {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @property {ComponentEmit[]} emits + * @property {ComponentProp[]} props * @property {CallExpression} [defineEmits] */ /** @type {VueTemplateDefineData | null} */ let vueTemplateDefineData = null /** - * @param {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits - * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param {ComponentEmit[]} emits + * @param {ComponentProp[]} props * @param {Literal} nameLiteralNode * @param {ObjectExpression | Program} vueDefineNode */ function verifyEmit(emits, props, nameLiteralNode, vueDefineNode) { const name = `${nameLiteralNode.value}` - if (emits.some((e) => e.emitName === name)) { + if (emits.some((e) => e.emitName === name || e.emitName == null)) { return } if (allowProps) { const key = `on${capitalize(name)}` - if (props.some((e) => e.propName === key)) { + if (props.some((e) => e.propName === key || e.propName == null)) { return } } @@ -311,9 +307,15 @@ module.exports = { }), utils.defineVueVisitor(context, { onVueObjectEnter(node) { - vueEmitsDeclarations.set(node, utils.getComponentEmits(node)) + vueEmitsDeclarations.set( + node, + utils.getComponentEmitsFromOptions(node) + ) if (allowProps) { - vuePropsDeclarations.set(node, utils.getComponentProps(node)) + vuePropsDeclarations.set( + node, + utils.getComponentPropsFromOptions(node) + ) } }, onSetupFunctionEnter(node, { node: vueNode }) { @@ -409,7 +411,7 @@ module.exports = { /** * @param {ObjectExpression|Program} define - * @param {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits + * @param {ComponentEmit[]} emits * @param {Literal} nameNode * @param {RuleContext} context * @returns {Rule.SuggestionReportDescriptor[]} diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js index f22da8118..aa022faee 100644 --- a/lib/rules/require-prop-type-constructor.js +++ b/lib/rules/require-prop-type-constructor.js @@ -8,9 +8,7 @@ const utils = require('../utils') const { isDef } = require('../utils') /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp - * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ // ------------------------------------------------------------------------------ @@ -82,7 +80,7 @@ module.exports = { ) } - /** @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props */ + /** @param {ComponentProp[]} props */ function verifyProps(props) { for (const prop of props) { if (!prop.value || prop.propName == null) { @@ -110,7 +108,7 @@ module.exports = { } }), utils.executeOnVueComponent(context, (obj) => { - verifyProps(utils.getComponentProps(obj)) + verifyProps(utils.getComponentPropsFromOptions(obj)) }) ) } diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js index 3978db3f0..b57bfc6fc 100644 --- a/lib/rules/require-prop-types.js +++ b/lib/rules/require-prop-types.js @@ -7,8 +7,7 @@ const utils = require('../utils') /** - * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp - * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp + * @typedef {import('../utils').ComponentProp} ComponentProp */ // ------------------------------------------------------------------------------ @@ -54,9 +53,13 @@ module.exports = { } /** - * @param { ComponentArrayProp | ComponentObjectProp } prop + * @param {ComponentProp} prop */ - function checkProperty({ value, node, propName }) { + function checkProperty(prop) { + if (prop.type !== 'object' && prop.type !== 'array') { + return + } + const { value, node, propName } = prop let hasType = true if (!value) { @@ -96,15 +99,12 @@ module.exports = { utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(_node, props) { for (const prop of props) { - if (prop.type === 'type') { - continue - } checkProperty(prop) } } }), utils.executeOnVue(context, (obj) => { - const props = utils.getComponentProps(obj) + const props = utils.getComponentPropsFromOptions(obj) for (const prop of props) { checkProperty(prop) diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index ab0355206..21191e86f 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -10,6 +10,7 @@ const { capitalize } = require('../utils/casing') * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp + * @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp * @typedef {import('../utils').VueObjectData} VueObjectData */ @@ -349,9 +350,9 @@ module.exports = { utils.defineVueVisitor(context, { onVueObjectEnter(obj) { /** @type {ComponentObjectDefineProp[]} */ - const props = utils.getComponentProps(obj).filter( + const props = utils.getComponentPropsFromOptions(obj).filter( /** - * @param {ComponentObjectProp | ComponentArrayProp} prop + * @param {ComponentObjectProp | ComponentArrayProp | ComponentUnknownProp} prop * @returns {prop is ComponentObjectDefineProp} */ (prop) => @@ -397,7 +398,7 @@ module.exports = { /** @type {(ComponentObjectDefineProp | ComponentTypeProp)[]} */ const props = baseProps.filter( /** - * @param {ComponentObjectProp | ComponentArrayProp | ComponentTypeProp} prop + * @param {ComponentObjectProp | ComponentArrayProp | ComponentTypeProp | ComponentUnknownProp} prop * @returns {prop is ComponentObjectDefineProp | ComponentTypeProp} */ (prop) => diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js index 03d197b18..94e94ff48 100644 --- a/lib/rules/return-in-emits-validator.js +++ b/lib/rules/return-in-emits-validator.js @@ -7,9 +7,8 @@ const utils = require('../utils') /** - * @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit + * @typedef {import('../utils').ComponentEmit} ComponentEmit * @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit - * @typedef {import('../utils').ComponentTypeEmit} ComponentTypeEmit */ /** @@ -69,7 +68,7 @@ module.exports = { let scopeStack = null /** - * @param {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[]} emits + * @param {ComponentEmit[]} emits */ function processEmits(emits) { for (const emit of emits) { @@ -94,7 +93,7 @@ module.exports = { utils.defineVueVisitor(context, { /** @param {ObjectExpression} obj */ onVueObjectEnter(obj) { - processEmits(utils.getComponentEmits(obj)) + processEmits(utils.getComponentEmitsFromOptions(obj)) } }), { diff --git a/lib/utils/index.js b/lib/utils/index.js index d6c3a6f5a..f4ac0cc63 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -15,9 +15,13 @@ * @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 {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownProp} ComponentUnknownProp + * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentProp} ComponentProp * @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 {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit + * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit */ /** * @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty @@ -811,58 +815,27 @@ module.exports = { /** * Get all props by looking at all component's properties * @param {ObjectExpression} componentObject Object with component definition - * @return {(ComponentArrayProp | ComponentObjectProp)[]} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}] + * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props */ - getComponentProps(componentObject) { - const propsNode = componentObject.properties.find( - /** - * @param {ESNode} p - * @returns {p is (Property & { key: Identifier & {name: 'props'}, value: ObjectExpression | ArrayExpression })} - */ - (p) => { - return ( - p.type === 'Property' && - getStaticPropertyName(p) === 'props' && - (p.value.type === 'ObjectExpression' || - p.value.type === 'ArrayExpression') - ) - } - ) - - if (!propsNode) { - return [] - } - - return getComponentPropsFromDefine(propsNode.value) - }, - + getComponentPropsFromOptions, + // TODO Since `utils` is used in some popular packages, we will remove it in the major version. + /** + * For backward compatibility. + * @deprecated Use getComponentPropsFromOptions instead. + */ + getComponentProps: getComponentPropsFromOptions, /** * Get all emits by looking at all component's properties * @param {ObjectExpression} componentObject Object with component definition - * @return {(ComponentArrayEmit | ComponentObjectEmit)[]} Array of component emits in format: [{key?: String, value?: ASTNode, node: ASTNod}] + * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits */ - getComponentEmits(componentObject) { - const emitsNode = componentObject.properties.find( - /** - * @param {ESNode} p - * @returns {p is (Property & { key: Identifier & {name: 'emits'}, value: ObjectExpression | ArrayExpression })} - */ - (p) => { - return ( - p.type === 'Property' && - getStaticPropertyName(p) === 'emits' && - (p.value.type === 'ObjectExpression' || - p.value.type === 'ArrayExpression') - ) - } - ) - - if (!emitsNode) { - return [] - } - - return getComponentEmitsFromDefine(emitsNode.value) - }, + getComponentEmitsFromOptions, + // TODO Since `utils` is used in some popular packages, we will remove it in the major version. + /** + * For backward compatibility. + * @deprecated Use getComponentEmitsFromOptions instead. + */ + getComponentEmits: getComponentEmitsFromOptions, /** * Get all computed properties by looking at all component's properties @@ -1143,35 +1116,6 @@ module.exports = { scriptSetupVisitor[key] = (node) => callVisitor(key, node) } - /** - * @param {ESNode} node - * @returns {ObjectExpression | ArrayExpression | null} - */ - function getObjectOrArray(node) { - if (node.type === 'ObjectExpression') { - return node - } - if (node.type === 'ArrayExpression') { - return node - } - if (node.type === 'Identifier') { - const variable = findVariable(context.getScope(), node) - - if (variable != null && variable.defs.length === 1) { - const def = variable.defs[0] - if ( - def.type === 'Variable' && - def.parent.kind === 'const' && - def.node.id.type === 'Identifier' && - def.node.init - ) { - return getObjectOrArray(def.node.init) - } - } - } - return null - } - const hasPropsEvent = visitor.onDefinePropsEnter || visitor.onDefinePropsExit const hasEmitsEvent = @@ -1216,22 +1160,9 @@ module.exports = { candidateMacro === getWithDefaults(node)) && 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] - ) - } + /** @type {ComponentProp[]} */ + const props = getComponentPropsFromDefineProps(context, node) + callVisitor('onDefinePropsEnter', node, props) definePropsMap.set(node, props) } else if ( @@ -1239,22 +1170,9 @@ module.exports = { candidateMacro === node && 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] - ) - } + /** @type {ComponentEmit[]} */ + const emits = getComponentEmitsFromDefineEmits(context, node) + callVisitor('onDefineEmitsEnter', node, emits) defineEmitsMap.set(node, emits) } @@ -2811,14 +2729,174 @@ function getWithDefaultsProps(node) { return result } +/** + * Get all props from component options object. + * @param {ObjectExpression} componentObject Object with component definition + * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props + */ +function getComponentPropsFromOptions(componentObject) { + const propsNode = componentObject.properties.find( + /** + * @param {ESNode} p + * @returns {p is (Property & { key: Identifier & {name: 'props'} })} + */ + (p) => { + return p.type === 'Property' && getStaticPropertyName(p) === 'props' + } + ) + + if (!propsNode) { + return [] + } + if ( + propsNode.value.type !== 'ObjectExpression' && + propsNode.value.type !== 'ArrayExpression' + ) { + return [ + { + type: 'unknown', + key: null, + propName: null, + value: null, + node: propsNode.value + } + ] + } + + return getComponentPropsFromDefine(propsNode.value) +} + +/** + * Get all emits from component options object. + * @param {ObjectExpression} componentObject Object with component definition + * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits + */ +function getComponentEmitsFromOptions(componentObject) { + const emitsNode = componentObject.properties.find( + /** + * @param {ESNode} p + * @returns {p is (Property & { key: Identifier & {name: 'emits'} })} + */ + (p) => { + return p.type === 'Property' && getStaticPropertyName(p) === 'emits' + } + ) + + if (!emitsNode) { + return [] + } + if ( + emitsNode.value.type !== 'ObjectExpression' && + emitsNode.value.type !== 'ArrayExpression' + ) { + return [ + { + type: 'unknown', + key: null, + emitName: null, + value: null, + node: emitsNode.value + } + ] + } + + return getComponentEmitsFromDefine(emitsNode.value) +} + +/** + * Get all props from `defineProps` call expression. + * @param {RuleContext} context The rule context object. + * @param {CallExpression} node `defineProps` call expression + * @return {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp | ComponentUnknownProp)[]} Array of component props + */ +function getComponentPropsFromDefineProps(context, node) { + if (node.arguments.length >= 1) { + const defNode = getObjectOrArray(context, node.arguments[0]) + if (defNode) { + return getComponentPropsFromDefine(defNode) + } + return [ + { + type: 'unknown', + key: null, + propName: null, + value: null, + node: node.arguments[0] + } + ] + } + if (node.typeParameters && node.typeParameters.params.length >= 1) { + return getComponentPropsFromTypeDefine( + context, + node.typeParameters.params[0] + ) + } + return [ + { + type: 'unknown', + key: null, + propName: null, + value: null, + node: null + } + ] +} + +/** + * Get all emits from `defineEmits` call expression. + * @param {RuleContext} context The rule context object. + * @param {CallExpression} node `defineEmits` call expression + * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit | ComponentUnknownEmit)[]} Array of component emits + */ +function getComponentEmitsFromDefineEmits(context, node) { + if (node.arguments.length >= 1) { + const defNode = getObjectOrArray(context, node.arguments[0]) + if (defNode) { + return getComponentEmitsFromDefine(defNode) + } + return [ + { + type: 'unknown', + key: null, + emitName: null, + value: null, + node: node.arguments[0] + } + ] + } + if (node.typeParameters && node.typeParameters.params.length >= 1) { + return getComponentEmitsFromTypeDefine( + context, + node.typeParameters.params[0] + ) + } + return [ + { + type: 'unknown', + key: null, + emitName: null, + value: null, + node: null + } + ] +} /** * Get all props by looking at all component's properties * @param {ObjectExpression|ArrayExpression} propsNode Object with props definition - * @return {(ComponentArrayProp | ComponentObjectProp)[]} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}] + * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props */ function getComponentPropsFromDefine(propsNode) { if (propsNode.type === 'ObjectExpression') { - return propsNode.properties.filter(isProperty).map((prop) => { + return propsNode.properties.map((prop) => { + if (!isProperty(prop)) { + return { + type: 'unknown', + key: null, + propName: null, + value: null, + node: prop + } + } const propName = getStaticPropertyName(prop) if (propName != null) { return { @@ -2865,11 +2943,20 @@ 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 + * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits. */ function getComponentEmitsFromDefine(emitsNode) { if (emitsNode.type === 'ObjectExpression') { - return emitsNode.properties.filter(isProperty).map((prop) => { + return emitsNode.properties.map((prop) => { + if (!isProperty(prop)) { + return { + type: 'unknown', + key: null, + emitName: null, + value: null, + node: prop + } + } const emitName = getStaticPropertyName(prop) if (emitName != null) { return { @@ -2912,3 +2999,33 @@ function getComponentEmitsFromDefine(emitsNode) { }) } } + +/** + * @param {RuleContext} context The rule context object. + * @param {ESNode} node + * @returns {ObjectExpression | ArrayExpression | null} + */ +function getObjectOrArray(context, node) { + if (node.type === 'ObjectExpression') { + return node + } + if (node.type === 'ArrayExpression') { + return node + } + if (node.type === 'Identifier') { + const variable = findVariable(context.getScope(), node) + + if (variable != null && variable.defs.length === 1) { + const def = variable.defs[0] + if ( + def.type === 'Variable' && + def.parent.kind === 'const' && + def.node.id.type === 'Identifier' && + def.node.init + ) { + return getObjectOrArray(context, def.node.init) + } + } + } + return null +} diff --git a/tests/lib/rules/require-explicit-emits.js b/tests/lib/rules/require-explicit-emits.js index 1ea1758d2..1146cd2cc 100644 --- a/tests/lib/rules/require-explicit-emits.js +++ b/tests/lib/rules/require-explicit-emits.js @@ -457,6 +457,142 @@ tester.run('require-explicit-emits', rule, { `, parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + + // unknown emits definition + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + + // unknown props definition + { + filename: 'test.vue', + code: ` + + + `, + options: [{ allowProps: true }] + }, + { + filename: 'test.vue', + code: ` + + + `, + options: [{ allowProps: true }] + }, + { + filename: 'test.vue', + code: ` + + + `, + options: [{ allowProps: true }] + }, + { + filename: 'test.vue', + code: ` + + + `, + options: [{ allowProps: true }] } ], invalid: [ @@ -1226,40 +1362,6 @@ emits: {'foo': null} } ] }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "foo" event has been triggered but not declared on `emits` option.', - suggestions: [ - { - desc: 'Add the "foo" to `emits` option.', - output: ` - - - ` - } - ] - } - ] - }, { filename: 'test.vue', code: ` @@ -1294,60 +1396,6 @@ emits: {'foo': null} } ] }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "foo" event has been triggered but not declared on `emits` option.', - suggestions: [ - { - desc: 'Add the "foo" to `emits` option.', - output: ` - - - ` - } - ] - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "foo" event has been triggered but not declared on `emits` option.', - suggestions: [] - } - ] - }, { filename: 'test.vue', code: ` diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js index b4a052090..89c81942d 100644 --- a/tests/lib/utils/index.js +++ b/tests/lib/utils/index.js @@ -120,19 +120,19 @@ describe('getStaticPropertyName', () => { const node = parse(`const test = { computed: { } }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse property expression with literal', () => { const node = parse(`const test = { ['computed'] () {} }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse property expression with template literal', () => { const node = parse(`const test = { [\`computed\`] () {} }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) }) @@ -146,13 +146,13 @@ describe('getStringLiteralValue', () => { const node = parse(`const test = { ['computed'] () {} }`) const parsed = utils.getStringLiteralValue(node.properties[0].key) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse template literal', () => { const node = parse(`const test = { [\`computed\`] () {} }`) const parsed = utils.getStringLiteralValue(node.properties[0].key) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) }) @@ -388,23 +388,27 @@ describe('getComponentProps', () => { } }`) - assert.equal(props.length, 4, 'it detects all props') + assert.equal(props.length, 5, 'it detects all props') + + assert.strictEqual(props[0].key, null) + assert.strictEqual(props[0].node.type, 'SpreadElement') + assert.strictEqual(props[0].value, null) - assert.ok(props[0].key.type === 'Identifier') - assert.ok(props[0].node.type === 'Property') - assert.ok(props[0].value.type === 'Identifier') + assert.strictEqual(props[1].key.type, 'Identifier') + assert.strictEqual(props[1].node.type, 'Property') + assert.strictEqual(props[1].value.type, 'Identifier') - assert.ok(props[1].key.type === 'Identifier') - assert.ok(props[1].node.type === 'Property') - assert.ok(props[1].value.type === 'ObjectExpression') + assert.strictEqual(props[2].key.type, 'Identifier') + assert.strictEqual(props[2].node.type, 'Property') + assert.strictEqual(props[2].value.type, 'ObjectExpression') - assert.ok(props[2].key.type === 'Identifier') - assert.ok(props[2].node.type === 'Property') - assert.ok(props[2].value.type === 'ArrayExpression') + assert.strictEqual(props[3].key.type, 'Identifier') + assert.strictEqual(props[3].node.type, 'Property') + assert.strictEqual(props[3].value.type, 'ArrayExpression') - assert.deepEqual(props[3].key, props[3].value) - assert.ok(props[3].node.type === 'Property') - assert.ok(props[3].value.type === 'Identifier') + assert.deepEqual(props[4].key, props[4].value) + assert.strictEqual(props[4].node.type, 'Property') + assert.strictEqual(props[4].value.type, 'Identifier') }) it('should return computed from array props', () => { @@ -418,21 +422,21 @@ describe('getComponentProps', () => { assert.equal(props.length, 4, 'it detects all props') - assert.ok(props[0].node.type === 'Literal') + assert.strictEqual(props[0].node.type, 'Literal') assert.deepEqual(props[0].key, props[0].node) - assert.ok(!props[0].value) + assert.strictEqual(props[0].value, null) - assert.ok(props[1].node.type === 'Identifier') - assert.ok(!props[1].key) - assert.ok(!props[1].value) + assert.strictEqual(props[1].node.type, 'Identifier') + assert.strictEqual(props[1].key, null) + assert.strictEqual(props[1].value, null) - assert.ok(props[2].node.type === 'TemplateLiteral') + assert.strictEqual(props[2].node.type, 'TemplateLiteral') assert.deepEqual(props[2].key, props[2].node) - assert.ok(!props[2].value) + assert.strictEqual(props[2].value, null) - assert.ok(props[3].node.type === 'Literal') - assert.ok(!props[3].key) - assert.ok(!props[3].value) + assert.strictEqual(props[3].node.type, 'Literal') + assert.strictEqual(props[3].key, null) + assert.strictEqual(props[3].value, null) }) }) diff --git a/typings/eslint-plugin-vue/util-types/utils.ts b/typings/eslint-plugin-vue/util-types/utils.ts index 73b9e81c7..d998761be 100644 --- a/typings/eslint-plugin-vue/util-types/utils.ts +++ b/typings/eslint-plugin-vue/util-types/utils.ts @@ -36,32 +36,14 @@ type ScriptSetupVisitorBase = { [T in keyof NodeListenerMap]?: (node: NodeListenerMap[T]) => void } export interface ScriptSetupVisitor extends ScriptSetupVisitorBase { - onDefinePropsEnter?( - node: CallExpression, - props: (ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[] - ): void - onDefinePropsExit?( - node: CallExpression, - props: (ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[] - ): void - onDefineEmitsEnter?( - node: CallExpression, - props: (ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[] - ): void - onDefineEmitsExit?( - node: CallExpression, - props: (ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[] - ): void + onDefinePropsEnter?(node: CallExpression, props: ComponentProp[]): void + onDefinePropsExit?(node: CallExpression, props: ComponentProp[]): void + onDefineEmitsEnter?(node: CallExpression, emits: ComponentEmit[]): void + onDefineEmitsExit?(node: CallExpression, emits: ComponentEmit[]): void [query: string]: | ((node: VAST.ParamNode) => void) - | (( - node: CallExpression, - props: (ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[] - ) => void) - | (( - node: CallExpression, - props: (ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit)[] - ) => void) + | ((node: CallExpression, props: ComponentProp[]) => void) + | ((node: CallExpression, emits: ComponentEmit[]) => void) | undefined } @@ -101,6 +83,14 @@ export type ComponentObjectProp = | ComponentObjectPropDetectName | ComponentObjectPropUnknownName +export type ComponentUnknownProp = { + type: 'unknown' + key: null + propName: null + value: null + node: Expression | SpreadElement | null +} + export type ComponentTypeProp = { type: 'type' key: Identifier | Literal @@ -112,6 +102,12 @@ export type ComponentTypeProp = { types: string[] } +export type ComponentProp = + | ComponentArrayProp + | ComponentObjectProp + | ComponentTypeProp + | ComponentUnknownProp + type ComponentArrayEmitDetectName = { type: 'array' key: Literal | TemplateLiteral @@ -143,10 +139,19 @@ type ComponentObjectEmitUnknownName = { value: Expression node: Property } + export type ComponentObjectEmit = | ComponentObjectEmitDetectName | ComponentObjectEmitUnknownName +export type ComponentUnknownEmit = { + type: 'unknown' + key: null + emitName: null + value: null + node: Expression | SpreadElement | null +} + export type ComponentTypeEmit = { type: 'type' key: TSLiteralType @@ -154,3 +159,9 @@ export type ComponentTypeEmit = { value: null node: TSCallSignatureDeclaration | TSFunctionType } + +export type ComponentEmit = + | ComponentArrayEmit + | ComponentObjectEmit + | ComponentTypeEmit + | ComponentUnknownEmit