From 7453a77126e9840e846fdb4452a8b0c7b1ad108c Mon Sep 17 00:00:00 2001 From: Armano Date: Sat, 22 Jul 2017 14:29:18 +0200 Subject: [PATCH 1/2] Add `no-shared-component-data` rule. --- docs/rules/no-shared-component-data.md | 33 +++++ lib/rules/name-property-casing.js | 2 +- lib/rules/no-shared-component-data.js | 52 ++++++++ .../no-side-effects-in-computed-properties.js | 2 +- lib/rules/order-in-components.js | 2 +- lib/rules/return-in-computed-property.js | 2 +- lib/utils/index.js | 24 +++- tests/lib/rules/no-shared-component-data.js | 123 ++++++++++++++++++ 8 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 docs/rules/no-shared-component-data.md create mode 100644 lib/rules/no-shared-component-data.js create mode 100644 tests/lib/rules/no-shared-component-data.js diff --git a/docs/rules/no-shared-component-data.md b/docs/rules/no-shared-component-data.md new file mode 100644 index 000000000..093b502f9 --- /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 31a201134..ace949087 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-shared-component-data.js b/lib/rules/no-shared-component-data.js new file mode 100644 index 000000000..da56ca91f --- /dev/null +++ b/lib/rules/no-shared-component-data.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Enforces component's data property to be a function. + * @author Armano + */ +'use strict' + +const utils = require('../utils') + +function create (context) { + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return Object.assign({}, + 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 968f7a814..009873e98 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 aed795106..d35d6458a 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 5a3367812..e2d4a7313 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 1c23b296e..018b9493c 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]) } } } 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 000000000..aad6b3049 --- /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 + }] + } + ] +}) From 8718debba436e9d3fa8caefbfdb42efd957c5886 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 23 Jul 2017 15:09:03 +0200 Subject: [PATCH 2/2] Remove redundant code & merge with master --- lib/rules/no-async-in-computed-properties.js | 2 +- lib/rules/no-shared-component-data.js | 32 +++++++++----------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js index 46e5e88e7..b42fa71c3 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 index da56ca91f..67e2ec45f 100644 --- a/lib/rules/no-shared-component-data.js +++ b/lib/rules/no-shared-component-data.js @@ -11,24 +11,22 @@ function create (context) { // Public // ---------------------------------------------------------------------- - return Object.assign({}, - 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' - }) + 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' }) - }) - ) + }) + }) } // ------------------------------------------------------------------------------