diff --git a/docs/rules/require-render-return.md b/docs/rules/require-render-return.md new file mode 100644 index 000000000..5a2479f93 --- /dev/null +++ b/docs/rules/require-render-return.md @@ -0,0 +1,37 @@ +# Enforces render function to always return value (require-render-return) + +This rule aims to enforce render function to allways return value + +## :book: Rule Details + +:-1: Examples of **incorrect** code for this rule: + +```js +export default { + render () { + } +} +``` +```js +export default { + render (h) { + if (foo) { + return + } + } +} +``` + +:+1: Examples of **correct** code for this rule: + +```js +export default { + render (h) { + return + } +} +``` + +## :wrench: Options + +Nothing. diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js new file mode 100644 index 000000000..79ec626d6 --- /dev/null +++ b/lib/rules/require-render-return.js @@ -0,0 +1,59 @@ +/** + * @fileoverview Enforces render function to always return value. + * @author Armano + */ +'use strict' + +const utils = require('../utils') + +function create (context) { + const forbiddenNodes = [] + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return Object.assign({}, + utils.executeOnFunctionsWithoutReturn(true, node => { + forbiddenNodes.push(node) + }), + utils.executeOnVue(context, obj => { + const node = obj.properties.find(item => item.type === 'Property' && + utils.getStaticPropertyName(item) === 'render' && + (item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression') && + !item.value.expression // render: () => test + ) + if (!node) return + + forbiddenNodes.forEach(el => { + if ( + el.loc.start.line >= node.value.loc.start.line && + el.loc.end.line <= node.value.loc.end.line + ) { + context.report({ + node: node.key, + message: 'Expected to return a value in render function.' + }) + } + }) + }) + ) +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforces render function to always return value.', + category: 'Possible Errors', + recommended: false + }, + fixable: null, // or "code" or "whitespace" + schema: [] + }, + + create +} diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js index e2d4a7313..4658e8766 100644 --- a/lib/rules/return-in-computed-property.js +++ b/lib/rules/return-in-computed-property.js @@ -10,57 +10,16 @@ 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 () { - if (!funcInfo.hasReturn) { - return false - } - return !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()) { - forbiddenNodes.push({ - hasReturn: funcInfo.hasReturn, - node: funcInfo.node, - type: 'return' - }) - } - } - }, + utils.executeOnFunctionsWithoutReturn(treatUndefinedAsUnspecified, node => { + forbiddenNodes.push(node) + }), utils.executeOnVue(context, properties => { const computedProperties = utils.getComputedProperties(properties) @@ -68,11 +27,11 @@ function create (context) { 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 + el.loc.start.line >= cp.value.loc.start.line && + el.loc.end.line <= cp.value.loc.end.line ) { context.report({ - node: el.node, + node: el, message: 'Expected to return a value in "{{name}}" computed property.', data: { name: cp.key diff --git a/lib/utils/index.js b/lib/utils/index.js index 2cf39ddc4..ebe2fc24a 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -520,5 +520,56 @@ module.exports = { } } } + }, + + /** + * Find all functions which do not always return values + * @param {boolean} treatUndefinedAsUnspecified + * @param {Function} cb Callback function + */ + executeOnFunctionsWithoutReturn (treatUndefinedAsUnspecified, cb) { + let funcInfo = { + funcInfo: null, + codePath: null, + hasReturn: false, + hasReturnValue: false, + node: null + } + + function isValidReturn () { + if (!funcInfo.hasReturn) { + return false + } + return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue + } + + return { + 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) + }, + 'ArrowFunctionExpression:exit' (node) { + if (!isValidReturn()) { + cb(funcInfo.node) + } + }, + 'FunctionExpression:exit' (node) { + if (!isValidReturn()) { + cb(funcInfo.node) + } + } + } } } diff --git a/tests/lib/rules/require-render-return.js b/tests/lib/rules/require-render-return.js new file mode 100644 index 000000000..d40c49209 --- /dev/null +++ b/tests/lib/rules/require-render-return.js @@ -0,0 +1,124 @@ +/** + * @fileoverview Enforces render function to always return value. + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/require-render-return') +const RuleTester = require('eslint').RuleTester + +const parserOptions = { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { experimentalObjectRestSpread: true, jsx: true } +} + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('require-render-return', rule, { + valid: [ + { + code: `Vue.component('test', { + render() { + return {} + } + })`, + parserOptions + }, + { + code: `Vue.component('test', { + foo() { + return {} + } + })`, + parserOptions + }, + { + code: `Vue.component('test', { + foo: {} + })`, + parserOptions + }, + { + code: `Vue.component('test', { + render: foo + })`, + parserOptions + }, + { + code: `Vue.component('test', { + render() { + return
+ } + })`, + parserOptions + }, + { + filename: 'test.vue', + code: `export default { + render() { + return {} + } + }`, + parserOptions + }, + { + filename: 'test.vue', + code: `export default { + render: () => null + }`, + parserOptions + }, + { + filename: 'test.vue', + code: `export default { + render() { + if (a) { + return \`
a
\` + } else { + return \`a\` + } + } + }`, + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: `export default { + render() { + } + }`, + parserOptions, + errors: [{ + message: 'Expected to return a value in render function.', + type: 'Identifier', + line: 2 + }] + }, + { + code: `Vue.component('test', { + render: function () { + if (a) { + return + } + } + })`, + parserOptions, + errors: [{ + message: 'Expected to return a value in render function.', + type: 'Identifier', + line: 2 + }] + } + ] +}) diff --git a/tests/lib/rules/return-in-computed-property.js b/tests/lib/rules/return-in-computed-property.js index 4ee565e41..7dde0ea63 100644 --- a/tests/lib/rules/return-in-computed-property.js +++ b/tests/lib/rules/return-in-computed-property.js @@ -38,6 +38,13 @@ ruleTester.run('return-in-computed-property', rule, { get () { return true } + }, + bar4 () { + if (foo) { + return true + } else { + return false + } } } }