diff --git a/docs/rules/README.md b/docs/rules/README.md
index cc2a97313..99bc361e7 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -44,6 +44,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/no-deprecated-slot-attribute](./no-deprecated-slot-attribute.md) | disallow deprecated `slot` attribute (in Vue.js 2.6.0+) | :wrench: |
| [vue/no-deprecated-slot-scope-attribute](./no-deprecated-slot-scope-attribute.md) | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: |
| [vue/no-deprecated-v-bind-sync](./no-deprecated-v-bind-sync.md) | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: |
+| [vue/no-deprecated-v-on-number-modifiers](./no-deprecated-v-on-number-modifiers.md) | disallow using deprecated number (keycode) modifiers | :wrench: |
| [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names | |
| [vue/no-duplicate-attributes](./no-duplicate-attributes.md) | disallow duplication of attributes | |
| [vue/no-lifecycle-after-await](./no-lifecycle-after-await.md) | disallow asynchronously registered lifecycle hooks | |
diff --git a/docs/rules/no-deprecated-v-on-number-modifiers.md b/docs/rules/no-deprecated-v-on-number-modifiers.md
new file mode 100644
index 000000000..5ecba24a8
--- /dev/null
+++ b/docs/rules/no-deprecated-v-on-number-modifiers.md
@@ -0,0 +1,52 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-v-on-number-modifiers
+description: disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
+---
+# vue/no-deprecated-v-on-number-modifiers
+> disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+)
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `KeyboardEvent.keyCode` modifier on `v-on` directive (in Vue.js 3.0.0+)
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related rules
+
+- [valid-v-on]
+
+[valid-v-on]: valid-v-on.md
+
+## :books: Further reading
+
+- [RFC: drop keycode support](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0014-drop-keycode-support.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-on-number-modifiers.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-on-number-modifiers.js)
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 3e45ddd2c..1b0c23e08 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -12,6 +12,7 @@ module.exports = {
'vue/no-deprecated-slot-attribute': 'error',
'vue/no-deprecated-slot-scope-attribute': 'error',
'vue/no-deprecated-v-bind-sync': 'error',
+ 'vue/no-deprecated-v-on-number-modifiers': 'error',
'vue/no-dupe-keys': 'error',
'vue/no-duplicate-attributes': 'error',
'vue/no-lifecycle-after-await': 'error',
diff --git a/lib/index.js b/lib/index.js
index 10e2e8766..df70ec82a 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -45,6 +45,7 @@ module.exports = {
'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
'no-deprecated-slot-scope-attribute': require('./rules/no-deprecated-slot-scope-attribute'),
'no-deprecated-v-bind-sync': require('./rules/no-deprecated-v-bind-sync'),
+ 'no-deprecated-v-on-number-modifiers': require('./rules/no-deprecated-v-on-number-modifiers'),
'no-dupe-keys': require('./rules/no-dupe-keys'),
'no-duplicate-attributes': require('./rules/no-duplicate-attributes'),
'no-empty-pattern': require('./rules/no-empty-pattern'),
diff --git a/lib/rules/no-deprecated-v-on-number-modifiers.js b/lib/rules/no-deprecated-v-on-number-modifiers.js
new file mode 100644
index 000000000..651991cd4
--- /dev/null
+++ b/lib/rules/no-deprecated-v-on-number-modifiers.js
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview disallow using deprecated number (keycode) modifiers
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const keyCodeToKey = require('../utils/keycode-to-key.json')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'disallow using deprecated number (keycode) modifiers',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/no-deprecated-v-on-number-modifiers.html'
+ },
+ fixable: 'code',
+ schema: [],
+ messages: {
+ numberModifierIsDeprecated: "'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."
+ }
+ },
+
+ create (context) {
+ return utils.defineTemplateBodyVisitor(context, {
+ "VAttribute[directive=true][key.name.name='on']" (node) {
+ const modifier = node.key.modifiers.find(mod => Number.isInteger(parseInt(mod.name, 10)))
+ if (!modifier) return
+
+ const keyCodes = parseInt(modifier.name, 10)
+ if (
+ keyCodes > 9 || keyCodes < 0
+ ) {
+ context.report({
+ node,
+ loc: node.loc,
+ messageId: 'numberModifierIsDeprecated',
+ fix: (fixer) => {
+ const key = keyCodeToKey[keyCodes]
+ if (!key) return
+
+ return fixer.replaceTextRange(modifier.range, `${key}`)
+ }
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/lib/utils/keycode-to-key.json b/lib/utils/keycode-to-key.json
new file mode 100644
index 000000000..43f480302
--- /dev/null
+++ b/lib/utils/keycode-to-key.json
@@ -0,0 +1,100 @@
+{
+ "8": "backspace",
+ "9": "tab",
+ "13": "enter",
+ "16": "shift",
+ "17": "ctrl",
+ "18": "alt",
+ "19": "pause-break",
+ "20": "caps-lock",
+ "27": "escape",
+ "33": "page-up",
+ "34": "page-down",
+ "35": "end",
+ "36": "home",
+ "37": "left-arrow",
+ "38": "up-arrow",
+ "39": "right-arrow",
+ "40": "down-arrow",
+ "45": "insert",
+ "46": "delete",
+ "48": "0",
+ "49": "1",
+ "50": "2",
+ "51": "3",
+ "52": "4",
+ "53": "5",
+ "54": "6",
+ "55": "7",
+ "56": "8",
+ "57": "9",
+ "65": "a",
+ "66": "b",
+ "67": "c",
+ "68": "d",
+ "69": "e",
+ "70": "f",
+ "71": "g",
+ "72": "h",
+ "73": "i",
+ "74": "j",
+ "75": "k",
+ "76": "l",
+ "77": "m",
+ "78": "n",
+ "79": "o",
+ "80": "p",
+ "81": "q",
+ "82": "r",
+ "83": "s",
+ "84": "t",
+ "85": "u",
+ "86": "v",
+ "87": "w",
+ "88": "x",
+ "89": "y",
+ "90": "z",
+ "91": "left-window-key",
+ "92": "right-window-key",
+ "93": "select-key",
+ "96": "numpad-0",
+ "97": "numpad-1",
+ "98": "numpad-2",
+ "99": "numpad-3",
+ "100": "numpad-4",
+ "101": "numpad-5",
+ "102": "numpad-6",
+ "103": "numpad-7",
+ "104": "numpad-8",
+ "105": "numpad-9",
+ "106": "multiply",
+ "107": "add",
+ "109": "subtract",
+ "110": "decimal-point",
+ "111": "divide",
+ "112": "f1",
+ "113": "f2",
+ "114": "f3",
+ "115": "f4",
+ "116": "f5",
+ "117": "f6",
+ "118": "f7",
+ "119": "f8",
+ "120": "f9",
+ "121": "f10",
+ "122": "f11",
+ "123": "f12",
+ "144": "num-lock",
+ "145": "scroll-lock",
+ "186": "semi-colon",
+ "187": "equal-sign",
+ "188": "comma",
+ "189": "dash",
+ "190": "period",
+ "191": "forward-slash",
+ "192": "grave-accent",
+ "219": "open-bracket",
+ "220": "back-slash",
+ "221": "close-braket",
+ "222": "single-quote"
+}
diff --git a/tests/lib/rules/no-deprecated-v-on-number-modifiers.js b/tests/lib/rules/no-deprecated-v-on-number-modifiers.js
new file mode 100644
index 000000000..8c6d72698
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-v-on-number-modifiers.js
@@ -0,0 +1,174 @@
+/**
+ * @fileoverview disallow using deprecated number (keyCodes) modifiers
+ * @author yoyo930021
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-v-on-number-modifiers')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2015 }
+})
+
+ruleTester.run('no-deprecated-v-bind-sync', 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: ""
+ },
+ {
+ filename: 'test.vue',
+ code: ""
+ },
+ {
+ filename: 'test.vue',
+ code: ""
+ },
+ {
+ filename: 'test.vue',
+ code: ""
+ }
+ ],
+
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: "",
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: null,
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: null,
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: null,
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ },
+ {
+ filename: 'test.vue',
+ code: "",
+ output: null,
+ errors: ["'KeyboardEvent.keyCode' modifier on 'v-on' directive is deprecated. Using 'KeyboardEvent.key' instead."]
+ }
+ ]
+})