Skip to content

Add support for stricter type-literal option in vue/define-emits-declaration #2315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
]
}
]
})