From 253a86ee29c499fdd3d73b05d18da4e4026db9df Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 19 Jul 2017 18:05:17 +0200 Subject: [PATCH 1/3] Add `return-in-computed-property` rule. --- docs/rules/return-in-computed-property.md | 44 ++++++ lib/rules/return-in-computed-property.js | 111 ++++++++++++++ .../lib/rules/return-in-computed-property.js | 138 ++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 docs/rules/return-in-computed-property.md create mode 100644 lib/rules/return-in-computed-property.js create mode 100644 tests/lib/rules/return-in-computed-property.js diff --git a/docs/rules/return-in-computed-property.md b/docs/rules/return-in-computed-property.md new file mode 100644 index 000000000..5f66d08b0 --- /dev/null +++ b/docs/rules/return-in-computed-property.md @@ -0,0 +1,44 @@ +# Enforces that a return statement is present in computed property (return-in-computed-property) + +## :book: Rule Details + +This rule enforces that a `return` statement is present in `computed` properties. + +:-1: Examples of **incorrect** code for this rule: + +```js +export default { + computed: { + foo () { + }, + bar: function () { + } + } +} +``` + +:+1: Examples of **correct** code for this rule: + +```js +export default { + computed: { + foo () { + return true + }, + bar: function () { + return false + } + } +} +``` + +## :wrench: Options + +This rule has an object option: +- `"treatUndefinedAsUnspecified"`: `true` (default) disallows implicitly returning undefined with a `return;` statement. + +``` +vue/return-in-computed-property: [2, { + treatUndefinedAsUnspecified: true +}] +``` diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js new file mode 100644 index 000000000..2b38a9d1b --- /dev/null +++ b/lib/rules/return-in-computed-property.js @@ -0,0 +1,111 @@ +/** + * @fileoverview Enforces that a return statement is present in computed property (return-in-computed-property) + * @author Armano + */ +'use strict' + +const utils = require('../utils') + +function create (context) { + const options = context.options[0] || {} + const treatUndefinedAsUnspecified = !(options.treatUndefinedAsUnspecified === false) + + let funcInfo = { + funcInfo: null, + codePath: null, + hasReturn: false, + hasReturnValue: false, + node: null + } + const forbiddenNodes = [] + + // ---------------------------------------------------------------------- + // Helpers + // ---------------------------------------------------------------------- + function isValidReturn () { + return (!treatUndefinedAsUnspecified && funcInfo.hasReturn) || (treatUndefinedAsUnspecified && funcInfo.hasReturnValue) + } + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return Object.assign({}, + { + onCodePathStart (codePath, node) { + funcInfo = { + codePath, + funcInfo: funcInfo, + hasReturn: false, + hasReturnValue: false, + node + } + }, + onCodePathEnd () { + funcInfo = funcInfo.funcInfo + }, + ReturnStatement (node) { + funcInfo.hasReturn = true + funcInfo.hasReturnValue = Boolean(node.argument) + }, + 'FunctionExpression:exit' (node) { + if (!isValidReturn() && funcInfo.codePath.currentSegments.some((segment) => segment.reachable)) { + forbiddenNodes.push({ + hasReturn: funcInfo.hasReturn, + node: funcInfo.node, + type: 'return' + }) + } + } + }, + utils.executeOnVueComponent(context, properties => { + const computedProperties = utils.getComputedProperties(properties) + + computedProperties.forEach(cp => { + forbiddenNodes.forEach(el => { + if ( + cp.value && + el.node.loc.start.line >= cp.value.loc.start.line && + el.node.loc.end.line <= cp.value.loc.end.line + ) { + context.report({ + node: el.node, + message: 'Expected to return a value in "{{name}}" computed property.', + data: { + name: cp.key + } + }) + } + }) + }) + }) + ) +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforces that a return statement is present in computed property.', + category: 'Possible Errors', + recommended: false + }, + fixable: null, // or "code" or "whitespace" + schema: [ + { + type: 'object', + properties: { + treatUndefinedAsUnspecified: { + type: 'boolean' + } + }, + additionalProperties: false + } + ] + }, + + create +} diff --git a/tests/lib/rules/return-in-computed-property.js b/tests/lib/rules/return-in-computed-property.js new file mode 100644 index 000000000..c8771775c --- /dev/null +++ b/tests/lib/rules/return-in-computed-property.js @@ -0,0 +1,138 @@ +/** + * @fileoverview Enforces that a return statement is present in computed property (return-in-computed-property) + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/return-in-computed-property') + +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('return-in-computed-property', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo () { + return true + }, + bar: function () { + return false + }, + bar3: { + set () { + return true + }, + get () { + return true + } + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' } + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo: { + get () { + return + } + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + options: [{ treatUndefinedAsUnspecified: false }] + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo () { + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo: function () { + } + } + } + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo: function () { + if (a) { + return + } + } + } + } + `, + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo: { + set () { + }, + get () { + } + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 7 + }] + } + ] +}) From 722f810d76eef951cc504a751da46dc7c8a75cd3 Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 19 Jul 2017 19:06:00 +0200 Subject: [PATCH 2/3] Add unit test proposed by @chrisvfritz --- .../lib/rules/return-in-computed-property.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/lib/rules/return-in-computed-property.js b/tests/lib/rules/return-in-computed-property.js index c8771775c..5af0df28a 100644 --- a/tests/lib/rules/return-in-computed-property.js +++ b/tests/lib/rules/return-in-computed-property.js @@ -133,6 +133,26 @@ ruleTester.run('return-in-computed-property', rule, { message: 'Expected to return a value in "foo" computed property.', line: 7 }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo: function () { + function bar () { + return this.baz * 2 + } + bar() + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] } ] }) From 59fd6a490c422ccd542c3eba00b68cba926f8db7 Mon Sep 17 00:00:00 2001 From: Armano Date: Thu, 20 Jul 2017 00:52:52 +0200 Subject: [PATCH 3/3] Fix issue with treatUndefinedAsUnspecified & add missing unit test --- lib/rules/return-in-computed-property.js | 7 +++- .../lib/rules/return-in-computed-property.js | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js index 2b38a9d1b..5a3367812 100644 --- a/lib/rules/return-in-computed-property.js +++ b/lib/rules/return-in-computed-property.js @@ -23,7 +23,10 @@ function create (context) { // Helpers // ---------------------------------------------------------------------- function isValidReturn () { - return (!treatUndefinedAsUnspecified && funcInfo.hasReturn) || (treatUndefinedAsUnspecified && funcInfo.hasReturnValue) + if (!funcInfo.hasReturn) { + return false + } + return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue } // ---------------------------------------------------------------------- @@ -49,7 +52,7 @@ function create (context) { funcInfo.hasReturnValue = Boolean(node.argument) }, 'FunctionExpression:exit' (node) { - if (!isValidReturn() && funcInfo.codePath.currentSegments.some((segment) => segment.reachable)) { + if (!isValidReturn()) { forbiddenNodes.push({ hasReturn: funcInfo.hasReturn, node: funcInfo.node, diff --git a/tests/lib/rules/return-in-computed-property.js b/tests/lib/rules/return-in-computed-property.js index 5af0df28a..4ee565e41 100644 --- a/tests/lib/rules/return-in-computed-property.js +++ b/tests/lib/rules/return-in-computed-property.js @@ -153,6 +153,44 @@ ruleTester.run('return-in-computed-property', rule, { message: 'Expected to return a value in "foo" computed property.', line: 4 }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo () { + }, + bar () { + return + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + options: [{ treatUndefinedAsUnspecified: false }], + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + computed: { + foo () { + return + } + } + } + `, + parserOptions: { ecmaVersion: 8, sourceType: 'module' }, + options: [{ treatUndefinedAsUnspecified: true }], + errors: [{ + message: 'Expected to return a value in "foo" computed property.', + line: 4 + }] } ] })