Skip to content

Commit

Permalink
feat(compiler-sfc): support limited built-in utility types in macros
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 13, 2023
1 parent fb8ecc8 commit 1cfab4c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 7 deletions.
25 changes: 25 additions & 0 deletions packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,31 @@ describe('resolveType', () => {
})
})

test('utility type: Pick', () => {
expect(
resolve(`
type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar'
type Target = Pick<T, K>
`).elements
).toStrictEqual({
foo: ['Number'],
bar: ['String']
})
})

test('utility type: Omit', () => {
expect(
resolve(`
type T = { foo: number, bar: string, baz: boolean }
type K = 'foo' | 'bar'
type Target = Omit<T, K>
`).elements
).toStrictEqual({
baz: ['Boolean']
})
})

describe('errors', () => {
test('error on computed keys', () => {
expect(() => resolve(`type Target = { [Foo]: string }`)).toThrow(
Expand Down
79 changes: 72 additions & 7 deletions packages/compiler-sfc/src/script/resolveType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,18 @@ function innerResolveTypeElements(
if (resolved) {
return resolveTypeElements(ctx, resolved)
} else {
// TODO Pick / Omit
ctx.error(`Failed to resolved type reference`, node)
const typeName = getReferenceName(node)
if (
typeof typeName === 'string' &&
// @ts-ignore
SupportedBuiltinsSet.has(typeName)
) {
return resolveBuiltin(ctx, node, typeName as any)
}
ctx.error(
`Failed to resolved type reference, or unsupported built-in utlility type.`,
node
)
}
}
case 'TSUnionType':
Expand Down Expand Up @@ -290,24 +300,79 @@ function resolveTemplateKeys(
return res
}

const SupportedBuiltinsSet = new Set([
'Partial',
'Required',
'Readonly',
'Pick',
'Omit'
] as const)

type GetSetType<T> = T extends Set<infer V> ? V : never

function resolveBuiltin(
ctx: ScriptCompileContext,
node: TSTypeReference | TSExpressionWithTypeArguments,
name: GetSetType<typeof SupportedBuiltinsSet>
): ResolvedElements {
const t = resolveTypeElements(ctx, node.typeParameters!.params[0])
switch (name) {
case 'Partial':
case 'Required':
case 'Readonly':
return t
case 'Pick': {
const picked = resolveStringType(ctx, node.typeParameters!.params[1])
const res: ResolvedElements = {}
if (t.__callSignatures) addCallSignature(res, t.__callSignatures)
for (const key of picked) {
res[key] = t[key]
}
return res
}
case 'Omit':
const omitted = resolveStringType(ctx, node.typeParameters!.params[1])
const res: ResolvedElements = {}
if (t.__callSignatures) addCallSignature(res, t.__callSignatures)
for (const key in t) {
if (!omitted.includes(key)) {
res[key] = t[key]
}
}
return res
}
}

function resolveTypeReference(
ctx: ScriptCompileContext,
node: TSTypeReference | TSExpressionWithTypeArguments,
scope = getRootScope(ctx)
): Node | undefined {
const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression
if (ref.type === 'Identifier') {
if (scope.imports[ref.name]) {
const name = getReferenceName(node)
if (typeof name === 'string') {
if (scope.imports[name]) {
// TODO external import
} else if (scope.types[ref.name]) {
return scope.types[ref.name]
} else if (scope.types[name]) {
return scope.types[name]
}
} else {
// TODO qualified name, e.g. Foo.Bar
// return resolveTypeReference()
}
}

function getReferenceName(
node: TSTypeReference | TSExpressionWithTypeArguments
): string | string[] {
const ref = node.type === 'TSTypeReference' ? node.typeName : node.expression
if (ref.type === 'Identifier') {
return ref.name
} else {
// TODO qualified name, e.g. Foo.Bar
return []
}
}

function getRootScope(ctx: ScriptCompileContext): TypeScope {
if (ctx.scope) {
return ctx.scope
Expand Down

0 comments on commit 1cfab4c

Please sign in to comment.