Skip to content

Commit

Permalink
[vue-component-meta] fix: recursive schema parsing (#1660)
Browse files Browse the repository at this point in the history
  • Loading branch information
stafyniaksacha committed Aug 1, 2022
1 parent 2d7e0ea commit a2e9f02
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 74 deletions.
122 changes: 70 additions & 52 deletions packages/vue-component-meta/src/index.ts
Expand Up @@ -130,12 +130,6 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:

const componentType = typeChecker.getTypeOfSymbolAtLocation(_export, symbolNode!);
const symbolProperties = componentType.getProperties() ?? [];
const {
resolveNestedProperties,
resolveEventSignature,
resolveExposedProperties,
resolveSlotProperties,
} = createSchemaResolvers(typeChecker, symbolNode!, checkerOptions.schema);

return {
props: getProps(),
Expand All @@ -155,7 +149,13 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:
const properties = type.getApparentProperties();

result = properties
.map(resolveNestedProperties)
.map((prop) => {
const {
resolveNestedProperties,
} = createSchemaResolvers(typeChecker, symbolNode!, checkerOptions.schema);

return resolveNestedProperties(prop);
})
.filter((prop) => !prop.name.match(propEventRegex));
}

Expand Down Expand Up @@ -189,14 +189,20 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:
}

function getEvents() {

const $emit = symbolProperties.find(prop => prop.escapedName === '$emit');

if ($emit) {
const type = typeChecker.getTypeOfSymbolAtLocation($emit, symbolNode!);
const calls = type.getCallSignatures();

return calls.map(resolveEventSignature).filter(event => event.name);
return calls.map((call) => {

const {
resolveEventSignature,
} = createSchemaResolvers(typeChecker, symbolNode!, checkerOptions.schema);

return resolveEventSignature(call);
}).filter(event => event.name);
}

return [];
Expand All @@ -211,20 +217,33 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:
const type = typeChecker.getTypeOfSymbolAtLocation($slots, symbolNode!);
const properties = type.getProperties();

return properties.map(resolveSlotProperties);
return properties.map((prop) => {
const {
resolveSlotProperties,
} = createSchemaResolvers(typeChecker, symbolNode!, checkerOptions.schema);

return resolveSlotProperties(prop);
});
}

return [];
}

