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`] = `
|
exports[`SFC compile <script setup> with TypeScript defineProps w/ type 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"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 { }
|
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)', () => {
|
test('withDefaults (static)', () => {
|
||||||
const { content, bindings } = compile(`
|
const { content, bindings } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
Expression,
|
Expression,
|
||||||
LabeledStatement,
|
LabeledStatement,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
RestElement
|
RestElement,
|
||||||
|
TSInterfaceBody
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
import { RawSourceMap } from 'source-map'
|
import { RawSourceMap } from 'source-map'
|
||||||
@ -195,7 +196,7 @@ export function compileScript(
|
|||||||
let hasDefineExposeCall = false
|
let hasDefineExposeCall = false
|
||||||
let propsRuntimeDecl: Node | undefined
|
let propsRuntimeDecl: Node | undefined
|
||||||
let propsRuntimeDefaults: Node | undefined
|
let propsRuntimeDefaults: Node | undefined
|
||||||
let propsTypeDecl: TSTypeLiteral | undefined
|
let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
|
||||||
let propsIdentifier: string | undefined
|
let propsIdentifier: string | undefined
|
||||||
let emitRuntimeDecl: Node | undefined
|
let emitRuntimeDecl: Node | undefined
|
||||||
let emitTypeDecl: TSFunctionType | TSTypeLiteral | 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') {
|
if (typeArg.type === 'TSTypeLiteral') {
|
||||||
propsTypeDecl = typeArg
|
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(
|
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
|
typeArg
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -661,7 +696,6 @@ export function compileScript(
|
|||||||
for (const node of scriptSetupAst) {
|
for (const node of scriptSetupAst) {
|
||||||
const start = node.start! + startOffset
|
const start = node.start! + startOffset
|
||||||
let end = node.end! + startOffset
|
let end = node.end! + startOffset
|
||||||
// import or type declarations: move to top
|
|
||||||
// locate comment
|
// locate comment
|
||||||
if (node.trailingComments && node.trailingComments.length > 0) {
|
if (node.trailingComments && node.trailingComments.length > 0) {
|
||||||
const lastCommentNode =
|
const lastCommentNode =
|
||||||
@ -1315,11 +1349,12 @@ function recordType(node: Node, declaredTypes: Record<string, string[]>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractRuntimeProps(
|
function extractRuntimeProps(
|
||||||
node: TSTypeLiteral,
|
node: TSTypeLiteral | TSInterfaceBody,
|
||||||
props: Record<string, PropTypeData>,
|
props: Record<string, PropTypeData>,
|
||||||
declaredTypes: Record<string, string[]>
|
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') {
|
if (m.type === 'TSPropertySignature' && m.key.type === 'Identifier') {
|
||||||
props[m.key.name] = {
|
props[m.key.name] = {
|
||||||
key: m.key.name,
|
key: m.key.name,
|
||||||
|
Loading…
Reference in New Issue
Block a user