Skip to content

Commit 4844612

Browse files
committed
feat: autofix in define-props-declaration: runtime syntax to type-based syntax (vuejs#2465)
handle native types (String, Boolean etc.)
1 parent 516253d commit 4844612

File tree

3 files changed

+239
-15
lines changed

3 files changed

+239
-15
lines changed

docs/rules/define-props-declaration.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ since: v9.5.0
1010

1111
> enforce declaration style of `defineProps`
1212
13+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
14+
1315
## :book: Rule Details
1416

1517
This rule enforces `defineProps` typing style which you should use `type-based` or `runtime` declaration.

lib/rules/define-props-declaration.js

+99-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,40 @@
66

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

9+
const mapNativeType = (/** @type {string} */ nativeType) => {
10+
switch (nativeType) {
11+
case 'String': {
12+
return 'string'
13+
}
14+
case 'Number': {
15+
return 'number'
16+
}
17+
case 'Boolean':
18+
case 'BigInt': {
19+
return 'boolean'
20+
}
21+
case 'Object': {
22+
return 'Record<string, any>'
23+
}
24+
case 'Array': {
25+
return 'any[]'
26+
}
27+
case 'Function': {
28+
return '() => void'
29+
}
30+
case 'Symbol': {
31+
return 'symbol'
32+
}
33+
default: {
34+
return nativeType
35+
}
36+
}
37+
}
38+
39+
/**
40+
* @typedef {import('../utils').ComponentProp} ComponentProp
41+
*/
42+
943
module.exports = {
1044
meta: {
1145
type: 'suggestion',
@@ -14,7 +48,7 @@ module.exports = {
1448
categories: undefined,
1549
url: 'https://eslint.vuejs.org/rules/define-props-declaration.html'
1650
},
17-
fixable: null,
51+
fixable: 'code',
1852
schema: [
1953
{
2054
enum: ['type-based', 'runtime']
@@ -27,20 +61,82 @@ module.exports = {
2761
},
2862
/** @param {RuleContext} context */
2963
create(context) {
64+
/**
65+
* @param {Expression} node
66+
* @returns {string | null}
67+
*/
68+
function optionGetType(node) {
69+
switch (node.type) {
70+
case 'Identifier': {
71+
return node.name
72+
}
73+
case 'ObjectExpression': {
74+
// foo: {
75+
const typeProperty = utils.findProperty(node, 'type')
76+
if (typeProperty == null) {
77+
return null
78+
}
79+
return optionGetType(typeProperty.value)
80+
}
81+
case 'ArrayExpression': {
82+
// foo: [
83+
return null
84+
// return node.elements.map((arrayElement) =>
85+
// optionGetType(arrayElement)
86+
// )
87+
}
88+
case 'FunctionExpression':
89+
case 'ArrowFunctionExpression': {
90+
return null
91+
}
92+
}
93+
94+
// Unknown
95+
return null
96+
}
97+
3098
const scriptSetup = utils.getScriptSetupElement(context)
3199
if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
32100
return {}
33101
}
34102

35103
const defineType = context.options[0] || 'type-based'
36104
return utils.defineScriptSetupVisitor(context, {
37-
onDefinePropsEnter(node) {
105+
onDefinePropsEnter(node, props) {
38106
switch (defineType) {
39107
case 'type-based': {
40108
if (node.arguments.length > 0) {
41109
context.report({
42110
node,
43-
messageId: 'hasArg'
111+
messageId: 'hasArg',
112+
*fix(fixer) {
113+
const propTypes = props.map((prop) => {
114+
const unknownType = { name: prop.propName, type: 'unknown' }
115+
116+
if (prop.type !== 'object') {
117+
return unknownType
118+
}
119+
const type = optionGetType(prop.value)
120+
if (type === null) {
121+
return unknownType
122+
}
123+
124+
return {
125+
name: prop.propName,
126+
type: mapNativeType(type)
127+
}
128+
})
129+
130+
const definePropsType = `{ ${propTypes
131+
.map(({ name, type }) => `${name}: ${type}`)
132+
.join(', ')} }`
133+
134+
yield fixer.insertTextAfter(
135+
node.callee,
136+
`<${definePropsType}>`
137+
)
138+
yield fixer.replaceText(node.arguments[0], '')
139+
}
44140
})
45141
}
46142
break

tests/lib/rules/define-props-declaration.js

+138-12
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ const rule = require('../../../lib/rules/define-props-declaration')
1010
const tester = new RuleTester({
1111
languageOptions: {
1212
parser: require('vue-eslint-parser'),
13-
ecmaVersion: 2020,
14-
sourceType: 'module'
13+
ecmaVersion: 'latest',
14+
sourceType: 'module',
15+
parserOptions: {
16+
parser: require.resolve('@typescript-eslint/parser')
17+
}
1518
}
1619
})
1720

@@ -108,6 +111,7 @@ tester.run('define-props-declaration', rule, {
108111
}
109112
],
110113
invalid: [
114+
// default
111115
{
112116
filename: 'test.vue',
113117
code: `
@@ -117,13 +121,42 @@ tester.run('define-props-declaration', rule, {
117121
})
118122
</script>
119123
`,
124+
output: `
125+
<script setup lang="ts">
126+
const props = defineProps<{ kind: string }>()
127+
</script>
128+
`,
129+
errors: [
130+
{
131+
message: 'Use type-based declaration instead of runtime declaration.',
132+
line: 3
133+
}
134+
]
135+
},
136+
/* TYPE-BASED */
137+
// shorthand syntax
138+
{
139+
filename: 'test.vue',
140+
code: `
141+
<script setup lang="ts">
142+
const props = defineProps({
143+
kind: String
144+
})
145+
</script>
146+
`,
147+
output: `
148+
<script setup lang="ts">
149+
const props = defineProps<{ kind: string }>()
150+
</script>
151+
`,
120152
errors: [
121153
{
122154
message: 'Use type-based declaration instead of runtime declaration.',
123155
line: 3
124156
}
125157
]
126158
},
159+
// String
127160
{
128161
filename: 'test.vue',
129162
code: `
@@ -133,6 +166,11 @@ tester.run('define-props-declaration', rule, {
133166
})
134167
</script>
135168
`,
169+
output: `
170+
<script setup lang="ts">
171+
const props = defineProps<{ kind: string }>()
172+
</script>
173+
`,
136174
options: ['type-based'],
137175
errors: [
138176
{
@@ -141,27 +179,115 @@ tester.run('define-props-declaration', rule, {
141179
}
142180
]
143181
},
182+
// Number
144183
{
145184
filename: 'test.vue',
146185
code: `
147186
<script setup lang="ts">
148-
const props = defineProps<{
149-
kind: string;
150-
}>()
187+
const props = defineProps({
188+
kind: { type: Number}
189+
})
190+
</script>
191+
`,
192+
output: `
193+
<script setup lang="ts">
194+
const props = defineProps<{ kind: number }>()
151195
</script>
152196
`,
153-
options: ['runtime'],
154197
errors: [
155198
{
156-
message: 'Use runtime declaration instead of type-based declaration.',
199+
message: 'Use type-based declaration instead of runtime declaration.',
157200
line: 3
158201
}
159-
],
160-
languageOptions: {
161-
parserOptions: {
162-
parser: require.resolve('@typescript-eslint/parser')
202+
]
203+
},
204+
// Boolean
205+
{
206+
filename: 'test.vue',
207+
code: `
208+
<script setup lang="ts">
209+
const props = defineProps({
210+
kind: { type:Boolean}
211+
})
212+
</script>
213+
`,
214+
output: `
215+
<script setup lang="ts">
216+
const props = defineProps<{ kind: boolean }>()
217+
</script>
218+
`,
219+
errors: [
220+
{
221+
message: 'Use type-based declaration instead of runtime declaration.',
222+
line: 3
163223
}
164-
}
224+
]
225+
},
226+
// Object
227+
{
228+
filename: 'test.vue',
229+
code: `
230+
<script setup lang="ts">
231+
const props = defineProps({
232+
kind: { type:Object}
233+
})
234+
</script>
235+
`,
236+
output: `
237+
<script setup lang="ts">
238+
const props = defineProps<{ kind: Record<string, any> }>()
239+
</script>
240+
`,
241+
errors: [
242+
{
243+
message: 'Use type-based declaration instead of runtime declaration.',
244+
line: 3
245+
}
246+
]
247+
},
248+
// Array
249+
{
250+
filename: 'test.vue',
251+
code: `
252+
<script setup lang="ts">
253+
const props = defineProps({
254+
kind: { type:Array}
255+
})
256+
</script>
257+
`,
258+
output: `
259+
<script setup lang="ts">
260+
const props = defineProps<{ kind: any[] }>()
261+
</script>
262+
`,
263+
errors: [
264+
{
265+
message: 'Use type-based declaration instead of runtime declaration.',
266+
line: 3
267+
}
268+
]
269+
},
270+
// Function
271+
{
272+
filename: 'test.vue',
273+
code: `
274+
<script setup lang="ts">
275+
const props = defineProps({
276+
kind: { type: Function}
277+
})
278+
</script>
279+
`,
280+
output: `
281+
<script setup lang="ts">
282+
const props = defineProps<{ kind: () => void }>()
283+
</script>
284+
`,
285+
errors: [
286+
{
287+
message: 'Use type-based declaration instead of runtime declaration.',
288+
line: 3
289+
}
290+
]
165291
}
166292
]
167293
})

0 commit comments

Comments
 (0)