UNPKG

ts-simple-type

Version:

Relationship type checker functions for Typescript types.

1,259 lines (1,244 loc) 94.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tsModule = require('typescript'); require('util'); // Collect all values on place. This is a map so Typescript will complain if we forget any kind. const SIMPLE_TYPE_MAP = { NUMBER_LITERAL: "primitive_literal", STRING_LITERAL: "primitive_literal", BIG_INT_LITERAL: "primitive_literal", BOOLEAN_LITERAL: "primitive_literal", ES_SYMBOL_UNIQUE: "primitive_literal", BIG_INT: "primitive", BOOLEAN: "primitive", NULL: "primitive", UNDEFINED: "primitive", VOID: "primitive", ES_SYMBOL: "primitive", NUMBER: "primitive", STRING: "primitive", NON_PRIMITIVE: undefined, ENUM_MEMBER: undefined, ALIAS: undefined, ANY: undefined, ARRAY: undefined, CLASS: undefined, DATE: undefined, ENUM: undefined, FUNCTION: undefined, GENERIC_ARGUMENTS: undefined, GENERIC_PARAMETER: undefined, INTERFACE: undefined, INTERSECTION: undefined, METHOD: undefined, NEVER: undefined, OBJECT: undefined, PROMISE: undefined, TUPLE: undefined, UNION: undefined, UNKNOWN: undefined }; const LITERAL_TYPE_KINDS = Object.keys(SIMPLE_TYPE_MAP).filter(kind => SIMPLE_TYPE_MAP[kind] === "primitive_literal"); function isSimpleTypeLiteral(type) { return LITERAL_TYPE_KINDS.includes(type.kind); } const PRIMITIVE_TYPE_KINDS = [...LITERAL_TYPE_KINDS, ...Object.keys(SIMPLE_TYPE_MAP).filter(kind => SIMPLE_TYPE_MAP[kind] === "primitive")]; function isSimpleTypePrimitive(type) { return PRIMITIVE_TYPE_KINDS.includes(type.kind); } // All kinds const SIMPLE_TYPE_KINDS = Object.keys(SIMPLE_TYPE_MAP); function isSimpleType(type) { return typeof type === "object" && type != null && "kind" in type && Object.values(SIMPLE_TYPE_KINDS).find((key) => key === type.kind) != null; } let selectedTSModule = tsModule; function setTypescriptModule(ts) { selectedTSModule = ts; } function getTypescriptModule() { return selectedTSModule; } function or(list, match) { return list.find((a, i) => match(a, i)) != null; } function and(list, match) { return list.find((a, i) => !match(a, i)) == null; } function isTypeChecker(obj) { return obj != null && typeof obj === "object" && "getSymbolAtLocation" in obj; } function isProgram(obj) { return obj != null && typeof obj === "object" && "getTypeChecker" in obj && "getCompilerOptions" in obj; } function isNode(obj) { return obj != null && typeof obj === "object" && "kind" in obj && "flags" in obj && "pos" in obj && "end" in obj; } function typeHasFlag(type, flag, op = "and") { return hasFlag(type.flags, flag, op); } function hasFlag(flags, flag, op = "and") { if (Array.isArray(flag)) { return (op === "and" ? and : or)(flag, f => hasFlag(flags, f)); } return (flags & flag) !== 0; } function isBoolean(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.BooleanLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Boolean"; } function isBooleanLiteral(type, ts) { return typeHasFlag(type, ts.TypeFlags.BooleanLiteral); } function isBigIntLiteral(type, ts) { return typeHasFlag(type, ts.TypeFlags.BigIntLiteral); } function isUniqueESSymbol(type, ts) { return typeHasFlag(type, ts.TypeFlags.UniqueESSymbol); } function isESSymbolLike(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.ESSymbolLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Symbol"; } function isLiteral(type, ts) { return type.isLiteral() || isBooleanLiteral(type, ts) || isBigIntLiteral(type, ts) || isUniqueESSymbol(type, ts); } function isString(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.StringLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "String"; } function isNumber(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.NumberLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Number"; } function isEnum(type, ts) { return typeHasFlag(type, ts.TypeFlags.EnumLike); } function isBigInt(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.BigIntLike) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "BigInt"; } function isObject(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.Object) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "Object"; } function isNonPrimitive(type, ts) { var _a; return typeHasFlag(type, ts.TypeFlags.NonPrimitive) || ((_a = type.symbol) === null || _a === void 0 ? void 0 : _a.name) === "object"; } function isThisType(type, ts) { var _a, _b; const kind = (_b = (_a = type.getSymbol()) === null || _a === void 0 ? void 0 : _a.valueDeclaration) === null || _b === void 0 ? void 0 : _b.kind; if (kind == null) { return false; } return hasFlag(kind, ts.SyntaxKind.ThisKeyword); } function isUnknown(type, ts) { return typeHasFlag(type, ts.TypeFlags.Unknown); } function isNull(type, ts) { return typeHasFlag(type, ts.TypeFlags.Null); } function isUndefined(type, ts) { return typeHasFlag(type, ts.TypeFlags.Undefined); } function isVoid(type, ts) { return typeHasFlag(type, ts.TypeFlags.VoidLike); } function isNever(type, ts) { return typeHasFlag(type, ts.TypeFlags.Never); } function isObjectTypeReference(type, ts) { return hasFlag(type.objectFlags, ts.ObjectFlags.Reference); } function isSymbol(obj) { return "flags" in obj && "name" in obj && "getDeclarations" in obj; } function isMethod(type, ts) { if (!isObject(type, ts)) return false; const symbol = type.getSymbol(); if (symbol == null) return false; return hasFlag(symbol.flags, ts.SymbolFlags.Method); } function getDeclaration(symbol, ts) { const declarations = symbol.getDeclarations(); if (declarations == null || declarations.length === 0) return symbol.valueDeclaration; return declarations[0]; } function isArray(type, checker, ts) { if (!isObject(type, ts)) return false; const symbol = type.getSymbol(); if (symbol == null) return false; return getTypeArguments(type, checker, ts).length === 1 && ["ArrayLike", "ReadonlyArray", "ConcatArray", "Array"].includes(symbol.getName()); } function isPromise(type, checker, ts) { if (!isObject(type, ts)) return false; const symbol = type.getSymbol(); if (symbol == null) return false; return getTypeArguments(type, checker, ts).length === 1 && ["PromiseLike", "Promise"].includes(symbol.getName()); } function isDate(type, ts) { if (!isObject(type, ts)) return false; const symbol = type.getSymbol(); if (symbol == null) return false; return symbol.getName() === "Date"; } function isTupleTypeReference(type, ts) { const target = getTargetType(type, ts); if (target == null) return false; return (target.objectFlags & ts.ObjectFlags.Tuple) !== 0; } function isFunction(type, ts) { if (!isObject(type, ts)) return false; const symbol = type.getSymbol(); if (symbol == null) return false; return (symbol.flags & ts.SymbolFlags.Function) !== 0 || symbol.escapedName === "Function" || (symbol.members != null && symbol.members.has("__call")); } function getTypeArguments(type, checker, ts) { if (isObject(type, ts)) { if (isObjectTypeReference(type, ts)) { if ("getTypeArguments" in checker) { return Array.from(checker.getTypeArguments(type) || []); } else { return Array.from(type.typeArguments || []); } } } return []; } function getTargetType(type, ts) { if (isObject(type, ts) && isObjectTypeReference(type, ts)) { return type.target; } } function getModifiersFromDeclaration(declaration, ts) { const tsModifiers = ts.getCombinedModifierFlags(declaration); const modifiers = []; const map = { [ts.ModifierFlags.Export]: "EXPORT", [ts.ModifierFlags.Ambient]: "AMBIENT", [ts.ModifierFlags.Public]: "PUBLIC", [ts.ModifierFlags.Private]: "PRIVATE", [ts.ModifierFlags.Protected]: "PROTECTED", [ts.ModifierFlags.Static]: "STATIC", [ts.ModifierFlags.Readonly]: "READONLY", [ts.ModifierFlags.Abstract]: "ABSTRACT", [ts.ModifierFlags.Async]: "ASYNC", [ts.ModifierFlags.Default]: "DEFAULT" }; Object.entries(map).forEach(([tsModifier, modifierKind]) => { if ((tsModifiers & Number(tsModifier)) !== 0) { modifiers.push(modifierKind); } }); return modifiers; } function isImplicitGeneric(type, checker, ts) { return isArray(type, checker, ts) || isTupleTypeReference(type, ts) || isPromise(type, checker, ts); } function isMethodSignature(type, ts) { const symbol = type.getSymbol(); if (symbol == null) return false; if (!isObject(type, ts)) return false; if (type.getCallSignatures().length === 0) return false; const decl = getDeclaration(symbol); if (decl == null) return false; return decl.kind === ts.SyntaxKind.MethodSignature; } const DEFAULT_TYPE_CACHE = new WeakMap(); const DEFAULT_RESULT_CACHE = new Map(); const DEFAULT_GENERIC_PARAMETER_TYPE = { kind: "UNKNOWN" }; const NEVER_TYPE = { kind: "NEVER" }; function resolveType(simpleType, parameterMap = new Map()) { switch (simpleType.kind) { case "GENERIC_PARAMETER": { const resolvedArgument = parameterMap === null || parameterMap === void 0 ? void 0 : parameterMap.get(simpleType.name); return resolveType(resolvedArgument || /*simpleType.default ||*/ DEFAULT_GENERIC_PARAMETER_TYPE, parameterMap); } case "GENERIC_ARGUMENTS": { const updatedGenericParameterMap = extendTypeParameterMap(simpleType, parameterMap); return resolveType(simpleType.target, updatedGenericParameterMap); } default: return simpleType; } } /** * Returns a type that represents the length of the Tuple type * Read more here: https://github.com/microsoft/TypeScript/pull/24897 * @param tuple */ function getTupleLengthType(tuple) { // When the tuple has rest argument, return "number" if (tuple.rest) { return { kind: "NUMBER" }; } // Else return an intersection of number literals that represents all possible lengths const minLength = tuple.members.filter(member => !member.optional).length; if (minLength === tuple.members.length) { return { kind: "NUMBER_LITERAL", value: minLength }; } return { kind: "UNION", types: new Array(tuple.members.length - minLength + 1).fill(0).map((_, i) => ({ kind: "NUMBER_LITERAL", value: minLength + i })) }; } function simplifySimpleTypes(types) { let newTypes = [...types]; const NULLABLE_TYPE_KINDS = ["UNDEFINED", "NULL"]; // Only include one instance of primitives and literals newTypes = newTypes.filter((type, i) => { // Only include one of each literal with specific value if (isSimpleTypeLiteral(type)) { return !newTypes.slice(0, i).some(newType => newType.kind === type.kind && newType.value === type.value); } if (PRIMITIVE_TYPE_KINDS.includes(type.kind) || NULLABLE_TYPE_KINDS.includes(type.kind)) { // Remove this type from the array if there is already a primitive in the array return !newTypes.slice(0, i).some(t => t.kind === type.kind); } return true; }); // Simplify boolean literals const booleanLiteralTypes = newTypes.filter((t) => t.kind === "BOOLEAN_LITERAL"); if (booleanLiteralTypes.find(t => t.value === true) != null && booleanLiteralTypes.find(t => t.value === false) != null) { newTypes = [...newTypes.filter(type => type.kind !== "BOOLEAN_LITERAL"), { kind: "BOOLEAN" }]; } // Reorder "NULL" and "UNDEFINED" to be last const nullableTypes = newTypes.filter((t) => NULLABLE_TYPE_KINDS.includes(t.kind)); if (nullableTypes.length > 0) { newTypes = [ ...newTypes.filter(t => !NULLABLE_TYPE_KINDS.includes(t.kind)), ...nullableTypes.sort((t1, t2) => (t1.kind === "NULL" ? (t2.kind === "UNDEFINED" ? -1 : 0) : t2.kind === "NULL" ? 1 : 0)) ]; } return newTypes; } function extendTypeParameterMap(genericType, existingMap) { const target = resolveType(genericType.target, existingMap); if ("typeParameters" in target) { const parameterEntries = (target.typeParameters || []).map((parameter, i) => { const typeArg = genericType.typeArguments[i]; const resolvedTypeArg = typeArg == null ? /*parameter.default || */ DEFAULT_GENERIC_PARAMETER_TYPE : resolveType(typeArg, existingMap); //return [parameter.name, genericType.typeArguments[i] || parameter.default || { kind: "ANY" }] as [string, SimpleType]; return [parameter.name, resolvedTypeArg]; }); const allParameterEntries = [...existingMap.entries(), ...parameterEntries]; return new Map(allParameterEntries); } return existingMap; } function toSimpleType(type, checker, options = {}) { if (isSimpleType(type)) { return type; } checker = checker; if (isNode(type)) { // "type" is a "Node", convert it to a "Type" and continue. return toSimpleType(checker.getTypeAtLocation(type), checker); } return toSimpleTypeCached(type, { checker, eager: options.eager, cache: options.cache || DEFAULT_TYPE_CACHE, ts: getTypescriptModule() }); } function toSimpleTypeCached(type, options) { if (options.cache.has(type)) { return options.cache.get(type); } // This function will resolve the type and assign the content to "target". // This way we can cache "target" before calling "toSimpleTypeInternal" recursively const resolveType = (target) => { // Construct the simple type recursively //const simpleTypeOverwrite = options.cache.has(type) ? options.cache.get(type)! : toSimpleTypeInternal(type, options); const simpleTypeOverwrite = toSimpleTypeInternal(type, options); // Strip undefined keys to make the output cleaner Object.entries(simpleTypeOverwrite).forEach(([k, v]) => { if (v == null) delete simpleTypeOverwrite[k]; }); // Transfer properties on the simpleType to the placeholder // This makes it possible to keep on using the reference "placeholder". Object.assign(target, simpleTypeOverwrite); }; if (options.eager === true) { // Make and cache placeholder const placeholder = {}; options.cache.set(type, placeholder); // Resolve type into placeholder resolveType(placeholder); Object.freeze(placeholder); return placeholder; } else { const placeholder = {}; // A function that only resolves the type once let didResolve = false; const ensureResolved = () => { if (!didResolve) { resolveType(placeholder); didResolve = true; } }; // Use "toStringTag" as a hook into resolving the type. // If we don't have this hook, console.log would always print "{}" because the type hasn't been resolved Object.defineProperty(placeholder, Symbol.toStringTag, { get() { resolveType(placeholder); // Don't return any tag. Only use this function as a hook for calling "resolveType" return undefined; } }); // Return a proxy with the purpose of resolving the type lazy const proxy = new Proxy(placeholder, { ownKeys(target) { ensureResolved(); return [...Object.getOwnPropertyNames(target), ...Object.getOwnPropertySymbols(target)]; }, has(target, p) { // Always return true if we test for "kind", but don't resolve the type // This way "isSimpleType" (which checks for "kind") will succeed without resolving the type if (p === "kind") { return true; } ensureResolved(); return p in target; }, getOwnPropertyDescriptor(target, p) { ensureResolved(); return Object.getOwnPropertyDescriptor(target, p); }, get: (target, p) => { ensureResolved(); return target[p]; }, set: (target, p) => { throw new TypeError(`Cannot assign to read only property '${p}'`); } }); options.cache.set(type, proxy); return proxy; } } /** * Tries to lift a potential generic type and wrap the result in a "GENERIC_ARGUMENTS" simple type and/or "ALIAS" type. * Returns the "simpleType" otherwise. * @param simpleType * @param type * @param options */ function liftGenericType(type, options) { // Check for alias reference if (type.aliasSymbol != null) { const aliasDeclaration = getDeclaration(type.aliasSymbol, options.ts); const typeParameters = getTypeParameters(aliasDeclaration, options); return { target: type, generic: target => { // Lift the simple type to an ALIAS type. const aliasType = { kind: "ALIAS", name: type.aliasSymbol.getName() || "", target, typeParameters }; // Lift the alias type if it uses generic arguments. if (type.aliasTypeArguments != null) { const typeArguments = Array.from(type.aliasTypeArguments || []).map(t => toSimpleTypeCached(t, options)); return { kind: "GENERIC_ARGUMENTS", target: aliasType, typeArguments }; } return target; } }; } // Check if the type is a generic interface/class reference and lift it. else if (isObject(type, options.ts) && isObjectTypeReference(type, options.ts) && type.typeArguments != null && type.typeArguments.length > 0) { // Special case for array, tuple and promise, they are generic in themselves if (isImplicitGeneric(type, options.checker, options.ts)) { return undefined; } return { target: type.target, generic: target => { const typeArguments = Array.from(type.typeArguments || []).map(t => toSimpleTypeCached(t, options)); return { kind: "GENERIC_ARGUMENTS", target, typeArguments }; } }; } return undefined; } function toSimpleTypeInternal(type, options) { const { checker, ts } = options; const symbol = type.getSymbol(); const name = symbol != null ? getRealSymbolName(symbol, ts) : undefined; let simpleType; const generic = liftGenericType(type, options); if (generic != null) { type = generic.target; } if (isLiteral(type, ts)) { const literalSimpleType = primitiveLiteralToSimpleType(type, checker, ts); if (literalSimpleType != null) { // Enum members if (symbol != null && symbol.flags & ts.SymbolFlags.EnumMember) { const parentSymbol = symbol.parent; if (parentSymbol != null) { return { name: name || "", fullName: `${parentSymbol.name}.${name}`, kind: "ENUM_MEMBER", type: literalSimpleType }; } } // Literals types return literalSimpleType; } } // Primitive types else if (isString(type, ts)) { simpleType = { kind: "STRING", name }; } else if (isNumber(type, ts)) { simpleType = { kind: "NUMBER", name }; } else if (isBoolean(type, ts)) { simpleType = { kind: "BOOLEAN", name }; } else if (isBigInt(type, ts)) { simpleType = { kind: "BIG_INT", name }; } else if (isESSymbolLike(type, ts)) { simpleType = { kind: "ES_SYMBOL", name }; } else if (isUndefined(type, ts)) { simpleType = { kind: "UNDEFINED", name }; } else if (isNull(type, ts)) { simpleType = { kind: "NULL", name }; } else if (isUnknown(type, ts)) { simpleType = { kind: "UNKNOWN", name }; } else if (isVoid(type, ts)) { simpleType = { kind: "VOID", name }; } else if (isNever(type, ts)) { simpleType = { kind: "NEVER", name }; } // Enum else if (isEnum(type, ts) && type.isUnion()) { simpleType = { name: name || "", kind: "ENUM", types: type.types.map(t => toSimpleTypeCached(t, options)) }; } // Promise else if (isPromise(type, checker, ts)) { simpleType = { kind: "PROMISE", name, type: toSimpleTypeCached(getTypeArguments(type, checker, ts)[0], options) }; } // Unions and intersections else if (type.isUnion()) { simpleType = { kind: "UNION", types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))), name }; } else if (type.isIntersection()) { simpleType = { kind: "INTERSECTION", types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))), name }; } // Date else if (isDate(type, ts)) { simpleType = { kind: "DATE", name }; } // Array else if (isArray(type, checker, ts)) { simpleType = { kind: "ARRAY", type: toSimpleTypeCached(getTypeArguments(type, checker, ts)[0], options), name }; } else if (isTupleTypeReference(type, ts)) { const types = getTypeArguments(type, checker, ts); const minLength = type.target.minLength; simpleType = { kind: "TUPLE", rest: type.target.hasRestElement || false, members: types.map((childType, i) => { return { optional: i >= minLength, type: toSimpleTypeCached(childType, options) }; }), name }; } // Method signatures else if (isMethodSignature(type, ts)) { const callSignatures = type.getCallSignatures(); simpleType = getSimpleFunctionFromCallSignatures(callSignatures, options); } // Class else if (type.isClass() && symbol != null) { const classDecl = getDeclaration(symbol); if (classDecl != null && ts.isClassDeclaration(classDecl)) { const ctor = (() => { const ctorSymbol = symbol != null && symbol.members != null ? symbol.members.get("__constructor") : undefined; if (ctorSymbol != null && symbol != null) { const ctorDecl = ctorSymbol.declarations.length > 0 ? ctorSymbol.declarations[0] : ctorSymbol.valueDeclaration; if (ctorDecl != null && ts.isConstructorDeclaration(ctorDecl)) { return getSimpleFunctionFromSignatureDeclaration(ctorDecl, options); } } })(); const call = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options); const members = checker .getPropertiesOfType(type) .map(symbol => { const declaration = getDeclaration(symbol); // Some instance properties may have an undefined declaration. // Since we can't do too much without a declaration, filtering // these out seems like the best strategy for the moment. // // See https://github.com/runem/web-component-analyzer/issues/60 for // more info. if (declaration == null) return null; return { name: symbol.name, optional: (symbol.flags & ts.SymbolFlags.Optional) !== 0, modifiers: getModifiersFromDeclaration(declaration, ts), type: toSimpleTypeCached(checker.getTypeAtLocation(declaration), options) }; }) .filter((member) => member != null); const typeParameters = getTypeParameters(getDeclaration(symbol), options); simpleType = { kind: "CLASS", name, call, ctor, typeParameters, members }; } } // Interface else if ((type.isClassOrInterface() || isObject(type, ts)) && !((symbol === null || symbol === void 0 ? void 0 : symbol.name) === "Function")) { // Handle the empty object if (isObject(type, ts) && (symbol === null || symbol === void 0 ? void 0 : symbol.name) === "Object") { return { kind: "OBJECT" }; } const members = type.getProperties().map(symbol => { const declaration = getDeclaration(symbol); return { name: symbol.name, optional: (symbol.flags & ts.SymbolFlags.Optional) !== 0, modifiers: declaration != null ? getModifiersFromDeclaration(declaration, ts) : [], type: toSimpleTypeCached(checker.getTypeAtLocation(symbol.valueDeclaration), options) }; }); const ctor = getSimpleFunctionFromCallSignatures(type.getConstructSignatures(), options); const call = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options); const typeParameters = (type.isClassOrInterface() && type.typeParameters != null ? type.typeParameters.map(t => toSimpleTypeCached(t, options)) : undefined) || (symbol != null ? getTypeParameters(getDeclaration(symbol), options) : undefined); let indexType = {}; if (type.getStringIndexType()) { indexType["STRING"] = toSimpleTypeCached(type.getStringIndexType(), options); } if (type.getNumberIndexType()) { indexType["NUMBER"] = toSimpleTypeCached(type.getNumberIndexType(), options); } if (Object.keys(indexType).length === 0) { indexType = undefined; } // Simplify: if there is only a single "call" signature and nothing else, just return the call signature /*if (call != null && members.length === 0 && ctor == null && indexType == null) { return { ...call, name, typeParameters }; }*/ simpleType = { kind: type.isClassOrInterface() ? "INTERFACE" : "OBJECT", typeParameters, ctor, members, name, indexType, call }; } // Handle "object" type else if (isNonPrimitive(type, ts)) { return { kind: "NON_PRIMITIVE" }; } // Function else if (symbol != null && (isFunction(type, ts) || isMethod(type, ts))) { simpleType = getSimpleFunctionFromCallSignatures(type.getCallSignatures(), options, name); if (simpleType == null) { simpleType = { kind: "FUNCTION", name }; } } // Type Parameter else if (type.isTypeParameter() && symbol != null) { // This type if (isThisType(type, ts) && symbol.valueDeclaration != null) { return toSimpleTypeCached(checker.getTypeAtLocation(symbol.valueDeclaration), options); } const defaultType = type.getDefault(); const defaultSimpleType = defaultType != null ? toSimpleTypeCached(defaultType, options) : undefined; simpleType = { kind: "GENERIC_PARAMETER", name: symbol.getName(), default: defaultSimpleType }; } // If no type was found, return "ANY" if (simpleType == null) { simpleType = { kind: "ANY", name }; } // Lift generic types and aliases if possible if (generic != null) { return generic.generic(simpleType); } return simpleType; } function primitiveLiteralToSimpleType(type, checker, ts) { if (type.isNumberLiteral()) { return { kind: "NUMBER_LITERAL", value: type.value }; } else if (type.isStringLiteral()) { return { kind: "STRING_LITERAL", value: type.value }; } else if (isBooleanLiteral(type, ts)) { // See https://github.com/Microsoft/TypeScript/issues/22269 for more information return { kind: "BOOLEAN_LITERAL", value: checker.typeToString(type) === "true" }; } else if (isBigIntLiteral(type, ts)) { return { kind: "BIG_INT_LITERAL", /* global BigInt */ value: BigInt(`${type.value.negative ? "-" : ""}${type.value.base10Value}`) }; } else if (isUniqueESSymbol(type, ts)) { return { kind: "ES_SYMBOL_UNIQUE", value: String(type.escapedName) || Math.floor(Math.random() * 100000000).toString() }; } } function getSimpleFunctionFromCallSignatures(signatures, options, fallbackName) { if (signatures.length === 0) { return undefined; } const signature = signatures[signatures.length - 1]; const signatureDeclaration = signature.getDeclaration(); return getSimpleFunctionFromSignatureDeclaration(signatureDeclaration, options, fallbackName); } function getSimpleFunctionFromSignatureDeclaration(signatureDeclaration, options, fallbackName) { const { checker } = options; const symbol = checker.getSymbolAtLocation(signatureDeclaration); const parameters = signatureDeclaration.parameters.map(parameterDecl => { const argType = checker.getTypeAtLocation(parameterDecl); return { name: parameterDecl.name.getText() || fallbackName, optional: parameterDecl.questionToken != null, type: toSimpleTypeCached(argType, options), rest: parameterDecl.dotDotDotToken != null, initializer: parameterDecl.initializer != null }; }); const name = symbol != null ? symbol.getName() : undefined; const type = checker.getTypeAtLocation(signatureDeclaration); const kind = isMethod(type, options.ts) ? "METHOD" : "FUNCTION"; const signature = checker.getSignatureFromDeclaration(signatureDeclaration); const returnType = signature == null ? undefined : toSimpleTypeCached(checker.getReturnTypeOfSignature(signature), options); const typeParameters = getTypeParameters(signatureDeclaration, options); return { name, kind, returnType, parameters, typeParameters }; } function getRealSymbolName(symbol, ts) { const name = symbol.getName(); if (name != null && [ts.InternalSymbolName.Type, ts.InternalSymbolName.Object, ts.InternalSymbolName.Function].includes(name)) { return undefined; } return name; } function getTypeParameters(obj, options) { if (obj == null) return undefined; if (isSymbol(obj)) { const decl = getDeclaration(obj, options.ts); return getTypeParameters(decl, options); } if (options.ts.isClassDeclaration(obj) || options.ts.isFunctionDeclaration(obj) || options.ts.isFunctionTypeNode(obj) || options.ts.isTypeAliasDeclaration(obj) || options.ts.isMethodDeclaration(obj) || options.ts.isMethodSignature(obj)) { return obj.typeParameters == null ? undefined : Array.from(obj.typeParameters) .map(td => options.checker.getTypeAtLocation(td)) .map(t => toSimpleTypeCached(t, options)); } return undefined; } function validateType(type, callback) { return validateTypeInternal(type, callback, new Map()); } function validateTypeInternal(type, callback, parameterMap) { const res = callback(type); if (res != null) { return res; } switch (type.kind) { case "ENUM": case "UNION": { return or(type.types, childType => validateTypeInternal(childType, callback, parameterMap)); } case "ALIAS": { return validateTypeInternal(type.target, callback, parameterMap); } case "INTERSECTION": { return and(type.types, childType => validateTypeInternal(childType, callback, parameterMap)); } case "GENERIC_PARAMETER": { const resolvedArgument = parameterMap === null || parameterMap === void 0 ? void 0 : parameterMap.get(type.name); return validateTypeInternal(resolvedArgument || DEFAULT_GENERIC_PARAMETER_TYPE, callback, parameterMap); } case "GENERIC_ARGUMENTS": { const updatedGenericParameterMap = extendTypeParameterMap(type, parameterMap); return validateTypeInternal(type.target, callback, updatedGenericParameterMap); } } return false; } function isAssignableToSimpleTypeKind(type, kind, optionsOrChecker, options = {}) { const checker = isTypeChecker(optionsOrChecker) ? optionsOrChecker : undefined; options = (isTypeChecker(optionsOrChecker) || optionsOrChecker == null ? options : optionsOrChecker) || {}; if (!isSimpleType(type)) { return isAssignableToSimpleTypeKind(toSimpleType(type, checker), kind, options); } return validateType(type, simpleType => { if (Array.isArray(kind) && or(kind, itemKind => simpleType.kind === itemKind)) { return true; } if (simpleType.kind === kind) { return true; } switch (simpleType.kind) { // Make sure that an object without members are treated as ANY case "OBJECT": { if (simpleType.members == null || simpleType.members.length === 0) { return isAssignableToSimpleTypeKind({ kind: "ANY" }, kind, options); } break; } case "ANY": { return options.matchAny || false; } case "ENUM_MEMBER": { return isAssignableToSimpleTypeKind(simpleType.type, kind, options); } } }); } function isAssignableToPrimitiveType(type, checkerOrOptions) { const checker = isTypeChecker(checkerOrOptions) ? checkerOrOptions : undefined; return isAssignableToSimpleTypeKind(type, PRIMITIVE_TYPE_KINDS, checker, { matchAny: true }); } /** * Converts a simple type to a string. * @param type Simple Type */ function simpleTypeToString(type) { return simpleTypeToStringInternal(type, new Set()); } function simpleTypeToStringInternal(type, visitTypeSet) { if (!isSimpleTypePrimitive(type)) { if (visitTypeSet.has(type)) { return ""; } visitTypeSet = new Set([...visitTypeSet, type]); } switch (type.kind) { case "BOOLEAN_LITERAL": return String(type.value); case "NUMBER_LITERAL": return String(type.value); case "STRING_LITERAL": return `"${type.value}"`; case "BIG_INT_LITERAL": return `${type.value}n`; case "ES_SYMBOL": return `Symbol()`; case "ES_SYMBOL_UNIQUE": return `Symbol(${type.name})`; case "STRING": return "string"; case "BOOLEAN": return "boolean"; case "NUMBER": return "number"; case "BIG_INT": return "bigint"; case "UNDEFINED": return "undefined"; case "NULL": return "null"; case "ANY": return "any"; case "UNKNOWN": return "unknown"; case "VOID": return "void"; case "NEVER": return "never"; case "FUNCTION": case "METHOD": { if (type.kind === "FUNCTION" && type.name != null) return type.name; const argText = functionArgTypesToString(type.parameters || [], visitTypeSet); return `${type.typeParameters != null ? `<${type.typeParameters.map(tp => tp.name).join(",")}>` : ""}(${argText})${type.returnType != null ? ` => ${simpleTypeToStringInternal(type.returnType, visitTypeSet)}` : ""}`; } case "ARRAY": { const hasMultipleTypes = ["UNION", "INTERSECTION"].includes(type.type.kind); let memberType = simpleTypeToStringInternal(type.type, visitTypeSet); if (type.name != null && ["ArrayLike", "ReadonlyArray"].includes(type.name)) return `${type.name}<${memberType}>`; if (hasMultipleTypes && type.type.name == null) memberType = `(${memberType})`; return `${memberType}[]`; } case "UNION": { if (type.name != null) return type.name; return truncateAndJoinList(type.types.map(t => simpleTypeToStringInternal(t, visitTypeSet)), " | ", { maxContentLength: 200 }); } case "ENUM": return type.name; case "ENUM_MEMBER": return type.fullName; case "INTERSECTION": if (type.name != null) return type.name; return truncateAndJoinList(type.types.map(t => simpleTypeToStringInternal(t, visitTypeSet)), " & ", { maxContentLength: 200 }); case "INTERFACE": if (type.name != null) return type.name; // this fallthrough is intentional case "OBJECT": { if (type.members == null || type.members.length === 0) { if (type.call == null && type.ctor == null) { return "{}"; } if (type.call != null && type.ctor == null) { return simpleTypeToStringInternal(type.call, visitTypeSet); } } const entries = (type.members || []).map(member => { // this check needs to change in the future if (member.type.kind === "FUNCTION" || member.type.kind === "METHOD") { const result = simpleTypeToStringInternal(member.type, visitTypeSet); return `${member.name}${result.replace(" => ", ": ")}`; } return `${member.name}: ${simpleTypeToStringInternal(member.type, visitTypeSet)}`; }); if (type.ctor != null) { entries.push(`new${simpleTypeToStringInternal(type.ctor, visitTypeSet)}`); } if (type.call != null) { entries.push(simpleTypeToStringInternal(type.call, visitTypeSet)); } return `{ ${entries.join("; ")}${entries.length > 0 ? ";" : ""} }`; } case "TUPLE": return `[${type.members.map(member => `${simpleTypeToStringInternal(member.type, visitTypeSet)}${member.optional ? "?" : ""}`).join(", ")}]`; case "GENERIC_ARGUMENTS": { const { target, typeArguments } = type; return typeArguments.length === 0 ? target.name || "" : `${target.name}<${typeArguments.map(t => simpleTypeToStringInternal(t, visitTypeSet)).join(", ")}>`; } case "PROMISE": return `${type.name || "Promise"}<${simpleTypeToStringInternal(type.type, visitTypeSet)}>`; case "DATE": return "Date"; default: return type.name || ""; } } function truncateAndJoinList(items, combine, { maxLength, maxContentLength }) { const text = items.join(combine); // Truncate if too long let slice = 0; if (maxContentLength != null && text.length > maxContentLength) { let curLength = 0; for (const item of items) { curLength += item.length; slice++; if (curLength > maxContentLength) { break; } } } else if (maxLength != null && items.length > maxLength) { slice = maxLength; } if (slice !== 0) { return [...items.slice(0, slice), `... ${items.length - slice} more ...`].join(combine); } return text; } function functionArgTypesToString(argTypes, visitTypeSet) { return argTypes .map(arg => { return `${arg.rest ? "..." : ""}${arg.name}${arg.optional ? "?" : ""}: ${simpleTypeToStringInternal(arg.type, visitTypeSet)}`; }) .join(", "); } /** * Returns if typeB is assignable to typeA. * @param typeA Type A * @param typeB Type B * @param config */ function isAssignableToSimpleType(typeA, typeB, config) { var _a, _b, _c, _d, _e, _f, _g; const userCache = config === null || config === void 0 ? void 0 : config.cache; config = { ...config, cache: undefined, strict: (_a = config === null || config === void 0 ? void 0 : config.strict) !== null && _a !== void 0 ? _a : true, strictFunctionTypes: (_c = (_b = config === null || config === void 0 ? void 0 : config.strictFunctionTypes) !== null && _b !== void 0 ? _b : config === null || config === void 0 ? void 0 : config.strict) !== null && _c !== void 0 ? _c : true, strictNullChecks: (_e = (_d = config === null || config === void 0 ? void 0 : config.strictNullChecks) !== null && _d !== void 0 ? _d : config === null || config === void 0 ? void 0 : config.strict) !== null && _e !== void 0 ? _e : true, maxDepth: (_f = config === null || config === void 0 ? void 0 : config.maxDepth) !== null && _f !== void 0 ? _f : 50, maxOps: (_g = config === null || config === void 0 ? void 0 : config.maxOps) !== null && _g !== void 0 ? _g : 1000 }; const cacheKey = `${config.strict}:${config.strictFunctionTypes}:${config.strictNullChecks}`; const cache = DEFAULT_RESULT_CACHE.get(cacheKey) || new WeakMap(); DEFAULT_RESULT_CACHE.set(cacheKey, cache); return isAssignableToSimpleTypeCached(typeA, typeB, { config, operations: { value: 0 }, depth: 0, cache: userCache || cache, insideType: new Set(), comparingTypes: new Map(), genericParameterMapA: new Map(), genericParameterMapB: new Map(), preventCaching: () => { } }); } function isAssignableToSimpleTypeCached(typeA, typeB, options) { let typeACache = options.cache.get(typeA); let preventCaching = false; if (typeACache === null || typeACache === void 0 ? void 0 : typeACache.has(typeB)) { if (options.config.debug) { logDebug(options, "caching", `Found cache when comparing: ${simpleTypeToStringLazy(typeA)} (${typeA.kind}) and ${simpleTypeToStringLazy(typeB)} (${typeB.kind}). Cache content: ${typeACache.get(typeB)}`); } return typeACache.get(typeB); } // Call "isAssignableToSimpleTypeInternal" with a mutated options object const result = isAssignableToSimpleTypeInternal(typeA, typeB, { depth: options.depth, operations: options.operations, genericParameterMapA: options.genericParameterMapA, genericParameterMapB: options.genericParameterMapB, config: options.config, insideType: options.insideType, comparingTypes: options.comparingTypes, cache: options.cache, preventCaching: () => { options.preventCaching(); preventCaching = true; } }); if (!preventCaching) { /*if (options.config.debug) { logDebug( options, "caching", `Setting cache for comparison between ${simpleTypeToStringLazy(typeA)} (${typeA.kind}) and ${simpleTypeToStringLazy(typeB)} (${typeB.kind}). Result: ${result}` ); }*/ if (typeACache == null) { typeACache = new WeakMap(); options.cache.set(typeA, typeACache); } typeACache.set(typeB, result); } return result; } function isCacheableType(simpleType, options) { switch (simpleType.kind) { case "UNION": case "INTERSECTION": if (options.genericParameterMapA.size !== 0 || options.genericParameterMapB.size !== 0) { return false; } break; } return !("typeParameters" in simpleType) && !["GENERIC_ARGUMENTS", "GENERIC_PARAMETER", "PROMISE", "LAZY"].includes(simpleType.kind); } function isAssignableToSimpleTypeInternal(typeA, typeB, options) { // It's assumed that the "options" parameter is already an unique reference that is safe to mutate. // Mutate depth and "operations" options.depth = options.depth + 1; options.operations.value++; // Handle debugging nested calls to isAssignable if (options.config.debug === true) { logDebugHeader(typeA, typeB, options); } if (options.depth >= options.config.maxDepth || options.operations.value >= options.config.maxOps) { options.preventCaching(); return true; } // When comparing types S and T, the relationship in question is assumed to be true // for every directly or indirectly nested occurrence of the same S and the same T if (options.comparingTypes.has(typeA)) { if (options.comparingTypes.get(typeA).has(typeB)) { options.preventCaching(); if (options.config.debug) { logDebug(options, "comparing types", "Returns true because this relation is already being checking"); } return true; } } // We might need a better way of handling refs, but these check are good for now if (options.insideType.has(typeA) || options.insideType.has(typeB)) { if (options.config.debug) { logDebug(options, "inside type", `{${typeA.kind}, ${typeB.kind}} {typeA: ${options.insideType.has(typeA)}} {typeB: ${options.insideType.has(typeB)}} {insideTypeMap: ${Array.from(options.insideType.keys()) .map(t => simpleTypeToStringLazy(t)) .join()}}`); } options.preventCaching(); return true; } // Handle two types being equal // Types are not necessarily equal if they have typeParams because we still need to check the actual generic arguments if (isCacheableType(typeA, options) && isCacheableType(typeB, options)) { if (typeA === typeB) { if (options.config.debug) { logDebug(options, "equal", "The two types are equal!", typeA.kind, typeB.kind); } return true; } } else { options.preventCaching(); } // Make it possible to overwrite default behavior by running user defined logic for comparing types if (options.config.isAssignable != null) { const result = options.config.isAssignable(typeA, typeB, options.config); if (result != null) { //options.preventCaching(); return result; } } // Any and unknown. Everything is assignable to "ANY" and "UNKNOWN" if (typeA.kind === "UNKNOWN" || typeA.kind === "ANY") { return true; } // Mutate options and add this comparison to "comparingTypes". // Only do this if one of the types is not a primitive to save memory. if (!isSimpleTypePrimitive(typeA) && !isSimpleTypePrimitive(typeB)) { const comparingTypes = new Map(options.comparingTypes); if (comparingTypes.has(typeA)) { comparingTypes.get(typeA).add(typeB); } else { comparingTypes.set(typeA, new Set([typeB])); } options.comparingTypes = comparingTypes; } // ##################### // Expand typeB // ##################### switch (typeB.kind) { // [typeB] (expand) case "UNION": { // Some types seems to absorb other types when type checking a union (eg. 'unknown'). // Usually typescript will absorb those types for us, but not when working with generic parameters. // The following line needs to be improved. const types = typeB.types.filter(t => resolveType$1(t, options.genericParameterMapB) !== DEFAULT_GENERIC_PARAMETER_TYPE); return and(types, childTypeB => isAssignableToSimpleTypeCached(typeA, childTypeB, options)); } // [typeB] (expand) case "INTERSECTION": { // If we compare an intersection against an intersection, we need to compare from typeA and not typeB // Example: [string, number] & [string] === [string, number] & [string] if (typeA.kind === "INTERSECTION") { break; } const combined = reduceIntersectionIfPossible(typeB, options.genericParameterMapB); if (combined.kind === "NEVER") { if (options.config.debug) { logDebug(options, "intersection", `Combining types in intersection is impossible. Comparing with 'never' instead.`); } return isAssignableToSimp