inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
269 lines (247 loc) • 8.83 kB
text/typescript
import * as tsutils from "tsutils/typeguard/3.0";
import ts from "typescript";
import type { VisitorContext } from "./visitor-context";
import * as VisitorTypeName from "./visitor-type-name";
import * as VisitorUtils from "./visitor-utils";
import { getIntrinsicName } from "./visitor-utils";
function visitUnionOrIntersectionType(
type: ts.UnionOrIntersectionType,
visitorContext: VisitorContext,
) {
const name = VisitorTypeName.visitType(type, visitorContext, {
type: "keyof",
});
return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => {
const functionNames = type.types.map((type) =>
visitType(type, visitorContext),
);
if (tsutils.isUnionType(type)) {
// keyof (T | U) = (keyof T) & (keyof U)
return VisitorUtils.createConjunctionFunction(functionNames, name);
} else {
// keyof (T & U) = (keyof T) | (keyof U)
return VisitorUtils.createDisjunctionFunction(
functionNames,
name,
visitorContext,
);
}
});
}
function visitIndexType(visitorContext: VisitorContext) {
// keyof keyof T = never (actually it's the methods of string, but we'll ignore those since they're not serializable)
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitNonPrimitiveType(type: ts.Type, visitorContext: VisitorContext) {
const intrinsicName = getIntrinsicName(type);
if (intrinsicName === "object") {
// keyof object = never
return VisitorUtils.getNeverFunction(visitorContext);
} else {
throw new Error(
`Unsupported non-primitive with intrinsic name: ${intrinsicName}.`,
);
}
}
function visitLiteralType(visitorContext: VisitorContext) {
// keyof 'string' = never and keyof 0xFF = never (actually they are the methods of string and number, but we'll ignore those since they're not serializable)
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitRegularObjectType(
type: ts.ObjectType,
visitorContext: VisitorContext,
) {
const stringIndexType = visitorContext.checker.getIndexTypeOfType(
type,
ts.IndexKind.String,
);
if (stringIndexType) {
// There is a string index type { [Key: string]: T }.
// keyof { [Key: string]: U } = string
return VisitorUtils.getStringFunction(visitorContext);
} else {
const name = VisitorTypeName.visitType(type, visitorContext, {
type: "keyof",
});
return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => {
// In keyof mode we check if the object is equal to one of the property names.
// keyof { x: T } = x
const properties = visitorContext.checker.getPropertiesOfType(type);
const names = properties.map((property) => property.name);
const condition = VisitorUtils.createBinaries(
names.map((name) =>
ts.createStrictInequality(
VisitorUtils.objectIdentifier,
ts.createStringLiteral(name),
),
),
ts.SyntaxKind.AmpersandAmpersandToken,
ts.createTrue(),
);
return VisitorUtils.createAssertionFunction(
condition,
{ type: "object-keyof", properties: names },
name,
visitorContext,
);
});
}
}
function visitTupleObjectType(visitorContext: VisitorContext) {
// keyof [U, T] = number
// TODO: actually they're only specific numbers (0, 1, 2...)
return VisitorUtils.getNumberFunction(visitorContext);
}
function visitArrayObjectType(visitorContext: VisitorContext) {
// keyof [] = number
return VisitorUtils.getNumberFunction(visitorContext);
}
function visitObjectType(type: ts.ObjectType, visitorContext: VisitorContext) {
if (tsutils.isTupleType(type)) {
// Tuple with finite length.
return visitTupleObjectType(visitorContext);
} else if (
visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.Number)
) {
// Index type is number -> array type.
return visitArrayObjectType(visitorContext);
} else {
// Index type is string -> regular object type.
return visitRegularObjectType(type, visitorContext);
}
}
function visitTypeReference(
type: ts.TypeReference,
visitorContext: VisitorContext,
) {
const mapping: Map<ts.Type, ts.Type> = VisitorUtils.getTypeReferenceMapping(
type,
visitorContext,
);
const previousTypeReference = visitorContext.previousTypeReference;
visitorContext.typeMapperStack.push(mapping);
visitorContext.previousTypeReference = type;
const result = visitType(type.target, visitorContext);
visitorContext.previousTypeReference = previousTypeReference;
visitorContext.typeMapperStack.pop();
return result;
}
function visitTypeParameter(type: ts.Type, visitorContext: VisitorContext) {
const mappedType = VisitorUtils.getResolvedTypeParameter(
type,
visitorContext,
);
if (mappedType === undefined) {
throw new Error("Unbound type parameter, missing type node.");
}
return visitType(mappedType, visitorContext);
}
function visitBoolean(visitorContext: VisitorContext) {
// keyof boolean = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitString(visitorContext: VisitorContext) {
// keyof string = never (actually it's all the methods of string, but we'll ignore those since they're not serializable)
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitBooleanLiteral(visitorContext: VisitorContext) {
// keyof true = never and keyof false = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitBigInt(visitorContext: VisitorContext) {
// keyof bigint = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitNumber(visitorContext: VisitorContext) {
// keyof number = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitUndefined(visitorContext: VisitorContext) {
// keyof undefined = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitNull(visitorContext: VisitorContext) {
// keyof null = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitNever(visitorContext: VisitorContext) {
// keyof never = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitUnknown(visitorContext: VisitorContext) {
// keyof unknown = never
return VisitorUtils.getNeverFunction(visitorContext);
}
function visitAny(visitorContext: VisitorContext) {
// keyof any = string (or symbol or number but we'll ignore those since they're not serializable)
return VisitorUtils.getStringFunction(visitorContext);
}
export function visitType(
type: ts.Type,
visitorContext: VisitorContext,
): string {
if ((ts.TypeFlags.Any & type.flags) !== 0) {
// Any
return visitAny(visitorContext);
} else if ((ts.TypeFlags.Unknown & type.flags) !== 0) {
// Unknown
return visitUnknown(visitorContext);
} else if ((ts.TypeFlags.Never & type.flags) !== 0) {
// Never
return visitNever(visitorContext);
} else if ((ts.TypeFlags.Null & type.flags) !== 0) {
// Null
return visitNull(visitorContext);
} else if ((ts.TypeFlags.Undefined & type.flags) !== 0) {
// Undefined
return visitUndefined(visitorContext);
} else if ((ts.TypeFlags.Number & type.flags) !== 0) {
// Number
return visitNumber(visitorContext);
} else if (VisitorUtils.isBigIntType(type)) {
// BigInt
return visitBigInt(visitorContext);
} else if ((ts.TypeFlags.Boolean & type.flags) !== 0) {
// Boolean
return visitBoolean(visitorContext);
} else if ((ts.TypeFlags.String & type.flags) !== 0) {
// String
return visitString(visitorContext);
} else if ((ts.TypeFlags.BooleanLiteral & type.flags) !== 0) {
// Boolean literal (true/false)
return visitBooleanLiteral(visitorContext);
} else if (
tsutils.isTypeReference(type) &&
visitorContext.previousTypeReference !== type
) {
// Type references.
return visitTypeReference(type, visitorContext);
} else if ((ts.TypeFlags.TypeParameter & type.flags) !== 0) {
// Type parameter
return visitTypeParameter(type, visitorContext);
} else if (tsutils.isObjectType(type)) {
// Object type (including interfaces, arrays, tuples)
return visitObjectType(type, visitorContext);
} else if (tsutils.isLiteralType(type)) {
// Literal string/number types ('foo')
return visitLiteralType(visitorContext);
} else if (tsutils.isUnionOrIntersectionType(type)) {
// Union or intersection type (| or &)
return visitUnionOrIntersectionType(type, visitorContext);
} else if ((ts.TypeFlags.NonPrimitive & type.flags) !== 0) {
// Non-primitive such as object
return visitNonPrimitiveType(type, visitorContext);
} else if ((ts.TypeFlags.Index & type.flags) !== 0) {
// Index type: keyof T
return visitIndexType(visitorContext);
} else if (tsutils.isIndexedAccessType(type)) {
// Indexed access type: T[U]
// return visitIndexedAccessType(type, visitorContext);
// TODO:
throw new Error("Not yet implemented.");
} else {
throw new Error(
`Could not generate type-check; unsupported type with flags: ${type.flags}`,
);
}
}