Skip to content

Commit

Permalink
Add support for stricter type-literal option in `vue/define-emits-d…
Browse files Browse the repository at this point in the history
…eclaration` (#2315)
  • Loading branch information
Meesayen authored Nov 29, 2023
1 parent b3129f9 commit 3e66445
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 10 deletions.
46 changes: 43 additions & 3 deletions docs/rules/define-emits-declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ title: vue/define-emits-declaration
description: enforce declaration style of `defineEmits`
since: v9.5.0
---

# vue/define-emits-declaration

> enforce declaration style of `defineEmits`
## :book: Rule Details

This rule enforces `defineEmits` typing style which you should use `type-based` or `runtime` declaration.
This rule enforces `defineEmits` typing style which you should use `type-based`, strict `type-literal`
(introduced in Vue 3.3), or `runtime` declaration.

This rule only works in setup script and `lang="ts"`.

Expand All @@ -25,6 +27,12 @@ const emit = defineEmits<{
(e: 'update', value: string): void
}>()
/* ✓ GOOD */
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
/* ✗ BAD */
const emit = defineEmits({
change: (id) => typeof id == 'number',
Expand All @@ -41,10 +49,11 @@ const emit = defineEmits(['change', 'update'])
## :wrench: Options

```json
"vue/define-emits-declaration": ["error", "type-based" | "runtime"]
"vue/define-emits-declaration": ["error", "type-based" | "type-literal" | "runtime"]
```

- `type-based` (default) enforces type-based declaration
- `type-based` (default) enforces type based declaration
- `type-literal` enforces strict "type literal" type based declaration
- `runtime` enforces runtime declaration

### `runtime`
Expand Down Expand Up @@ -72,6 +81,37 @@ const emit = defineEmits(['change', 'update'])

</eslint-code-block>

### `type-literal`

<eslint-code-block :rules="{'vue/define-emits-declaration': ['error', 'type-literal']}">

```vue
<script setup lang="ts">
/* ✗ BAD */
const emit = defineEmits(['change', 'update'])
/* ✗ BAD */
const emit = defineEmits({
change: (id) => typeof id == 'number',
update: (value) => typeof value == 'string'
})
/* ✗ BAD */
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
/* ✓ GOOD */
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
```

</eslint-code-block>

## :couple: Related Rules

- [vue/define-props-declaration](./define-props-declaration.md)
Expand Down
35 changes: 32 additions & 3 deletions lib/rules/define-emits-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

const utils = require('../utils')

/**
* @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
*
*/

module.exports = {
meta: {
type: 'suggestion',
Expand All @@ -17,12 +22,14 @@ module.exports = {
fixable: null,
schema: [
{
enum: ['type-based', 'runtime']
enum: ['type-based', 'type-literal', 'runtime']
}
],
messages: {
hasArg: 'Use type-based declaration instead of runtime declaration.',
hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
hasArg: 'Use type based declaration instead of runtime declaration.',
hasTypeArg: 'Use runtime declaration instead of type based declaration.',
hasTypeCallArg:
'Use new type literal declaration instead of the old call signature declaration.'
}
},
/** @param {RuleContext} context */
Expand All @@ -46,6 +53,28 @@ module.exports = {
break
}

case 'type-literal': {
if (node.arguments.length > 0) {
context.report({
node,
messageId: 'hasArg'
})
return
}

const typeArguments = node.typeArguments || node.typeParameters
const param = /** @type {TSTypeLiteral} */ (typeArguments.params[0])
for (const memberNode of param.members) {
if (memberNode.type !== 'TSPropertySignature') {
context.report({
node: memberNode,
messageId: 'hasTypeCallArg'
})
}
}
break
}

case 'runtime': {
const typeArguments = node.typeArguments || node.typeParameters
if (typeArguments && typeArguments.params.length > 0) {
Expand Down
105 changes: 101 additions & 4 deletions tests/lib/rules/define-emits-declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ tester.run('define-emits-declaration', rule, {
`,
options: ['runtime']
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
`,
options: ['type-based'],
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
}
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
`,
options: ['type-literal'],
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
}
},
{
filename: 'test.vue',
// ignore code without defineEmits
Expand All @@ -82,7 +112,7 @@ tester.run('define-emits-declaration', rule, {
code: `
<script lang="ts">
import { PropType } from 'vue'
export default {
props: {
kind: { type: String as PropType<'primary' | 'secondary'> },
Expand All @@ -106,7 +136,7 @@ tester.run('define-emits-declaration', rule, {
`,
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
message: 'Use type based declaration instead of runtime declaration.',
line: 3
}
]
Expand All @@ -121,7 +151,25 @@ tester.run('define-emits-declaration', rule, {
options: ['type-based'],
errors: [
{
message: 'Use type-based declaration instead of runtime declaration.',
message: 'Use type based declaration instead of runtime declaration.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const emit = defineEmits(['change', 'update'])
</script>
`,
options: ['type-literal'],
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
errors: [
{
message: 'Use type based declaration instead of runtime declaration.',
line: 3
}
]
Expand All @@ -142,10 +190,59 @@ tester.run('define-emits-declaration', rule, {
},
errors: [
{
message: 'Use runtime declaration instead of type-based declaration.',
message: 'Use runtime declaration instead of type based declaration.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
`,
options: ['type-literal'],
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
errors: [
{
message:
'Use new type literal declaration instead of the old call signature declaration.',
line: 4
},
{
message:
'Use new type literal declaration instead of the old call signature declaration.',
line: 5
}
]
},
{
filename: 'test.vue',
code: `
<script setup lang="ts">
const emit = defineEmits<{
'change': [id: number]
(e: 'update', value: string): void
}>()
</script>
`,
options: ['type-literal'],
parserOptions: {
parser: require.resolve('@typescript-eslint/parser')
},
errors: [
{
message:
'Use new type literal declaration instead of the old call signature declaration.',
line: 5
}
]
}
]
})

0 comments on commit 3e66445

Please sign in to comment.