Skip to content

Commit

Permalink
custom object handling demo
Browse files Browse the repository at this point in the history
  • Loading branch information
justjake committed Jul 9, 2022
1 parent e79ed73 commit 49b3a7c
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/simple-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export interface SimpleTypeMethod extends SimpleTypeBase {
export interface SimpleTypeGenericArguments extends SimpleTypeBase {
readonly kind: "GENERIC_ARGUMENTS"; // TODO: rename
/** The generic type being instantiated */
readonly target: Extract<SimpleType, { typeParameters?: unknown }>;
readonly target: Extract<SimpleType, { typeParameters?: unknown }> | SimpleTypeCustom;
/** The arguments passed to the generic */
readonly typeArguments: SimpleType[];
/** The concrete type resulting from applying the type parameters to the generic */
Expand Down Expand Up @@ -304,7 +304,7 @@ export interface SimpleTypePromise extends SimpleTypeBase {

export interface SimpleTypeCustom<T = unknown> extends SimpleTypeBase {
readonly kind: "CUSTOM";
readonly data: T;
readonly extra?: T;
}

export type SimpleType =
Expand Down
70 changes: 55 additions & 15 deletions src/transform/to-simple-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,25 @@ interface ToSimpleTypePureOptions {
cache?: WeakMap<Type, SimpleType>;
}

interface ToCustomTypeArguments {
type: ts.Type;
checker: ts.TypeChecker;
ts: typeof tsModule;
/** True when `type` is the target of a GENERIC_ARGUMENTS instantiation */
generic: boolean;
}

type ToCustomType = (args: ToCustomTypeArguments) => SimpleType | ((concrete: SimpleType) => SimpleType) | undefined;

interface ToSimpleTypeConfigureTypeConstruction extends ToSimpleTypePureOptions {
/** With these options, the user must provide a cache because options modify how types are built, making repeat calls with the default cache non-deterministic */
cache: WeakMap<Type, SimpleType>;
/** Add methods like .getType(), .getTypeChecker() to each simple type */
addMethods?: boolean;
/** Add { kind: "ALIAS" } wrapper types around simple aliases. Otherwise, remove these wrappers. */
preserveSimpleAliases?: boolean;
/** If defined, called with each type, should return a CUSTOM type or undefined */
toCustomType?: ToCustomType;
}

export type ToSimpleTypeOptions = ToSimpleTypePureOptions | ToSimpleTypeConfigureTypeConstruction;
Expand Down Expand Up @@ -112,6 +124,7 @@ export function toSimpleType(type: Type | Node | SimpleType, checker?: TypeCheck
cache: options.cache || DEFAULT_TYPE_CACHE,
addMethods: "addMethods" in options ? options.addMethods : undefined,
preserveSimpleAliases: "preserveSimpleAliases" in options ? options.preserveSimpleAliases : undefined,
toCustomType: "toCustomType" in options ? options.toCustomType : undefined,
ts: getTypescriptModule()
});
}
Expand Down Expand Up @@ -212,8 +225,8 @@ function toSimpleTypeCached(type: Type, options: ToSimpleTypeInternalOptions): S
* @param type
* @param options
*/
function liftGenericType(type: Type, options: ToSimpleTypeInternalOptions): { generic: (instantiated: SimpleType) => SimpleType; instantiated: Type } | undefined {
const enhance = (instantiated: SimpleType) => withMethods(instantiated, type, options);
function liftGenericType(type: Type, options: ToSimpleTypeInternalOptions): { wrap: (instantiated: SimpleType) => SimpleType; instantiated: Type } | undefined {
const addMethods = (instantiated: SimpleType) => withMethods(instantiated, type, options);
const wrapIfAlias = (instantiated: SimpleType, ignoreTypeParams?: boolean): SimpleType => {
if (isAlias(type, options.ts)) {
const aliasName = type.aliasSymbol!.getName() || "";
Expand Down Expand Up @@ -262,19 +275,34 @@ function liftGenericType(type: Type, options: ToSimpleTypeInternalOptions): { ge

return {
instantiated: type,
generic: instantiated => {
wrap: instantiated => {
const typeArgumentsSimpleType = typeArguments.map(t => toSimpleTypeCached(t, options));

const generic: SimpleTypeGenericArguments = {
const customType = options.toCustomType?.({
...options,
type: type.target,
generic: true
});

const targetSimpleType =
typeof customType === "function"
? toSimpleTypeCached(type.target, options) /// XXX unlimited recursion?
: customType;

let generic: SimpleType = {
kind: "GENERIC_ARGUMENTS",
target: toSimpleTypeCached(type.target, options) as any,
target: targetSimpleType as any,
instantiated,
typeArguments: typeArgumentsSimpleType
};

if (typeof customType === "function") {
generic = customType(generic);
}

// This makes current tests work, but may be actually incorrect.
// vvvvvv
return enhance(wrapIfAlias(generic, true));
return addMethods(wrapIfAlias(generic, true));
}
};
}
Expand All @@ -284,8 +312,8 @@ function liftGenericType(type: Type, options: ToSimpleTypeInternalOptions): { ge
return {
// TODO: better type safety
instantiated: (type as any).target || type,
generic: instantiated => {
return enhance(wrapIfAlias(instantiated));
wrap: instantiated => {
return addMethods(wrapIfAlias(instantiated));
}
};
}
Expand All @@ -306,20 +334,37 @@ function withMethods(obj: SimpleType, type: Type, options: ToSimpleTypeInternalO
};
}

function toSimpleTypeInternal(type: Type, options: ToSimpleTypeInternalOptions): SimpleType {
function toSimpleTypeInternal(outerType: Type, options: ToSimpleTypeInternalOptions): SimpleType {
const { checker, ts } = options;

let type = outerType;
const symbol: ESSymbol | undefined = type.getSymbol();
const name = symbol != null ? getRealSymbolName(symbol, ts) : undefined;

let simpleType: SimpleType | undefined;
let enhance: (instantiated: SimpleType) => SimpleType = t => withMethods(t, type, options);

const generic = liftGenericType(type, options);
if (generic != null) {
type = generic.instantiated;
const originalEnhance = enhance;
enhance = t => generic.wrap(originalEnhance(t));
}

const enhance = (obj: SimpleType) => withMethods(obj, type, options);
// Custom types
const customType = options.toCustomType?.({
...options,
type,
generic: false
});
if (customType) {
if (typeof customType === "function") {
const originalEnhance = enhance;
enhance = t => withMethods(customType(originalEnhance(t)), outerType, options);
} else {
return enhance(customType);
}
}

// Literal types
if (isLiteral(type, ts)) {
Expand Down Expand Up @@ -619,11 +664,6 @@ function toSimpleTypeInternal(type: Type, options: ToSimpleTypeInternalOptions):
};
}

// Lift generic types and aliases if possible
if (generic != null) {
return generic.generic(enhance(simpleType));
}

return enhance(simpleType);
}

Expand Down
Loading

0 comments on commit 49b3a7c

Please sign in to comment.