Repository: vuejs/eslint-plugin-vue
Pull request #1302 (2020-09-19 02:44:15)

Add `vue/no-deprecated-props-default-this` rule
This PR add `vue/no-deprecated-props-default-this`  rule.

The `vue/no-deprecated-props-default-this`  rule reports the use of `this` within the props default value factory functions.


Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 9798e777..c0e7bc6a 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -50,6 +50,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 | [vue/no-deprecated-functional-template](./no-deprecated-functional-template.md) | disallow using deprecated the `functional` template (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-html-element-is](./no-deprecated-html-element-is.md) | disallow using deprecated the `is` attribute on HTML elements (in Vue.js 3.0.0+) |  |
 | [vue/no-deprecated-inline-template](./no-deprecated-inline-template.md) | disallow using deprecated `inline-template` attribute (in Vue.js 3.0.0+) |  |
+| [vue/no-deprecated-props-default-this](./no-deprecated-props-default-this.md) | disallow props default function `this` access |  |
 | [vue/no-deprecated-scope-attribute](./no-deprecated-scope-attribute.md) | disallow deprecated `scope` attribute (in Vue.js 2.5.0+) | :wrench: |
 | [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: |
diff --git a/docs/rules/no-deprecated-props-default-this.md b/docs/rules/no-deprecated-props-default-this.md
new file mode 100644
index 00000000..51ae7101
--- /dev/null
+++ b/docs/rules/no-deprecated-props-default-this.md
@@ -0,0 +1,70 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-deprecated-props-default-this
+description: disallow props default function `this` access
+---
+# vue/no-deprecated-props-default-this
+> disallow props default function `this` access
+
+- :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 the use of `this` within the props default value factory functions.
+In Vue.js 3.0.0+, props default value factory functions no longer have access to `this`.
+
+See [Migration Guide - Props Default Function `this` Access](https://v3.vuejs.org/guide/migration/props-default-this.html) for more details.
+
+<eslint-code-block :rules="{'vue/no-deprecated-props-default-this': ['error']}">
+
+```vue
+<script>
+export default {
+  props: {
+    a: String,
+    b: {
+      default () {
+        /* ✗ BAD */
+        return this.a
+      }
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/no-deprecated-props-default-this': ['error']}">
+
+```vue
+<script>
+export default {
+  props: {
+    a: String,
+    b: {
+      default (props) {
+        /* ✓ GOOD */
+        return props.a
+      }
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further Reading
+
+- [Migration Guide - Props Default Function `this` Access](https://v3.vuejs.org/guide/migration/props-default-this.html)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-deprecated-props-default-this.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-deprecated-props-default-this.js)
diff --git a/docs/rules/no-potential-component-option-typo.md b/docs/rules/no-potential-component-option-typo.md
index a3a9a6e7..2233361e 100644
--- a/docs/rules/no-potential-component-option-typo.md
+++ b/docs/rules/no-potential-component-option-typo.md
@@ -53,7 +53,7 @@ export default {
 
 </eslint-code-block>
 
-> we use editdistance to compare two string similarity, threshold is an option to control upper bound of editdistance to report
+> we use edit distance to compare two string similarity, threshold is an option to control upper bound of edit distance to report
 
 **Here is the another example about config option `threshold`**
 
@@ -75,15 +75,15 @@ export default {
   props: {
 
   },
-  /* ✓ GOOD, due to threshold is 5 */
-  method: {
+  /* ✗ BAD, due to threshold is 5 */
+  mehtod: {
 
   },
   /* ✓ GOOD, due to threshold is 5 */
   data: {
 
   },
-  /* ✗ BAD, due to we don't choose vue-router preset or add a custom option */
+  /* ✓ GOOD, due to we don't choose vue-router preset or add a custom option */
   beforeRouteEnteR() {
 
   }
@@ -97,7 +97,7 @@ export default {
 
 ```json
 {
-  "vue/no-unsed-vars": ["error", {
+  "vue/no-potential-component-option-typo": ["error", {
     "presets": ["vue"],
     "custom": [],
     "threshold": 1
@@ -111,11 +111,11 @@ export default {
 
 ## :rocket: Suggestion
 
-- We provide all the possible component option that editdistance between your vue component option and configuration options is greater than 0 and lessEqual than threshold
+- We provide all the possible component option that edit distance between your vue component option and configuration options is greater than 0 and less equal than threshold
 
 ## :books: Further Reading
 
-- [Edit_distance](https://en.wikipedia.org/wiki/Edit_distance)
+- [Edit distance](https://en.wikipedia.org/wiki/Edit_distance)
 
 ## :mag: Implementation
 
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 7d715c18..d1e2d176 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -18,6 +18,7 @@ module.exports = {
     'vue/no-deprecated-functional-template': 'error',
     'vue/no-deprecated-html-element-is': 'error',
     'vue/no-deprecated-inline-template': 'error',
+    'vue/no-deprecated-props-default-this': 'error',
     'vue/no-deprecated-scope-attribute': 'error',
     'vue/no-deprecated-slot-attribute': 'error',
     'vue/no-deprecated-slot-scope-attribute': 'error',
diff --git a/lib/index.js b/lib/index.js
index 556fadeb..df641f04 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -59,6 +59,7 @@ module.exports = {
     'no-deprecated-functional-template': require('./rules/no-deprecated-functional-template'),
     'no-deprecated-html-element-is': require('./rules/no-deprecated-html-element-is'),
     'no-deprecated-inline-template': require('./rules/no-deprecated-inline-template'),
+    'no-deprecated-props-default-this': require('./rules/no-deprecated-props-default-this'),
     'no-deprecated-scope-attribute': require('./rules/no-deprecated-scope-attribute'),
     'no-deprecated-slot-attribute': require('./rules/no-deprecated-slot-attribute'),
     'no-deprecated-slot-scope-attribute': require('./rules/no-deprecated-slot-scope-attribute'),
diff --git a/lib/rules/no-deprecated-props-default-this.js b/lib/rules/no-deprecated-props-default-this.js
new file mode 100644
index 00000000..926476c1
--- /dev/null
+++ b/lib/rules/no-deprecated-props-default-this.js
@@ -0,0 +1,107 @@
+/**
+ * @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 props default function `this` access',
+      categories: ['vue3-essential'],
+      url:
+        'https://eslint.vuejs.org/rules/no-deprecated-props-default-this.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      deprecated:
+        'Props default value factory functions no longer have access to `this`.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /**
+     * @typedef {object} ScopeStack
+     * @property {ScopeStack | null} upper
+     * @property {FunctionExpression | FunctionDeclaration} node
+     * @property {boolean} propDefault
+     */
+    /** @type {Set<FunctionExpression>} */
+    const propsDefault = new Set()
+    /** @type {ScopeStack | null} */
+    let scopeStack = null
+
+    /**
+     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+     */
+    function onFunctionEnter(node) {
+      if (node.type === 'ArrowFunctionExpression') {
+        return
+      }
+      if (scopeStack) {
+        scopeStack = {
+          upper: scopeStack,
+          node,
+          propDefault: false
+        }
+      } else if (node.type === 'FunctionExpression' && propsDefault.has(node)) {
+        scopeStack = {
+          upper: scopeStack,
+          node,
+          propDefault: true
+        }
+      }
+    }
+
+    /**
+     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
+     */
+    function onFunctionExit(node) {
+      if (scopeStack && scopeStack.node === node) {
+        scopeStack = scopeStack.upper
+      }
+    }
+    return utils.defineVueVisitor(context, {
+      onVueObjectEnter(node) {
+        for (const prop of utils.getComponentProps(node)) {
+          if (prop.type !== 'object') {
+            continue
+          }
+          if (prop.value.type !== 'ObjectExpression') {
+            continue
+          }
+          const def = utils.findProperty(prop.value, 'default')
+          if (!def) {
+            continue
+          }
+          if (def.value.type !== 'FunctionExpression') {
+            continue
+          }
+          propsDefault.add(def.value)
+        }
+      },
+      ':function': onFunctionEnter,
+      ':function:exit': onFunctionExit,
+      ThisExpression(node) {
+        if (scopeStack && scopeStack.propDefault) {
+          context.report({
+            node,
+            messageId: 'deprecated'
+          })
+        }
+      }
+    })
+  }
+}


Test patch:
diff --git a/tests/lib/rules/no-deprecated-props-default-this.js b/tests/lib/rules/no-deprecated-props-default-this.js
new file mode 100644
index 00000000..0993e817
--- /dev/null
+++ b/tests/lib/rules/no-deprecated-props-default-this.js
@@ -0,0 +1,187 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-deprecated-props-default-this')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+
+ruleTester.run('no-deprecated-props-default-this', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default (props) {
+                return props.a
+              }
+            }
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default: () => {
+                return this.a
+              }
+            }
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default () {
+                return function () {
+                  return this.a
+                }
+              }
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [{}, {}]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        const Foo = {
+          props: {
+            a: String,
+            b: {
+              default () {
+                return this.a
+              }
+            }
+          }
+        }
+        </script>
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default () {
+                return this.a
+              }
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          message:
+            'Props default value factory functions no longer have access to `this`.',
+          line: 9,
+          column: 24,
+          endLine: 9,
+          endColumn: 28
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default () {
+                return () => this.a
+              }
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        {
+          message:
+            'Props default value factory functions no longer have access to `this`.',
+          line: 9,
+          column: 30,
+          endLine: 9,
+          endColumn: 34
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template><div /></template>
+        <script>
+        export default {
+          props: {
+            a: String,
+            b: {
+              default () {
+                return this.a
+              }
+            },
+            c: {
+              default () {
+                return this.a
+              }
+            }
+          }
+        }
+        </script>
+      `,
+      errors: [
+        'Props default value factory functions no longer have access to `this`.',
+        'Props default value factory functions no longer have access to `this`.'
+      ]
+    }
+  ]
+})