export interface TypeScope {
filename: string
source: string
+ offset: number
imports: Record<string, Import>
types: Record<
string,
} else {
ctx.error(
`Unsupported index type: ${node.indexType.type}`,
- node.indexType
+ node.indexType,
+ scope
)
}
}
// @ts-ignore
SupportedBuiltinsSet.has(typeName)
) {
- return resolveBuiltin(ctx, node, typeName as any)
+ return resolveBuiltin(ctx, node, typeName as any, scope)
}
ctx.error(
- `Failed to resolve type reference, or unsupported built-in utlility type.`,
- node
+ `Unresolvable type reference or unsupported built-in utlility type`,
+ node,
+ scope
)
}
}
}
- ctx.error(`Unresolvable type in SFC macro: ${node.type}`, node)
+ ctx.error(`Unresolvable type: ${node.type}`, node, scope)
}
function typeElementsToMap(
if (name && !e.computed) {
res.props[name] = e as ResolvedElements['props'][string]
} else if (e.key.type === 'TemplateLiteral') {
- for (const key of resolveTemplateKeys(ctx, e.key)) {
+ for (const key of resolveTemplateKeys(ctx, e.key, scope)) {
res.props[key] = e as ResolvedElements['props'][string]
}
} else {
ctx.error(
- `computed keys are not supported in types referenced by SFC macros.`,
- e
+ `Unsupported computed key in type referenced by a macro`,
+ e.key,
+ scope
)
}
} else if (e.type === 'TSCallSignatureDeclaration') {
scope: TypeScope
): ResolvedElements {
const res: ResolvedElements = { props: {} }
- if (!node.typeParameter.constraint) {
- ctx.error(`mapped type used in macros must have a finite constraint.`, node)
- }
- const keys = resolveStringType(ctx, node.typeParameter.constraint)
+ const keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
for (const key of keys) {
res.props[key] = createProperty(
{
return res
}
-function resolveStringType(ctx: ScriptCompileContext, node: Node): string[] {
+function resolveStringType(
+ ctx: ScriptCompileContext,
+ node: Node,
+ scope: TypeScope
+): string[] {
switch (node.type) {
case 'StringLiteral':
return [node.value]
case 'TSLiteralType':
- return resolveStringType(ctx, node.literal)
+ return resolveStringType(ctx, node.literal, scope)
case 'TSUnionType':
- return node.types.map(t => resolveStringType(ctx, t)).flat()
+ return node.types.map(t => resolveStringType(ctx, t, scope)).flat()
case 'TemplateLiteral': {
- return resolveTemplateKeys(ctx, node)
+ return resolveTemplateKeys(ctx, node, scope)
}
case 'TSTypeReference': {
- const resolved = resolveTypeReference(ctx, node)
+ const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) {
- return resolveStringType(ctx, resolved)
+ return resolveStringType(ctx, resolved, scope)
}
if (node.typeName.type === 'Identifier') {
const getParam = (index = 0) =>
- resolveStringType(ctx, node.typeParameters!.params[index])
+ resolveStringType(ctx, node.typeParameters!.params[index], scope)
switch (node.typeName.name) {
case 'Extract':
return getParam(1)
case 'Uncapitalize':
return getParam().map(s => s[0].toLowerCase() + s.slice(1))
default:
- ctx.error('Failed to resolve type reference', node)
+ ctx.error('Failed to resolve type reference', node, scope)
}
}
}
}
- ctx.error('Failed to resolve string type into finite keys', node)
+ ctx.error('Failed to resolve string type into finite keys', node, scope)
}
function resolveTemplateKeys(
ctx: ScriptCompileContext,
- node: TemplateLiteral
+ node: TemplateLiteral,
+ scope: TypeScope
): string[] {
if (!node.expressions.length) {
return [node.quasis[0].value.raw]
const e = node.expressions[0]
const q = node.quasis[0]
const leading = q ? q.value.raw : ``
- const resolved = resolveStringType(ctx, e)
- const restResolved = resolveTemplateKeys(ctx, {
- ...node,
- expressions: node.expressions.slice(1),
- quasis: q ? node.quasis.slice(1) : node.quasis
- })
+ const resolved = resolveStringType(ctx, e, scope)
+ const restResolved = resolveTemplateKeys(
+ ctx,
+ {
+ ...node,
+ expressions: node.expressions.slice(1),
+ quasis: q ? node.quasis.slice(1) : node.quasis
+ },
+ scope
+ )
for (const r of resolved) {
for (const rr of restResolved) {
function resolveBuiltin(
ctx: ScriptCompileContext,
node: TSTypeReference | TSExpressionWithTypeArguments,
- name: GetSetType<typeof SupportedBuiltinsSet>
+ name: GetSetType<typeof SupportedBuiltinsSet>,
+ scope: TypeScope
): ResolvedElements {
const t = resolveTypeElements(ctx, node.typeParameters!.params[0])
switch (name) {
case 'Readonly':
return t
case 'Pick': {
- const picked = resolveStringType(ctx, node.typeParameters!.params[1])
+ const picked = resolveStringType(
+ ctx,
+ node.typeParameters!.params[1],
+ scope
+ )
const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key of picked) {
res.props[key] = t.props[key]
return res
}
case 'Omit':
- const omitted = resolveStringType(ctx, node.typeParameters!.params[1])
+ const omitted = resolveStringType(
+ ctx,
+ node.typeParameters!.params[1],
+ scope
+ )
const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key in t.props) {
if (!omitted.includes(key)) {
): Node | undefined {
if (typeof name === 'string') {
if (scope.imports[name]) {
- return resolveTypeFromImport(ctx, scope, scope.imports[name], node)
+ return resolveTypeFromImport(ctx, node, name, scope)
} else {
const types = onlyExported ? scope.exportedTypes : scope.types
return types[name]
function resolveTypeFromImport(
ctx: ScriptCompileContext,
- scope: TypeScope,
- { source, imported }: Import,
- node: TSTypeReference | TSExpressionWithTypeArguments
+ node: TSTypeReference | TSExpressionWithTypeArguments,
+ name: string,
+ scope: TypeScope
): Node | undefined {
const fs = ctx.options.fs
if (!fs) {
ctx.error(
`fs options for compileScript are required for resolving imported types`,
- node
+ node,
+ scope
)
}
// TODO (hmr) register dependency file on ctx
const containingFile = scope.filename
+ const { source, imported } = scope.imports[name]
if (source.startsWith('.')) {
// relative import - fast path
const filename = path.join(containingFile, '..', source)
true
)
} else {
- ctx.error(`Failed to resolve import source for type`, node)
+ ctx.error(
+ `Failed to resolve import source ${JSON.stringify(
+ source
+ )} for type ${name}`,
+ node,
+ scope
+ )
}
} else {
// TODO module or aliased import - use full TS resolution
): TypeScope {
// TODO cache
const source = fs.readFile(filename)
- const body = parseFile(ctx, filename, source)
+ const [body, offset] = parseFile(ctx, filename, source)
const scope: TypeScope = {
filename,
source,
+ offset,
types: Object.create(null),
exportedTypes: Object.create(null),
imports: recordImports(body)
ctx: ScriptCompileContext,
filename: string,
content: string
-): Statement[] {
+): [Statement[], number] {
+ let body: Statement[] = []
+ let offset = 0
const ext = path.extname(filename)
if (ext === '.ts' || ext === '.tsx') {
- return babelParse(content, {
+ body = babelParse(content, {
plugins: resolveParserPlugins(
ext.slice(1),
ctx.options.babelParserPlugins
} = parse(content)
const scriptContent = (script?.content || '') + (scriptSetup?.content || '')
const lang = script?.lang || scriptSetup?.lang
- return babelParse(scriptContent, {
+ body = babelParse(scriptContent, {
plugins: resolveParserPlugins(lang!, ctx.options.babelParserPlugins),
sourceType: 'module'
}).program.body
+ offset = scriptSetup ? scriptSetup.loc.start.offset : 0
}
- return []
+ return [body, offset]
}
function ctxToScope(ctx: ScriptCompileContext): TypeScope {
const scope: TypeScope = {
filename: ctx.descriptor.filename,
source: ctx.descriptor.source,
+ offset: ctx.startOffset!,
imports: Object.create(ctx.userImports),
types: Object.create(null),
exportedTypes: Object.create(null)