Skip to content

Commit d03ce92

Browse files
authored
Add vue/require-emit-validator rule (#1487)
* Add `vue/require-emit-types` rule * remove from categories * allow identifiers * add suggestion for skipped validation * rename rule
1 parent 3da8d31 commit d03ce92

File tree

4 files changed

+482
-0
lines changed

4 files changed

+482
-0
lines changed

docs/rules/require-emit-validator.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/require-emit-validator
5+
description: require type definitions in emits
6+
---
7+
# vue/require-emit-validator
8+
9+
> require type definitions in emits
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :gear: This rule is included in .
13+
14+
## :book: Rule Details
15+
16+
This rule enforces that a `emits` statement contains type definition.
17+
18+
Declaring `emits` with types can bring better maintenance.
19+
Even if using with TypeScript, this can provide better type inference when annotating parameters with types.
20+
21+
<eslint-code-block :rules="{'vue/require-emit-validator': ['error']}">
22+
23+
```vue
24+
<script>
25+
/* ✓ GOOD */
26+
Vue.component('foo', {
27+
emits: {
28+
// Emit with arguments
29+
foo: (payload) => { /* validate payload */ },
30+
// Emit without parameters
31+
bar: () => true,
32+
}
33+
})
34+
35+
/* ✗ BAD */
36+
Vue.component('bar', {
37+
emits: ['foo']
38+
})
39+
40+
Vue.component('baz', {
41+
emits: {
42+
foo: null,
43+
}
44+
})
45+
</script>
46+
```
47+
48+
</eslint-code-block>
49+
50+
## :wrench: Options
51+
52+
Nothing.
53+
54+
## :books: Further Reading
55+
56+
- [API Reference](https://v3.vuejs.org/api/options-data.html#emits)
57+
58+
## :mag: Implementation
59+
60+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-emit-validator.js)
61+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-emit-validator.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ module.exports = {
140140
'require-component-is': require('./rules/require-component-is'),
141141
'require-default-prop': require('./rules/require-default-prop'),
142142
'require-direct-export': require('./rules/require-direct-export'),
143+
'require-emit-validator': require('./rules/require-emit-validator'),
143144
'require-explicit-emits': require('./rules/require-explicit-emits'),
144145
'require-name-property': require('./rules/require-name-property'),
145146
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),

lib/rules/require-emit-validator.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @fileoverview Emit definitions should be detailed
3+
* @author Pig Fang
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/**
10+
* @typedef {import('../utils').ComponentArrayEmit} ComponentArrayEmit
11+
* @typedef {import('../utils').ComponentObjectEmit} ComponentObjectEmit
12+
*/
13+
14+
// ------------------------------------------------------------------------------
15+
// Rule Definition
16+
// ------------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
type: 'suggestion',
21+
docs: {
22+
description: 'require type definitions in emits',
23+
categories: [],
24+
url: 'https://eslint.vuejs.org/rules/require-emit-validator.html'
25+
},
26+
fixable: null,
27+
messages: {
28+
missing: 'Emit "{{name}}" should define at least its validator function.',
29+
skipped:
30+
'Emit "{{name}}" should not skip validation, or you may define a validator function with no parameters.',
31+
emptyValidation: 'Replace with a validator function with no parameters.'
32+
},
33+
schema: []
34+
},
35+
/** @param {RuleContext} context */
36+
create(context) {
37+
// ----------------------------------------------------------------------
38+
// Helpers
39+
// ----------------------------------------------------------------------
40+
41+
/**
42+
* @param {ComponentArrayEmit|ComponentObjectEmit} emit
43+
*/
44+
function checker({ value, node, emitName }) {
45+
const hasType =
46+
!!value &&
47+
(value.type === 'ArrowFunctionExpression' ||
48+
value.type === 'FunctionExpression' ||
49+
// validator may from outer scope
50+
value.type === 'Identifier')
51+
52+
if (!hasType) {
53+
const name =
54+
emitName ||
55+
(node.type === 'Identifier' && node.name) ||
56+
'Unknown emit'
57+
58+
if (value && value.type === 'Literal' && value.value === null) {
59+
context.report({
60+
node,
61+
messageId: 'skipped',
62+
data: { name },
63+
suggest: [
64+
{
65+
messageId: 'emptyValidation',
66+
fix: (fixer) => fixer.replaceText(value, '() => true')
67+
}
68+
]
69+
})
70+
71+
return
72+
}
73+
74+
context.report({
75+
node,
76+
messageId: 'missing',
77+
data: { name }
78+
})
79+
}
80+
}
81+
82+
// ----------------------------------------------------------------------
83+
// Public
84+
// ----------------------------------------------------------------------
85+
86+
return utils.executeOnVue(context, (obj) => {
87+
utils.getComponentEmits(obj).forEach(checker)
88+
})
89+
}
90+
}

0 commit comments

Comments
 (0)