diff --git a/docs/rules/README.md b/docs/rules/README.md index 7e0b1dd96..6e9102682 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -335,6 +335,8 @@ The following rules extend the rules provided by ESLint itself and apply them to | [vue/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: | | [vue/keyword-spacing](./keyword-spacing.md) | enforce consistent spacing before and after keywords | :wrench: | | [vue/max-len](./max-len.md) | enforce a maximum line length | | +| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | +| [vue/no-computed-in-data](./no-computed-in-data.md) | disallow computed properties used in the data property | | | [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | | | [vue/no-extra-parens](./no-extra-parens.md) | disallow unnecessary parentheses | :wrench: | | [vue/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | | diff --git a/docs/rules/no-computed-in-data.md b/docs/rules/no-computed-in-data.md new file mode 100644 index 000000000..2965d2e8f --- /dev/null +++ b/docs/rules/no-computed-in-data.md @@ -0,0 +1,55 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/no-computed-in-data +description: disallow computed properties used in the data property +--- +# vue/no-computed-in-data +> disallow computed properties used in the data property + +## :book: Rule Details + +This rule report computed properties that used in data property + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further reading + +[Computed Properties](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-computed-in-data.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-computed-in-data.js) diff --git a/lib/index.js b/lib/index.js index 5f285be69..2af7ccfc9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -48,6 +48,7 @@ module.exports = { 'no-async-in-computed-properties': require('./rules/no-async-in-computed-properties'), 'no-bare-strings-in-template': require('./rules/no-bare-strings-in-template'), 'no-boolean-default': require('./rules/no-boolean-default'), + 'no-computed-in-data': require('./rules/no-computed-in-data'), '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'), diff --git a/lib/rules/no-computed-in-data.js b/lib/rules/no-computed-in-data.js new file mode 100644 index 000000000..ae9b76efd --- /dev/null +++ b/lib/rules/no-computed-in-data.js @@ -0,0 +1,105 @@ +/** + * @fileoverview Ensure computed properties are not used in the data() + * @author IWANABETHATGUY + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ +const utils = require('../utils') +function topOfStack (stack) { + console.assert(Array.isArray(stack)) + return stack[stack.length - 1] +} +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'disallow computed properties used in the data property', + categories: undefined, + recommended: false, + url: 'https://eslint.vuejs.org/rules/no-computed-in-data.html' + }, + fixable: null, // or "code" or "whitespace" + schema: [ + // fill in your schema + ] + }, + + create: function (context) { + let dataAstNode + let targetObjectExpression + const memberExpressionMap = Object.create(null) + const scopeStack = [] + // if data is a function, should reference its scope, other wise should be undefined + let dataPropertyScope + return { + + ObjectExpression (node) { + if (!dataAstNode) { + dataAstNode = node.properties.find( + p => + p.type === 'Property' && + p.key.type === 'Identifier' && + p.key.name === 'data' + ) + if (dataAstNode) { + targetObjectExpression = node + if (dataAstNode.value.type === 'FunctionExpression') { + dataPropertyScope = dataAstNode.value + scopeStack.push(dataPropertyScope) + } + } + } + }, + ':function' (node) { + scopeStack.push(node) + }, + ':function:exit' (node) { + scopeStack.pop() + }, + 'Property:exit' (node) { + if (node === dataAstNode) { + dataAstNode = undefined + } + }, + MemberExpression (node) { + if (dataAstNode && dataPropertyScope === topOfStack(scopeStack)) { + // a memberExpression `like this.a.c.d` -> when traverse to c.d we can got the the full name -> this.a.c.d + const fullName = utils.parseMemberExpression(node).slice(0, 2).join('.') + if (memberExpressionMap[fullName]) { + // check if parent node in this array, if true ignore this node, such as `{a: this.a.c.d}`, this traverse function visit order is `this.a.c.d` -> `a.c.d` -> `c.d` + const hasParentNodeInArray = memberExpressionMap[fullName].some(nodeInMap => node.parent === nodeInMap) + if (!hasParentNodeInArray) { + memberExpressionMap[fullName] = [...memberExpressionMap[fullName], node] + } + } else { + memberExpressionMap[fullName] = [node] + } + } + }, + ...utils.executeOnVue(context, obj => { + // check if targetObjectExpression is Vue component + if (targetObjectExpression !== obj) { + return + } + const computedPropertyNameList = utils.getComputedProperties(obj).map(item => `this.${item.key}`) + Object.keys(memberExpressionMap).forEach(fullName => { + const index = computedPropertyNameList.findIndex(name => fullName.startsWith(name)) + if (index !== -1) { + memberExpressionMap[fullName].forEach(memberExpressionNode => { + context.report({ + node: memberExpressionNode, + message: `Computed property '{{name}}' can't use in data property.`, + data: { + name: computedPropertyNameList[index] + } + }) + }) + } + }) + }) + } + } +} diff --git a/tests/lib/rules/no-computed-in-data.js b/tests/lib/rules/no-computed-in-data.js new file mode 100644 index 000000000..a555cd525 --- /dev/null +++ b/tests/lib/rules/no-computed-in-data.js @@ -0,0 +1,271 @@ +/** + * @fileoverview Ensure computed properties are not used in the data() + * @author IWANABETHATGUY + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-computed-in-data') + +var RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2019, + sourceType: 'module' + } +}) +ruleTester.run('no-computed-in-data', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + // should not warn when use computed in methods + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + // should not warn when objectExpression is not a vue component + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + // should warn when prop key is an String literal + { + filename: 'test.vue', + code: ` + + `, + errors: [ + `Computed property 'this.test' can't use in data property.`, + `Computed property 'this.foo' can't use in data property.` + ] + }, + // should report when data is objectExpression, when this is a root component + { + filename: 'test.vue', + code: ` + + `, + errors: [ + `Computed property 'this.test' can't use in data property.` + ] + }, + // same computed data referenced by multi data property + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: `Computed property 'this.test' can't use in data property.`, + line: 6, + column: 16 + }, + { + message: `Computed property 'this.test' can't use in data property.`, + line: 7, + column: 16 + } + ] + }, + // should also report when computed data return an object + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: `Computed property 'this.test' can't use in data property.`, + line: 6, + column: 16, + endColumn: 29, + endLine: 6 + }, + { + message: `Computed property 'this.test' can't use in data property.`, + line: 7, + column: 16, + endLine: 7, + endColumn: 29 + } + ] + }, + // should only report the data function scope computed property + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: `Computed property 'this.foo' can't use in data property.`, + line: 6, + column: 18, + endColumn: 26, + endLine: 6 + } + ] + } + ] +})