diff --git a/docs/rules/no-template-shadow.md b/docs/rules/no-template-shadow.md index a03d85938..5bddba45c 100644 --- a/docs/rules/no-template-shadow.md +++ b/docs/rules/no-template-shadow.md @@ -11,7 +11,7 @@ since: v5.0.0 - :gear: This rule is included in all of `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`. -`no-template-shadow` should report variable definitions of v-for directives or scope attributes if those shadows the variables in parent scopes. +`no-template-shadow` should report variable definitions of v-for directives or scope attributes if they shadow the variables in parent scopes. ## :book: Rule Details diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js index b405c0475..a762d3f58 100644 --- a/lib/rules/no-async-in-computed-properties.js +++ b/lib/rules/no-async-in-computed-properties.js @@ -58,6 +58,24 @@ function isPromise(node) { return false } +/** + * @param {CallExpression} node + * @param {RuleContext} context + */ +function isNextTick(node, context) { + const callee = utils.skipChainExpression(node.callee) + if (callee.type === 'MemberExpression') { + const name = utils.getStaticPropertyName(callee) + return ( + (utils.isThis(callee.object, context) && name === '$nextTick') || + (callee.object.type === 'Identifier' && + callee.object.name === 'Vue' && + name === 'nextTick') + ) + } + return false +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -90,6 +108,7 @@ module.exports = { const expressionTypes = { promise: 'asynchronous action', + nextTick: 'asynchronous action', await: 'await operator', async: 'async function declaration', new: 'Promise object', @@ -211,6 +230,13 @@ module.exports = { 'timed', info ? computedPropertiesMap.get(info.node) : null ) + } else if (isNextTick(node, context)) { + verify( + node, + scopeStack.body, + 'nextTick', + info ? computedPropertiesMap.get(info.node) : null + ) } }, 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/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js index a0d508014..6e43266fb 100644 --- a/lib/rules/no-side-effects-in-computed-properties.js +++ b/lib/rules/no-side-effects-in-computed-properties.js @@ -83,15 +83,24 @@ module.exports = { ) }) if (computedProperty) { - if (!utils.isThis(node, context)) { - return - } const mem = node.parent if (mem.object !== node) { return } - const invalid = utils.findMutating(mem) + const isThis = utils.isThis(node, context) + const isVue = node.type === 'Identifier' && node.name === 'Vue' + + const isVueSet = + mem.parent.type === 'CallExpression' && + mem.property.type === 'Identifier' && + ((isThis && mem.property.name === '$set') || + (isVue && mem.property.name === 'set')) + + const invalid = isVueSet + ? { node: mem.property } + : isThis && utils.findMutating(mem) + if (invalid) { context.report({ node: invalid.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/indent-common.js b/lib/utils/indent-common.js index d208d5a08..6897ea4e7 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -301,7 +301,7 @@ module.exports.defineVisitor = function create( tokenStore.getTokenAfter(node) /** @type {SourceCode.CursorWithSkipOptions} */ - const option = { + const cursorOptions = { includeComments: true, filter: (token) => token != null && @@ -311,11 +311,11 @@ module.exports.defineVisitor = function create( token.type === 'HTMLEndTagOpen' || token.type === 'HTMLComment') } - for (const token of tokenStore.getTokensBetween( - node.startTag, - endToken, - option - )) { + const contentTokens = endToken + ? tokenStore.getTokensBetween(node.startTag, endToken, cursorOptions) + : tokenStore.getTokensAfter(node.startTag, cursorOptions) + + for (const token of contentTokens) { ignoreTokens.add(token) } ignoreTokens.add(endToken) 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/package.json b/package.json index bcc6bfcdd..5f9cdff49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-vue", - "version": "8.2.0", + "version": "8.3.0", "description": "Official ESLint plugin for Vue.js", "main": "lib/index.js", "scripts": { diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js index 316eb9257..8340f9423 100644 --- a/tests/lib/rules/html-indent.js +++ b/tests/lib/rules/html-indent.js @@ -403,6 +403,22 @@ tester.run( text ` + }, + { + filename: 'test.vue', + code: unIndent` +