Skip to content

Commit

Permalink
perf(vue-component-meta): resolve schema on demand (#2288)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsoncodehk committed Jan 7, 2023
1 parent 6a39232 commit 8aa4622
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 50 deletions.
101 changes: 56 additions & 45 deletions vue-language-tools/vue-component-meta/src/index.ts
Expand Up @@ -409,157 +409,168 @@ function createSchemaResolvers(
{ rawType, schema: options }: MetaCheckerOptions,
ts: typeof import('typescript/lib/tsserverlibrary'),
) {
const enabled = !!options;
const ignore = typeof options === 'object' ? [...options?.ignore ?? []] : [];
const visited = new Set<ts.Type>();;

function shouldIgnore(subtype: ts.Type) {
const name = typeChecker.typeToString(subtype);
if (name === 'any') {
return true;
}

if (ignore.length === 0) {
return false;
if (visited.has(subtype)) {
return true;
}

for (const item of ignore) {
if (typeof item === 'function') {
const result = item(name, subtype, typeChecker);
if (result != null)
return result;
}
else if (name === item) {
return true;
if (typeof options === 'object') {
for (const item of options.ignore ?? []) {
if (typeof item === 'function') {
const result = item(name, subtype, typeChecker);
if (typeof result === 'boolean')
return result;
}
else if (name === item) {
return true;
}
}
}

return false;
}

function setVisited(subtype: ts.Type) {
const type = typeChecker.typeToString(subtype);
ignore.push(type);
}

function reducer(acc: any, cur: any) {
acc[cur.name] = cur;
return acc;
}

function resolveNestedProperties(prop: ts.Symbol): PropertyMeta {
const subtype = typeChecker.getTypeOfSymbolAtLocation(prop, symbolNode!);
const schema = enabled ? resolveSchema(subtype) : undefined;
let schema: PropertyMetaSchema;

return {
name: prop.getEscapedName().toString(),
global: false,
description: ts.displayPartsToString(prop.getDocumentationComment(typeChecker)),
tags: prop.getJsDocTags(typeChecker).map(tag => ({
name: tag.name,
text: tag.text?.map(part => part.text).join(''),
text: tag.text !== undefined ? ts.displayPartsToString(tag.text) : undefined,
})),
required: !(prop.flags & ts.SymbolFlags.Optional),
type: typeChecker.typeToString(subtype),
rawType: rawType ? subtype : undefined,
schema,
get schema() {
return schema ??= resolveSchema(subtype);
},
};
}
function resolveSlotProperties(prop: ts.Symbol): SlotMeta {
const subtype = typeChecker.getTypeOfSymbolAtLocation(typeChecker.getTypeOfSymbolAtLocation(prop, symbolNode!).getCallSignatures()[0].parameters[0], symbolNode!);
const schema = enabled ? resolveSchema(subtype) : undefined;
let schema: PropertyMetaSchema;

return {
name: prop.getName(),
type: typeChecker.typeToString(subtype),
rawType: rawType ? subtype : undefined,
description: ts.displayPartsToString(prop.getDocumentationComment(typeChecker)),
schema,
get schema() {
return schema ??= resolveSchema(subtype);
},
};
}
function resolveExposedProperties(expose: ts.Symbol): ExposeMeta {
const subtype = typeChecker.getTypeOfSymbolAtLocation(expose, symbolNode!);
const schema = enabled ? resolveSchema(subtype) : undefined;
let schema: PropertyMetaSchema;

return {
name: expose.getName(),
type: typeChecker.typeToString(subtype),
rawType: rawType ? subtype : undefined,
description: ts.displayPartsToString(expose.getDocumentationComment(typeChecker)),
schema,
get schema() {
return schema ??= resolveSchema(subtype);
},
};
}
function resolveEventSignature(call: ts.Signature): EventMeta {
const subtype = typeChecker.getTypeOfSymbolAtLocation(call.parameters[1], symbolNode!);
const schema = enabled
? typeChecker.getTypeArguments(subtype as ts.TypeReference).map(resolveSchema)
: undefined;
let schema: PropertyMetaSchema[];

return {
name: (typeChecker.getTypeOfSymbolAtLocation(call.parameters[0], symbolNode!) as ts.StringLiteralType).value,
type: typeChecker.typeToString(subtype),
rawType: rawType ? subtype : undefined,
signature: typeChecker.signatureToString(call),
schema,
get schema() {
return schema ??= typeChecker.getTypeArguments(subtype as ts.TypeReference).map(resolveSchema);
},
};
}

function resolveCallbackSchema(signature: ts.Signature): PropertyMetaSchema {
const schema = enabled && signature.parameters.length > 0
? typeChecker
.getTypeArguments(typeChecker.getTypeOfSymbolAtLocation(signature.parameters[0], symbolNode) as ts.TypeReference)
.map(resolveSchema)
: undefined;
let schema: PropertyMetaSchema[] | undefined;

return {
kind: 'event',
type: typeChecker.signatureToString(signature),
schema,
get schema() {
return schema ??= signature.parameters.length > 0
? typeChecker
.getTypeArguments(typeChecker.getTypeOfSymbolAtLocation(signature.parameters[0], symbolNode) as ts.TypeReference)
.map(resolveSchema)
: undefined;
},
};
}
function resolveSchema(subtype: ts.Type): PropertyMetaSchema {
const type = typeChecker.typeToString(subtype);
let schema: PropertyMetaSchema = type;

if (shouldIgnore(subtype)) {
return type;
}

setVisited(subtype);
visited.add(subtype);

if (subtype.isUnion()) {
schema = {
let schema: PropertyMetaSchema[];
return {
kind: 'enum',
type,
schema: subtype.types.map(resolveSchema)
get schema() {
return schema ??= subtype.types.map(resolveSchema);
},
};
}

// @ts-ignore - typescript internal, isArrayLikeType exists
else if (typeChecker.isArrayLikeType(subtype)) {
schema = {
let schema: PropertyMetaSchema[];
return {
kind: 'array',
type,
schema: typeChecker.getTypeArguments(subtype as ts.TypeReference).map(resolveSchema)
get schema() {
return schema ??= typeChecker.getTypeArguments(subtype as ts.TypeReference).map(resolveSchema);
},
};
}

else if (
subtype.getCallSignatures().length === 0 &&
(subtype.isClassOrInterface() || subtype.isIntersection() || (subtype as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous)
) {
// setVisited(subtype);
schema = {
let schema: Record<string, PropertyMeta>;
return {
kind: 'object',
type,
schema: subtype.getProperties().map(resolveNestedProperties).reduce(reducer, {})
get schema() {
return schema ??= subtype.getProperties().map(resolveNestedProperties).reduce(reducer, {});
},
};
}

else if (subtype.getCallSignatures().length === 1) {
schema = resolveCallbackSchema(subtype.getCallSignatures()[0]);
return resolveCallbackSchema(subtype.getCallSignatures()[0]);
}

return schema;
return type;
}

return {
Expand Down
10 changes: 5 additions & 5 deletions vue-language-tools/vue-component-meta/src/types.ts
Expand Up @@ -15,28 +15,28 @@ export interface PropertyMeta {
type: string;
rawType?: ts.Type;
tags: { name: string, text?: string; }[];
schema?: PropertyMetaSchema;
schema: PropertyMetaSchema;
};
export interface EventMeta {
name: string;
type: string;
rawType?: ts.Type;
signature: string;
schema?: PropertyMetaSchema[];
schema: PropertyMetaSchema[];
}
export interface SlotMeta {
name: string;
type: string;
rawType?: ts.Type;
description: string;
schema?: PropertyMetaSchema;
schema: PropertyMetaSchema;
}
export interface ExposeMeta {
name: string;
description: string;
type: string;
rawType?: ts.Type;
schema?: PropertyMetaSchema;
schema: PropertyMetaSchema;
}

export type PropertyMetaSchema = string
Expand All @@ -56,6 +56,6 @@ export type MetaCheckerSchemaOptions = boolean | {
export interface MetaCheckerOptions {
schema?: MetaCheckerSchemaOptions;
forceUseTs?: boolean;
printer?: import('typescript').PrinterOptions;
printer?: ts.PrinterOptions;
rawType?: boolean;
}

0 comments on commit 8aa4622

Please sign in to comment.