Skip to content

Commit 8de47f1

Browse files
authored
Add vue/return-in-emits-validator rule (#1129)
1 parent 7c53cd4 commit 8de47f1

File tree

6 files changed

+450
-0
lines changed

6 files changed

+450
-0
lines changed

Diff for: docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
7373
| [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | |
7474
| [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | |
7575
| [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property | |
76+
| [vue/return-in-emits-validator](./return-in-emits-validator.md) | enforce that a return statement is present in emits validator | |
7677
| [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | |
7778
| [vue/valid-template-root](./valid-template-root.md) | enforce valid template root | |
7879
| [vue/valid-v-bind](./valid-v-bind.md) | enforce valid `v-bind` directives | |

Diff for: docs/rules/return-in-emits-validator.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/return-in-emits-validator
5+
description: enforce that a return statement is present in emits validator
6+
---
7+
# vue/return-in-emits-validator
8+
> enforce that a return statement is present in emits validator
9+
10+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
11+
12+
## :book: Rule Details
13+
14+
This rule enforces that a `return` statement is present in `emits` validators.
15+
16+
<eslint-code-block :rules="{'vue/return-in-emits-validator': ['error']}">
17+
18+
```vue
19+
<script>
20+
export default {
21+
emits: {
22+
/* ✓ GOOD */
23+
foo (evt) {
24+
if (evt) {
25+
return true
26+
} else {
27+
return false
28+
}
29+
},
30+
bar: function () {
31+
return true
32+
},
33+
baz (evt) {
34+
if (evt) {
35+
return true
36+
}
37+
},
38+
/* ✗ BAD */
39+
qux: function () {},
40+
quux (evt) {
41+
if (!evt) {
42+
return false
43+
}
44+
}
45+
}
46+
}
47+
</script>
48+
```
49+
50+
</eslint-code-block>
51+
52+
## :wrench: Options
53+
54+
Nothing.
55+
56+
## :books: Further reading
57+
58+
- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
59+
60+
## :mag: Implementation
61+
62+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/return-in-emits-validator.js)
63+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/return-in-emits-validator.js)

Diff for: lib/configs/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
'vue/require-v-for-key': 'error',
4242
'vue/require-valid-default-prop': 'error',
4343
'vue/return-in-computed-property': 'error',
44+
'vue/return-in-emits-validator': 'error',
4445
'vue/use-v-on-exact': 'error',
4546
'vue/valid-template-root': 'error',
4647
'vue/valid-v-bind': 'error',

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ module.exports = {
9797
'require-v-for-key': require('./rules/require-v-for-key'),
9898
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
9999
'return-in-computed-property': require('./rules/return-in-computed-property'),
100+
'return-in-emits-validator': require('./rules/return-in-emits-validator'),
100101
'script-indent': require('./rules/script-indent'),
101102
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
102103
'sort-keys': require('./rules/sort-keys'),

Diff for: lib/rules/return-in-emits-validator.js

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/**
10+
* @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
11+
*/
12+
13+
/**
14+
* Checks if the given node value is falsy.
15+
* @param {Expression} node The node to check
16+
* @returns {boolean} If `true`, the given node value is falsy.
17+
*/
18+
function isFalsy (node) {
19+
if (node.type === 'Literal') {
20+
if (node.bigint) {
21+
return node.bigint === '0'
22+
} else if (!node.value) {
23+
return true
24+
}
25+
} else if (node.type === 'Identifier') {
26+
if (node.name === 'undefined' || node.name === 'NaN') {
27+
return true
28+
}
29+
}
30+
return false
31+
}
32+
// ------------------------------------------------------------------------------
33+
// Rule Definition
34+
// ------------------------------------------------------------------------------
35+
36+
module.exports = {
37+
meta: {
38+
type: 'problem',
39+
docs: {
40+
description: 'enforce that a return statement is present in emits validator',
41+
categories: ['vue3-essential'],
42+
url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
43+
},
44+
fixable: null, // or "code" or "whitespace"
45+
schema: []
46+
},
47+
48+
create (context) {
49+
const emitsValidators = []
50+
51+
// ----------------------------------------------------------------------
52+
// Public
53+
// ----------------------------------------------------------------------
54+
55+
let scopeStack = null
56+
57+
return Object.assign({},
58+
utils.defineVueVisitor(context,
59+
{
60+
ObjectExpression (obj, { node: vueNode }) {
61+
if (obj !== vueNode) {
62+
return
63+
}
64+
for (const emits of utils.getComponentEmits(obj)) {
65+
const emitsValue = emits.value
66+
if (!emitsValue) {
67+
continue
68+
}
69+
if (emitsValue.type !== 'FunctionExpression' && emitsValue.type !== 'ArrowFunctionExpression') {
70+
continue
71+
}
72+
emitsValidators.push(emits)
73+
}
74+
},
75+
':function' (node) {
76+
scopeStack = { upper: scopeStack, functionNode: node, hasReturnValue: false, possibleOfReturnTrue: false }
77+
},
78+
ReturnStatement (node) {
79+
if (node.argument) {
80+
scopeStack.hasReturnValue = true
81+
82+
if (!isFalsy(node.argument)) {
83+
scopeStack.possibleOfReturnTrue = true
84+
}
85+
}
86+
},
87+
':function:exit' (node) {
88+
if (!scopeStack.possibleOfReturnTrue) {
89+
const emits = emitsValidators.find(e => e.value === node)
90+
if (emits) {
91+
context.report({
92+
node,
93+
message: scopeStack.hasReturnValue
94+
? 'Expected to return a true value in "{{name}}" emits validator.'
95+
: 'Expected to return a boolean value in "{{name}}" emits validator.',
96+
data: {
97+
name: emits.emitName
98+
}
99+
})
100+
}
101+
}
102+
103+
scopeStack = scopeStack.upper
104+
}
105+
}
106+
),
107+
)
108+
}
109+
}

0 commit comments

Comments
 (0)