Skip to content

Commit 60cfbaf

Browse files
authored
feat: basic support for typescript defineProps (#25)
1 parent 8f89275 commit 60cfbaf

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

src/types.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
export interface ComponentPropType {
2+
type: string
3+
}
4+
15
export interface ComponentProp {
26
name: string
3-
type?: string,
7+
type?: string | ComponentPropType,
48
default?: any
59
required?: boolean,
610
values?: any,

src/utils/ast.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export function visit (node, test, visitNode) {
2525
case 'ObjectExpression':
2626
visit(node.properties, test, visitNode)
2727
break
28+
case 'ExpressionStatement':
29+
visit(node.expression, test, visitNode)
30+
break
2831
case 'ObjectProperty':
2932
visit(node.key, test, visitNode)
3033
visit(node.value, test, visitNode)

src/utils/parseSetupScript.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function parseSetupScript (id: string, descriptor: SFCDescriptor) {
2424
}
2525
}
2626
function getType (tsProperty) {
27-
const { type } = tsProperty.typeAnnotation.typeAnnotation
27+
const { type, typeName, elementType } = tsProperty.typeAnnotation?.typeAnnotation || tsProperty
2828
switch (type) {
2929
case 'TSStringKeyword':
3030
return 'String'
@@ -34,6 +34,13 @@ export function parseSetupScript (id: string, descriptor: SFCDescriptor) {
3434
return 'Boolean'
3535
case 'TSObjectKeyword':
3636
return 'Object'
37+
case 'TSTypeReference':
38+
return typeName.name
39+
case 'TSArrayType':
40+
return {
41+
type: 'Array',
42+
elementType: getType(elementType)
43+
}
3744
}
3845
}
3946

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<template>
2+
<div>
3+
<slot />
4+
<hr>
5+
<slot name="nuxt" />
6+
</div>
7+
</template>
8+
9+
<script setup lang="ts">
10+
export type TestObject = {
11+
size: string
12+
}
13+
14+
defineProps<{
15+
stringProp: string,
16+
booleanProp?: boolean,
17+
numberProp?: number,
18+
arrayProp?: string[]
19+
objectProp?: TestObject
20+
}>()
21+
</script>

test/typed-component.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fsp from 'fs/promises'
2+
import { fileURLToPath } from 'url'
3+
import { test, describe, expect } from 'vitest'
4+
import { ComponentPropType } from '../src/types'
5+
import { parseComponent } from '../src/utils/parseComponent'
6+
7+
describe('Basic Component', async () => {
8+
const path = fileURLToPath(new URL('./fixtures/basic/components/TestTypedComponent.vue', import.meta.url))
9+
const source = await fsp.readFile(path, { encoding: 'utf-8' })
10+
// Parse component source
11+
const { props, slots } = parseComponent('TestTypedComponent', source)
12+
13+
test('Slots', () => {
14+
expect(slots).toEqual([
15+
{ name: 'default' },
16+
{ name: 'nuxt' }
17+
])
18+
})
19+
20+
test('Props', () => {
21+
expect(props).toBeDefined()
22+
expect(props.length > 0)
23+
})
24+
25+
test('String', () => {
26+
const stringProps = props.filter(p => p.type === 'String')
27+
28+
expect(stringProps.length).toBe(1)
29+
expect(stringProps[0].name).toBe('stringProp')
30+
expect(stringProps[0].required).toBe(true)
31+
})
32+
33+
test('Boolean', () => {
34+
const booleanProps = props.filter(p => p.type === 'Boolean')
35+
36+
expect(booleanProps.length).toBe(1)
37+
expect(booleanProps[0].name).toBe('booleanProp')
38+
expect(booleanProps[0].required).toBe(false)
39+
})
40+
41+
test('Number', () => {
42+
const numberProps = props.filter(p => p.type === 'Number')
43+
44+
expect(numberProps.length).toBe(1)
45+
expect(numberProps[0].name).toBe('numberProp')
46+
expect(numberProps[0].required).toBe(false)
47+
})
48+
49+
test('Array', () => {
50+
const arrayProps = props.filter(p => p.type === 'Array' || (p.type as ComponentPropType)?.type === 'Array')
51+
52+
expect(arrayProps.length).toBe(1)
53+
expect(arrayProps[0].name).toBe('arrayProp')
54+
expect(arrayProps[0].required).toBe(false)
55+
})
56+
57+
test('Custom type', () => {
58+
const objectProps = props.filter(p => p.type === 'TestObject')
59+
60+
expect(objectProps.length).toBe(1)
61+
expect(objectProps[0].name).toBe('objectProp')
62+
expect(objectProps[0].required).toBe(false)
63+
})
64+
})

0 commit comments

Comments
 (0)