Skip to content

Commit 49d3456

Browse files
armano2michalsnik
authored andcommitted
Add rule require-render-return. (#114)
* Add rule `require-render-return`. * findMissingReturns -> executeOnFunctionsWithoutReturn
1 parent 48fc537 commit 49d3456

File tree

6 files changed

+284
-47
lines changed

6 files changed

+284
-47
lines changed

docs/rules/require-render-return.md

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Enforces render function to always return value (require-render-return)
2+
3+
This rule aims to enforce render function to allways return value
4+
5+
## :book: Rule Details
6+
7+
:-1: Examples of **incorrect** code for this rule:
8+
9+
```js
10+
export default {
11+
render () {
12+
}
13+
}
14+
```
15+
```js
16+
export default {
17+
render (h) {
18+
if (foo) {
19+
return
20+
}
21+
}
22+
}
23+
```
24+
25+
:+1: Examples of **correct** code for this rule:
26+
27+
```js
28+
export default {
29+
render (h) {
30+
return
31+
}
32+
}
33+
```
34+
35+
## :wrench: Options
36+
37+
Nothing.

lib/rules/require-render-return.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @fileoverview Enforces render function to always return value.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
function create (context) {
10+
const forbiddenNodes = []
11+
12+
// ----------------------------------------------------------------------
13+
// Public
14+
// ----------------------------------------------------------------------
15+
16+
return Object.assign({},
17+
utils.executeOnFunctionsWithoutReturn(true, node => {
18+
forbiddenNodes.push(node)
19+
}),
20+
utils.executeOnVue(context, obj => {
21+
const node = obj.properties.find(item => item.type === 'Property' &&
22+
utils.getStaticPropertyName(item) === 'render' &&
23+
(item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression') &&
24+
!item.value.expression // render: () => test
25+
)
26+
if (!node) return
27+
28+
forbiddenNodes.forEach(el => {
29+
if (
30+
el.loc.start.line >= node.value.loc.start.line &&
31+
el.loc.end.line <= node.value.loc.end.line
32+
) {
33+
context.report({
34+
node: node.key,
35+
message: 'Expected to return a value in render function.'
36+
})
37+
}
38+
})
39+
})
40+
)
41+
}
42+
43+
// ------------------------------------------------------------------------------
44+
// Rule Definition
45+
// ------------------------------------------------------------------------------
46+
47+
module.exports = {
48+
meta: {
49+
docs: {
50+
description: 'Enforces render function to always return value.',
51+
category: 'Possible Errors',
52+
recommended: false
53+
},
54+
fixable: null, // or "code" or "whitespace"
55+
schema: []
56+
},
57+
58+
create
59+
}

lib/rules/return-in-computed-property.js

+6-47
Original file line numberDiff line numberDiff line change
@@ -10,69 +10,28 @@ function create (context) {
1010
const options = context.options[0] || {}
1111
const treatUndefinedAsUnspecified = !(options.treatUndefinedAsUnspecified === false)
1212

13-
let funcInfo = {
14-
funcInfo: null,
15-
codePath: null,
16-
hasReturn: false,
17-
hasReturnValue: false,
18-
node: null
19-
}
2013
const forbiddenNodes = []
2114

22-
// ----------------------------------------------------------------------
23-
// Helpers
24-
// ----------------------------------------------------------------------
25-
function isValidReturn () {
26-
if (!funcInfo.hasReturn) {
27-
return false
28-
}
29-
return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue
30-
}
31-
3215
// ----------------------------------------------------------------------
3316
// Public
3417
// ----------------------------------------------------------------------
3518

3619
return Object.assign({},
37-
{
38-
onCodePathStart (codePath, node) {
39-
funcInfo = {
40-
codePath,
41-
funcInfo: funcInfo,
42-
hasReturn: false,
43-
hasReturnValue: false,
44-
node
45-
}
46-
},
47-
onCodePathEnd () {
48-
funcInfo = funcInfo.funcInfo
49-
},
50-
ReturnStatement (node) {
51-
funcInfo.hasReturn = true
52-
funcInfo.hasReturnValue = Boolean(node.argument)
53-
},
54-
'FunctionExpression:exit' (node) {
55-
if (!isValidReturn()) {
56-
forbiddenNodes.push({
57-
hasReturn: funcInfo.hasReturn,
58-
node: funcInfo.node,
59-
type: 'return'
60-
})
61-
}
62-
}
63-
},
20+
utils.executeOnFunctionsWithoutReturn(treatUndefinedAsUnspecified, node => {
21+
forbiddenNodes.push(node)
22+
}),
6423
utils.executeOnVue(context, properties => {
6524
const computedProperties = utils.getComputedProperties(properties)
6625

6726
computedProperties.forEach(cp => {
6827
forbiddenNodes.forEach(el => {
6928
if (
7029
cp.value &&
71-
el.node.loc.start.line >= cp.value.loc.start.line &&
72-
el.node.loc.end.line <= cp.value.loc.end.line
30+
el.loc.start.line >= cp.value.loc.start.line &&
31+
el.loc.end.line <= cp.value.loc.end.line
7332
) {
7433
context.report({
75-
node: el.node,
34+
node: el,
7635
message: 'Expected to return a value in "{{name}}" computed property.',
7736
data: {
7837
name: cp.key

lib/utils/index.js

+51
Original file line numberDiff line numberDiff line change
@@ -512,5 +512,56 @@ module.exports = {
512512
}
513513
}
514514
}
515+
},
516+
517+
/**
518+
* Find all functions which do not always return values
519+
* @param {boolean} treatUndefinedAsUnspecified
520+
* @param {Function} cb Callback function
521+
*/
522+
executeOnFunctionsWithoutReturn (treatUndefinedAsUnspecified, cb) {
523+
let funcInfo = {
524+
funcInfo: null,
525+
codePath: null,
526+
hasReturn: false,
527+
hasReturnValue: false,
528+
node: null
529+
}
530+
531+
function isValidReturn () {
532+
if (!funcInfo.hasReturn) {
533+
return false
534+
}
535+
return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue
536+
}
537+
538+
return {
539+
onCodePathStart (codePath, node) {
540+
funcInfo = {
541+
codePath,
542+
funcInfo: funcInfo,
543+
hasReturn: false,
544+
hasReturnValue: false,
545+
node
546+
}
547+
},
548+
onCodePathEnd () {
549+
funcInfo = funcInfo.funcInfo
550+
},
551+
ReturnStatement (node) {
552+
funcInfo.hasReturn = true
553+
funcInfo.hasReturnValue = Boolean(node.argument)
554+
},
555+
'ArrowFunctionExpression:exit' (node) {
556+
if (!isValidReturn()) {
557+
cb(funcInfo.node)
558+
}
559+
},
560+
'FunctionExpression:exit' (node) {
561+
if (!isValidReturn()) {
562+
cb(funcInfo.node)
563+
}
564+
}
565+
}
515566
}
516567
}
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @fileoverview Enforces render function to always return value.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/require-render-return')
12+
const RuleTester = require('eslint').RuleTester
13+
14+
const parserOptions = {
15+
ecmaVersion: 6,
16+
sourceType: 'module',
17+
ecmaFeatures: { experimentalObjectRestSpread: true, jsx: true }
18+
}
19+
20+
// ------------------------------------------------------------------------------
21+
// Tests
22+
// ------------------------------------------------------------------------------
23+
24+
const ruleTester = new RuleTester()
25+
ruleTester.run('require-render-return', rule, {
26+
valid: [
27+
{
28+
code: `Vue.component('test', {
29+
render() {
30+
return {}
31+
}
32+
})`,
33+
parserOptions
34+
},
35+
{
36+
code: `Vue.component('test', {
37+
foo() {
38+
return {}
39+
}
40+
})`,
41+
parserOptions
42+
},
43+
{
44+
code: `Vue.component('test', {
45+
foo: {}
46+
})`,
47+
parserOptions
48+
},
49+
{
50+
code: `Vue.component('test', {
51+
render: foo
52+
})`,
53+
parserOptions
54+
},
55+
{
56+
code: `Vue.component('test', {
57+
render() {
58+
return <div></div>
59+
}
60+
})`,
61+
parserOptions
62+
},
63+
{
64+
filename: 'test.vue',
65+
code: `export default {
66+
render() {
67+
return {}
68+
}
69+
}`,
70+
parserOptions
71+
},
72+
{
73+
filename: 'test.vue',
74+
code: `export default {
75+
render: () => null
76+
}`,
77+
parserOptions
78+
},
79+
{
80+
filename: 'test.vue',
81+
code: `export default {
82+
render() {
83+
if (a) {
84+
return \`<div>a</div>\`
85+
} else {
86+
return \`<span>a</span>\`
87+
}
88+
}
89+
}`,
90+
parserOptions
91+
}
92+
],
93+
94+
invalid: [
95+
{
96+
filename: 'test.vue',
97+
code: `export default {
98+
render() {
99+
}
100+
}`,
101+
parserOptions,
102+
errors: [{
103+
message: 'Expected to return a value in render function.',
104+
type: 'Identifier',
105+
line: 2
106+
}]
107+
},
108+
{
109+
code: `Vue.component('test', {
110+
render: function () {
111+
if (a) {
112+
return
113+
}
114+
}
115+
})`,
116+
parserOptions,
117+
errors: [{
118+
message: 'Expected to return a value in render function.',
119+
type: 'Identifier',
120+
line: 2
121+
}]
122+
}
123+
]
124+
})

tests/lib/rules/return-in-computed-property.js

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ ruleTester.run('return-in-computed-property', rule, {
3838
get () {
3939
return true
4040
}
41+
},
42+
bar4 () {
43+
if (foo) {
44+
return true
45+
} else {
46+
return false
47+
}
4148
}
4249
}
4350
}

0 commit comments

Comments
 (0)