UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

372 lines (349 loc) 10.9 kB
import * as tsutils from "tsutils/typeguard/3.0"; import ts from "typescript"; import { sliceSet } from "./utils"; import type { VisitorContext } from "./visitor-context"; import * as VisitorIsNumber from "./visitor-is-number"; import * as VisitorIsString from "./visitor-is-string"; import * as VisitorTypeCheck from "./visitor-type-check"; import * as VisitorTypeName from "./visitor-type-name"; import * as VisitorUtils from "./visitor-utils"; function visitRegularObjectType( type: ts.ObjectType, indexType: ts.Type, visitorContext: VisitorContext, ) { const name = VisitorTypeName.visitType(type, visitorContext, { type: "indexed-access", indexType, }); return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => { // TODO: check property index // const stringIndexType = visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.String); const properties = visitorContext.checker.getPropertiesOfType(type); const propertiesInfo = properties.map((property) => VisitorUtils.getPropertyInfo(type, property, visitorContext), ); const stringType = VisitorIsString.visitType(indexType, visitorContext); if (typeof stringType === "boolean") { if (!stringType) { throw new Error( "A non-string type was used to index an object type.", ); } const functionNames = propertiesInfo.map((propertyInfo) => propertyInfo.isMethod ? VisitorUtils.getIgnoredTypeFunction(visitorContext) : VisitorTypeCheck.visitType( propertyInfo.type!, visitorContext, ), ); return VisitorUtils.createDisjunctionFunction( functionNames, name, visitorContext, ); } else { const strings = sliceSet(stringType); if ( strings.some((value) => propertiesInfo.every( (propertyInfo) => propertyInfo.name !== value, ), ) ) { throw new Error( "Indexed access on object type with an index that does not exist.", ); } const stringPropertiesInfo = strings.map( (value) => propertiesInfo.find( (propertyInfo) => propertyInfo.name === value, )!, ); const functionNames = stringPropertiesInfo.map((propertyInfo) => propertyInfo.isMethod ? VisitorUtils.getIgnoredTypeFunction(visitorContext) : VisitorTypeCheck.visitType( propertyInfo.type!, visitorContext, ), ); return VisitorUtils.createDisjunctionFunction( functionNames, name, visitorContext, ); } }); } function visitTupleObjectType( type: ts.TupleType, indexType: ts.Type, visitorContext: VisitorContext, ) { const name = VisitorTypeName.visitType(type, visitorContext, { type: "indexed-access", indexType, }); return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => { if (type.typeArguments === undefined) { throw new Error("Expected tuple type to have type arguments."); } const numberType = VisitorIsNumber.visitType(indexType, visitorContext); if (typeof numberType === "boolean") { if (!numberType) { throw new Error( "A non-number type was used to index a tuple type.", ); } const functionNames = type.typeArguments.map((type) => VisitorTypeCheck.visitType(type, visitorContext), ); return VisitorUtils.createDisjunctionFunction( functionNames, name, visitorContext, ); } else { const numbers = sliceSet(numberType); if (numbers.some((value) => value >= type.typeArguments!.length)) { throw new Error( "Indexed access on tuple type exceeds length of tuple.", ); } const functionNames = numbers.map((value) => VisitorTypeCheck.visitType( type.typeArguments![value], visitorContext, ), ); return VisitorUtils.createDisjunctionFunction( functionNames, name, visitorContext, ); } }); } function visitArrayObjectType( type: ts.ObjectType, indexType: ts.Type, visitorContext: VisitorContext, ) { const numberIndexType = visitorContext.checker.getIndexTypeOfType( type, ts.IndexKind.Number, ); if (numberIndexType === undefined) { throw new Error( "Expected array ObjectType to have a number index type.", ); } const numberType = VisitorIsNumber.visitType(indexType, visitorContext); if (numberType !== false) { return VisitorTypeCheck.visitType(numberIndexType, visitorContext); } else { throw new Error("A non-number type was used to index an array type."); } } function visitObjectType( type: ts.ObjectType, indexType: ts.Type, visitorContext: VisitorContext, ) { if (tsutils.isTupleType(type)) { // Tuple with finite length. return visitTupleObjectType(type, indexType, visitorContext); } else if ( visitorContext.checker.getIndexTypeOfType(type, ts.IndexKind.Number) ) { // Index type is number -> array type. return visitArrayObjectType(type, indexType, visitorContext); } else { // Index type is string -> regular object type. return visitRegularObjectType(type, indexType, visitorContext); } } function visitUnionOrIntersectionType( type: ts.UnionOrIntersectionType, indexType: ts.Type, visitorContext: VisitorContext, ) { const name = VisitorTypeName.visitType(type, visitorContext, { type: "indexed-access", indexType, }); return VisitorUtils.setFunctionIfNotExists(name, visitorContext, () => { const functionNames = type.types.map((type) => visitType(type, indexType, visitorContext), ); if (tsutils.isUnionType(type)) { // (T | U)[I] = T[I] & U[I] return VisitorUtils.createConjunctionFunction(functionNames, name); } else { // (T & U)[I] = T[I] | U[I] return VisitorUtils.createDisjunctionFunction( functionNames, name, visitorContext, ); } }); } function visitIndexType(): string { // (keyof U)[T] is an error (actually it can be String.toString or String.valueOf but we don't support this edge case) throw new Error("Index types cannot be used as indexed types."); } function visitNonPrimitiveType(): string { // object[T] is an error throw new Error("Non-primitive object cannot be used as an indexed type."); } function visitLiteralType(): string { // 'string'[T] or 0xFF[T] is an error throw new Error( "Literal strings/numbers cannot be used as an indexed type.", ); } function visitTypeReference( type: ts.TypeReference, indexType: ts.Type, 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, indexType, visitorContext); visitorContext.previousTypeReference = previousTypeReference; visitorContext.typeMapperStack.pop(); return result; } function visitTypeParameter( type: ts.Type, indexType: 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, indexType, visitorContext); } function visitBigInt(): string { // bigint[T] is an error throw new Error("BigInt cannot be used as an indexed type."); } function visitBoolean(): string { // boolean[T] is an error throw new Error("Boolean cannot be used as an indexed type."); } function visitString(): string { // string[T] is an error throw new Error("String cannot be used as an indexed type."); } function visitBooleanLiteral(): string { // true[T] or false[T] is an error throw new Error("True/false cannot be used as an indexed type."); } function visitNumber(): string { // number[T] is an error throw new Error("Number cannot be used as an indexed type."); } function visitUndefined(): string { // undefined[T] is an error throw new Error("Undefined cannot be used as an indexed type."); } function visitNull(): string { // null[T] is an error throw new Error("Null cannot be used as an indexed type."); } function visitNever(visitorContext: VisitorContext) { // never[T] = never return VisitorUtils.getNeverFunction(visitorContext); } function visitUnknown(visitorContext: VisitorContext) { // unknown[T] = unknown return VisitorUtils.getUnknownFunction(visitorContext); } function visitAny(visitorContext: VisitorContext) { // any[T] = any return VisitorUtils.getAnyFunction(visitorContext); } export function visitType( type: ts.Type, indexType: 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(); } 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, indexType, visitorContext); } else if ((ts.TypeFlags.TypeParameter & type.flags) !== 0) { // Type parameter return visitTypeParameter(type, indexType, visitorContext); } else if (tsutils.isObjectType(type)) { // Object type (including interfaces, arrays, tuples) return visitObjectType(type, indexType, visitorContext); } else if (tsutils.isLiteralType(type)) { // Literal string/number types ('foo') return visitLiteralType(); } else if (tsutils.isUnionOrIntersectionType(type)) { // Union or intersection type (| or &) return visitUnionOrIntersectionType(type, indexType, 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}`, ); } }