ts-simple-type
Version:
Relationship type checker functions for Typescript types.
1,259 lines (1,244 loc) • 94.3 kB
JavaScript
'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