diff --git a/docs/rules/index.md b/docs/rules/index.md
index 1ba5978d2..a88f14e81 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -250,6 +250,7 @@ For example:
| [vue/no-restricted-v-on](./no-restricted-v-on.md) | disallow specific argument in `v-on` | | :hammer: |
| [vue/no-root-v-if](./no-root-v-if.md) | disallow `v-if` directives on root element | | :hammer: |
| [vue/no-setup-props-reactivity-loss](./no-setup-props-reactivity-loss.md) | disallow usages that lose the reactivity of `props` passed to `setup` | | :hammer: |
+| [vue/no-shadow-native-events](./no-shadow-native-events.md) | disallow the use of event names that collide with native web event names | | :warning: |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | | :hammer: |
| [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" | :bulb: | :warning: |
| [vue/no-this-in-before-route-enter](./no-this-in-before-route-enter.md) | disallow `this` usage in a `beforeRouteEnter` method | | :warning: |
diff --git a/docs/rules/no-shadow-native-events.md b/docs/rules/no-shadow-native-events.md
new file mode 100644
index 000000000..f9581f4b9
--- /dev/null
+++ b/docs/rules/no-shadow-native-events.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-shadow-native-events
+description: disallow the use of event names that collide with native web event names
+---
+
+# vue/no-shadow-native-events
+
+> disallow the use of event names that collide with native web event names
+
+- :exclamation: _**This rule has not been released yet.**_
+
+## :book: Rule Details
+
+This rule reports emits that shadow native HTML events.
+
+Using native event names for emits can lead to incorrect assumptions about an emit and cause confusion. This is caused by Vue emits behaving differently from native events. E.g. :
+
+- The payload of an emit can be chosen arbitrarily
+- Vue emits do not bubble, while most native events do
+- [Event modifiers](https://vuejs.org/guide/essentials/event-handling.html#event-modifiers) only work on HTML events or when the original event is re-emitted as emit payload.
+- When the native event is re-emitted, the `event.target` might not match the actual event-listeners location.
+
+The rule is mostly aimed at developers of component libraries.
+
+
+
+```vue
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md)
+- [vue/require-explicit-emits](./require-explicit-emits.md)
+
+## :books: Further Reading
+
+- [Components In-Depth - Events / Component Events](https://vuejs.org/guide/components/events.html#event-arguments)
+- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-shadow-native-events.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-shadow-native-events.js)
diff --git a/docs/rules/require-explicit-emits.md b/docs/rules/require-explicit-emits.md
index d04dec8b3..507f47398 100644
--- a/docs/rules/require-explicit-emits.md
+++ b/docs/rules/require-explicit-emits.md
@@ -114,10 +114,11 @@ export default {
- [vue/no-unused-emit-declarations](./no-unused-emit-declarations.md)
- [vue/require-explicit-slots](./require-explicit-slots.md)
+- [vue/no-shadow-native-events](./no-shadow-native-events.md)
## :books: Further Reading
-- [Guide - Custom Events / Defining Custom Events](https://v3.vuejs.org/guide/component-custom-events.html#defining-custom-events)
+- [Components In-Depth - Events / Component Events](https://vuejs.org/guide/components/events.html#event-arguments)
- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
## :rocket: Version
diff --git a/lib/index.js b/lib/index.js
index bb4abf40f..01dd41745 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -160,6 +160,7 @@ const plugin = {
'no-root-v-if': require('./rules/no-root-v-if'),
'no-setup-props-destructure': require('./rules/no-setup-props-destructure'),
'no-setup-props-reactivity-loss': require('./rules/no-setup-props-reactivity-loss'),
+ 'no-shadow-native-events': require('./rules/no-shadow-native-events'),
'no-shared-component-data': require('./rules/no-shared-component-data'),
'no-side-effects-in-computed-properties': require('./rules/no-side-effects-in-computed-properties'),
'no-spaces-around-equal-signs-in-attribute': require('./rules/no-spaces-around-equal-signs-in-attribute'),
diff --git a/lib/rules/no-shadow-native-events.js b/lib/rules/no-shadow-native-events.js
new file mode 100644
index 000000000..b13be9c6f
--- /dev/null
+++ b/lib/rules/no-shadow-native-events.js
@@ -0,0 +1,349 @@
+/**
+ * @author Jonathan Carle
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const domEvents = require('../utils/dom-events.json')
+const { findVariable } = require('@eslint-community/eslint-utils')
+/**
+ * @typedef {import('../utils').ComponentEmit} ComponentEmit
+ * @typedef {import('../utils').ComponentProp} ComponentProp
+ * @typedef {import('../utils').VueObjectData} VueObjectData
+ * @typedef {import('./require-explicit-emits.js').NameWithLoc} NameWithLoc
+ */
+
+/**
+ * Get the name param node from the given CallExpression
+ * @param {CallExpression} node CallExpression
+ * @returns { NameWithLoc | null }
+ */
+function getNameParamNode(node) {
+ const nameLiteralNode = node.arguments[0]
+ if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
+ const name = utils.getStringLiteralValue(nameLiteralNode)
+ if (name != null) {
+ return { name, loc: nameLiteralNode.loc, range: nameLiteralNode.range }
+ }
+ }
+
+ // cannot check
+ return null
+}
+
+/**
+ * Check if the given name matches defineEmitsNode variable name
+ * @param {string} name
+ * @param {CallExpression | undefined} defineEmitsNode
+ * @returns {boolean}
+ */
+function isEmitVariableName(name, defineEmitsNode) {
+ const node = defineEmitsNode?.parent
+
+ if (node?.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
+ return name === node.id.name
+ }
+
+ return false
+}
+
+/**
+ * @type {import('eslint').Rule.RuleModule}
+ */
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description:
+ 'disallow the use of event names that collide with native web event names',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-shadow-native-events.html'
+ },
+ schema: [],
+ messages: {
+ violation:
+ 'Use a different emit name to avoid shadowing the native event with name "{{ name }}". Consider an emit name which communicates the users intent, if applicable.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ /** @type {Map, emitReferenceIds: Set }>} */
+ const setupContexts = new Map()
+
+ /**
+ * Tracks violating emit definitions, so that calls of this emit are not reported additionally.
+ * @type {Set}
+ * */
+ const definedAndReportedEmits = new Set()
+
+ /**
+ * @typedef {object} VueTemplateDefineData
+ * @property {'export' | 'mark' | 'definition' | 'setup'} type
+ * @property {ObjectExpression | Program} define
+ * @property {CallExpression} [defineEmits]
+ */
+ /** @type {VueTemplateDefineData | null} */
+ let vueTemplateDefineData = null
+
+ const programNode = context.getSourceCode().ast
+ if (utils.isScriptSetup(context)) {
+ // init
+ vueTemplateDefineData = {
+ type: 'setup',
+ define: programNode
+ }
+ }
+
+ /**
+ * Verify if an emit call violates the rule of not using a native dom event name.
+ * @param {NameWithLoc} nameWithLoc
+ */
+ function verifyEmit(nameWithLoc) {
+ const name = nameWithLoc.name.toLowerCase()
+ if (!domEvents.includes(name) || definedAndReportedEmits.has(name)) {
+ return
+ }
+ context.report({
+ loc: nameWithLoc.loc,
+ messageId: 'violation',
+ data: {
+ name
+ }
+ })
+ }
+
+ /**
+ * Verify if an emit declaration violates the rule of not using a native dom event name.
+ * @param {ComponentEmit[]} emits
+ */
+ const verifyEmitDeclaration = (emits) => {
+ for (const { node, emitName } of emits) {
+ if (!node || !emitName || !domEvents.includes(emitName.toLowerCase())) {
+ continue
+ }
+
+ definedAndReportedEmits.add(emitName)
+ context.report({
+ messageId: 'violation',
+ data: { name: emitName },
+ loc: node.loc
+ })
+ }
+ }
+
+ const callVisitor = {
+ /**
+ * @param {CallExpression} node
+ * @param {VueObjectData} [info]
+ */
+ CallExpression(node, info) {
+ const callee = utils.skipChainExpression(node.callee)
+ const nameWithLoc = getNameParamNode(node)
+ if (!nameWithLoc) {
+ // cannot check
+ return
+ }
+ const vueDefineNode = info ? info.node : programNode
+
+ let emit
+ if (callee.type === 'MemberExpression') {
+ const name = utils.getStaticPropertyName(callee)
+ if (name === 'emit' || name === '$emit') {
+ emit = { name, member: callee }
+ }
+ }
+
+ // verify setup context
+ const setupContext = setupContexts.get(vueDefineNode)
+ if (setupContext) {
+ const { contextReferenceIds, emitReferenceIds } = setupContext
+ if (callee.type === 'Identifier' && emitReferenceIds.has(callee)) {
+ // verify setup(props,{emit}) {emit()}
+ verifyEmit(nameWithLoc)
+ } else if (emit && emit.name === 'emit') {
+ const memObject = utils.skipChainExpression(emit.member.object)
+ if (
+ memObject.type === 'Identifier' &&
+ contextReferenceIds.has(memObject)
+ ) {
+ // verify setup(props,context) {context.emit()}
+ verifyEmit(nameWithLoc)
+ }
+ }
+ }
+
+ // verify $emit
+ if (emit && emit.name === '$emit') {
+ const memObject = utils.skipChainExpression(emit.member.object)
+ if (utils.isThis(memObject, context)) {
+ // verify this.$emit()
+ verifyEmit(nameWithLoc)
+ }
+ }
+ }
+ }
+
+ return utils.compositingVisitors(
+ utils.defineTemplateBodyVisitor(
+ context,
+ {
+ /** @param { CallExpression } node */
+ CallExpression(node) {
+ const callee = utils.skipChainExpression(node.callee)
+ const nameWithLoc = getNameParamNode(node)
+ if (!nameWithLoc) {
+ // cannot check
+ return
+ }
+
+ // e.g. $emit() / emit() in template
+ if (
+ callee.type === 'Identifier' &&
+ (callee.name === '$emit' ||
+ (vueTemplateDefineData?.defineEmits &&
+ isEmitVariableName(
+ callee.name,
+ vueTemplateDefineData.defineEmits
+ )))
+ ) {
+ verifyEmit(nameWithLoc)
+ }
+ }
+ },
+ utils.compositingVisitors(
+ utils.defineScriptSetupVisitor(context, {
+ onDefineEmitsEnter: (node, emits) => {
+ verifyEmitDeclaration(emits)
+ if (
+ vueTemplateDefineData &&
+ vueTemplateDefineData.type === 'setup'
+ ) {
+ vueTemplateDefineData.defineEmits = node
+ }
+
+ if (
+ !node.parent ||
+ node.parent.type !== 'VariableDeclarator' ||
+ node.parent.init !== node
+ ) {
+ return
+ }
+
+ const emitParam = node.parent.id
+ const variable =
+ emitParam.type === 'Identifier'
+ ? findVariable(utils.getScope(context, emitParam), emitParam)
+ : null
+ if (!variable) {
+ return
+ }
+ /** @type {Set} */
+ const emitReferenceIds = new Set()
+ for (const reference of variable.references) {
+ if (!reference.isRead()) {
+ continue
+ }
+
+ emitReferenceIds.add(reference.identifier)
+ }
+ setupContexts.set(programNode, {
+ contextReferenceIds: new Set(),
+ emitReferenceIds
+ })
+ },
+ ...callVisitor
+ }),
+ utils.defineVueVisitor(context, {
+ onSetupFunctionEnter(node, { node: vueNode }) {
+ const contextParam = node.params[1]
+ if (!contextParam) {
+ // no arguments
+ return
+ }
+ if (contextParam.type === 'RestElement') {
+ // cannot check
+ return
+ }
+ if (contextParam.type === 'ArrayPattern') {
+ // cannot check
+ return
+ }
+ /** @type {Set} */
+ const contextReferenceIds = new Set()
+ /** @type {Set} */
+ const emitReferenceIds = new Set()
+ if (contextParam.type === 'ObjectPattern') {
+ const emitProperty = utils.findAssignmentProperty(
+ contextParam,
+ 'emit'
+ )
+ if (!emitProperty) {
+ return
+ }
+ const emitParam = emitProperty.value
+ // `setup(props, {emit})`
+ const variable =
+ emitParam.type === 'Identifier'
+ ? findVariable(
+ utils.getScope(context, emitParam),
+ emitParam
+ )
+ : null
+ if (!variable) {
+ return
+ }
+ for (const reference of variable.references) {
+ if (!reference.isRead()) {
+ continue
+ }
+
+ emitReferenceIds.add(reference.identifier)
+ }
+ } else if (contextParam.type === 'Identifier') {
+ // `setup(props, context)`
+ const variable = findVariable(
+ utils.getScope(context, contextParam),
+ contextParam
+ )
+ if (!variable) {
+ return
+ }
+ for (const reference of variable.references) {
+ if (!reference.isRead()) {
+ continue
+ }
+
+ contextReferenceIds.add(reference.identifier)
+ }
+ }
+ setupContexts.set(vueNode, {
+ contextReferenceIds,
+ emitReferenceIds
+ })
+ },
+ onVueObjectEnter(node) {
+ const emits = utils.getComponentEmitsFromOptions(node)
+ verifyEmitDeclaration(emits)
+ },
+ onVueObjectExit(node, { type }) {
+ if (
+ (!vueTemplateDefineData ||
+ (vueTemplateDefineData.type !== 'export' &&
+ vueTemplateDefineData.type !== 'setup')) &&
+ (type === 'mark' || type === 'export' || type === 'definition')
+ ) {
+ vueTemplateDefineData = {
+ type,
+ define: node
+ }
+ }
+ setupContexts.delete(node)
+ },
+ ...callVisitor
+ })
+ )
+ )
+ )
+ }
+}
diff --git a/lib/utils/dom-events.json b/lib/utils/dom-events.json
new file mode 100644
index 000000000..c3ae47089
--- /dev/null
+++ b/lib/utils/dom-events.json
@@ -0,0 +1,85 @@
+[
+ "copy",
+ "cut",
+ "paste",
+ "compositionend",
+ "compositionstart",
+ "compositionupdate",
+ "drag",
+ "dragend",
+ "dragenter",
+ "dragexit",
+ "dragleave",
+ "dragover",
+ "dragstart",
+ "drop",
+ "focus",
+ "focusin",
+ "focusout",
+ "blur",
+ "change",
+ "beforeinput",
+ "input",
+ "reset",
+ "submit",
+ "invalid",
+ "load",
+ "error",
+ "keydown",
+ "keypress",
+ "keyup",
+ "auxclick",
+ "click",
+ "contextmenu",
+ "dblclick",
+ "mousedown",
+ "mouseenter",
+ "mouseleave",
+ "mousemove",
+ "mouseout",
+ "mouseover",
+ "mouseup",
+ "abort",
+ "canplay",
+ "canplaythrough",
+ "durationchange",
+ "emptied",
+ "encrypted",
+ "ended",
+ "loadeddata",
+ "loadedmetadata",
+ "loadstart",
+ "pause",
+ "play",
+ "playing",
+ "progress",
+ "ratechange",
+ "seeked",
+ "seeking",
+ "stalled",
+ "suspend",
+ "timeupdate",
+ "volumechange",
+ "waiting",
+ "select",
+ "scroll",
+ "scrollend",
+ "touchcancel",
+ "touchend",
+ "touchmove",
+ "touchstart",
+ "pointerdown",
+ "pointermove",
+ "pointerup",
+ "pointercancel",
+ "pointerenter",
+ "pointerleave",
+ "pointerover",
+ "pointerout",
+ "wheel",
+ "animationstart",
+ "animationend",
+ "animationiteration",
+ "transitionend",
+ "transitionstart"
+]
diff --git a/tests/fixtures/typescript/src/test01.ts b/tests/fixtures/typescript/src/test01.ts
index d14550843..169dfb1c7 100644
--- a/tests/fixtures/typescript/src/test01.ts
+++ b/tests/fixtures/typescript/src/test01.ts
@@ -7,6 +7,10 @@ export type Emits1 = {
(e: 'foo' | 'bar', payload: string): void
(e: 'baz', payload: number): void
}
+export type Emits2 = {
+ (e: 'click' | 'bar', payload: string): void
+ (e: 'keydown', payload: number): void
+}
export type Props2 = {
a: string
b?: number
diff --git a/tests/lib/rules/no-shadow-native-events.js b/tests/lib/rules/no-shadow-native-events.js
new file mode 100644
index 000000000..63ccca7bc
--- /dev/null
+++ b/tests/lib/rules/no-shadow-native-events.js
@@ -0,0 +1,1162 @@
+/**
+ * @author Jonathan Carle
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/no-shadow-native-events')
+const {
+ getTypeScriptFixtureTestOptions
+} = require('../../test-utils/typescript')
+
+const tester = new RuleTester({
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('no-shadow-native-events', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ // quoted
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ // unknown
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ // allowProps
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+
+ //
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+
+ `,
+ languageOptions: {
+ 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: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `
+ },
+ {
+ // new syntax in Vue 3.3
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ // new syntax in Vue 3.3
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ }
+ },
+ {
+ code: `
+ `,
+ ...getTypeScriptFixtureTestOptions()
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ line: 3,
+ column: 28,
+ messageId: 'violation',
+ endLine: 3,
+ endColumn: 35
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ line: 7,
+ column: 17,
+ messageId: 'violation',
+ endLine: 7,
+ endColumn: 24
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ line: 7,
+ column: 17,
+ messageId: 'violation',
+ endLine: 7,
+ endColumn: 26
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 6,
+ column: 24,
+ messageId: 'violation',
+ endLine: 6,
+ endColumn: 31
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 8,
+ column: 22,
+ messageId: 'violation',
+ endLine: 8,
+ endColumn: 29
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 24,
+ messageId: 'violation',
+ endLine: 5,
+ endColumn: 31
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 16,
+ messageId: 'violation',
+ endLine: 5,
+ endColumn: 23
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 16,
+ messageId: 'violation',
+ endLine: 5,
+ endColumn: 23
+ },
+ {
+ line: 6,
+ column: 16,
+ messageId: 'violation',
+ endLine: 6,
+ endColumn: 25
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 7,
+ column: 25,
+ endLine: 7,
+ endColumn: 32
+ },
+ {
+ messageId: 'violation',
+ line: 8,
+ column: 28,
+ endLine: 8,
+ endColumn: 37
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 5,
+ column: 21,
+ endLine: 5,
+ endColumn: 28
+ },
+ {
+ messageId: 'violation',
+ line: 6,
+ column: 24,
+ endLine: 6,
+ endColumn: 33
+ }
+ ]
+ },
+ //
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 6,
+ column: 20,
+ endLine: 6,
+ endColumn: 27
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 7,
+ column: 9,
+ endLine: 7,
+ endColumn: 27
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 6,
+ column: 19,
+ endLine: 6,
+ endColumn: 39
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 28,
+ endLine: 3,
+ endColumn: 35
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 32,
+ endLine: 3,
+ endColumn: 52
+ },
+ {
+ messageId: 'violation',
+ line: 5,
+ column: 12,
+ endLine: 5,
+ endColumn: 21
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 33,
+ endLine: 3,
+ endColumn: 40
+ },
+ {
+ messageId: 'violation',
+ line: 5,
+ column: 12,
+ endLine: 5,
+ endColumn: 21
+ }
+ ]
+ },
+ {
+ // new syntax in Vue 3.3
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 33,
+ endLine: 3,
+ endColumn: 42
+ },
+ {
+ messageId: 'violation',
+ line: 5,
+ column: 12,
+ endLine: 5,
+ endColumn: 21
+ }
+ ]
+ },
+ {
+ // new syntax in Vue 3.3
+ filename: 'test.vue',
+ code: `
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 21,
+ endLine: 3,
+ endColumn: 30
+ },
+ {
+ messageId: 'violation',
+ line: 6,
+ column: 12,
+ endLine: 6,
+ endColumn: 21
+ }
+ ]
+ },
+ {
+ code: `
+ `,
+ errors: [
+ {
+ messageId: 'violation',
+ line: 4,
+ column: 32,
+ endLine: 4,
+ endColumn: 37
+ },
+ {
+ messageId: 'violation',
+ line: 4,
+ column: 32,
+ endLine: 4,
+ endColumn: 37
+ }
+ ],
+ ...getTypeScriptFixtureTestOptions()
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+
+ `,
+ languageOptions: {
+ parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
+ },
+ errors: [
+ {
+ messageId: 'violation',
+ line: 3,
+ column: 27,
+ endLine: 3,
+ endColumn: 36
+ },
+ {
+ messageId: 'violation',
+ line: 6,
+ column: 32,
+ endLine: 6,
+ endColumn: 52
+ }
+ ]
+ }
+ ]
+})