Skip to content

Commit 3a83a68

Browse files
committed
Add rule require-render-return.
1 parent 39c9df5 commit 3a83a68

File tree

6 files changed

+280
-47
lines changed

6 files changed

+280
-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

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.findMissingReturns(forbiddenNodes, true),
18+
utils.executeOnVue(context, obj => {
19+
const node = obj.properties.find(item => item.type === 'Property' &&
20+
utils.getStaticPropertyName(item) === 'render' &&
21+
(item.value.type === 'ArrowFunctionExpression' || item.value.type === 'FunctionExpression') &&
22+
!item.value.expression // render: () => test
23+
)
24+
if (!node) return
25+
26+
forbiddenNodes.forEach(el => {
27+
if (
28+
el.loc.start.line >= node.value.loc.start.line &&
29+
el.loc.end.line <= node.value.loc.end.line
30+
) {
31+
context.report({
32+
node: node.key,
33+
message: 'Expected to return a value in render function.'
34+
})
35+
}
36+
})
37+
})
38+
)
39+
}
40+
41+
// ------------------------------------------------------------------------------
42+
// Rule Definition
43+
// ------------------------------------------------------------------------------
44+
45+
module.exports = {
46+
meta: {
47+
docs: {
48+
description: 'Enforces render function to always return value.',
49+
category: 'Possible Errors',
50+
recommended: false
51+
},
52+
fixable: null, // or "code" or "whitespace"
53+
schema: []
54+
},
55+
56+
create
57+
}

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

+4-47
Original file line numberDiff line numberDiff line change
@@ -10,69 +10,26 @@ 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.findMissingReturns(forbiddenNodes, treatUndefinedAsUnspecified),
6421
utils.executeOnVue(context, properties => {
6522
const computedProperties = utils.getComputedProperties(properties)
6623

6724
computedProperties.forEach(cp => {
6825
forbiddenNodes.forEach(el => {
6926
if (
7027
cp.value &&
71-
el.node.loc.start.line >= cp.value.loc.start.line &&
72-
el.node.loc.end.line <= cp.value.loc.end.line
28+
el.loc.start.line >= cp.value.loc.start.line &&
29+
el.loc.end.line <= cp.value.loc.end.line
7330
) {
7431
context.report({
75-
node: el.node,
32+
node: el,
7633
message: 'Expected to return a value in "{{name}}" computed property.',
7734
data: {
7835
name: cp.key

lib/utils/index.js

+51
Original file line numberDiff line numberDiff line change
@@ -520,5 +520,56 @@ module.exports = {
520520
}
521521
}
522522
}
523+
},
524+
525+
/**
526+
* Find all function witch do not allways return values
527+
* @param {Array} nodes an array nodes
528+
* @param {boolean} treatUndefinedAsUnspecified
529+
*/
530+
findMissingReturns (nodes, treatUndefinedAsUnspecified) {
531+
let funcInfo = {
532+
funcInfo: null,
533+
codePath: null,
534+
hasReturn: false,
535+
hasReturnValue: false,
536+
node: null
537+
}
538+
539+
function isValidReturn () {
540+
if (!funcInfo.hasReturn) {
541+
return false
542+
}
543+
return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue
544+
}
545+
546+
return {
547+
onCodePathStart (codePath, node) {
548+
funcInfo = {
549+
codePath,
550+
funcInfo: funcInfo,
551+
hasReturn: false,
552+
hasReturnValue: false,
553+
node
554+
}
555+
},
556+
onCodePathEnd () {
557+
funcInfo = funcInfo.funcInfo
558+
},
559+
ReturnStatement (node) {
560+
funcInfo.hasReturn = true
561+
funcInfo.hasReturnValue = Boolean(node.argument)
562+
},
563+
'ArrowFunctionExpression:exit' (node) {
564+
if (!isValidReturn()) {
565+
nodes.push(funcInfo.node)
566+
}
567+
},
568+
'FunctionExpression:exit' (node) {
569+
if (!isValidReturn()) {
570+
nodes.push(funcInfo.node)
571+
}
572+
}
573+
}
523574
}
524575
}
+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)