Repository: vuejs/eslint-plugin-vue
Pull request #84 (2017-07-16 13:59:43)

Add `no-shared-component-data` rule.
This PR implements rules proposed in #79 

**DONE:**
- [x] Create documtation
- [x] Add tests
- [x] Implement rule

**Importnant:**

This rule splits `executeOnVueComponent` into `executeOnVueInstance` and `executeOnVueComponent` and adds alias `executeOnVue`

- if you want to process only Vue components use `utils.executeOnVueComponent`
- if you want to process only `new Vue` use `utils.executeOnVueInstance`
- If you want to process both use `utils.executeOnVue`


armano2: @michalsnik cleaned up and merged
mysticatea: I'm considering the rule name. In general, eslint rules which disallow something starts with `no-`.

I don't have the best idea, but maybe `no-shared-component-data`?
I guess that it's shared by component instances if the `data` is not a function.
armano2: @mysticatea `no-shared-component-data` make more sense than `component-data`, i will rename it
armano2: @michalsnik suggestions applied
armano2: @michalsnik fixed

Patch:
diff --git a/docs/rules/no-shared-component-data.md b/docs/rules/no-shared-component-data.md
new file mode 100644
index 00000000..093b502f
--- /dev/null
+++ b/docs/rules/no-shared-component-data.md
@@ -0,0 +1,33 @@
+# Enforces component's data property to be a function (no-shared-component-data)
+
+When using the data property on a component (i.e. anywhere except on `new Vue`), the value must be a function that returns an object.
+
+## :book: Rule Details
+
+When the value of `data` is an object, it’s shared across all instances of a component.
+
+:-1: Examples of **incorrect** code for this rule:
+
+```js
+Vue.component('some-comp', {
+  data: {
+    foo: 'bar'
+  }
+})
+```
+
+:+1: Examples of **correct** code for this rule:
+
+```js
+Vue.component('some-comp', {
+  data: function () {
+    return {
+      foo: 'bar'
+    }
+  }
+})
+```
+
+## :wrench: Options
+
+Nothing.
diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js
index 31a20113..ace94908 100644
--- a/lib/rules/name-property-casing.js
+++ b/lib/rules/name-property-casing.js
@@ -55,7 +55,7 @@ function create (context) {
   // Public
   // ----------------------------------------------------------------------
 
-  return utils.executeOnVueComponent(context, (obj) => {
+  return utils.executeOnVue(context, (obj) => {
     const node = obj.properties
       .filter(item => (
         item.type === 'Property' &&
diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js
index 46e5e88e..b42fa71c 100644
--- a/lib/rules/no-async-in-computed-properties.js
+++ b/lib/rules/no-async-in-computed-properties.js
@@ -114,7 +114,7 @@ function create (context) {
         })
       }
     },
-    utils.executeOnVueComponent(context, (obj) => {
+    utils.executeOnVue(context, (obj) => {
       const computedProperties = utils.getComputedProperties(obj)
 
       computedProperties.forEach(cp => {
diff --git a/lib/rules/no-shared-component-data.js b/lib/rules/no-shared-component-data.js
new file mode 100644
index 00000000..67e2ec45
--- /dev/null
+++ b/lib/rules/no-shared-component-data.js
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Enforces component's data property to be a function.
+ * @author Armano
+ */
+'use strict'
+
+const utils = require('../utils')
+
+function create (context) {
+  // ----------------------------------------------------------------------
+  // Public
+  // ----------------------------------------------------------------------
+
+  return utils.executeOnVueComponent(context, (obj) => {
+    obj.properties
+      .filter(p =>
+        p.type === 'Property' &&
+        p.key.type === 'Identifier' &&
+        p.key.name === 'data' &&
+        p.value.type !== 'FunctionExpression' &&
+        p.value.type !== 'Identifier'
+      )
+      .forEach(cp => {
+        context.report({
+          node: cp.value,
+          message: '`data` property in component must be a function'
+        })
+      })
+  })
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: "Enforces component's data property to be a function.",
+      category: 'Possible Errors',
+      recommended: false
+    },
+    fixable: null,  // or "code" or "whitespace"
+    schema: [
+      // fill in your schema
+    ]
+  },
+
+  create
+}
diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js
index beb928ad..ddc376f1 100644
--- a/lib/rules/no-side-effects-in-computed-properties.js
+++ b/lib/rules/no-side-effects-in-computed-properties.js
@@ -33,7 +33,7 @@ function create (context) {
         }
       }
     },
-    utils.executeOnVueComponent(context, (obj) => {
+    utils.executeOnVue(context, (obj) => {
       const computedProperties = utils.getComputedProperties(obj)
 
       computedProperties.forEach(cp => {
diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js
index aed79510..d35d6458 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -80,7 +80,7 @@ function create (context) {
   const extendedOrder = order.map(property => groups[property] || property)
   const orderMap = getOrderMap(extendedOrder)
 
-  return utils.executeOnVueComponent(context, (obj) => {
+  return utils.executeOnVue(context, (obj) => {
     checkOrder(obj.properties, orderMap, context)
   })
 }
diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js
index 5a336781..e2d4a731 100644
--- a/lib/rules/return-in-computed-property.js
+++ b/lib/rules/return-in-computed-property.js
@@ -61,7 +61,7 @@ function create (context) {
         }
       }
     },
-    utils.executeOnVueComponent(context, properties => {
+    utils.executeOnVue(context, properties => {
       const computedProperties = utils.getComputedProperties(properties)
 
       computedProperties.forEach(cp => {
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 1c23b296..018b9493 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -363,6 +363,25 @@ module.exports = {
       node.arguments[0].type === 'ObjectExpression'
   },
 
+  executeOnVue (context, cb) {
+    return Object.assign(
+      this.executeOnVueComponent(context, cb),
+      this.executeOnVueInstance(context, cb)
+    )
+  },
+
+  executeOnVueInstance (context, cb) {
+    const _this = this
+
+    return {
+      'NewExpression:exit' (node) {
+        // new Vue({})
+        if (!_this.isVueInstance(node)) return
+        cb(node.arguments[0])
+      }
+    }
+  },
+
   executeOnVueComponent (context, cb) {
     const filePath = context.getFilename()
     const _this = this
@@ -377,11 +396,6 @@ module.exports = {
         // Vue.component('xxx', {}) || component('xxx', {})
         if (!_this.isVueComponent(node)) return
         cb(node.arguments.slice(-1)[0])
-      },
-      'NewExpression:exit' (node) {
-        // new Vue({})
-        if (!_this.isVueInstance(node)) return
-        cb(node.arguments[0])
       }
     }
   }


Test patch:
diff --git a/tests/lib/rules/no-shared-component-data.js b/tests/lib/rules/no-shared-component-data.js
new file mode 100644
index 00000000..aad6b304
--- /dev/null
+++ b/tests/lib/rules/no-shared-component-data.js
@@ -0,0 +1,123 @@
+/**
+ * @fileoverview Enforces component's data property to be a function.
+ * @author Armano
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-shared-component-data')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester()
+ruleTester.run('no-shared-component-data', rule, {
+
+  valid: [
+    {
+      filename: 'test.js',
+      code: `
+        new Vue({
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        })
+      `
+    },
+    {
+      filename: 'test.js',
+      code: `
+        new Vue({
+          data: {
+            foo: 'bar'
+          }
+        })
+      `
+    },
+    {
+      filename: 'test.js',
+      code: `
+        Vue.component('some-comp', {
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        })
+      `,
+      parserOptions: { ecmaVersion: 6 }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: function () {
+            return {
+              foo: 'bar'
+            }
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          ...foo
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module', ecmaFeatures: { experimentalObjectRestSpread: true }}
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' }
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.js',
+      code: `
+        Vue.component('some-comp', {
+          data: {
+            foo: 'bar'
+          }
+        })
+      `,
+      parserOptions: { ecmaVersion: 6 },
+      errors: [{
+        message: '`data` property in component must be a function',
+        line: 3
+      }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          data: {
+            foo: 'bar'
+          }
+        }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [{
+        message: '`data` property in component must be a function',
+        line: 3
+      }]
+    }
+  ]
+})