inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
233 lines (210 loc) • 5.91 kB
text/typescript
import * as tsutils from "tsutils/typeguard/3.0";
import ts from "typescript";
import type { VisitorContext } from "./visitor-context";
import * as VisitorUtils from "./visitor-utils";
function visitRegularObjectType() {
return false;
}
function visitTupleObjectType() {
return false;
}
function visitArrayObjectType() {
return false;
}
function visitObjectType(type: ts.ObjectType, visitorContext: VisitorContext) {
if (tsutils.isTupleType(type)) {
// Tuple with finite length.
return visitTupleObjectType();
} else if (
visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.Number)
) {
// Index type is number -> array type.
return visitArrayObjectType();
} else {
// Index type is string -> regular object type.
return visitRegularObjectType();
}
}
function visitUnionOrIntersectionType(
type: ts.UnionOrIntersectionType,
visitorContext: VisitorContext,
) {
const numberTypes = type.types.map((type) =>
visitType(type, visitorContext),
);
if (tsutils.isUnionType(type)) {
if (numberTypes.some((numberType) => numberType === false)) {
return false;
}
if (numberTypes.some((numberType) => numberType === true)) {
return true;
}
const numbers: Set<number> = new Set();
for (const numberType of numberTypes) {
for (const value of numberType as Set<number>) {
numbers.add(value);
}
}
return numbers;
} else {
const numbers: Set<number> = new Set();
for (const numberType of numberTypes) {
if (typeof numberType !== "boolean") {
for (const value of numberType) {
numbers.add(value);
}
}
}
if (numbers.size === 1) {
return numbers;
}
if (numbers.size > 1) {
return false;
}
if (numberTypes.some((numberType) => numberType === true)) {
return true;
}
return false;
}
}
function visitIndexType(): boolean {
// TODO: implement a visitor that checks if the index type is an array/tuple, then this can be a number.
throw new Error("Not yet implemented.");
}
function visitNonPrimitiveType() {
return false;
}
function visitLiteralType(type: ts.LiteralType) {
if (typeof type.value === "string") {
return false;
} else if (typeof type.value === "number") {
return new Set([type.value]);
} else {
throw new Error("Type value is expected to be a string or number.");
}
}
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 visitBigInt() {
return false;
}
function visitBoolean() {
return false;
}
function visitString() {
return false;
}
function visitBooleanLiteral() {
return false;
}
function visitNumber() {
return true;
}
function visitUndefined() {
return false;
}
function visitNull() {
return false;
}
function visitNever() {
return false;
}
function visitUnknown() {
return false;
}
function visitAny() {
return true;
}
export function visitType(
type: ts.Type,
visitorContext: VisitorContext,
): Set<number> | boolean {
if ((ts.TypeFlags.Any & type.flags) !== 0) {
// Any
return visitAny();
} else if ((ts.TypeFlags.Unknown & type.flags) !== 0) {
// Unknown
return visitUnknown();
} else if ((ts.TypeFlags.Never & type.flags) !== 0) {
// Never
return visitNever();
} else if ((ts.TypeFlags.Null & type.flags) !== 0) {
// Null
return visitNull();
} else if ((ts.TypeFlags.Undefined & type.flags) !== 0) {
// Undefined
return visitUndefined();
} else if ((ts.TypeFlags.Number & type.flags) !== 0) {
// Number
return visitNumber();
} else if (VisitorUtils.isBigIntType(type)) {
// BigInt
return visitBigInt();
} else if ((ts.TypeFlags.Boolean & type.flags) !== 0) {
// Boolean
return visitBoolean();
} else if ((ts.TypeFlags.String & type.flags) !== 0) {
// String
return visitString();
} else if ((ts.TypeFlags.BooleanLiteral & type.flags) !== 0) {
// Boolean literal (true/false)
return visitBooleanLiteral();
} 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(type);
} 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();
} else if ((ts.TypeFlags.Index & type.flags) !== 0) {
// Index type: keyof T
return visitIndexType();
} 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}`,
);
}
}