})"
`;
+exports[`SFC compile <script setup> with TypeScript defineProps w/ exported interface 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export interface Props { x?: number }
+
+export default _defineComponent({
+ props: {
+ x: { type: Number, required: false }
+ } as unknown as undefined,
+ setup(__props: { x?: number }, { expose }) {
+ expose()
+
+
+
+return { }
+}
+
+})"
+`;
+
+exports[`SFC compile <script setup> with TypeScript defineProps w/ exported type alias 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export type Props = { x?: number }
+
+export default _defineComponent({
+ props: {
+ x: { type: Number, required: false }
+ } as unknown as undefined,
+ setup(__props: { x?: number }, { expose }) {
+ expose()
+
+
+
+return { }
+}
+
+})"
+`;
+
+exports[`SFC compile <script setup> with TypeScript defineProps w/ interface 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Props { x?: number }
+
+export default _defineComponent({
+ props: {
+ x: { type: Number, required: false }
+ } as unknown as undefined,
+ setup(__props: { x?: number }, { expose }) {
+ expose()
+
+
+
+return { }
+}
+
+})"
+`;
+
exports[`SFC compile <script setup> with TypeScript defineProps w/ type 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
+return { }
+}
+
+})"
+`;
+
+exports[`SFC compile <script setup> with TypeScript defineProps w/ type alias 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type Props = { x?: number }
+
+export default _defineComponent({
+ props: {
+ x: { type: Number, required: false }
+ } as unknown as undefined,
+ setup(__props: { x?: number }, { expose }) {
+ expose()
+
+
+
return { }
}
})
})
+ test('defineProps w/ interface', () => {
+ const { content, bindings } = compile(`
+ <script setup lang="ts">
+ interface Props { x?: number }
+ defineProps<Props>()
+ </script>
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('defineProps w/ exported interface', () => {
+ const { content, bindings } = compile(`
+ <script setup lang="ts">
+ export interface Props { x?: number }
+ defineProps<Props>()
+ </script>
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('defineProps w/ type alias', () => {
+ const { content, bindings } = compile(`
+ <script setup lang="ts">
+ type Props = { x?: number }
+ defineProps<Props>()
+ </script>
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('defineProps w/ exported type alias', () => {
+ const { content, bindings } = compile(`
+ <script setup lang="ts">
+ export type Props = { x?: number }
+ defineProps<Props>()
+ </script>
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
test('withDefaults (static)', () => {
const { content, bindings } = compile(`
<script setup lang="ts">
Expression,
LabeledStatement,
CallExpression,
- RestElement
+ RestElement,
+ TSInterfaceBody
} from '@babel/types'
import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map'
let hasDefineExposeCall = false
let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: Node | undefined
- let propsTypeDecl: TSTypeLiteral | undefined
+ let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
let propsIdentifier: string | undefined
let emitRuntimeDecl: Node | undefined
let emitTypeDecl: TSFunctionType | TSTypeLiteral | undefined
)
}
- const typeArg = node.typeParameters.params[0]
+ let typeArg: Node = node.typeParameters.params[0]
if (typeArg.type === 'TSTypeLiteral') {
propsTypeDecl = typeArg
- } else {
+ } else if (
+ typeArg.type === 'TSTypeReference' &&
+ typeArg.typeName.type === 'Identifier'
+ ) {
+ const refName = typeArg.typeName.name
+ const isValidType = (node: Node): boolean => {
+ if (
+ node.type === 'TSInterfaceDeclaration' &&
+ node.id.name === refName
+ ) {
+ propsTypeDecl = node.body
+ return true
+ } else if (
+ node.type === 'TSTypeAliasDeclaration' &&
+ node.id.name === refName &&
+ node.typeAnnotation.type === 'TSTypeLiteral'
+ ) {
+ propsTypeDecl = node.typeAnnotation
+ return true
+ } else if (
+ node.type === 'ExportNamedDeclaration' &&
+ node.declaration
+ ) {
+ return isValidType(node.declaration)
+ }
+ return false
+ }
+
+ for (const node of scriptSetupAst) {
+ if (isValidType(node)) break
+ }
+ }
+
+ if (!propsTypeDecl) {
error(
- `type argument passed to ${DEFINE_PROPS}() must be a literal type.`,
+ `type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
+ `or a reference to a interface or literal type.`,
typeArg
)
}
for (const node of scriptSetupAst) {
const start = node.start! + startOffset
let end = node.end! + startOffset
- // import or type declarations: move to top
// locate comment
if (node.trailingComments && node.trailingComments.length > 0) {
const lastCommentNode =
}
function extractRuntimeProps(
- node: TSTypeLiteral,
+ node: TSTypeLiteral | TSInterfaceBody,
props: Record<string, PropTypeData>,
declaredTypes: Record<string, string[]>
) {
- for (const m of node.members) {
+ const members = node.type === 'TSTypeLiteral' ? node.members : node.body
+ for (const m of members) {
if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
props[m.key.name] = {
key: m.key.name,