feat(sfc): support using declared interface or type alias with defineProps()
This commit is contained in:
parent
d069796b8f
commit
2f91db30cd
@ -780,6 +780,63 @@ return { emit }
|
||||
})"
|
||||
`;
|
||||
|
||||
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'
|
||||
|
||||
@ -840,6 +897,25 @@ export default _defineComponent({
|
||||
|
||||
|
||||
|
||||
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 { }
|
||||
}
|
||||
|
||||
|
@ -592,6 +592,62 @@ const emit = defineEmits(['a', 'b'])
|
||||
})
|
||||
})
|
||||
|
||||
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">
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
Expression,
|
||||
LabeledStatement,
|
||||
CallExpression,
|
||||
RestElement
|
||||
RestElement,
|
||||
TSInterfaceBody
|
||||
} from '@babel/types'
|
||||
import { walk } from 'estree-walker'
|
||||
import { RawSourceMap } from 'source-map'
|
||||
@ -195,7 +196,7 @@ export function compileScript(
|
||||
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
|
||||
@ -287,12 +288,46 @@ export function compileScript(
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
@ -661,7 +696,6 @@ export function compileScript(
|
||||
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 =
|
||||
@ -1315,11 +1349,12 @@ function recordType(node: Node, declaredTypes: Record<string, string[]>) {
|
||||
}
|
||||
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user