mirror of https://github.com/vuejs/core.git
fix(compiler-sfc): support using extends interface with defineProps() (#4512)
fix #4498
This commit is contained in:
parent
183e4e6152
commit
83f7e6f8a6
|
|
@ -1467,6 +1467,33 @@ export default /*#__PURE__*/_defineComponent({
|
|||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> with TypeScript defineProps w/ extends interface 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Bar extends Foo { y?: number }
|
||||
interface Props extends Bar {
|
||||
z: number
|
||||
y: string
|
||||
}
|
||||
|
||||
interface Foo { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: {
|
||||
z: { type: Number, required: true },
|
||||
y: { type: String, required: true },
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
setup(__props: any, { expose }) {
|
||||
expose()
|
||||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -885,6 +885,31 @@ const emit = defineEmits(['a', 'b'])
|
|||
})
|
||||
})
|
||||
|
||||
test('defineProps w/ extends interface', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script lang="ts">
|
||||
interface Foo { x?: number }
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
interface Bar extends Foo { y?: number }
|
||||
interface Props extends Bar {
|
||||
z: number
|
||||
y: string
|
||||
}
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`z: { type: Number, required: true }`)
|
||||
expect(content).toMatch(`y: { type: String, required: true }`)
|
||||
expect(content).toMatch(`x: { type: Number, required: false }`)
|
||||
expect(bindings).toStrictEqual({
|
||||
x: BindingTypes.PROPS,
|
||||
y: BindingTypes.PROPS,
|
||||
z: BindingTypes.PROPS
|
||||
})
|
||||
})
|
||||
|
||||
test('defineProps w/ exported interface', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup lang="ts">
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
CallExpression,
|
||||
RestElement,
|
||||
TSInterfaceBody,
|
||||
TSTypeElement,
|
||||
AwaitExpression,
|
||||
Program,
|
||||
ObjectMethod,
|
||||
|
|
@ -558,6 +559,82 @@ export function compileScript(
|
|||
return true
|
||||
}
|
||||
|
||||
function getAstBody(): Statement[] {
|
||||
return scriptAst
|
||||
? [...scriptSetupAst.body, ...scriptAst.body]
|
||||
: scriptSetupAst.body
|
||||
}
|
||||
|
||||
function resolveExtendsType(
|
||||
node: Node,
|
||||
qualifier: (node: Node) => boolean,
|
||||
cache: Array<Node> = []
|
||||
): Array<Node> {
|
||||
if (node.type === 'TSInterfaceDeclaration' && node.extends) {
|
||||
node.extends.forEach(extend => {
|
||||
if (
|
||||
extend.type === 'TSExpressionWithTypeArguments' &&
|
||||
extend.expression.type === 'Identifier'
|
||||
) {
|
||||
const body = getAstBody()
|
||||
for (const node of body) {
|
||||
const qualified = isQualifiedType(
|
||||
node,
|
||||
qualifier,
|
||||
extend.expression.name
|
||||
)
|
||||
if (qualified) {
|
||||
cache.push(qualified)
|
||||
resolveExtendsType(node, qualifier, cache)
|
||||
return cache
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
function isQualifiedType(
|
||||
node: Node,
|
||||
qualifier: (node: Node) => boolean,
|
||||
refName: String
|
||||
): Node | undefined {
|
||||
if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
|
||||
return node.body
|
||||
} else if (
|
||||
node.type === 'TSTypeAliasDeclaration' &&
|
||||
node.id.name === refName &&
|
||||
qualifier(node.typeAnnotation)
|
||||
) {
|
||||
return node.typeAnnotation
|
||||
} else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
||||
return isQualifiedType(node.declaration, qualifier, refName)
|
||||
}
|
||||
}
|
||||
|
||||
// filter all extends types to keep the override declaration
|
||||
function filterExtendsType(extendsTypes: Node[], bodies: TSTypeElement[]) {
|
||||
extendsTypes.forEach(extend => {
|
||||
const body = (extend as TSInterfaceBody).body
|
||||
body.forEach(newBody => {
|
||||
if (
|
||||
newBody.type === 'TSPropertySignature' &&
|
||||
newBody.key.type === 'Identifier'
|
||||
) {
|
||||
const name = newBody.key.name
|
||||
const hasOverride = bodies.some(
|
||||
seenBody =>
|
||||
seenBody.type === 'TSPropertySignature' &&
|
||||
seenBody.key.type === 'Identifier' &&
|
||||
seenBody.key.name === name
|
||||
)
|
||||
if (!hasOverride) bodies.push(newBody)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function resolveQualifiedType(
|
||||
node: Node,
|
||||
qualifier: (node: Node) => boolean
|
||||
|
|
@ -570,28 +647,20 @@ export function compileScript(
|
|||
node.typeName.type === 'Identifier'
|
||||
) {
|
||||
const refName = node.typeName.name
|
||||
const isQualifiedType = (node: Node): Node | undefined => {
|
||||
if (
|
||||
node.type === 'TSInterfaceDeclaration' &&
|
||||
node.id.name === refName
|
||||
) {
|
||||
return node.body
|
||||
} else if (
|
||||
node.type === 'TSTypeAliasDeclaration' &&
|
||||
node.id.name === refName &&
|
||||
qualifier(node.typeAnnotation)
|
||||
) {
|
||||
return node.typeAnnotation
|
||||
} else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
||||
return isQualifiedType(node.declaration)
|
||||
}
|
||||
}
|
||||
const body = scriptAst
|
||||
? [...scriptSetupAst.body, ...scriptAst.body]
|
||||
: scriptSetupAst.body
|
||||
const body = getAstBody()
|
||||
for (const node of body) {
|
||||
const qualified = isQualifiedType(node)
|
||||
let qualified = isQualifiedType(
|
||||
node,
|
||||
qualifier,
|
||||
refName
|
||||
) as TSInterfaceBody
|
||||
if (qualified) {
|
||||
const extendsTypes = resolveExtendsType(node, qualifier)
|
||||
if (extendsTypes.length) {
|
||||
const bodies: TSTypeElement[] = [...qualified.body]
|
||||
filterExtendsType(extendsTypes, bodies)
|
||||
qualified.body = bodies
|
||||
}
|
||||
return qualified
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue