Repository: vuejs/eslint-plugin-vue
Pull request #1510 (2021-06-08 03:46:51)

Add `vue/no-deprecated-v-is` rule


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index d38c44a2..914242d1 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -298,6 +298,7 @@ For example:
 | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: |
 | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` |  |
 | [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
+| [vue/no-deprecated-v-is](./no-deprecated-v-is.md) | disallow deprecated `v-is` directive (in Vue.js 3.1.0+) | :wrench: |
 | [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` |  |
 | [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty |  |
 | [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | require valid keys in model option |  |
@@ -313,6 +314,7 @@ For example:
 | [vue/no-restricted-v-bind](./no-restricted-v-bind.md) | disallow specific argument in `v-bind` |  |
 | [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes |  |
 | [vue/no-template-target-blank](./no-template-target-blank.md) | disallow target="_blank" attribute without rel="noopener noreferrer" |  |
+| [vue/no-this-in-before-route-enter](./no-this-in-before-route-enter.md) | disallow this usage in a beforeRouteEnter method |  |
 | [vue/no-unregistered-components](./no-unregistered-components.md) | disallow using components that are not registered inside templates |  |
 | [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
 | [vue/no-unused-properties](./no-unused-properties.md) | disallow unused properties |  |
diff --git a/docs/rules/no-deprecated-v-is.md b/docs/rules/no-deprecated-v-is.md
new file mode 100644
index 00000000..5b16a8c8
--- /dev/null
+++ b/docs/rules/no-deprecated-v-is.md
@@ -0,0 +1,42 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-v-is
+description: disallow deprecated `v-is` directive (in Vue.js 3.1.0+)
+---
+# vue/no-deprecated-v-is
+
+> disallow deprecated `v-is` directive (in Vue.js 3.1.0+)
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :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 deprecated `v-is` directive in Vue.js v3.1.0+.
+
+Use [`is` attribute with `vue:` prefix](https://v3.vuejs.org/api/special-attributes.html#is) instead.
+
+<eslint-code-block fix :rules="{'vue/no-deprecated-v-is': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <div is="vue:MyComponent" />
+  <component is="MyComponent" />
+  
+  <!-- ✗ BAD -->
+  <div v-is="'MyComponent'" />
+</template>
+```
+
+</eslint-code-block>
+
+## :books: Further Reading
+
+- [v-is](https://v3.vuejs.org/api/directives.html#v-is)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-v-is.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-v-is.js)
diff --git a/docs/rules/no-this-in-before-route-enter.md b/docs/rules/no-this-in-before-route-enter.md
new file mode 100644
index 00000000..7409b48e
--- /dev/null
+++ b/docs/rules/no-this-in-before-route-enter.md
@@ -0,0 +1,115 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-this-in-before-route-enter
+description: disallow this usage in a beforeRouteEnter method
+---
+# vue/no-this-in-before-route-enter
+
+> disallow this usage in a beforeRouteEnter method
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## Rule Details
+
+Because lack of `this` in the `beforeRouteEnter` [(docs)](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards). This behavior isn't obvious, so it's pretty easy to make a `TypeError`. Especially during some refactor. 
+
+Bad:
+
+<eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
+
+```vue
+<script>
+export default {
+  beforeRouteEnter() {
+    this.method(); // Uncaught TypeError: Cannot read property 'method' of undefined      
+  }   
+}
+</script>
+```
+
+</eslint-code-block>
+
+Bad:
+
+<eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
+
+```vue
+<script>
+export default {
+  beforeRouteEnter() {
+    this.attribute = 42;
+  }   
+}
+</script>
+```
+
+</eslint-code-block>
+
+Bad:
+
+<eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
+
+```vue
+<script>
+export default {
+  beforeRouteEnter() {
+    if (this.value === 42) {
+        
+    }
+  }   
+}
+</script>
+```
+
+</eslint-code-block>
+
+
+Bad:
+
+<eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
+
+```vue
+<script>
+export default {
+  beforeRouteEnter() {
+    this.attribute = this.method();
+  }   
+}
+</script>
+```
+
+</eslint-code-block>
+
+Good:
+
+<eslint-code-block :rules="{'vue/no-this-in-before-route-enter': ['error']}">
+
+```vue
+<script>
+export default {
+  beforeRouteEnter() {
+    // anything without this
+  }   
+}
+</script>
+```
+
+</eslint-code-block>
+
+### Options
+
+Nothing.
+
+## When Not To Use It
+
+When [vue-router](https://router.vuejs.org/) is not installed.
+
+## Further Reading
+
+[vue-router - in-component-guards](https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-this-in-before-route-enter.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-this-in-before-route-enter.js)
diff --git a/lib/index.js b/lib/index.js
index 1379e284..313cc693 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -71,6 +71,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-is': require('./rules/no-deprecated-v-is'),
     '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'),
@@ -113,6 +114,7 @@ module.exports = {
     'no-template-shadow': require('./rules/no-template-shadow'),
     'no-template-target-blank': require('./rules/no-template-target-blank'),
     'no-textarea-mustache': require('./rules/no-textarea-mustache'),
+    'no-this-in-before-route-enter': require('./rules/no-this-in-before-route-enter'),
     'no-unregistered-components': require('./rules/no-unregistered-components'),
     'no-unsupported-features': require('./rules/no-unsupported-features'),
     'no-unused-components': require('./rules/no-unused-components'),
diff --git a/lib/rules/no-deprecated-destroyed-lifecycle.js b/lib/rules/no-deprecated-destroyed-lifecycle.js
index 6176daa9..9cb690cb 100644
--- a/lib/rules/no-deprecated-destroyed-lifecycle.js
+++ b/lib/rules/no-deprecated-destroyed-lifecycle.js
@@ -23,15 +23,13 @@ module.exports = {
       categories: ['vue3-essential'],
       url: 'https://eslint.vuejs.org/rules/no-deprecated-destroyed-lifecycle.html'
     },
-    fixable: null,
+    fixable: 'code',
     schema: [],
     messages: {
       deprecatedDestroyed:
         'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
       deprecatedBeforeDestroy:
-        'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-      insteadUnmounted: 'Instead, change to `unmounted`.',
-      insteadBeforeUnmount: 'Instead, change to `beforeUnmount`.'
+        'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.'
     }
   },
   /** @param {RuleContext} context */
@@ -43,15 +41,9 @@ module.exports = {
         context.report({
           node: destroyed.key,
           messageId: 'deprecatedDestroyed',
-          // I don't know if they have exactly the same function, so don't do autofix.
-          suggest: [
-            {
-              messageId: 'insteadUnmounted',
-              fix(fixer) {
-                return fix(fixer, destroyed, 'unmounted')
-              }
-            }
-          ]
+          fix(fixer) {
+            return fix(fixer, destroyed, 'unmounted')
+          }
         })
       }
 
@@ -60,15 +52,9 @@ module.exports = {
         context.report({
           node: beforeDestroy.key,
           messageId: 'deprecatedBeforeDestroy',
-          // I don't know if they have exactly the same function, so don't do autofix.
-          suggest: [
-            {
-              messageId: 'insteadBeforeUnmount',
-              fix(fixer) {
-                return fix(fixer, beforeDestroy, 'beforeUnmount')
-              }
-            }
-          ]
+          fix(fixer) {
+            return fix(fixer, beforeDestroy, 'beforeUnmount')
+          }
         })
       }
 
diff --git a/lib/rules/no-deprecated-v-is.js b/lib/rules/no-deprecated-v-is.js
new file mode 100644
index 00000000..cc4ad0a3
--- /dev/null
+++ b/lib/rules/no-deprecated-v-is.js
@@ -0,0 +1,31 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+const vIs = require('./syntaxes/v-is')
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'disallow deprecated `v-is` directive (in Vue.js 3.1.0+)',
+      // TODO Switch to `vue3-essential` in the major version.
+      // categories: ['vue3-essential'],
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/no-deprecated-v-is.html'
+    },
+    fixable: 'code',
+    schema: [],
+    messages: {
+      forbiddenVIs: '`v-is` directive is deprecated.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    const templateBodyVisitor = vIs.createTemplateBodyVisitor(context)
+    return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
+  }
+}
diff --git a/lib/rules/no-this-in-before-route-enter.js b/lib/rules/no-this-in-before-route-enter.js
new file mode 100644
index 00000000..968f45c1
--- /dev/null
+++ b/lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Don't use this in a beforeRouteEnter method
+ * @author Przemyslaw Jan Beigert
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'disallow this usage in a beforeRouteEnter method',
+      categories: null,
+      url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      disallow:
+        "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards."
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /**
+     * @typedef {object} ScopeStack
+     * @property {ScopeStack | null} upper
+     * @property {FunctionExpression | FunctionDeclaration} node
+     * @property {boolean} beforeRouteEnter
+     */
+    /** @type {Set<FunctionExpression>} */
+    const beforeRouteEnterFunctions = new Set()
+    /** @type {ScopeStack | null} */
+    let scopeStack = null
+
+    /**
+     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+     */
+    function onFunctionEnter(node) {
+      if (node.type === 'ArrowFunctionExpression') {
+        return
+      }
+      scopeStack = {
+        upper: scopeStack,
+        node,
+        beforeRouteEnter: beforeRouteEnterFunctions.has(
+          /** @type {never} */ (node)
+        )
+      }
+    }
+
+    /**
+     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+     */
+    function onFunctionExit(node) {
+      if (scopeStack && scopeStack.node === node) {
+        scopeStack = scopeStack.upper
+      }
+    }
+    return utils.defineVueVisitor(context, {
+      onVueObjectEnter(node) {
+        const beforeRouteEnter = utils.findProperty(node, 'beforeRouteEnter')
+        if (
+          beforeRouteEnter &&
+          beforeRouteEnter.value.type === 'FunctionExpression'
+        ) {
+          beforeRouteEnterFunctions.add(beforeRouteEnter.value)
+        }
+      },
+      ':function': onFunctionEnter,
+      ':function:exit': onFunctionExit,
+      ThisExpression(node) {
+        if (scopeStack && scopeStack.beforeRouteEnter) {
+          context.report({
+            node,
+            messageId: 'disallow'
+          })
+        }
+      }
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/no-deprecated-destroyed-lifecycle.js b/tests/lib/rules/no-deprecated-destroyed-lifecycle.js
index 00dda6ed..c8c6774d 100644
--- a/tests/lib/rules/no-deprecated-destroyed-lifecycle.js
+++ b/tests/lib/rules/no-deprecated-destroyed-lifecycle.js
@@ -111,42 +111,24 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
       }
       </script>
       `,
-      errors: [
-        {
-          message:
-            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-          line: 4,
-          suggestions: [
-            {
-              desc: 'Instead, change to `beforeUnmount`.',
-              output: `
+      output: `
       <script>
       export default {
         beforeUnmount () {},
-        destroyed () {},
+        unmounted () {},
       }
       </script>
-      `
-            }
-          ]
+      `,
+      errors: [
+        {
+          message:
+            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
+          line: 4
         },
         {
           message:
             'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
-          line: 5,
-          suggestions: [
-            {
-              desc: 'Instead, change to `unmounted`.',
-              output: `
-      <script>
-      export default {
-        beforeDestroy () {},
-        unmounted () {},
-      }
-      </script>
-      `
-            }
-          ]
+          line: 5
         }
       ]
     },
@@ -160,42 +142,24 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
       }
       </script>
       `,
-      errors: [
-        {
-          message:
-            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-          line: 4,
-          suggestions: [
-            {
-              desc: 'Instead, change to `beforeUnmount`.',
-              output: `
+      output: `
       <script>
       export default {
         beforeUnmount:beforeDestroy,
-        destroyed,
+        unmounted:destroyed,
       }
       </script>
-      `
-            }
-          ]
+      `,
+      errors: [
+        {
+          message:
+            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
+          line: 4
         },
         {
           message:
             'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
-          line: 5,
-          suggestions: [
-            {
-              desc: 'Instead, change to `unmounted`.',
-              output: `
-      <script>
-      export default {
-        beforeDestroy,
-        unmounted:destroyed,
-      }
-      </script>
-      `
-            }
-          ]
+          line: 5
         }
       ]
     },
@@ -218,15 +182,7 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
       }
       </script>
       `,
-      errors: [
-        {
-          message:
-            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-          line: 12,
-          suggestions: [
-            {
-              desc: 'Instead, change to `beforeUnmount`.',
-              output: `
+      output: `
       <script>
       export default {
         beforeCreate,
@@ -238,40 +194,21 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
         activated,
         deactivated,
         beforeUnmount:beforeDestroy,
-        destroyed,
+        unmounted:destroyed,
         errorCaptured,
       }
       </script>
-      `
-            }
-          ]
+      `,
+      errors: [
+        {
+          message:
+            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
+          line: 12
         },
         {
           message:
             'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
-          line: 13,
-          suggestions: [
-            {
-              desc: 'Instead, change to `unmounted`.',
-              output: `
-      <script>
-      export default {
-        beforeCreate,
-        created,
-        beforeMount,
-        mounted,
-        beforeUpdate,
-        updated,
-        activated,
-        deactivated,
-        beforeDestroy,
-        unmounted:destroyed,
-        errorCaptured,
-      }
-      </script>
-      `
-            }
-          ]
+          line: 13
         }
       ]
     },
@@ -285,42 +222,24 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
       }
       </script>
       `,
-      errors: [
-        {
-          message:
-            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-          line: 4,
-          suggestions: [
-            {
-              desc: 'Instead, change to `beforeUnmount`.',
-              output: `
+      output: `
       <script>
       export default {
         ['beforeUnmount']() {},
-        ['destroyed']() {},
+        ['unmounted']() {},
       }
       </script>
-      `
-            }
-          ]
+      `,
+      errors: [
+        {
+          message:
+            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
+          line: 4
         },
         {
           message:
             'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
-          line: 5,
-          suggestions: [
-            {
-              desc: 'Instead, change to `unmounted`.',
-              output: `
-      <script>
-      export default {
-        ['beforeDestroy']() {},
-        ['unmounted']() {},
-      }
-      </script>
-      `
-            }
-          ]
+          line: 5
         }
       ]
     },
@@ -334,42 +253,24 @@ ruleTester.run('no-deprecated-destroyed-lifecycle', rule, {
       }
       </script>
       `,
-      errors: [
-        {
-          message:
-            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
-          line: 4,
-          suggestions: [
-            {
-              desc: 'Instead, change to `beforeUnmount`.',
-              output: `
+      output: `
       <script>
       export default {
         [\`beforeUnmount\`]() {},
-        [\`destroyed\`]() {},
+        [\`unmounted\`]() {},
       }
       </script>
-      `
-            }
-          ]
+      `,
+      errors: [
+        {
+          message:
+            'The `beforeDestroy` lifecycle hook is deprecated. Use `beforeUnmount` instead.',
+          line: 4
         },
         {
           message:
             'The `destroyed` lifecycle hook is deprecated. Use `unmounted` instead.',
-          line: 5,
-          suggestions: [
-            {
-              desc: 'Instead, change to `unmounted`.',
-              output: `
-      <script>
-      export default {
-        [\`beforeDestroy\`]() {},
-        [\`unmounted\`]() {},
-      }
-      </script>
-      `
-            }
-          ]
+          line: 5
         }
       ]
     }
diff --git a/tests/lib/rules/no-deprecated-v-is.js b/tests/lib/rules/no-deprecated-v-is.js
new file mode 100644
index 00000000..cdc47b9a
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-v-is.js
@@ -0,0 +1,36 @@
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-deprecated-v-is')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 2015
+  }
+})
+
+tester.run('no-deprecated-v-is', rule, {
+  valid: [
+    `<template>
+      <div is="vue:MyComponent" />
+    </template>`,
+    `<template>
+      <component is="MyComponent" />
+    </template>`
+  ],
+  invalid: [
+    {
+      code: `
+      <template>
+        <div v-is="'MyComponent'" />
+      </template>`,
+      errors: [
+        {
+          message: '`v-is` directive is deprecated.',
+          line: 3
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/no-this-in-before-route-enter.js b/tests/lib/rules/no-this-in-before-route-enter.js
new file mode 100644
index 00000000..1171959a
--- /dev/null
+++ b/tests/lib/rules/no-this-in-before-route-enter.js
@@ -0,0 +1,190 @@
+/**
+ * @fileoverview  Don&#39;t use &#34;this&#34; i a beforeRouteEnter method
+ * @author Przemyslaw Jan Beigert
+ */
+'use strict'
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-this-in-before-route-enter')
+const RuleTester = require('eslint').RuleTester
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const template = (beforeRouteEnter) => `
+<template>
+  <p>{{ greeting }} World!</p>
+</template>
+
+<script>
+export default {
+  data () {
+    return {
+      greeting: "Hello"
+    };
+  },
+  beforeRouteEnter() {
+      ${beforeRouteEnter}
+  }
+};
+</script>
+
+<style scoped>
+p {
+  font-size: 2em;
+  text-align: center;
+}
+</style>`
+
+const functionTemplate = (beforeRouteEnter) => `
+<template>
+  <p>{{ greeting }} World!</p>
+</template>
+
+<script>
+export default {
+  data () {
+    return {
+      greeting: "Hello"
+    };
+  },
+  beforeRouteEnter: function() {
+      ${beforeRouteEnter}
+  }
+};
+</script>`
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+ruleTester.run('no-this-in-before-route-enter', rule, {
+  valid: [
+    template(''),
+    template('const variable = 42;'),
+    template('someFunction(42)'),
+    `
+<template>
+  <p>{{ greeting }} World!</p>
+</template>
+
+<script>
+export default {
+  data () {
+    return {
+      greeting: "Hello"
+    };
+  },
+};`
+  ],
+  invalid: [
+    {
+      code: template(`this.xxx();`),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 7,
+          endLine: 14,
+          endColumn: 11
+        }
+      ]
+    },
+    {
+      code: functionTemplate('this.method();'),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 7,
+          endLine: 14,
+          endColumn: 11
+        }
+      ]
+    },
+    {
+      code: template('this.attr = this.method();'),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 7,
+          endLine: 14,
+          endColumn: 11
+        },
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 19,
+          endLine: 14,
+          endColumn: 23
+        }
+      ]
+    },
+    {
+      code: functionTemplate('this.attr = this.method();'),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 7,
+          endLine: 14,
+          endColumn: 11
+        },
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 14,
+          column: 19,
+          endLine: 14,
+          endColumn: 23
+        }
+      ]
+    },
+    {
+      code: template(`
+                if (this.method()) {}
+            `),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 15,
+          column: 21,
+          endLine: 15,
+          endColumn: 25
+        }
+      ]
+    },
+    {
+      code: functionTemplate(`
+                if (true) { this.method(); }
+            `),
+      filename: 'ValidComponent.vue',
+      errors: [
+        {
+          message:
+            "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards.",
+          line: 15,
+          column: 29,
+          endLine: 15,
+          endColumn: 33
+        }
+      ]
+    }
+  ]
+})