Repository: vuejs/eslint-plugin-vue
Pull request #1133 (2020-05-16 01:59:47)

Add `vue/no-deprecated-dollar-listeners-api` rule
ref #1035 


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index d022dd67..6ff3780d 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -40,6 +40,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 |:--------|:------------|:---|
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  |
 | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: |
+| [vue/no-deprecated-dollar-listeners-api](./no-deprecated-dollar-listeners-api.md) | disallow using deprecated `$listeners` (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-events-api](./no-deprecated-events-api.md) | disallow using deprecated events api (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-filter](./no-deprecated-filter.md) | disallow using deprecated filters syntax (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-functional-template](./no-deprecated-functional-template.md) | disallow using deprecated the `functional` template (in Vue.js 3.0.0+) |  |
@@ -49,6 +50,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-native-modifier](./no-deprecated-v-on-native-modifier.md) | disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-v-on-number-modifiers](./no-deprecated-v-on-number-modifiers.md) | disallow using deprecated number (keycode) modifiers (in Vue.js 3.0.0+) | :wrench: |
 | [vue/no-deprecated-vue-config-keycodes](./no-deprecated-vue-config-keycodes.md) | disallow using deprecated `Vue.config.keyCodes` (in Vue.js 3.0.0+) |  |
 | [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names |  |
diff --git a/docs/rules/no-deprecated-dollar-listeners-api.md b/docs/rules/no-deprecated-dollar-listeners-api.md
new file mode 100644
index 00000000..99097e15
--- /dev/null
+++ b/docs/rules/no-deprecated-dollar-listeners-api.md
@@ -0,0 +1,51 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-dollar-listeners-api
+description: disallow using deprecated `$listeners` (in Vue.js 3.0.0+)
+---
+# vue/no-deprecated-dollar-listeners-api
+> disallow using deprecated `$listeners` (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"`.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `$listeners`. (in Vue.js 3.0.0+).
+
+<eslint-code-block :rules="{'vue/no-deprecated-dollar-listeners-api': ['error']}">
+
+```vue
+<template>
+  <!-- ✗ BAD -->
+  <MyInput v-on="$listeners">
+</template>
+<script>
+export default {
+  computed: {
+    listeners() {
+      return {
+        /* ✗ BAD */
+        ...this.$listeners,
+        input() { /* */ }
+      }
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0031-attr-fallthrough](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-dollar-listeners-api.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-dollar-listeners-api.js)
diff --git a/docs/rules/no-deprecated-v-on-native-modifier.md b/docs/rules/no-deprecated-v-on-native-modifier.md
new file mode 100644
index 00000000..93620cd7
--- /dev/null
+++ b/docs/rules/no-deprecated-v-on-native-modifier.md
@@ -0,0 +1,49 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-v-on-native-modifier
+description: disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+)
+---
+# vue/no-deprecated-v-on-native-modifier
+> disallow using deprecated `.native` 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"`.
+
+## :book: Rule Details
+
+This rule reports use of deprecated `.native` modifier on `v-on` directive (in Vue.js 3.0.0+)
+
+<eslint-code-block :rules="{'vue/no-deprecated-v-on-native-modifier': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <CoolInput v-on:keydown.enter="onKeydownEnter" />
+  <CoolInput @keydown.enter="onKeydownEnter" />
+
+  <!-- ✗ BAD -->
+  <CoolInput v-on:keydown.native="onKeydown" />
+  <CoolInput @keydown.enter.native="onKeydownEnter" />
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related rules
+
+- [valid-v-on]
+
+[valid-v-on]: valid-v-on.md
+
+## :books: Further reading
+
+- [Vue RFCs - 0031-attr-fallthrough](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0031-attr-fallthrough.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-on-native-modifier.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-on-native-modifier.js)
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 651ff096..64d5eb72 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -8,6 +8,7 @@ module.exports = {
   rules: {
     'vue/no-async-in-computed-properties': 'error',
     'vue/no-deprecated-data-object-declaration': 'error',
+    'vue/no-deprecated-dollar-listeners-api': 'error',
     'vue/no-deprecated-events-api': 'error',
     'vue/no-deprecated-filter': 'error',
     'vue/no-deprecated-functional-template': 'error',
@@ -17,6 +18,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-native-modifier': 'error',
     'vue/no-deprecated-v-on-number-modifiers': 'error',
     'vue/no-deprecated-vue-config-keycodes': 'error',
     'vue/no-dupe-keys': 'error',
diff --git a/lib/index.js b/lib/index.js
index fa2bc434..63a16b25 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -41,6 +41,7 @@ module.exports = {
     'no-confusing-v-for-v-if': require('./rules/no-confusing-v-for-v-if'),
     'no-custom-modifiers-on-v-model': require('./rules/no-custom-modifiers-on-v-model'),
     'no-deprecated-data-object-declaration': require('./rules/no-deprecated-data-object-declaration'),
+    'no-deprecated-dollar-listeners-api': require('./rules/no-deprecated-dollar-listeners-api'),
     'no-deprecated-events-api': require('./rules/no-deprecated-events-api'),
     'no-deprecated-filter': require('./rules/no-deprecated-filter'),
     'no-deprecated-functional-template': require('./rules/no-deprecated-functional-template'),
@@ -50,6 +51,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-native-modifier': require('./rules/no-deprecated-v-on-native-modifier'),
     'no-deprecated-v-on-number-modifiers': require('./rules/no-deprecated-v-on-number-modifiers'),
     'no-deprecated-vue-config-keycodes': require('./rules/no-deprecated-vue-config-keycodes'),
     'no-dupe-keys': require('./rules/no-dupe-keys'),
diff --git a/lib/rules/no-deprecated-dollar-listeners-api.js b/lib/rules/no-deprecated-dollar-listeners-api.js
new file mode 100644
index 00000000..29c0655d
--- /dev/null
+++ b/lib/rules/no-deprecated-dollar-listeners-api.js
@@ -0,0 +1,65 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow using deprecated `$listeners` (in Vue.js 3.0.0+)',
+      categories: ['vue3-essential'],
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-dollar-listeners-api.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      deprecated: 'The `$listeners` is deprecated.'
+    }
+  },
+
+  create (context) {
+    return utils.defineTemplateBodyVisitor(
+      context,
+      {
+        'VExpressionContainer' (node) {
+          for (const reference of node.references) {
+            if (reference.variable != null) {
+              // Not vm reference
+              continue
+            }
+            if (reference.id.name === '$listeners') {
+              context.report({
+                node: reference.id,
+                messageId: 'deprecated'
+              })
+            }
+          }
+        }
+      },
+      utils.defineVueVisitor(context,
+        {
+          'MemberExpression > ThisExpression' (node) {
+            if (node.parent.property.name !== '$listeners') return
+
+            context.report({
+              node: node.parent.property,
+              messageId: 'deprecated'
+            })
+          }
+        }
+      )
+    )
+  }
+}
diff --git a/lib/rules/no-deprecated-v-on-native-modifier.js b/lib/rules/no-deprecated-v-on-native-modifier.js
new file mode 100644
index 00000000..d9375543
--- /dev/null
+++ b/lib/rules/no-deprecated-v-on-native-modifier.js
@@ -0,0 +1,45 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow using deprecated `.native` modifiers (in Vue.js 3.0.0+)',
+      categories: ['vue3-essential'],
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-v-on-native-modifier.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      deprecated: "'.native' modifier on 'v-on' directive is deprecated."
+    }
+  },
+
+  create (context) {
+    return utils.defineTemplateBodyVisitor(context, {
+      "VAttribute[directive=true][key.name.name='on'] > VDirectiveKey > VIdentifier[name='native']" (node) {
+        const key = node.parent
+        if (!key.modifiers.includes(node)) return
+
+        context.report({
+          node,
+          messageId: 'deprecated'
+        })
+      }
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/no-deprecated-dollar-listeners-api.js b/tests/lib/rules/no-deprecated-dollar-listeners-api.js
new file mode 100644
index 00000000..76f7c73c
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-dollar-listeners-api.js
@@ -0,0 +1,184 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-dollar-listeners-api')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2018, sourceType: 'module' }
+})
+ruleTester.run('no-deprecated-dollar-listeners-api', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-bind="$attrs"/>
+        </template>
+        <script>
+        export default {
+          mounted () {
+            this.$emit('start')
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+          methods: {
+            click () {
+              this.$emit('click')
+            }
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <script>
+        export default {
+        }
+        const another = function () {
+          console.log(this.$listeners)
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div foo="$listeners"/>
+        </template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-on="() => {
+            function click ($listeners) {
+              fn(foo.$listeners)
+              fn($listeners)
+            }
+          }"/>
+          <div v-for="$listeners in list">
+            <div v-on="$listeners">
+          </div>
+          <VueComp>
+            <template v-slot="{$listeners}">
+              <div v-on="$listeners">
+            </template>
+          </VueComp>
+        </template>
+        <script>
+        export default {
+          methods: {
+            click ($listeners) {
+              foo.$listeners
+            }
+          }
+        }
+        </script>
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-on="$listeners"/>
+        </template>
+        <script>
+        export default {
+          computed: {
+            foo () {
+              return this.$listeners
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 3,
+          column: 22,
+          messageId: 'deprecated',
+          endLine: 3,
+          endColumn: 32
+        },
+        {
+          line: 9,
+          column: 27,
+          messageId: 'deprecated',
+          endLine: 9,
+          endColumn: 37
+        }
+      ]
+    },
+
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="listener in $listeners"/>
+          <div :foo="$listeners"/>
+        </template>
+        <script>
+        export default {
+          computed: {
+            foo () {
+              fn(this.$listeners)
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          line: 3,
+          column: 35,
+          messageId: 'deprecated',
+          endLine: 3,
+          endColumn: 45
+        },
+        {
+          line: 4,
+          column: 22,
+          messageId: 'deprecated',
+          endLine: 4,
+          endColumn: 32
+        },
+        {
+          line: 10,
+          column: 23,
+          messageId: 'deprecated',
+          endLine: 10,
+          endColumn: 33
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-deprecated-events-api.js b/tests/lib/rules/no-deprecated-events-api.js
index 59a6c685..215a47d8 100644
--- a/tests/lib/rules/no-deprecated-events-api.js
+++ b/tests/lib/rules/no-deprecated-events-api.js
@@ -1,4 +1,3 @@
-/* eslint-disable eslint-plugin/consistent-output */
 /**
  * @fileoverview disallow using deprecated events api
  * @author yoyo930021
diff --git a/tests/lib/rules/no-deprecated-v-on-native-modifier.js b/tests/lib/rules/no-deprecated-v-on-native-modifier.js
new file mode 100644
index 00000000..772a5691
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-v-on-native-modifier.js
@@ -0,0 +1,76 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-v-on-native-modifier')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+ruleTester.run('no-deprecated-v-on-native-modifier', rule, {
+
+  valid: [
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.enter='fire'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @keyup.enter='fire'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-native:foo.native.foo.bar='fire'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input @native.enter='fire'></template>"
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input :keydown.native='fire'></template>"
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.native='fore'></template>",
+      errors: [
+        {
+          line: 1,
+          column: 29,
+          messageId: 'deprecated',
+          endLine: 1,
+          endColumn: 35
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: "<template><input v-on:keyup.foo.native.bar='fore'></template>",
+      errors: [
+        {
+          line: 1,
+          column: 33,
+          messageId: 'deprecated',
+          endLine: 1,
+          endColumn: 39
+        }
+      ]
+    }
+  ]
+})