function getExposed() {

const exposed = symbolProperties.filter(prop =>
// only exposed props will have a syntheticOrigin
Boolean((prop as any).syntheticOrigin)
);

if (exposed.length) {
return exposed.map(resolveExposedProperties);
return exposed.map((prop) => {
const {
resolveExposedProperties,
} = createSchemaResolvers(typeChecker, symbolNode!, checkerOptions.schema);

return resolveExposedProperties(prop);
});
}

return [];
Expand Down Expand Up @@ -272,7 +291,7 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions:

function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expression, options: MetaCheckerSchemaOptions = false) {
const enabled = !!options;
const ignore = typeof options === 'object' ? options.ignore ?? [] : [];
const ignore = typeof options === 'object' ? [...options?.ignore ?? []] : [];

function shouldIgnore(subtype: ts.Type) {
const type = typeChecker.typeToString(subtype);
Expand All @@ -287,6 +306,11 @@ function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expre
return ignore.includes(type);
}

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;
Expand Down Expand Up @@ -358,63 +382,57 @@ function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expre
schema,
};
}
function resolveEventSchema(subtype: ts.Type): PropertyMetaSchema {
return (subtype.getCallSignatures().length === 1)
? resolveCallbackSchema(subtype.getCallSignatures()[0])
: typeChecker.typeToString(subtype);
}
function resolveNestedSchema(subtype: ts.Type): PropertyMetaSchema {
if (
function resolveSchema(subtype: ts.Type): PropertyMetaSchema {
const type = typeChecker.typeToString(subtype);
let schema: PropertyMetaSchema = type;

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

setVisited(subtype);

if (subtype.isUnion()) {
schema = {
kind: 'enum',
type,
schema: subtype.types.map(resolveSchema)
};
}

// @ts-ignore - typescript internal, isArrayLikeType exists
else if (typeChecker.isArrayLikeType(subtype)) {
schema = {
kind: 'array',
type,
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)
) {
if (shouldIgnore(subtype)) {
return typeChecker.typeToString(subtype);
}

return {
// setVisited(subtype);
schema = {
kind: 'object',
type: typeChecker.typeToString(subtype),
type,
schema: subtype.getProperties().map(resolveNestedProperties).reduce(reducer, {})
};
}
return resolveEventSchema(subtype);
}
function resolveArraySchema(subtype: ts.Type): PropertyMetaSchema {
// @ts-ignore - typescript internal, isArrayLikeType exists
if (typeChecker.isArrayLikeType(subtype)) {
if (shouldIgnore(subtype)) {
return typeChecker.typeToString(subtype);
}

return {
kind: 'array',
type: typeChecker.typeToString(subtype),
schema: typeChecker.getTypeArguments(subtype as ts.TypeReference).map(resolveSchema)
};
else if (subtype.getCallSignatures().length === 1) {
schema = resolveCallbackSchema(subtype.getCallSignatures()[0]);
}

return resolveNestedSchema(subtype);
}
function resolveSchema(subtype: ts.Type): PropertyMetaSchema {
return subtype.isUnion()
? {
kind: 'enum',
type: typeChecker.typeToString(subtype),
schema: subtype.types.map(resolveArraySchema)
}
: resolveArraySchema(subtype);
return schema;
}

return {
resolveNestedProperties,
resolveSlotProperties,
resolveEventSignature,
resolveExposedProperties,
resolveCallbackSchema,
resolveEventSchema,
resolveNestedSchema,
resolveArraySchema,
resolveSchema,
};
}
Expand Down
41 changes: 19 additions & 22 deletions packages/vue-component-meta/tests/index.spec.ts
Expand Up @@ -56,7 +56,7 @@ describe(`vue-component-meta`, () => {
const enumValue = meta.props.find(prop => prop.name === 'enumValue');
const literalFromContext = meta.props.find(prop => prop.name === 'literalFromContext');
const inlined = meta.props.find(prop => prop.name === 'inlined');
// const onEvent = meta.props.find(prop => prop.name === 'onEvent');
const recursive = meta.props.find(prop => prop.name === 'recursive');

expect(foo).toBeDefined();
expect(foo?.required).toBeTruthy();
Expand Down Expand Up @@ -311,27 +311,24 @@ describe(`vue-component-meta`, () => {
]
});

// expect(onEvent).toBeDefined();
// // expect(onEvent?.required).toBeFalsy()
// expect(onEvent?.type).toEqual('((...args: any[]) => any) | undefined');
// expect(onEvent?.schema).toEqual({
// kind: 'enum',
// type: '((...args: any[]) => any) | undefined',
// schema: [
// 'undefined',
// {
// kind: 'event',
// type: '(...args: any[]): any',
// schema: [
// {
// kind: 'array',
// type: 'any',
// schema: [],
// }
// ]
// }
// ]
// });
expect(recursive).toBeDefined();
expect(recursive?.required).toBeTruthy();
expect(recursive?.type).toEqual('MyNestedRecursiveProps');
expect(recursive?.schema).toEqual({
kind: 'object',
type: 'MyNestedRecursiveProps',
schema: {
recursive: {
name: 'recursive',
description: '',
tags: [],
global: false,
required: true,
type: 'MyNestedRecursiveProps',
schema: 'MyNestedRecursiveProps'
}
}
});
});

test('reference-type-props-js', () => {
Expand Down
Expand Up @@ -9,6 +9,10 @@ export interface MyIgnoredNestedProps {
nestedProp: string;
}

export interface MyNestedRecursiveProps {
recursive: MyNestedRecursiveProps
}

enum MyEnum {
Small,
Medium,
Expand Down Expand Up @@ -91,4 +95,5 @@ export interface MyProps {
*/
literalFromContext: MyCategories,
inlined: { foo: string; },
recursive: MyNestedRecursiveProps
}

0 comments on commit a2e9f02

Please sign in to comment.