UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

263 lines (240 loc) 7.23 kB
import * as tsutils from "tsutils/typeguard/3.0"; import ts from "typescript"; import { setIntersection, setUnion } from "./utils"; import type { VisitorContext } from "./visitor-context"; import * as VisitorUtils from "./visitor-utils"; function visitRegularObjectType(type: ts.Type, visitorContext: VisitorContext) { const stringIndexType = visitorContext.checker.getIndexTypeOfType( type, ts.IndexKind.String, ); if (stringIndexType) { return true; } const properties = visitorContext.checker.getPropertiesOfType(type); const propertiesInfo = properties.map((property) => VisitorUtils.getPropertyInfo(type, property, visitorContext), ); const propertiesName = propertiesInfo.map( (propertyInfo) => propertyInfo.name, ); return new Set(propertiesName); } 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(type, visitorContext); } } function visitUnionOrIntersectionType( type: ts.UnionOrIntersectionType, visitorContext: VisitorContext, ) { const stringTypes = type.types.map((type) => visitType(type, visitorContext), ); if (tsutils.isUnionType(type)) { // keyof (T | U) = (keyof T) & (keyof U) if (stringTypes.some((stringType) => stringType === false)) { // If keyof T or keyof U is not assignable to string then keyof T & keyof U is not assignable to string. return false; } if (stringTypes.some((stringType) => stringType !== true)) { // Some keyof T or keyof U is a union of specific string literals. const stringSets = stringTypes.filter( (stringType) => stringType !== true, ) as Set<string>[]; let strings = stringSets[0]; for (let i = 1; i < stringSets.length; i++) { strings = setIntersection(strings, stringSets[i]); } return strings; } else { // Both keyof T and keyof U are the string type. return true; } } else { // keyof (T & U) = (keyof T) | (keyof U) if (stringTypes.some((stringType) => stringType === true)) { // If keyof T or keyof U is the string type then keyof T | keyof U is assignable to the string type. return true; } if (stringTypes.some((stringType) => stringType !== false)) { const stringSets = stringTypes.filter( (stringType) => stringType !== false, ) as Set<string>[]; let strings = stringSets[0]; for (let i = 1; i < stringSets.length; i++) { strings = setUnion(strings, stringSets[i]); } return strings; } else { // Both keyof T and keyof U are not assignable to the string type. return false; } } } function visitIndexType(): boolean { // TODO: implement a visitor that checks if the index type is an object, then this can be a string. 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 false; } 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() { // keyof boolean return false; } function visitString() { // keyof string return false; } function visitBooleanLiteral() { // keyof true/keyof false return false; } function visitNumber() { // keyof number return false; } function visitUndefined() { // keyof undefined return false; } function visitNull() { // keyof null return false; } function visitNever() { // keyof never return false; } function visitUnknown() { // keyof unknown return false; } function visitAny() { // keyof any return true; } export function visitType( type: ts.Type, visitorContext: VisitorContext, ): Set<string> | 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}`, ); } }