diff --git a/docs/rules/use-v-on-exact.md b/docs/rules/use-v-on-exact.md index b89b15e2c..08e0629fe 100644 --- a/docs/rules/use-v-on-exact.md +++ b/docs/rules/use-v-on-exact.md @@ -32,11 +32,7 @@ This rule enforce usage of `exact` modifier on `v-on` when there is another `v-o ## :wrench: Options -```json -{ - "vue/use-v-on-exact": ["error"] -} -``` +Nothing. ## :couple: Related Rules diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index fc1849b18..487af7714 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -123,7 +123,8 @@ module.exports = { if ( (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || utils.isHtmlWellKnownElementName(node.rawName) || - utils.isSvgWellKnownElementName(node.rawName) + utils.isSvgWellKnownElementName(node.rawName) || + utils.isVueBuiltInElementName(node.rawName) ) { return false } diff --git a/lib/rules/define-emits-declaration.js b/lib/rules/define-emits-declaration.js index 4daf4115c..478e1d840 100644 --- a/lib/rules/define-emits-declaration.js +++ b/lib/rules/define-emits-declaration.js @@ -47,7 +47,8 @@ module.exports = { } case 'runtime': { - if (node.typeParameters && node.typeParameters.params.length > 0) { + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params.length > 0) { context.report({ node, messageId: 'hasTypeArg' diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js index c6fe4ffdc..d355bb2fc 100644 --- a/lib/rules/define-props-declaration.js +++ b/lib/rules/define-props-declaration.js @@ -47,7 +47,8 @@ module.exports = { } case 'runtime': { - if (node.typeParameters && node.typeParameters.params.length > 0) { + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params.length > 0) { context.report({ node, messageId: 'hasTypeArg' diff --git a/lib/rules/prefer-define-options.js b/lib/rules/prefer-define-options.js index cd9240445..90c83fa9a 100644 --- a/lib/rules/prefer-define-options.js +++ b/lib/rules/prefer-define-options.js @@ -34,9 +34,14 @@ module.exports = { let defineOptionsNode = null /** @type {ExportDefaultDeclaration | null} */ let exportDefaultDeclaration = null + /** @type {ImportDeclaration|null} */ + let lastImportDeclaration = null return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { + ImportDeclaration(node) { + lastImportDeclaration = node + }, onDefineOptionsEnter(node) { defineOptionsNode = node } @@ -109,10 +114,13 @@ module.exports = { }) } + /** @type {VStartTag | ImportDeclaration} */ + const insertAfterTag = lastImportDeclaration || scriptSetup.startTag + return [ fixer.removeRange(removeRange), fixer.insertTextAfter( - scriptSetup.startTag, + insertAfterTag, `\ndefineOptions(${sourceCode.getText(node.declaration)})\n` ) ] diff --git a/lib/rules/require-toggle-inside-transition.js b/lib/rules/require-toggle-inside-transition.js index 365fbc713..bdbb5c2dd 100644 --- a/lib/rules/require-toggle-inside-transition.js +++ b/lib/rules/require-toggle-inside-transition.js @@ -47,7 +47,8 @@ module.exports = { return utils.defineTemplateBodyVisitor(context, { /** @param {VElement} node */ "VElement[name='transition'] > VElement"(node) { - if (node.parent.children[0] !== node) { + const child = node.parent.children.find(utils.isVElement) + if (child !== node) { return } verifyInsideElement(node) diff --git a/lib/rules/require-typed-ref.js b/lib/rules/require-typed-ref.js index 4ae12b040..2f691fec1 100644 --- a/lib/rules/require-typed-ref.js +++ b/lib/rules/require-typed-ref.js @@ -83,7 +83,9 @@ module.exports = { continue } - if (ref.node.typeParameters == null) { + const typeArguments = + ref.node.typeArguments || ref.node.typeParameters + if (typeArguments == null) { if ( ref.node.parent.type === 'VariableDeclarator' && ref.node.parent.id.type === 'Identifier' diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js index 64d3fd15a..994302c60 100644 --- a/lib/rules/valid-define-emits.js +++ b/lib/rules/valid-define-emits.js @@ -47,8 +47,9 @@ module.exports = { onDefineEmitsEnter(node) { defineEmitsNodes.push(node) + const typeArguments = node.typeArguments || node.typeParameters if (node.arguments.length > 0) { - if (node.typeParameters && node.typeParameters.params.length > 0) { + if (typeArguments && typeArguments.params.length > 0) { // `defineEmits` has both a literal type and an argument. context.report({ node, @@ -59,10 +60,7 @@ module.exports = { emitsDefExpressions.add(node.arguments[0]) } else { - if ( - !node.typeParameters || - node.typeParameters.params.length === 0 - ) { + if (!typeArguments || typeArguments.params.length === 0) { emptyDefineEmits = node } } diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js index 784ac3fbd..c1cd5b993 100644 --- a/lib/rules/valid-define-options.js +++ b/lib/rules/valid-define-options.js @@ -74,9 +74,10 @@ module.exports = { }) } - if (node.typeParameters) { + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments) { context.report({ - node: node.typeParameters, + node: typeArguments, messageId: 'typeArgs' }) } diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js index 43cce1d3c..ecde56f15 100644 --- a/lib/rules/valid-define-props.js +++ b/lib/rules/valid-define-props.js @@ -48,8 +48,9 @@ module.exports = { onDefinePropsEnter(node) { definePropsNodes.push(node) + const typeArguments = node.typeArguments || node.typeParameters if (node.arguments.length > 0) { - if (node.typeParameters && node.typeParameters.params.length > 0) { + if (typeArguments && typeArguments.params.length > 0) { // `defineProps` has both a literal type and an argument. context.report({ node, @@ -60,10 +61,7 @@ module.exports = { propsDefExpressions.add(node.arguments[0]) } else { - if ( - !node.typeParameters || - node.typeParameters.params.length === 0 - ) { + if (!typeArguments || typeArguments.params.length === 0) { emptyDefineProps = node } } diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index 5b97f37b5..6119406e7 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -1168,21 +1168,22 @@ module.exports.defineVisitor = function create( }, /** @param {CallExpression} node */ CallExpression(node) { + const typeArguments = node.typeArguments || node.typeParameters const firstToken = tokenStore.getFirstToken(node) const rightToken = tokenStore.getLastToken(node) const leftToken = /** @type {Token} */ ( tokenStore.getTokenAfter( - node.typeParameters || node.callee, + typeArguments || node.callee, isOpeningParenToken ) ) - if (node.typeParameters) { - setOffset(tokenStore.getFirstToken(node.typeParameters), 1, firstToken) + if (typeArguments) { + setOffset(tokenStore.getFirstToken(typeArguments), 1, firstToken) } for (const optionalToken of tokenStore.getTokensBetween( - tokenStore.getLastToken(node.typeParameters || node.callee), + tokenStore.getLastToken(typeArguments || node.callee), leftToken, isOptionalToken )) { @@ -1694,19 +1695,20 @@ module.exports.defineVisitor = function create( }, /** @param {NewExpression} node */ NewExpression(node) { + const typeArguments = node.typeArguments || node.typeParameters const newToken = tokenStore.getFirstToken(node) const calleeToken = tokenStore.getTokenAfter(newToken) const rightToken = tokenStore.getLastToken(node) const leftToken = isClosingParenToken(rightToken) ? tokenStore.getFirstTokenBetween( - node.typeParameters || node.callee, + typeArguments || node.callee, rightToken, isOpeningParenToken ) : null - if (node.typeParameters) { - setOffset(tokenStore.getFirstToken(node.typeParameters), 1, calleeToken) + if (typeArguments) { + setOffset(tokenStore.getFirstToken(typeArguments), 1, calleeToken) } setOffset(calleeToken, 1, newToken) diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js index c6f146f66..a4323b38f 100644 --- a/lib/utils/indent-ts.js +++ b/lib/utils/indent-ts.js @@ -332,9 +332,10 @@ function defineVisitor({ * @param {TSTypeReference | TSInstantiationExpression} node */ 'TSTypeReference, TSInstantiationExpression'(node) { - if (node.typeParameters) { + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments) { const firstToken = tokenStore.getFirstToken(node) - setOffset(tokenStore.getFirstToken(node.typeParameters), 1, firstToken) + setOffset(tokenStore.getFirstToken(typeArguments), 1, firstToken) } }, /** diff --git a/lib/utils/index.js b/lib/utils/index.js index 0c9a4ac99..22119ae98 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -54,6 +54,7 @@ const VUE2_BUILTIN_COMPONENT_NAMES = new Set( const VUE3_BUILTIN_COMPONENT_NAMES = new Set( require('./vue3-builtin-components') ) +const VUE_BUILTIN_ELEMENT_NAMES = new Set(require('./vue-builtin-elements')) const path = require('path') const vueEslintParser = require('vue-eslint-parser') const { traverseNodes, getFallbackKeys, NS } = vueEslintParser.AST @@ -867,6 +868,15 @@ module.exports = { ) }, + /** + * Check whether the given name is Vue builtin element name or not. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is a builtin Vue element name + */ + isVueBuiltInElementName(name) { + return VUE_BUILTIN_ELEMENT_NAMES.has(name.toLowerCase()) + }, + /** * Check whether the given name is Vue builtin directive name or not. * @param {string} name The name to check. @@ -2990,11 +3000,9 @@ function getComponentPropsFromDefineProps(context, node) { } ] } - if (node.typeParameters && node.typeParameters.params.length > 0) { - return getComponentPropsFromTypeDefine( - context, - node.typeParameters.params[0] - ) + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params.length > 0) { + return getComponentPropsFromTypeDefine(context, typeArguments.params[0]) } return [ { @@ -3025,11 +3033,9 @@ function getComponentEmitsFromDefineEmits(context, node) { } ] } - if (node.typeParameters && node.typeParameters.params.length > 0) { - return getComponentEmitsFromTypeDefine( - context, - node.typeParameters.params[0] - ) + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params.length > 0) { + return getComponentEmitsFromTypeDefine(context, typeArguments.params[0]) } return [ { diff --git a/lib/utils/ts-utils/ts-ast.js b/lib/utils/ts-utils/ts-ast.js index d019b77cd..8a95dd9b1 100644 --- a/lib/utils/ts-utils/ts-ast.js +++ b/lib/utils/ts-utils/ts-ast.js @@ -430,33 +430,28 @@ function inferRuntimeType(context, node, checked = new Set()) { return ['Array'] } case 'NonNullable': { - if (node.typeParameters && node.typeParameters.params[0]) { + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params[0]) { return inferRuntimeType( context, - node.typeParameters.params[0], + typeArguments.params[0], checked ).filter((t) => t !== 'null') } break } case 'Extract': { - if (node.typeParameters && node.typeParameters.params[1]) { - return inferRuntimeType( - context, - node.typeParameters.params[1], - checked - ) + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params[1]) { + return inferRuntimeType(context, typeArguments.params[1], checked) } break } case 'Exclude': case 'OmitThisParameter': { - if (node.typeParameters && node.typeParameters.params[0]) { - return inferRuntimeType( - context, - node.typeParameters.params[0], - checked - ) + const typeArguments = node.typeArguments || node.typeParameters + if (typeArguments && typeArguments.params[0]) { + return inferRuntimeType(context, typeArguments.params[0], checked) } break } diff --git a/lib/utils/vue-builtin-elements.js b/lib/utils/vue-builtin-elements.js new file mode 100644 index 000000000..70ddb6b7d --- /dev/null +++ b/lib/utils/vue-builtin-elements.js @@ -0,0 +1 @@ +module.exports = ['template', 'slot', 'component'] diff --git a/lib/utils/vue3-export-names.json b/lib/utils/vue3-export-names.json index 15b72e2fc..f676e8440 100644 --- a/lib/utils/vue3-export-names.json +++ b/lib/utils/vue3-export-names.json @@ -276,6 +276,7 @@ "IframeHTMLAttributes", "ImgHTMLAttributes", "InsHTMLAttributes", + "InputTypeHTMLAttribute", "InputHTMLAttributes", "KeygenHTMLAttributes", "LabelHTMLAttributes", diff --git a/package.json b/package.json index eb3750f4a..274aa2da2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-vue", - "version": "9.17.0", + "version": "9.18.0", "description": "Official ESLint plugin for Vue.js", "main": "lib/index.js", "scripts": { diff --git a/tests/integrations/eslint-plugin-import.js b/tests/integrations/eslint-plugin-import.js index a408fdcdd..4e26508ce 100644 --- a/tests/integrations/eslint-plugin-import.js +++ b/tests/integrations/eslint-plugin-import.js @@ -30,10 +30,12 @@ describe('Integration with eslint-plugin-import', () => { if ( !semver.satisfies( process.version, - require(path.join( - __dirname, - 'eslint-plugin-import/node_modules/eslint/package.json' - )).engines.node + require( + path.join( + __dirname, + 'eslint-plugin-import/node_modules/eslint/package.json' + ) + ).engines.node ) ) { return diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js index 3aa4ee43d..992063808 100644 --- a/tests/lib/rules/component-name-in-template-casing.js +++ b/tests/lib/rules/component-name-in-template-casing.js @@ -86,6 +86,10 @@ tester.run('component-name-in-template-casing', rule, { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { + code: '', + options: ['PascalCase', { registeredComponentsOnly: false }] + }, // kebab-case { @@ -108,6 +112,10 @@ tester.run('component-name-in-template-casing', rule, { code: '', options: ['kebab-case', { registeredComponentsOnly: false }] }, + { + code: '', + options: ['kebab-case', { registeredComponentsOnly: false }] + }, // ignores { @@ -859,7 +867,7 @@ tester.run('component-name-in-template-casing', rule, { `, output: ` `, errors: [ @@ -1058,11 +1065,6 @@ tester.run('component-name-in-template-casing', rule, { message: 'Component name "hello-world5" is not PascalCase.', line: 18, column: 17 - }, - { - message: 'Component name "component" is not PascalCase.', - line: 19, - column: 17 } ] } diff --git a/tests/lib/rules/prefer-define-options.js b/tests/lib/rules/prefer-define-options.js index 2fd9bdfec..f3d9d88b0 100644 --- a/tests/lib/rules/prefer-define-options.js +++ b/tests/lib/rules/prefer-define-options.js @@ -104,6 +104,32 @@ defineOptions({ name: 'Foo' }) line: 4 } ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + `, + errors: [ + { + message: 'Use `defineOptions` instead of default export.', + line: 7 + } + ] } ] }) diff --git a/tests/lib/rules/require-toggle-inside-transition.js b/tests/lib/rules/require-toggle-inside-transition.js index 73ec47206..0b64daaa3 100644 --- a/tests/lib/rules/require-toggle-inside-transition.js +++ b/tests/lib/rules/require-toggle-inside-transition.js @@ -97,6 +97,11 @@ tester.run('require-toggle-inside-transition', rule, { filename: 'test.vue', code: '', errors: [{ messageId: 'expected' }] + }, + { + filename: 'test.vue', + code: '', + errors: [{ messageId: 'expected' }] } ] }) diff --git a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts index 944246411..db3a7f39c 100644 --- a/typings/eslint-plugin-vue/util-types/ast/es-ast.ts +++ b/typings/eslint-plugin-vue/util-types/ast/es-ast.ts @@ -519,7 +519,10 @@ export interface CallExpression extends HasParentNode { callee: Expression | Super arguments: (Expression | SpreadElement)[] optional: boolean - typeParameters?: TS.TSTypeParameterInstantiation + typeArguments?: TS.TSTypeParameterInstantiation + + /* @deprecated */ + typeParameters: never } export interface Super extends HasParentNode { type: 'Super' @@ -528,7 +531,10 @@ export interface NewExpression extends HasParentNode { type: 'NewExpression' callee: Expression arguments: (Expression | SpreadElement)[] - typeParameters?: TSTypeParameterInstantiation + typeArguments?: TSTypeParameterInstantiation + + /* @deprecated */ + typeParameters: never } interface BaseMemberExpression extends HasParentNode { type: 'MemberExpression'