ts-simple-type
Version:
Relationship type checker functions for Typescript types.
1,068 lines (1,052 loc) • 46.2 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var tsModuleType = require('typescript');
(function (SimpleTypeKind) {
SimpleTypeKind["STRING_LITERAL"] = "STRING_LITERAL";
SimpleTypeKind["NUMBER_LITERAL"] = "NUMBER_LITERAL";
SimpleTypeKind["BOOLEAN_LITERAL"] = "BOOLEAN_LITERAL";
SimpleTypeKind["BIG_INT_LITERAL"] = "BIG_INT_LITERAL";
SimpleTypeKind["STRING"] = "STRING";
SimpleTypeKind["NUMBER"] = "NUMBER";
SimpleTypeKind["BOOLEAN"] = "BOOLEAN";
SimpleTypeKind["BIG_INT"] = "BIG_INT";
SimpleTypeKind["NULL"] = "NULL";
SimpleTypeKind["UNDEFINED"] = "UNDEFINED";
SimpleTypeKind["ANY"] = "ANY";
SimpleTypeKind["UNKNOWN"] = "UNKNOWN";
SimpleTypeKind["VOID"] = "VOID";
SimpleTypeKind["UNION"] = "UNION";
SimpleTypeKind["ENUM"] = "ENUM";
SimpleTypeKind["ENUM_MEMBER"] = "ENUM_MEMBER";
SimpleTypeKind["INTERSECTION"] = "INTERSECTION";
SimpleTypeKind["TUPLE"] = "TUPLE";
SimpleTypeKind["INTERFACE"] = "INTERFACE";
SimpleTypeKind["OBJECT"] = "OBJECT";
SimpleTypeKind["FUNCTION"] = "FUNCTION";
SimpleTypeKind["METHOD"] = "METHOD";
SimpleTypeKind["CLASS"] = "CLASS";
SimpleTypeKind["CIRCULAR_TYPE_REF"] = "CIRCULAR_TYPE_REF";
SimpleTypeKind["GENERIC_ARGUMENTS"] = "GENERIC_ARGUMENTS";
SimpleTypeKind["GENERIC_PARAMETER"] = "GENERIC_PARAMETER";
SimpleTypeKind["ALIAS"] = "ALIAS";
SimpleTypeKind["DATE"] = "DATE";
SimpleTypeKind["ARRAY"] = "ARRAY";
SimpleTypeKind["PROMISE"] = "PROMISE";
})(exports.SimpleTypeKind || (exports.SimpleTypeKind = {}));
(function (SimpleTypeModifierKind) {
SimpleTypeModifierKind["EXPORT"] = "EXPORT";
SimpleTypeModifierKind["AMBIENT"] = "AMBIENT";
SimpleTypeModifierKind["PUBLIC"] = "PUBLIC";
SimpleTypeModifierKind["PRIVATE"] = "PRIVATE";
SimpleTypeModifierKind["PROTECTED"] = "PROTECTED";
SimpleTypeModifierKind["STATIC"] = "STATIC";
SimpleTypeModifierKind["READONLY"] = "READONLY";
SimpleTypeModifierKind["ABSTRACT"] = "ABSTRACT";
SimpleTypeModifierKind["ASYNC"] = "ASYNC";
SimpleTypeModifierKind["DEFAULT"] = "DEFAULT";
})(exports.SimpleTypeModifierKind || (exports.SimpleTypeModifierKind = {}));
function isSimpleType(type) {
return typeof type === "object" && "kind" in type && Object.keys(exports.SimpleTypeKind).find((key) => exports.SimpleTypeKind[key] === type.kind) != null;
}
const LITERAL_TYPE_KINDS = [exports.SimpleTypeKind.STRING_LITERAL, exports.SimpleTypeKind.NUMBER_LITERAL, exports.SimpleTypeKind.BOOLEAN_LITERAL, exports.SimpleTypeKind.BIG_INT_LITERAL];
function isSimpleTypeLiteral(type) {
return LITERAL_TYPE_KINDS.includes(type.kind);
}
const PRIMITIVE_TYPE_KINDS = [
...LITERAL_TYPE_KINDS,
exports.SimpleTypeKind.STRING,
exports.SimpleTypeKind.NUMBER,
exports.SimpleTypeKind.BOOLEAN,
exports.SimpleTypeKind.BIG_INT,
exports.SimpleTypeKind.NULL,
exports.SimpleTypeKind.UNDEFINED
];
function isSimpleTypePrimitive(type) {
return PRIMITIVE_TYPE_KINDS.includes(type.kind);
}
const PRIMITIVE_TYPE_TO_LITERAL_MAP = {
[exports.SimpleTypeKind.STRING]: exports.SimpleTypeKind.STRING_LITERAL,
[exports.SimpleTypeKind.NUMBER]: exports.SimpleTypeKind.NUMBER_LITERAL,
[exports.SimpleTypeKind.BOOLEAN]: exports.SimpleTypeKind.BOOLEAN_LITERAL,
[exports.SimpleTypeKind.BIG_INT]: exports.SimpleTypeKind.BIG_INT_LITERAL
};
const IMPLICIT_GENERIC = [exports.SimpleTypeKind.ARRAY, exports.SimpleTypeKind.TUPLE, exports.SimpleTypeKind.PROMISE];
function isImplicitGenericType(type) {
return IMPLICIT_GENERIC.includes(type.kind);
}
const tsModule = { ts: tsModuleType };
function setTypescriptModule(newModule) {
tsModule.ts = newModule;
}
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 isNode(obj) {
return obj != null && typeof obj === "object" && "kind" in obj && "flags" in obj && "pos" in obj && "end" in obj;
}
function hasFlag(type, flag, op = "and") {
if (Array.isArray(flag)) {
return (op === "and" ? and : or)(flag, f => hasFlag(type, f));
}
return (type.flags & flag) !== 0;
}
function isBoolean(type) {
return hasFlag(type, tsModule.ts.TypeFlags.BooleanLike);
}
function isBooleanLiteral(type) {
return hasFlag(type, tsModule.ts.TypeFlags.BooleanLiteral);
}
function isBigIntLiteral(type) {
return hasFlag(type, tsModule.ts.TypeFlags.BigIntLiteral);
}
function isLiteral(type) {
return type.isLiteral() || isBooleanLiteral(type) || isBigIntLiteral(type);
}
function isString(type) {
return hasFlag(type, tsModule.ts.TypeFlags.StringLike);
}
function isNumber(type) {
return hasFlag(type, tsModule.ts.TypeFlags.NumberLike);
}
function isEnum(type) {
return hasFlag(type, tsModule.ts.TypeFlags.EnumLike);
}
function isBigInt(type) {
return hasFlag(type, tsModule.ts.TypeFlags.BigIntLike);
}
function isObject(type) {
return hasFlag(type, tsModule.ts.TypeFlags.Object);
}
function isUnknown(type) {
return hasFlag(type, tsModule.ts.TypeFlags.Unknown);
}
function isNull(type) {
return hasFlag(type, tsModule.ts.TypeFlags.Null);
}
function isUndefined(type) {
return hasFlag(type, tsModule.ts.TypeFlags.Undefined);
}
function isVoid(type) {
return hasFlag(type, tsModule.ts.TypeFlags.VoidLike);
}
function isObjectTypeReference(type) {
return (type.objectFlags & tsModule.ts.ObjectFlags.Reference) !== 0;
}
function isMethod(type) {
if (!isObject(type))
return false;
const symbol = type.getSymbol();
if (symbol == null)
return false;
const decl = getDeclaration(symbol);
if (decl == null)
return false;
return tsModule.ts.isMethodDeclaration(decl);
}
function getDeclaration(symbol) {
// Note: Somehow "symbol.declarations" can be undefined under some circumstances
if (symbol.declarations == null)
return undefined;
return symbol.declarations.length > 0 ? symbol.declarations[0] : symbol.valueDeclaration;
}
function isArray(type) {
if (!isObject(type))
return false;
const symbol = type.getSymbol();
if (symbol == null)
return false;
return getTypeArguments(type).length === 1 && ["ReadonlyArray", "Array"].includes(symbol.getName());
//return symbol.getName() === "Array"; // && getTypeArguments(type).length === 1;
}
function isPromise(type) {
if (!isObject(type))
return false;
const symbol = type.getSymbol();
if (symbol == null)
return false;
return getTypeArguments(type).length === 1 && symbol.getName() === "Promise";
}
function isDate(type) {
if (!isObject(type))
return false;
const symbol = type.getSymbol();
if (symbol == null)
return false;
return symbol.getName() === "Date";
}
function isTuple(type) {
const target = getTargetType(type);
if (target == null)
return false;
return (target.objectFlags & tsModule.ts.ObjectFlags.Tuple) !== 0;
}
function isFunction(type) {
if (!isObject(type))
return false;
const symbol = type.getSymbol();
if (symbol == null)
return false;
return (symbol.flags & tsModule.ts.SymbolFlags.Function) !== 0 || (symbol.members != null && symbol.members.has("__call"));
}
function getTypeArguments(type) {
if (isObject(type)) {
if (isObjectTypeReference(type)) {
return Array.from(type.typeArguments || []);
}
}
return [];
}
function getTargetType(type) {
if (isObject(type) && isObjectTypeReference(type)) {
return type.target;
}
}
function getModifiersFromDeclaration(declaration) {
const tsModifiers = tsModule.ts.getCombinedModifierFlags(declaration);
const modifiers = [];
const map = {
[tsModule.ts.ModifierFlags.Export]: exports.SimpleTypeModifierKind.EXPORT,
[tsModule.ts.ModifierFlags.Ambient]: exports.SimpleTypeModifierKind.AMBIENT,
[tsModule.ts.ModifierFlags.Public]: exports.SimpleTypeModifierKind.PUBLIC,
[tsModule.ts.ModifierFlags.Private]: exports.SimpleTypeModifierKind.PRIVATE,
[tsModule.ts.ModifierFlags.Protected]: exports.SimpleTypeModifierKind.PROTECTED,
[tsModule.ts.ModifierFlags.Static]: exports.SimpleTypeModifierKind.STATIC,
[tsModule.ts.ModifierFlags.Readonly]: exports.SimpleTypeModifierKind.READONLY,
[tsModule.ts.ModifierFlags.Abstract]: exports.SimpleTypeModifierKind.ABSTRACT,
[tsModule.ts.ModifierFlags.Async]: exports.SimpleTypeModifierKind.ASYNC,
[tsModule.ts.ModifierFlags.Default]: exports.SimpleTypeModifierKind.DEFAULT
};
Object.entries(map).forEach(([tsModifier, modifierKind]) => {
if ((tsModifiers & Number(tsModifier)) !== 0) {
modifiers.push(modifierKind);
}
});
return modifiers;
}
function toSimpleType(type, checker, cache) {
if (isNode(type)) {
// "type" is a "Node", convert it to a "Type" and continue.
return toSimpleType(checker.getTypeAtLocation(type), checker);
}
return toSimpleTypeInternalCaching(type, {
checker,
circularCache: new WeakMap(),
cache: cache || new WeakMap()
});
}
function toSimpleTypeInternalCaching(type, options) {
const placeholder = {};
// Cache the result of the conversion to a SimpleType if the type doesn't refer to an alias and is not a type parameter.
// Here we make sure that all SimpleTypes refer to the same instance.
if (!options.cache.has(type)) {
if (type.aliasSymbol == null && type.aliasTypeArguments == null && !type.isTypeParameter() && !(isObject(type) && isObjectTypeReference(type))) {
options.cache.set(type, placeholder);
}
}
else {
return options.cache.get(type);
}
if (options.circularCache.has(type)) {
return {
kind: exports.SimpleTypeKind.CIRCULAR_TYPE_REF,
ref: options.circularCache.get(type)
};
}
else {
// Connect the type to the placeholder reference
// Circular types will point to this reference
// Only return a circular ref if it's a class of interface.
// Don't return circular ref if it's a primitive like a "number"
if (type.isClassOrInterface() || (isObject(type) && isObjectTypeReference(type))) {
options.circularCache.set(type, placeholder);
}
}
// Construct the simple type recursively
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(placeholder, simpleTypeOverwrite);
// Try to lift a potential generic type and wrap the result in a "GENERIC_ARGUMENTS" simple type and/or "ALIAS" type.
return liftGenericType(placeholder, type, options);
}
/**
* 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(simpleType, type, options) {
// Check for alias reference
if (type.aliasSymbol != null) {
const aliasDeclaration = getDeclaration(type.aliasSymbol);
const typeParameters = getTypeParameters(aliasDeclaration, options);
// Lift the simple type to an ALIAS type.
const aliasType = {
kind: exports.SimpleTypeKind.ALIAS,
name: type.aliasSymbol.getName(),
target: simpleType,
typeParameters
};
// Lift the alias type if it uses generic arguments.
if (type.aliasTypeArguments != null) {
const typeArguments = Array.from(type.aliasTypeArguments || []).map(t => toSimpleTypeInternalCaching(t, options));
return {
kind: exports.SimpleTypeKind.GENERIC_ARGUMENTS,
target: aliasType,
typeArguments
};
}
return aliasType;
}
// Check if the type is a generic interface/class reference and lift it.
else if (isObject(type) && isObjectTypeReference(type) && type.typeArguments != null) {
// Special case for array, tuple and promise, they are generic in themselves
if (isImplicitGenericType(simpleType)) {
return simpleType;
}
const typeArguments = Array.from(type.typeArguments || []).map(t => toSimpleTypeInternalCaching(t, options));
return {
kind: exports.SimpleTypeKind.GENERIC_ARGUMENTS,
target: simpleType,
typeArguments
};
}
return simpleType;
}
function toSimpleTypeInternal(type, options) {
const { checker } = options;
const symbol = type.getSymbol();
const name = symbol != null ? getRealSymbolName(symbol) : undefined;
if (isLiteral(type)) {
const literalSimpleType = literalToSimpleType(type, checker);
if (literalSimpleType != null) {
// Enum members
if (symbol != null && symbol.flags & tsModule.ts.SymbolFlags.EnumMember) {
const parentSymbol = symbol.parent;
if (parentSymbol != null) {
return {
name: name || "",
fullName: `${parentSymbol.name}.${name}`,
kind: exports.SimpleTypeKind.ENUM_MEMBER,
type: literalSimpleType
};
}
}
// Literals types
return literalSimpleType;
}
}
// Primitive types
else if (isString(type)) {
return { kind: exports.SimpleTypeKind.STRING, name };
}
else if (isNumber(type)) {
return { kind: exports.SimpleTypeKind.NUMBER, name };
}
else if (isBoolean(type)) {
return { kind: exports.SimpleTypeKind.BOOLEAN, name };
}
else if (isBigInt(type)) {
return { kind: exports.SimpleTypeKind.BIG_INT, name };
}
else if (isUndefined(type)) {
return { kind: exports.SimpleTypeKind.UNDEFINED, name };
}
else if (isNull(type)) {
return { kind: exports.SimpleTypeKind.NULL, name };
}
else if (isUnknown(type)) {
return { kind: exports.SimpleTypeKind.UNKNOWN, name };
}
else if (isVoid(type)) {
return { kind: exports.SimpleTypeKind.VOID, name };
}
// Enum
else if (isEnum(type) && type.isUnion()) {
return {
name: name || "",
kind: exports.SimpleTypeKind.ENUM,
types: type.types.map(t => toSimpleTypeInternalCaching(t, options))
};
}
// Promise
else if (isPromise(type)) {
return {
kind: exports.SimpleTypeKind.PROMISE,
name,
type: toSimpleTypeInternalCaching(getTypeArguments(type)[0], options)
};
}
// Unions and intersections
else if (type.isUnion()) {
return {
kind: exports.SimpleTypeKind.UNION,
types: simplifySimpleTypeArray(type.types.map(t => toSimpleTypeInternalCaching(t, options))),
name
};
}
else if (type.isIntersection()) {
return {
kind: exports.SimpleTypeKind.INTERSECTION,
types: simplifySimpleTypeArray(type.types.map(t => toSimpleTypeInternalCaching(t, options))),
name
};
}
// Date
else if (isDate(type)) {
return {
kind: exports.SimpleTypeKind.DATE,
name
};
}
// Array
else if (isArray(type)) {
return {
kind: exports.SimpleTypeKind.ARRAY,
type: toSimpleTypeInternalCaching(getTypeArguments(type)[0], options),
name
};
}
else if (isTuple(type)) {
const types = getTypeArguments(type);
return {
kind: exports.SimpleTypeKind.TUPLE,
members: types.map(childType => {
const childSymbol = childType.getSymbol();
return {
optional: childSymbol != null ? (childSymbol.flags & tsModule.ts.SymbolFlags.Optional) !== 0 : false,
type: toSimpleTypeInternalCaching(childType, options)
};
}),
name
};
}
// Function
else if (symbol != null && (isFunction(type) || isMethod(type))) {
const functionDeclaration = getDeclaration(symbol);
if (functionDeclaration != null) {
const simpleType = getSimpleFunctionFromDeclaration(functionDeclaration, options, true);
if (simpleType != null) {
simpleType.name = simpleType.name || name;
}
if (simpleType != null) {
return simpleType;
}
}
}
// Alternative way of getting functions
else if (isObject(type) && type.getCallSignatures().length > 0) {
const functionDeclaration = type.getCallSignatures()[0].getDeclaration();
const simpleType = getSimpleFunctionFromDeclaration(functionDeclaration, options, true);
if (simpleType != null) {
return simpleType;
}
}
// Class
else if (type.isClass() && symbol != null) {
const classDecl = getDeclaration(symbol);
if (classDecl != null && tsModule.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 && tsModule.ts.isConstructorDeclaration(ctorDecl)) {
return getSimpleFunctionFromDeclaration(ctorDecl, options, false);
}
}
})();
const members = checker.getPropertiesOfType(type).map(symbol => ({
name: symbol.name,
modifiers: getModifiersFromDeclaration(symbol.valueDeclaration),
type: toSimpleTypeInternalCaching(checker.getTypeAtLocation(symbol.valueDeclaration), options)
}));
const typeParameters = getTypeParameters(getDeclaration(symbol), options);
return {
kind: exports.SimpleTypeKind.CLASS,
name,
ctor,
typeParameters,
properties: members.filter(m => m.type.kind !== exports.SimpleTypeKind.METHOD),
methods: members.filter(m => m.type.kind === exports.SimpleTypeKind.METHOD)
};
}
}
// Interface
else if (type.isClassOrInterface() || isObject(type)) {
const members = checker.getPropertiesOfType(type).map(symbol => ({
name: symbol.name,
optional: (symbol.flags & tsModule.ts.SymbolFlags.Optional) !== 0,
type: toSimpleTypeInternalCaching(checker.getTypeAtLocation(symbol.valueDeclaration), options)
}));
const typeParameters = (isObjectTypeReference(type) && type.target.typeParameters ? type.target.typeParameters.map(t => toSimpleTypeInternalCaching(t, options)) : undefined) ||
(symbol != null ? getTypeParameters(getDeclaration(symbol), options) : undefined);
return {
kind: type.isClassOrInterface() ? exports.SimpleTypeKind.INTERFACE : exports.SimpleTypeKind.OBJECT,
typeParameters,
members,
name
};
}
if (type.isTypeParameter() && symbol != null) {
const defaultType = type.getDefault();
const defaultSimpleType = defaultType != null ? toSimpleTypeInternalCaching(defaultType, options) : undefined;
return {
kind: exports.SimpleTypeKind.GENERIC_PARAMETER,
name: symbol.getName(),
default: defaultSimpleType
};
}
return {
kind: exports.SimpleTypeKind.ANY,
name
};
}
function simplifySimpleTypeArray(types) {
let newTypes = [...types];
const NULLABLE_TYPE_KINDS = [exports.SimpleTypeKind.UNDEFINED, exports.SimpleTypeKind.NULL];
// Only include one instance of primitives
newTypes = newTypes.filter((type, i) => {
if (isSimpleTypeLiteral(type))
return true;
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)
.map(t => t.kind)
.includes(type.kind);
}
return true;
});
// Simplify boolean literals
const booleanLiteralTypes = newTypes.filter((t) => t.kind === exports.SimpleTypeKind.BOOLEAN_LITERAL);
if (booleanLiteralTypes.find(t => t.value === true) != null && booleanLiteralTypes.find(t => t.value === false) != null) {
newTypes = [...newTypes.filter(type => type.kind !== exports.SimpleTypeKind.BOOLEAN_LITERAL), { kind: exports.SimpleTypeKind.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 === exports.SimpleTypeKind.NULL ? (t2.kind === exports.SimpleTypeKind.UNDEFINED ? -1 : 0) : t2.kind === exports.SimpleTypeKind.NULL ? 1 : 0))
];
}
return newTypes;
}
function literalToSimpleType(type, checker) {
if (type.isNumberLiteral()) {
return {
kind: exports.SimpleTypeKind.NUMBER_LITERAL,
value: type.value
};
}
else if (type.isStringLiteral()) {
return {
kind: exports.SimpleTypeKind.STRING_LITERAL,
value: type.value
};
}
else if (isBooleanLiteral(type)) {
// See https://github.com/Microsoft/TypeScript/issues/22269 for more information
return {
kind: exports.SimpleTypeKind.BOOLEAN_LITERAL,
value: checker.typeToString(type) === "true"
};
}
else if (isBigIntLiteral(type)) {
return {
kind: exports.SimpleTypeKind.BIG_INT_LITERAL,
value: BigInt(`${type.value.negative ? "-" : ""}${type.value.base10Value}`)
};
}
}
function getSimpleFunctionFromDeclaration(functionDeclaration, options, checkReturnType) {
const { checker } = options;
const symbol = checker.getSymbolAtLocation(functionDeclaration);
const type = checker.getTypeAtLocation(functionDeclaration);
if (tsModule.ts.isFunctionLike(functionDeclaration)) {
const signature = checker.getSignatureFromDeclaration(functionDeclaration);
if (signature != null) {
const argTypes = functionDeclaration.parameters.map(parameterDecl => {
const argType = checker.getTypeAtLocation(parameterDecl);
return {
name: parameterDecl.name.getText(),
optional: parameterDecl.questionToken != null,
type: toSimpleTypeInternalCaching(argType, options),
spread: parameterDecl.dotDotDotToken != null,
initializer: parameterDecl.initializer != null
};
});
const name = symbol != null ? symbol.getName() : undefined;
const kind = isMethod(type) ? exports.SimpleTypeKind.METHOD : exports.SimpleTypeKind.FUNCTION;
const returnType = toSimpleTypeInternalCaching(checker.getReturnTypeOfSignature(signature), options);
const typeParameters = getTypeParameters(functionDeclaration, options);
return { name, kind, returnType, argTypes, typeParameters };
}
}
}
const BLACKLISTED_SYMBOL_NAMES = ["__type", "__object", "__function"];
function getRealSymbolName(symbol) {
const name = symbol.getName();
if (name != null && BLACKLISTED_SYMBOL_NAMES.includes(name)) {
return undefined;
}
return name;
}
function getTypeParameters(declaration, options) {
if (declaration == null)
return undefined;
if (tsModule.ts.isClassDeclaration(declaration) ||
tsModule.ts.isFunctionDeclaration(declaration) ||
tsModule.ts.isTypeAliasDeclaration(declaration) ||
tsModule.ts.isMethodDeclaration(declaration)) {
return declaration.typeParameters == null
? undefined
: Array.from(declaration.typeParameters)
.map(td => options.checker.getTypeAtLocation(td))
.map(t => toSimpleTypeInternalCaching(t, options));
}
return undefined;
}
function isAssignableToSimpleTypeKind(type, kind, optionsOrChecker, options = {}) {
if (!isSimpleType(type)) {
return isAssignableToSimpleTypeKind(toSimpleType(type, optionsOrChecker), kind, options);
}
else {
options = optionsOrChecker || {};
}
// Make sure that an object without members is treated as ANY
switch (type.kind) {
case exports.SimpleTypeKind.OBJECT:
if (type.members == null || type.members.length === 0) {
return isAssignableToSimpleTypeKind({ kind: exports.SimpleTypeKind.ANY }, kind, options);
}
break;
}
switch (type.kind) {
case exports.SimpleTypeKind.ENUM:
case exports.SimpleTypeKind.UNION:
return or(type.types, childType => isAssignableToSimpleTypeKind(childType, kind, options));
case exports.SimpleTypeKind.INTERSECTION:
return and(type.types, childType => isAssignableToSimpleTypeKind(childType, kind, options));
case exports.SimpleTypeKind.ANY:
return options.matchAny || false;
case exports.SimpleTypeKind.ENUM_MEMBER:
return isAssignableToSimpleTypeKind(type.type, kind, options);
case exports.SimpleTypeKind.ALIAS:
return isAssignableToSimpleTypeKind(type.target, kind, options);
case exports.SimpleTypeKind.GENERIC_PARAMETER:
return isAssignableToSimpleTypeKind(type.default || { kind: exports.SimpleTypeKind.ANY }, kind, options);
default:
if (Array.isArray(kind)) {
return (options.op === "or" ? or : and)(kind, itemKind => type.kind === itemKind);
}
return type.kind === kind;
}
}
function isAssignableToPrimitiveType(type, checker) {
if (isSimpleType(type)) {
return isAssignableToSimpleTypeKind(type, PRIMITIVE_TYPE_KINDS, { op: "or", matchAny: true });
}
return isAssignableToSimpleTypeKind(type, PRIMITIVE_TYPE_KINDS, checker, { op: "or", matchAny: true });
}
/**
* Returns if typeB is assignable to typeA.
* @param typeA Type A
* @param typeB Type B
*/
function isAssignableToSimpleType(typeA, typeB) {
return isAssignabletoSimpleTypeInternal(typeA, typeB, {
inCircularA: false,
inCircularB: false,
insideType: new Set(),
genericParameterMapA: new Map(),
genericParameterMapB: new Map()
});
}
function isAssignabletoSimpleTypeInternal(typeA, typeB, options) {
/*
options = {...options};
(options as any).depth = ((options as any).depth || 0) + 1;
console.log( "###", " ".repeat((options as any).depth), require("./simple-type-to-string").simpleTypeToString(typeA), "===", require("./simple-type-to-string").simpleTypeToString(typeB), "(", typeA.kind, "===", typeB.kind, ")", (options as any).depth, "###" );
*/
if (typeA === typeB) {
return true;
}
// We might need a better way of handling refs, but these check are good for now
if (options.insideType.has(typeA)) {
return true;
}
if (options.inCircularA && options.inCircularB) {
return true;
}
if (typeA.kind === exports.SimpleTypeKind.UNKNOWN || typeA.kind === exports.SimpleTypeKind.ANY || typeB.kind === exports.SimpleTypeKind.ANY) {
return true;
}
switch (typeB.kind) {
case exports.SimpleTypeKind.CIRCULAR_TYPE_REF:
return isAssignabletoSimpleTypeInternal(typeA, typeB.ref, {
...options,
inCircularB: true,
insideType: new Set([...options.insideType, typeB])
});
case exports.SimpleTypeKind.ENUM_MEMBER:
return isAssignabletoSimpleTypeInternal(typeA, typeB.type, options);
case exports.SimpleTypeKind.ENUM:
return and(typeB.types, childTypeB => isAssignabletoSimpleTypeInternal(typeA, childTypeB, options));
case exports.SimpleTypeKind.UNION:
return and(typeB.types, childTypeB => isAssignabletoSimpleTypeInternal(typeA, childTypeB, options));
case exports.SimpleTypeKind.INTERSECTION:
return and(typeB.types, childTypeB => isAssignabletoSimpleTypeInternal(typeA, childTypeB, options));
case exports.SimpleTypeKind.ALIAS:
return isAssignabletoSimpleTypeInternal(typeA, typeB.target, options);
case exports.SimpleTypeKind.GENERIC_ARGUMENTS:
return isAssignabletoSimpleTypeInternal(typeA, typeB.target, {
...options,
genericParameterMapB: extendTypeParameterMap(typeB, options.genericParameterMapB)
});
case exports.SimpleTypeKind.GENERIC_PARAMETER:
const realType = options.genericParameterMapB.get(typeB.name);
return isAssignabletoSimpleTypeInternal(typeA, realType || typeB.default || { kind: exports.SimpleTypeKind.ANY }, options);
}
switch (typeA.kind) {
// Circular references
case exports.SimpleTypeKind.CIRCULAR_TYPE_REF:
return isAssignabletoSimpleTypeInternal(typeA.ref, typeB, {
...options,
inCircularA: true,
insideType: new Set([...options.insideType, typeA])
});
// Literals and enum members
case exports.SimpleTypeKind.NUMBER_LITERAL:
case exports.SimpleTypeKind.STRING_LITERAL:
case exports.SimpleTypeKind.BIG_INT_LITERAL:
case exports.SimpleTypeKind.BOOLEAN_LITERAL:
return isSimpleTypeLiteral(typeB) ? typeA.value === typeB.value : false;
case exports.SimpleTypeKind.ENUM_MEMBER:
return isAssignabletoSimpleTypeInternal(typeA.type, typeB, options);
// Primitive types
case exports.SimpleTypeKind.STRING:
case exports.SimpleTypeKind.BOOLEAN:
case exports.SimpleTypeKind.NUMBER:
case exports.SimpleTypeKind.BIG_INT:
case exports.SimpleTypeKind.UNDEFINED:
case exports.SimpleTypeKind.NULL:
if (isSimpleTypeLiteral(typeB)) {
return PRIMITIVE_TYPE_TO_LITERAL_MAP[typeA.kind] === typeB.kind;
}
return typeA.kind === typeB.kind;
// Void
case exports.SimpleTypeKind.VOID:
return typeB.kind === exports.SimpleTypeKind.VOID;
// Alias
case exports.SimpleTypeKind.ALIAS:
return isAssignabletoSimpleTypeInternal(typeA.target, typeB, options);
// Generic types
case exports.SimpleTypeKind.GENERIC_PARAMETER:
const realType = options.genericParameterMapA.get(typeA.name);
return isAssignabletoSimpleTypeInternal(realType || typeA.default || { kind: exports.SimpleTypeKind.ANY }, typeB, options);
case exports.SimpleTypeKind.GENERIC_ARGUMENTS:
return isAssignabletoSimpleTypeInternal(typeA.target, typeB, {
...options,
genericParameterMapA: extendTypeParameterMap(typeA, options.genericParameterMapA)
});
// Arrays
case exports.SimpleTypeKind.ARRAY:
if (typeB.kind === exports.SimpleTypeKind.ARRAY) {
if (typeA) {
return isAssignabletoSimpleTypeInternal(typeA.type, typeB.type, options);
}
}
return false;
// Functions
case exports.SimpleTypeKind.FUNCTION:
case exports.SimpleTypeKind.METHOD:
if (typeB.kind !== exports.SimpleTypeKind.FUNCTION && typeB.kind !== exports.SimpleTypeKind.METHOD)
return false;
if (!isAssignabletoSimpleTypeInternal(typeA.returnType, typeB.returnType, options))
return false;
for (let i = 0; i < Math.max(typeA.argTypes.length, typeB.argTypes.length); i++) {
const argA = typeA.argTypes[i];
const argB = typeB.argTypes[i];
if (argB == null && argA != null && !argA.optional) {
return false;
}
if (argB != null && argA == null) {
return false;
}
if (!isAssignabletoSimpleTypeInternal(argA.type, argB.type, options)) {
if (argA.spread && argA.type.kind === exports.SimpleTypeKind.ARRAY && (!argB.spread && argB.type.kind !== exports.SimpleTypeKind.ARRAY)) {
if (!isAssignabletoSimpleTypeInternal(argA.type.type, argB.type, options)) {
return false;
}
}
}
}
return true;
// Unions and enum members
case exports.SimpleTypeKind.ENUM:
case exports.SimpleTypeKind.UNION:
return or(typeA.types, childTypeA => isAssignabletoSimpleTypeInternal(childTypeA, typeB, options));
// Intersections
case exports.SimpleTypeKind.INTERSECTION:
return and(typeA.types, childTypeA => isAssignabletoSimpleTypeInternal(childTypeA, typeB, options));
// Interfaces
case exports.SimpleTypeKind.INTERFACE:
case exports.SimpleTypeKind.OBJECT:
case exports.SimpleTypeKind.CLASS:
// If there are no members check that "typeB" is not assignable to 'null' and 'undefined'.
// Here we allow assigning anything but 'null' and 'undefined' to the type '{}'
if ("members" in typeA && (typeA.members == null || typeA.members.length === 0)) {
return !isAssignabletoSimpleTypeInternal({
kind: exports.SimpleTypeKind.UNION,
types: [{ kind: exports.SimpleTypeKind.NULL }, { kind: exports.SimpleTypeKind.UNDEFINED }]
}, typeB, options);
}
switch (typeB.kind) {
case exports.SimpleTypeKind.INTERFACE:
case exports.SimpleTypeKind.OBJECT:
case exports.SimpleTypeKind.CLASS:
const membersA = typeA.kind === exports.SimpleTypeKind.CLASS ? [...typeA.methods, ...typeA.properties] : typeA.members || [];
const membersB = typeB.kind === exports.SimpleTypeKind.CLASS ? [...typeB.methods, ...typeB.properties] : typeB.members || [];
const newOptions = {
...options,
insideType: new Set([...options.insideType, typeA, typeB])
};
return (and(membersA, memberA => {
// Make sure that every required prop in typeA is present
const memberB = membersB.find(memberB => memberA.name === memberB.name);
return memberB == null ? memberA.optional : true;
}) &&
and(membersB, memberB => {
// Do not allow new props in subtype: contravariance
// Strict type checking
const memberA = membersA.find(memberA => memberA.name === memberB.name);
if (memberA == null)
return false;
return isAssignabletoSimpleTypeInternal(memberA.type, memberB.type, newOptions);
}));
default:
return false;
}
case exports.SimpleTypeKind.TUPLE:
if (typeB.kind !== exports.SimpleTypeKind.TUPLE)
return false;
return and(typeA.members, (memberA, i) => {
const memberB = typeB.members[i];
if (memberB == null)
return memberA.optional;
return isAssignabletoSimpleTypeInternal(memberA.type, memberB.type, options);
});
case exports.SimpleTypeKind.PROMISE:
return typeB.kind === exports.SimpleTypeKind.PROMISE && isAssignabletoSimpleTypeInternal(typeA.type, typeB.type, options);
case exports.SimpleTypeKind.DATE:
return typeB.kind === exports.SimpleTypeKind.DATE;
//default:
//throw new Error(`Unsupported comparison: ${typeA.kind}`);
}
}
function extendTypeParameterMap(genericType, existingMap) {
if ("typeParameters" in genericType.target) {
const parameterEntries = (genericType.target.typeParameters || []).map((parameter, i) => [parameter.name, genericType.typeArguments[i] || parameter.default || { kind: exports.SimpleTypeKind.ANY }]);
const allParameterEntries = [...existingMap.entries(), ...parameterEntries];
return new Map(allParameterEntries);
}
throw new Error(`Couldn't find 'typeParameter' for type '${genericType.target.kind}'`);
//return existingMap;
}
const simpleTypeCache = new WeakMap();
const isAssignableTypeCache = new WeakMap();
function isAssignableToType(typeA, typeB, checker) {
if (isNode(typeA)) {
return isAssignableToType(checker.getTypeAtLocation(typeA), typeB, checker);
}
if (isNode(typeB)) {
return isAssignableToType(typeA, checker.getTypeAtLocation(typeB), checker);
}
const simpleTypeA = isSimpleType(typeA) ? typeA : toSimpleType(typeA, checker, simpleTypeCache);
const simpleTypeB = isSimpleType(typeB) ? typeB : toSimpleType(typeB, checker, simpleTypeCache);
const typeAResultCache = (() => {
if (isAssignableTypeCache.has(simpleTypeA)) {
return isAssignableTypeCache.get(simpleTypeA);
}
const newResultCache = new WeakMap();
isAssignableTypeCache.set(simpleTypeA, newResultCache);
return newResultCache;
})();
if (typeAResultCache.has(simpleTypeB)) {
return typeAResultCache.get(simpleTypeB);
}
/*console.log("Type A");
console.dir(simpleTypeA, { depth: 5 });
console.log("Type B");
console.dir(simpleTypeB, { depth: 5 });*/
const result = isAssignableToSimpleType(simpleTypeA, simpleTypeB);
typeAResultCache.set(simpleTypeB, result);
return result;
}
function isAssignableToValue(type, value, checker) {
if (isSimpleType(type)) {
if (typeof value === "string") {
return isAssignableToType(type, {
kind: exports.SimpleTypeKind.STRING_LITERAL,
value
});
}
else if (typeof value === "number") {
return isAssignableToType(type, {
kind: exports.SimpleTypeKind.NUMBER_LITERAL,
value
});
}
else if (typeof value === "boolean") {
return isAssignableToType(type, {
kind: exports.SimpleTypeKind.BOOLEAN_LITERAL,
value
});
}
else if (value instanceof Promise) {
return isAssignableToType(type, {
kind: exports.SimpleTypeKind.PROMISE,
type: { kind: exports.SimpleTypeKind.ANY }
});
}
else if (value instanceof Date) {
return isAssignableToType(type, {
kind: exports.SimpleTypeKind.DATE
});
}
throw new Error(`Comparing type "${type.kind}" to value ${value}, type ${typeof value} not supported yet.`);
}
return isAssignableToValue(toSimpleType(type, checker), value);
}
/**
* Converts a simple type to a string.
* @param type Simple Type
*/
function simpleTypeToString(type) {
//}, options: SimpleTypeToStringOptions): string {
switch (type.kind) {
case exports.SimpleTypeKind.CIRCULAR_TYPE_REF:
return type.ref.name || "[Circular]";
case exports.SimpleTypeKind.BOOLEAN_LITERAL:
return String(type.value);
case exports.SimpleTypeKind.NUMBER_LITERAL:
return String(type.value);
case exports.SimpleTypeKind.STRING_LITERAL:
return `"${type.value}"`;
case exports.SimpleTypeKind.BIG_INT_LITERAL:
return `${type.value}n`;
case exports.SimpleTypeKind.STRING:
return "string";
case exports.SimpleTypeKind.BOOLEAN:
return "boolean";
case exports.SimpleTypeKind.NUMBER:
return "number";
case exports.SimpleTypeKind.BIG_INT:
return "bigint";
case exports.SimpleTypeKind.UNDEFINED:
return "undefined";
case exports.SimpleTypeKind.NULL:
return "null";
case exports.SimpleTypeKind.ANY:
return "any";
case exports.SimpleTypeKind.UNKNOWN:
return "unknown";
case exports.SimpleTypeKind.VOID:
return "void";
case exports.SimpleTypeKind.FUNCTION:
case exports.SimpleTypeKind.METHOD:
if (type.kind === exports.SimpleTypeKind.FUNCTION && type.name != null)
return type.name;
const argText = functionArgTypesToString(type.argTypes);
return `(${argText})${type.returnType != null ? ` => ${simpleTypeToString(type.returnType)}` : ""}`;
case exports.SimpleTypeKind.ARRAY:
const hasMultipleTypes = [exports.SimpleTypeKind.UNION, exports.SimpleTypeKind.INTERSECTION].includes(type.type.kind);
let memberType = simpleTypeToString(type.type);
if (type.name != null && ["ReadonlyArray"].includes(type.name))
return `ReadonlyArray<${memberType}>`;
if (hasMultipleTypes && type.type.name == null)
memberType = `(${memberType})`;
return `${memberType}[]`;
case exports.SimpleTypeKind.UNION:
if (type.name != null)
return type.name;
return type.types.map(simpleTypeToString).join(" | ");
case exports.SimpleTypeKind.ENUM:
return type.name;
case exports.SimpleTypeKind.ENUM_MEMBER:
return type.fullName;
case exports.SimpleTypeKind.INTERSECTION:
if (type.name != null)
return type.name;
return type.types.map(simpleTypeToString).join(" & ");
case exports.SimpleTypeKind.INTERFACE:
if (type.name != null)
return type.name;
// this fallthrough is intentional
case exports.SimpleTypeKind.OBJECT:
if (type.members == null || type.members.length === 0)
return "{}";
return `{ ${type.members
.map(member => {
// this check needs to change in the future
if (member.type.kind === exports.SimpleTypeKind.FUNCTION || member.type.kind === exports.SimpleTypeKind.METHOD) {
const result = simpleTypeToString(member.type);
const genericParameters = member.type.typeParameters != null ? `<${member.type.typeParameters.map(simpleTypeToString).join(", ")}>` : "";
return `${member.name}${result.replace(" => ", ": ").replace("(", `${genericParameters}(`)}`;
}
return `${member.name}: ${simpleTypeToString(member.type)}`;
})
.join("; ")}${type.members.length > 0 ? ";" : ""} }`;
case exports.SimpleTypeKind.TUPLE:
return `[${type.members.map(member => `${simpleTypeToString(member.type)}${member.optional ? "?" : ""}`).join(", ")}]`;
case exports.SimpleTypeKind.GENERIC_ARGUMENTS:
const { target, typeArguments } = type;
return typeArguments.length === 0 ? target.name || "" : `${target.name}<${typeArguments.map(simpleTypeToString).join(", ")}>`;
case exports.SimpleTypeKind.PROMISE:
return `${type.name || "Promise"}<${simpleTypeToString(type.type)}>`;
case exports.SimpleTypeKind.DATE:
return "Date";
default:
return type.name || "";
}
}
function functionArgTypesToString(argTypes) {
return argTypes
.map(arg => {
return `${arg.spread ? "..." : ""}${arg.name}${arg.optional ? "?" : ""}: ${simpleTypeToString(arg.type)}`;
})
.join(", ");
}
function toTypeString(type, checker) {
if (isSimpleType(type)) {
return simpleTypeToString(type);
}
// Use the typescript checker to return a string for a type
return checker.typeToString(type);
}
exports.setTypescriptModule = setTypescriptModule;
exports.isAssignableToPrimitiveType = isAssignableToPrimitiveType;
exports.isAssignableToType = isAssignableToType;
exports.isAssignableToValue = isAssignableToValue;
exports.toSimpleType = toSimpleType;
exports.toTypeString = toTypeString;
exports.isAssignableToSimpleTypeKind = isAssignableToSimpleTypeKind;
exports.simpleTypeToString = simpleTypeToString;
exports.isSimpleType = isSimpleType;
exports.LITERAL_TYPE_KINDS = LITERAL_TYPE_KINDS;
exports.isSimpleTypeLiteral = isSimpleTypeLiteral;
exports.PRIMITIVE_TYPE_KINDS = PRIMITIVE_TYPE_KINDS;
exports.isSimpleTypePrimitive = isSimpleTypePrimitive;
exports.PRIMITIVE_TYPE_TO_LITERAL_MAP = PRIMITIVE_TYPE_TO_LITERAL_MAP;
exports.IMPLICIT_GENERIC = IMPLICIT_GENERIC;
exports.isImplicitGenericType = isImplicitGenericType;