feat(compiler-sfc): expose type resolve APIs

This commit is contained in:
Evan You 2023-04-16 11:11:26 +08:00
parent 6b13e04b4c
commit f22e32e365
3 changed files with 83 additions and 33 deletions

View File

@ -6,6 +6,7 @@ export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle' export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript' export { compileScript } from './compileScript'
export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault' export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
export { resolveTypeElements, inferRuntimeType } from './script/resolveType'
export { export {
shouldTransform as shouldTransformRef, shouldTransform as shouldTransformRef,
transform as transformRef, transform as transformRef,
@ -52,6 +53,11 @@ export type {
SFCStyleCompileResults SFCStyleCompileResults
} from './compileStyle' } from './compileStyle'
export type { SFCScriptCompileOptions } from './compileScript' export type { SFCScriptCompileOptions } from './compileScript'
export type { ScriptCompileContext } from './script/context'
export type {
TypeResolveContext,
SimpleTypeResolveContext
} from './script/resolveType'
export type { export type {
AssetURLOptions, AssetURLOptions,
AssetURLTagConfig AssetURLTagConfig

View File

@ -16,12 +16,14 @@ export class ScriptCompileContext {
scriptAst: Program | null scriptAst: Program | null
scriptSetupAst: Program | null scriptSetupAst: Program | null
s = new MagicString(this.descriptor.source) source = this.descriptor.source
filename = this.descriptor.filename
s = new MagicString(this.source)
startOffset = this.descriptor.scriptSetup?.loc.start.offset startOffset = this.descriptor.scriptSetup?.loc.start.offset
endOffset = this.descriptor.scriptSetup?.loc.end.offset endOffset = this.descriptor.scriptSetup?.loc.end.offset
// import / type analysis // import / type analysis
scope: TypeScope | undefined scope?: TypeScope
userImports: Record<string, ImportBinding> = Object.create(null) userImports: Record<string, ImportBinding> = Object.create(null)
// macros presence check // macros presence check
@ -69,7 +71,7 @@ export class ScriptCompileContext {
constructor( constructor(
public descriptor: SFCDescriptor, public descriptor: SFCDescriptor,
public options: SFCScriptCompileOptions public options: Partial<SFCScriptCompileOptions>
) { ) {
const { script, scriptSetup } = descriptor const { script, scriptSetup } = descriptor
const scriptLang = script && script.lang const scriptLang = script && script.lang

View File

@ -36,6 +36,32 @@ import { createCache } from '../cache'
import type TS from 'typescript' import type TS from 'typescript'
import { join, extname, dirname } from 'path' import { join, extname, dirname } from 'path'
/**
* TypeResolveContext is compatible with ScriptCompileContext
* but also allows a simpler version of it with minimal required properties
* when resolveType needs to be used in a non-SFC context, e.g. in a babel
* plugin. The simplest context can be just:
* ```ts
* const ctx: SimpleTypeResolveContext = {
* filename: '...',
* source: '...',
* options: {},
* error() {},
* ast: []
* }
* ```
*/
export type SimpleTypeResolveContext = Pick<
ScriptCompileContext,
// required
'source' | 'filename' | 'error' | 'options'
> &
Partial<Pick<ScriptCompileContext, 'scope' | 'deps'>> & {
ast: Statement[]
}
export type TypeResolveContext = ScriptCompileContext | SimpleTypeResolveContext
type Import = Pick<ImportBinding, 'source' | 'imported'> type Import = Pick<ImportBinding, 'source' | 'imported'>
export interface TypeScope { export interface TypeScope {
@ -79,7 +105,7 @@ interface ResolvedElements {
* mapped to runtime props or emits. * mapped to runtime props or emits.
*/ */
export function resolveTypeElements( export function resolveTypeElements(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: Node & WithScope & { _resolvedElements?: ResolvedElements }, node: Node & WithScope & { _resolvedElements?: ResolvedElements },
scope?: TypeScope scope?: TypeScope
): ResolvedElements { ): ResolvedElements {
@ -94,7 +120,7 @@ export function resolveTypeElements(
} }
function innerResolveTypeElements( function innerResolveTypeElements(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: Node, node: Node,
scope: TypeScope scope: TypeScope
): ResolvedElements { ): ResolvedElements {
@ -138,7 +164,7 @@ function innerResolveTypeElements(
) { ) {
return resolveBuiltin(ctx, node, typeName as any, scope) return resolveBuiltin(ctx, node, typeName as any, scope)
} }
ctx.error( return ctx.error(
`Unresolvable type reference or unsupported built-in utlility type`, `Unresolvable type reference or unsupported built-in utlility type`,
node, node,
scope scope
@ -146,11 +172,11 @@ function innerResolveTypeElements(
} }
} }
} }
ctx.error(`Unresolvable type: ${node.type}`, node, scope) return ctx.error(`Unresolvable type: ${node.type}`, node, scope)
} }
function typeElementsToMap( function typeElementsToMap(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
elements: TSTypeElement[], elements: TSTypeElement[],
scope = ctxToScope(ctx) scope = ctxToScope(ctx)
): ResolvedElements { ): ResolvedElements {
@ -227,7 +253,7 @@ function createProperty(
} }
function resolveInterfaceMembers( function resolveInterfaceMembers(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TSInterfaceDeclaration & WithScope, node: TSInterfaceDeclaration & WithScope,
scope: TypeScope scope: TypeScope
): ResolvedElements { ): ResolvedElements {
@ -246,7 +272,7 @@ function resolveInterfaceMembers(
} }
function resolveMappedType( function resolveMappedType(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TSMappedType, node: TSMappedType,
scope: TypeScope scope: TypeScope
): ResolvedElements { ): ResolvedElements {
@ -266,7 +292,7 @@ function resolveMappedType(
} }
function resolveIndexType( function resolveIndexType(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TSIndexedAccessType, node: TSIndexedAccessType,
scope: TypeScope scope: TypeScope
): (TSType & WithScope)[] { ): (TSType & WithScope)[] {
@ -297,7 +323,7 @@ function resolveIndexType(
} }
function resolveArrayElementType( function resolveArrayElementType(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: Node, node: Node,
scope: TypeScope scope: TypeScope
): TSType[] { ): TSType[] {
@ -322,11 +348,15 @@ function resolveArrayElementType(
} }
} }
} }
ctx.error('Failed to resolve element type from target type', node) return ctx.error(
'Failed to resolve element type from target type',
node,
scope
)
} }
function resolveStringType( function resolveStringType(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: Node, node: Node,
scope: TypeScope scope: TypeScope
): string[] { ): string[] {
@ -373,11 +403,11 @@ function resolveStringType(
} }
} }
} }
ctx.error('Failed to resolve index type into finite keys', node, scope) return ctx.error('Failed to resolve index type into finite keys', node, scope)
} }
function resolveTemplateKeys( function resolveTemplateKeys(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TemplateLiteral, node: TemplateLiteral,
scope: TypeScope scope: TypeScope
): string[] { ): string[] {
@ -420,7 +450,7 @@ const SupportedBuiltinsSet = new Set([
type GetSetType<T> = T extends Set<infer V> ? V : never type GetSetType<T> = T extends Set<infer V> ? V : never
function resolveBuiltin( function resolveBuiltin(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TSTypeReference | TSExpressionWithTypeArguments, node: TSTypeReference | TSExpressionWithTypeArguments,
name: GetSetType<typeof SupportedBuiltinsSet>, name: GetSetType<typeof SupportedBuiltinsSet>,
scope: TypeScope scope: TypeScope
@ -460,7 +490,7 @@ function resolveBuiltin(
} }
function resolveTypeReference( function resolveTypeReference(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: (TSTypeReference | TSExpressionWithTypeArguments) & { node: (TSTypeReference | TSExpressionWithTypeArguments) & {
_resolvedReference?: Node _resolvedReference?: Node
}, },
@ -481,7 +511,7 @@ function resolveTypeReference(
} }
function innerResolveTypeReference( function innerResolveTypeReference(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
scope: TypeScope, scope: TypeScope,
name: string | string[], name: string | string[],
node: TSTypeReference | TSExpressionWithTypeArguments, node: TSTypeReference | TSExpressionWithTypeArguments,
@ -536,6 +566,9 @@ function qualifiedNameToPath(node: Identifier | TSQualifiedName): string[] {
let ts: typeof TS let ts: typeof TS
/**
* @private
*/
export function registerTS(_ts: any) { export function registerTS(_ts: any) {
ts = _ts ts = _ts
} }
@ -543,7 +576,7 @@ export function registerTS(_ts: any) {
type FS = NonNullable<SFCScriptCompileOptions['fs']> type FS = NonNullable<SFCScriptCompileOptions['fs']>
function resolveTypeFromImport( function resolveTypeFromImport(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: TSTypeReference | TSExpressionWithTypeArguments, node: TSTypeReference | TSExpressionWithTypeArguments,
name: string, name: string,
scope: TypeScope scope: TypeScope
@ -685,13 +718,16 @@ function resolveWithTS(
const fileToScopeCache = createCache<TypeScope>() const fileToScopeCache = createCache<TypeScope>()
/**
* @private
*/
export function invalidateTypeCache(filename: string) { export function invalidateTypeCache(filename: string) {
fileToScopeCache.delete(filename) fileToScopeCache.delete(filename)
tsConfigCache.delete(filename) tsConfigCache.delete(filename)
} }
function fileToScope( function fileToScope(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
filename: string, filename: string,
fs: FS fs: FS
): TypeScope { ): TypeScope {
@ -717,7 +753,7 @@ function fileToScope(
} }
function parseFile( function parseFile(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
filename: string, filename: string,
content: string content: string
): Statement[] { ): Statement[] {
@ -763,24 +799,30 @@ function parseFile(
return [] return []
} }
function ctxToScope(ctx: ScriptCompileContext): TypeScope { function ctxToScope(ctx: TypeResolveContext): TypeScope {
if (ctx.scope) { if (ctx.scope) {
return ctx.scope return ctx.scope
} }
const body =
'ast' in ctx
? ctx.ast
: ctx.scriptAst
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
: ctx.scriptSetupAst!.body
const scope: TypeScope = { const scope: TypeScope = {
filename: ctx.descriptor.filename, filename: ctx.filename,
source: ctx.descriptor.source, source: ctx.source,
offset: ctx.startOffset!, offset: 'startOffset' in ctx ? ctx.startOffset! : 0,
imports: Object.create(ctx.userImports), imports:
'userImports' in ctx
? Object.create(ctx.userImports)
: recordImports(body),
types: Object.create(null), types: Object.create(null),
exportedTypes: Object.create(null) exportedTypes: Object.create(null)
} }
const body = ctx.scriptAst
? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body]
: ctx.scriptSetupAst!.body
recordTypes(body, scope) recordTypes(body, scope)
return (ctx.scope = scope) return (ctx.scope = scope)
@ -894,7 +936,7 @@ function recordImport(node: Node, imports: TypeScope['imports']) {
} }
export function inferRuntimeType( export function inferRuntimeType(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
node: Node & WithScope, node: Node & WithScope,
scope = node._ownerScope || ctxToScope(ctx) scope = node._ownerScope || ctxToScope(ctx)
): string[] { ): string[] {
@ -1052,7 +1094,7 @@ export function inferRuntimeType(
} }
function flattenTypes( function flattenTypes(
ctx: ScriptCompileContext, ctx: TypeResolveContext,
types: TSType[], types: TSType[],
scope: TypeScope scope: TypeScope
): string[] { ): string[] {