@jitl/ts-simple-type
Version:
Static analysis and compiler framework for TypeScript types
1,257 lines (1,243 loc) • 158 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
require('path');
var sourceMap = require('source-map');
var ts = require('typescript');
require('util');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () {
return e[k];
}
});
}
});
}
n['default'] = e;
return Object.freeze(n);
}
var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
// 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 = ts__namespace;
function setTypescriptModule(ts) {
selectedTSModule = ts;
}
function getTypescriptModule() {
return selectedTSModule;
}
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
/** Returned by {@link walkRecursive} and similar functions to prevent infinite loops */
class Cyclical {
constructor(cycle) {
this.cycle = cycle;
}
static is(value) {
return Boolean(value && value instanceof Cyclical);
}
static preventCycles(visitor) {
const preventCyclesVisitor = args => {
const cycle = SimpleTypePath.getSubpathFrom(args.path, args.type);
if (cycle) {
return new Cyclical(cycle);
}
return visitor(args);
};
return preventCyclesVisitor;
}
}
function makeVisitChildFn(path, type, fn) {
const visit = function visit(step, childType, childFn) {
const childPath = SimpleTypePath.concat(path, step);
return walkRecursive(childPath, childType, childFn !== null && childFn !== void 0 ? childFn : fn);
};
visit.with = newFn => makeVisitChildFn(path, type, newFn);
return visit;
}
const ALREADY_ANNOTATED_ERROR_WITH_PATH = new WeakSet();
/**
* Perform a custom recursive walk of `type` by calling `fn` it.
* `fn` can recurse by calling its `args.visit(path, otherType)`.
* @returns result of `fn`
*/
function walkRecursive(path, type, fn) {
const args = {
path,
type,
visit: makeVisitChildFn(path, type, fn)
};
try {
return fn(args);
}
catch (e) {
if (e instanceof Error && !ALREADY_ANNOTATED_ERROR_WITH_PATH.has(e)) {
e.message += `\nPath: ${SimpleTypePath.toString(path, type)}`;
ALREADY_ANNOTATED_ERROR_WITH_PATH.add(e);
}
throw e;
}
}
/** Walk the given SimpleType in depth-first order; does not return a result. */
function walkDepthFirst(path, type, visitors) {
walkRecursive(path, type, args => {
var _l, _m;
const traverse = visitors.traverse || mapAnyStep;
(_l = visitors.before) === null || _l === void 0 ? void 0 : _l.call(visitors, args);
traverse(args);
(_m = visitors.after) === null || _m === void 0 ? void 0 : _m.call(visitors, args);
});
}
class CallableVisitors {
constructor() {
this.mapTypeParameters = ({ visit, type }) => { var _l, _m; return (_m = (_l = type.typeParameters) === null || _l === void 0 ? void 0 : _l.map((param, i) => visit({ from: type, index: i, step: "TYPE_PARAMETER", name: param.name }, param))) !== null && _m !== void 0 ? _m : []; };
this.mapParameters = ({ visit, type }) => { var _l, _m; return (_m = (_l = type.parameters) === null || _l === void 0 ? void 0 : _l.map((param, i) => visit({ from: type, index: i, step: "PARAMETER", parameter: param }, param.type))) !== null && _m !== void 0 ? _m : []; };
this.return = ({ visit, type }) => type.returnType && visit({ from: type, step: "RETURN" }, type.returnType);
}
}
_a = CallableVisitors;
CallableVisitors.instance = new _a();
class VariantTypesVisitors {
constructor() {
this.mapVariants = ({ visit, type }) => type.types.map((variant, i) => visit({ from: type, index: i, step: "VARIANT" }, variant));
}
}
_b = VariantTypesVisitors;
VariantTypesVisitors.instance = new _b();
class ObjectLikeVisitors {
constructor() {
this.mapTypeParameters = ({ visit, type }) => { var _l, _m; return (_m = (_l = type.typeParameters) === null || _l === void 0 ? void 0 : _l.map((param, i) => visit({ from: type, index: i, step: "TYPE_PARAMETER", name: param.name }, param))) !== null && _m !== void 0 ? _m : []; };
this.callSignature = ({ visit, type }) => type.call && visit({ from: type, step: "CALL_SIGNATURE" }, type.call);
this.ctorSignature = ({ visit, type }) => type.ctor && visit({ from: type, step: "CTOR_SIGNATURE" }, type.ctor);
this.mapNamedMembers = ({ visit, type }) => { var _l, _m; return (_m = (_l = type.members) === null || _l === void 0 ? void 0 : _l.map((member, i) => visit({ from: type, index: i, step: "NAMED_MEMBER", member }, member.type))) !== null && _m !== void 0 ? _m : []; };
this.numberIndex = ({ visit, type }) => { var _l; return ((_l = type.indexType) === null || _l === void 0 ? void 0 : _l.NUMBER) && visit({ from: type, step: "NUMBER_INDEX" }, type.indexType.NUMBER); };
this.stringIndex = ({ visit, type }) => { var _l; return ((_l = type.indexType) === null || _l === void 0 ? void 0 : _l.STRING) && visit({ from: type, step: "STRING_INDEX" }, type.indexType.STRING); };
}
}
_c = ObjectLikeVisitors;
ObjectLikeVisitors.instance = new _c();
class GenericArgumentsVisitors {
constructor() {
this.aliased = ({ visit, type }) => visit({ from: type, step: "ALIASED" }, type.instantiated);
this.genericTarget = ({ visit, type }) => visit({ from: type, step: "GENERIC_TARGET" }, type.target);
this.mapGenericArguments = ({ visit, type }) => type.typeArguments.map((arg, i) => visit({ from: type, index: i, step: "GENERIC_ARGUMENT", name: arg.name }, arg));
}
}
_d = GenericArgumentsVisitors;
GenericArgumentsVisitors.instance = new _d();
class GenericParameterVisitors {
constructor() {
this.typeParameterConstraint = ({ visit, type }) => type.constraint && visit({ from: type, step: "TYPE_PARAMETER_CONSTRAINT" }, type.constraint);
this.typeParameterDefault = ({ visit, type }) => type.default && visit({ from: type, step: "TYPE_PARAMETER_DEFAULT" }, type.default);
}
}
_e = GenericParameterVisitors;
GenericParameterVisitors.instance = new _e();
class TupleVisitors {
constructor() {
this.mapIndexedMembers = ({ visit, type }) => { var _l; return (_l = type.members) === null || _l === void 0 ? void 0 : _l.map((member, i) => visit({ from: type, index: i, step: "INDEXED_MEMBER", member }, member.type)); };
}
}
_f = TupleVisitors;
TupleVisitors.instance = new _f();
class AliasVisitorsImpl {
constructor() {
this.mapIndexedMembers = ({ visit, type }) => { var _l; return (_l = type.members) === null || _l === void 0 ? void 0 : _l.map((member, i) => visit({ from: type, index: i, step: "INDEXED_MEMBER", member }, member.type)); };
this.aliased = ({ visit, type }) => visit({ from: type, step: "ALIASED" }, type.target);
this.mapTypeParameters = ({ visit, type }) => { var _l, _m; return (_m = (_l = type.typeParameters) === null || _l === void 0 ? void 0 : _l.map((param, i) => visit({ from: type, index: i, step: "TYPE_PARAMETER", name: param.name }, param))) !== null && _m !== void 0 ? _m : []; };
}
}
_g = AliasVisitorsImpl;
AliasVisitorsImpl.instance = new _g();
class ArrayVisitors {
constructor() {
this.numberIndex = ({ visit, type }) => visit({ from: type, step: "NUMBER_INDEX" }, type.type);
}
}
_h = ArrayVisitors;
ArrayVisitors.instance = new _h();
class PromiseVisitors {
constructor() {
this.awaited = ({ visit, type }) => visit({ from: type, step: "AWAITED" }, type.type);
}
}
_j = PromiseVisitors;
PromiseVisitors.instance = new _j();
class EnumMemberVisitors {
constructor() {
this.aliased = ({ visit, type }) => visit({ from: type, step: "ALIASED" }, type.type);
}
}
_k = EnumMemberVisitors;
EnumMemberVisitors.instance = new _k();
const KindVisitors = {
ENUM: VariantTypesVisitors.instance,
UNION: VariantTypesVisitors.instance,
INTERSECTION: VariantTypesVisitors.instance,
INTERFACE: ObjectLikeVisitors.instance,
OBJECT: ObjectLikeVisitors.instance,
CLASS: ObjectLikeVisitors.instance,
FUNCTION: CallableVisitors.instance,
METHOD: CallableVisitors.instance,
GENERIC_ARGUMENTS: GenericArgumentsVisitors.instance,
GENERIC_PARAMETER: GenericParameterVisitors.instance,
TUPLE: TupleVisitors.instance,
ALIAS: AliasVisitorsImpl.instance,
ARRAY: ArrayVisitors.instance,
PROMISE: PromiseVisitors.instance,
ENUM_MEMBER: EnumMemberVisitors.instance
};
// ============================================================================
// Higher-level visitors
// ============================================================================
const mapAnyStep = ({ type, path, visit }) => {
if (type.kind in Visitor) {
const visitors = Visitor[type.kind];
let results = [];
for (const [name, _visitor] of Object.entries(visitors)) {
const visitor = _visitor;
const visited = visitor({ type, path, visit });
if (typeof visited === "undefined") {
continue;
}
if (name.startsWith("map") && Array.isArray(visited)) {
results = results.concat(visited);
continue;
}
results.push(visited);
}
return results;
}
else {
return [];
}
};
const array = (...values) => values.flatMap(v => (v === undefined ? [] : v));
const mapJsonStep = ({ type, path, visit }) => {
switch (type.kind) {
case "ENUM":
return Visitor.ENUM.mapVariants({ type, path, visit });
case "UNION":
return Visitor.UNION.mapVariants({ type, path, visit });
case "INTERSECTION":
return Visitor.INTERSECTION.mapVariants({ type, path, visit });
case "INTERFACE":
return array(Visitor.INTERFACE.mapNamedMembers({ type, path, visit }), Visitor.INTERFACE.numberIndex({ type, path, visit }), Visitor.INTERFACE.stringIndex({ type, path, visit }));
case "OBJECT":
return array(Visitor.OBJECT.mapNamedMembers({ type, path, visit }), Visitor.OBJECT.numberIndex({ type, path, visit }), Visitor.OBJECT.stringIndex({ type, path, visit }));
case "CLASS":
return array(Visitor.CLASS.mapNamedMembers({ type, path, visit }), Visitor.CLASS.numberIndex({ type, path, visit }), Visitor.CLASS.stringIndex({ type, path, visit }));
case "TUPLE":
return Visitor.TUPLE.mapIndexedMembers({ type, path, visit });
case "ALIAS":
return array(Visitor.ALIAS.aliased({ type, path, visit }));
case "ARRAY":
return array(Visitor.ARRAY.numberIndex({ type, path, visit }));
case "GENERIC_ARGUMENTS":
return array(Visitor.GENERIC_ARGUMENTS.aliased({ type, path, visit }));
}
return [];
};
/**
* Visitors for path steps from a SimpleType to other SimpleTypes.
* Use these to implement your own type traversals, or inside {@link walkRecursive}.
*/
const Visitor = {
...KindVisitors,
/** Visit all concrete object properties. Ignores function types and generics */
mapJsonStep,
/** Visit all possible steps into the given type. */
mapAnyStep
};
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$1(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$1(resolvedArgument || /*simpleType.default ||*/ DEFAULT_GENERIC_PARAMETER_TYPE, parameterMap);
}
case "GENERIC_ARGUMENTS": {
const updatedGenericParameterMap = extendTypeParameterMap(simpleType, parameterMap);
return resolveType$1(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$1(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$1(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 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 isAlias(type, _ts) {
return Boolean(type.aliasSymbol);
}
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 isAny(type, ts) {
return typeHasFlag(type, ts.TypeFlags.Any);
}
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 isInstantiated(type, ts) {
return hasFlag(type.objectFlags, ts.ObjectFlags.Instantiated);
}
function isSymbol(obj) {
return "flags" in obj && "name" in obj && "getDeclarations" in obj;
}
function isType(obj) {
return "flags" in obj && "getSymbol" 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) {
var _a;
if (isObject(type, ts)) {
if (isObjectTypeReference(type, ts)) {
if ("getTypeArguments" in checker) {
return Array.from(checker.getTypeArguments(type) || []);
}
else {
return Array.from(type.typeArguments || []);
}
}
}
if (isInstantiated(type, ts) && type.aliasTypeArguments) {
return Array.from(type.aliasTypeArguments);
}
// https://stackoverflow.com/questions/66389805/how-to-extract-type-arguments-and-type-parameters-from-a-type-aliases-references
// This doesn't work in all cases.
// For example, sometimes the aliasNode isn't a TypeReferenceNode, and/or
// maybe we need to "climb" upwards from the node looking for a type reference?
// or something. This stuff is really confusing.
if (isInstantiated(type, ts) && ("mapper" in type)) {
const symbol = type.aliasSymbol || type.getSymbol();
const node = type.node || (symbol && getDeclaration(symbol));
const typeNode = node && (ts.isTypeNode(node) ? node : ts.isTypeAliasDeclaration(node) ? node.type : undefined);
// const typeNode = checker.typeToTypeNode(type, undefined, undefined); // doesnt work
// console.log(checker.typeToString(type), "typeNode:", typeNode);
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
const typeArguments = (_a = typeNode.typeArguments) === null || _a === void 0 ? void 0 : _a.map(node => checker.getTypeAtLocation(node));
if (typeArguments) {
return 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;
}
function getModuleSymbol(sourceFileOrModuleSymbol, checker) {
const moduleSymbol = isSymbol(sourceFileOrModuleSymbol) ? sourceFileOrModuleSymbol : checker.getSymbolAtLocation(sourceFileOrModuleSymbol);
if (!moduleSymbol) {
throw new Error(`No symbol found for this ts.SourceFile. Did this come from the same ts.Program as the ts.TypeChecker?`);
}
return moduleSymbol;
}
function getTypeOfTypeSymbol(symbol, checker) {
return checker.getDeclaredTypeOfSymbol(symbol);
}
function getTypeOfValueSymbol(symbol, checker) {
const internalChecker = checker;
if (internalChecker.getTypeOfSymbol) {
return internalChecker.getTypeOfSymbol(symbol);
}
const walker = internalChecker.getSymbolWalker(sym => sym === symbol);
const { visitedTypes } = walker.walkSymbol(symbol);
if (visitedTypes.length === 0) {
throw new Error(`No types walked for symbol '${symbol.getName()}'`);
}
// Logic here: type IDs are assigned by instantiation order.
// Therefor, if one of the visited types composes the other visited types,
// it will have a higher ID. Selecting the highest ID seems to be a best guess.
let maxType = visitedTypes[0];
for (const visited of visitedTypes) {
if (visited.id > maxType.id) {
maxType = visited;
}
}
return maxType;
}
function getModuleExport(sourceFileOrModuleSymbol, exportName, checker) {
const moduleSymbol = getModuleSymbol(sourceFileOrModuleSymbol, checker);
return checker.tryGetMemberInModuleExports(exportName, moduleSymbol);
}
function symbolIsOptional(sym, ts) {
return (sym.flags & ts.SymbolFlags.Optional) !== 0;
}
/**
* Find the discriminant property symbols of a union type.
* If the union has no discriminant, returns undefined.
*
* @see https://github.com/microsoft/TypeScript/blob/main/src/compiler/checker.ts `findDiscriminantProperties`, `isDiscriminantProperty`
*/
function getDiscriminantPropertiesOfType(type) {
/** Non-exported type copied from Typescript compiler internals. */
let CheckFlags;
(function (CheckFlags) {
CheckFlags[CheckFlags["SyntheticProperty"] = 2] = "SyntheticProperty";
CheckFlags[CheckFlags["HasNonUniformType"] = 64] = "HasNonUniformType";
CheckFlags[CheckFlags["HasLiteralType"] = 128] = "HasLiteralType";
CheckFlags[CheckFlags["Discriminant"] = 192] = "Discriminant";
})(CheckFlags || (CheckFlags = {}));
function isDiscriminantProperty(property) {
if ("isDiscriminantProperty" in property && property.isDiscriminantProperty) {
return true;
}
if ("checkFlags" in property) {
const checkFlags = property.checkFlags;
if (checkFlags & CheckFlags.SyntheticProperty && (checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant) {
// TODO: Exclude if the property is generic.
// Too difficult to extract from Typescript source.
return true;
}
}
return false;
}
const discriminants = type.getProperties().filter(isDiscriminantProperty);
return discriminants.length ? discriminants : undefined;
}
function isTypeErrorType(type, checker) {
return type === getTypeCheckErrorType(checker);
}
/**
* If you ask for a type that doesn't make sense, or the type of a symbol that has errors,
* the checker will return a type that means "any", but actually indicates an error.
*
* There's no public API to check for that special "any (type error)" type, but
* we can fetch it easily.
*/
function getTypeCheckErrorType(checker) {
return checker.getTypeOfSymbolAtLocation(undefined, undefined);
}
var tsUtil = /*#__PURE__*/Object.freeze({
__proto__: null,
isTypeChecker: isTypeChecker,
isProgram: isProgram,
isNode: isNode,
isBoolean: isBoolean,
isBooleanLiteral: isBooleanLiteral,
isBigIntLiteral: isBigIntLiteral,
isUniqueESSymbol: isUniqueESSymbol,
isESSymbolLike: isESSymbolLike,
isAlias: isAlias,
isLiteral: isLiteral,
isString: isString,
isNumber: isNumber,
isAny: isAny,
isEnum: isEnum,
isBigInt: isBigInt,
isObject: isObject,
isNonPrimitive: isNonPrimitive,
isThisType: isThisType,
isUnknown: isUnknown,
isNull: isNull,
isUndefined: isUndefined,
isVoid: isVoid,
isNever: isNever,
isObjectTypeReference: isObjectTypeReference,
isInstantiated: isInstantiated,
isSymbol: isSymbol,
isType: isType,
isMethod: isMethod,
getDeclaration: getDeclaration,
isArray: isArray,
isPromise: isPromise,
isDate: isDate,
isTupleTypeReference: isTupleTypeReference,
isFunction: isFunction,
getTypeArguments: getTypeArguments,
getTargetType: getTargetType,
getModifiersFromDeclaration: getModifiersFromDeclaration,
isImplicitGeneric: isImplicitGeneric,
isMethodSignature: isMethodSignature,
getModuleSymbol: getModuleSymbol,
getTypeOfTypeSymbol: getTypeOfTypeSymbol,
getTypeOfValueSymbol: getTypeOfValueSymbol,
getModuleExport: getModuleExport,
symbolIsOptional: symbolIsOptional,
getDiscriminantPropertiesOfType: getDiscriminantPropertiesOfType,
isTypeErrorType: isTypeErrorType,
getTypeCheckErrorType: getTypeCheckErrorType
});
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,
addMethods: options.addMethods,
preserveSimpleAliases: options.preserveSimpleAliases,
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) {
const enhance = (instantiated) => withMethods(instantiated, type, options);
const wrapIfAlias = (instantiated, ignoreTypeParams) => {
if (isAlias(type, options.ts)) {
const aliasName = type.aliasSymbol.getName() || "";
// console.log("wrap if alias: was alias", aliasName);
// TODO: if we're an instantiation of an alias, we don't want the params.
// currently we always get params, leading to double-wrapping of a generic, when sometimes
// we should just be the instantiation of a generic.
const aliasDeclaration = getDeclaration(type.aliasSymbol, options.ts);
const typeParameters = getTypeParameters(aliasDeclaration, options);
if (!options.preserveSimpleAliases && (ignoreTypeParams || !(typeParameters === null || typeParameters === void 0 ? void 0 : typeParameters.length))) {
return {
...instantiated,
name: aliasName || instantiated.name
};
}
return {
kind: "ALIAS",
name: aliasName,
target: instantiated,
typeParameters
};
}
else {
return instantiated;
}
};
// Check if the type is a generic interface/class reference and lift it.
// TODO: we need to track down instantiated types that were instantiated with a "mapper".
// currently, don't know how to squeeze the type arguments out of those...
// will need to do more research, or find a hacky way.
if (isObject(type, options.ts) && (isObjectTypeReference(type, options.ts) || isInstantiated(type, options.ts)) /* TODO: figure this case out */) {
const typeArguments = getTypeArguments(type, options.checker, options.ts);
if (typeArguments.length > 0) {
// Special case for array, tuple and promise, they are generic in themselves
if (isImplicitGeneric(type, options.checker, options.ts)) {
return undefined;
}
if (type.target === type) {
// Circular self-target.
// No need for a wrapper, we can infer this generic interface type correctly
return undefined;
}
return {
instantiated: type,
generic: instantiated => {
const typeArgumentsSimpleType = typeArguments.map(t => toSimpleTypeCached(t, options));
const generic = {
kind: "GENERIC_ARGUMENTS",
target: toSimpleTypeCached(type.target, options),
instantiated,
typeArguments: typeArgumentsSimpleType
};
// This makes current tests work, but may be actually incorrect.
// vvvvvv
return enhance(wrapIfAlias(generic, true));
}
};
}
}
if (isAlias(type, options.ts)) {
return {
// TODO: better type safety
instantiated: type.target || type,
generic: instantiated => {
return enhance(wrapIfAlias(instantiated));
}
};
}
return undefined;
}
function withMethods(obj, type, options) {
if (!options.addMethods) {
return obj;
}
const checker = options.checker;
return {
...obj,
getTypescript: () => ({
type,
checker,
// TODO: pass this in?
symbol: type.aliasSymbol || type.getSymbol()
})
};
}
function memberWithMethods(obj, symbol, memberOfType, options) {
if (!options.addMethods) {
return obj;
}
const checker = options.checker;
return {
...obj,
getTypescript: () => ({
checker,
memberOfType,
symbol
})
};
}
function namedMember(symbol, memberOfType, options) {
const declaration = getDeclaration(symbol);
const result = {
name: symbol.name,
type: toSimpleTypeCached(getTypeOfValueSymbol(symbol, options.checker), options)
};
if (symbolIsOptional(symbol, ts__namespace)) {
result.optional = true;
}
const modifiers = declaration != null ? getModifiersFromDeclaration(declaration, ts__namespace) : [];
if (modifiers.length) {
result.modifiers = modifiers;
}
return memberWithMethods(result, symbol, memberOfType, options);
}
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.instantiated;
}
const enhance = (obj) => withMethods(obj, type, options);
// Literal types
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 enhance({
name: name || "",
fullName: `${parentSymbol.name}.${name}`,
kind: "ENUM_MEMBER",
type: literalSimpleType
});
}
}
// Literals types
return enhance(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()) {
const result = {
kind: "UNION",
types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))),
name
};
const discriminants = getDiscriminantPropertiesOfType(type);
if (discriminants) {
const discriminantMembers = discriminants.map(symbol => namedMember(symbol, type, options));
result.discriminantMembers = discriminantMembers;
}
simpleType = result;
}
else if (type.isIntersection()) {
// Approximate the concrete intersection as properties.
// TODO: call signatures, etc.
const members = type.getProperties().map(symbol => namedMember(symbol, type, options));
const intersection = {
kind: "INTERSECTION",
types: simplifySimpleTypes(type.types.map(t => toSimpleTypeCached(t, options))),
name
};
if (members.length) {
intersection.intersected = withMethods({
kind: "OBJECT",
name,
members
}, type, options);
}
simpleType = intersection;
}
// 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),
index: i
};
}),
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 = (() => {
var _a;
const ctorSymbol = symbol != null && symbol.members != null ? symbol.members.get("__constructor") : undefined;
if (ctorSymbol != null && symbol != null) {
const ctorDecl = ctorSymbol.declarations !== undefined && ((_a = ctorSymbol.declarations) === null || _a === void 0 ? void 0 : _a.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 namedMember(symbol, type, 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 => namedMember(symbol, type, 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 };
}*/
const result = {
kind: type.isClassOrInterface() ? "INTERFACE" : "OBJECT",
name
};
if (typeParameters) {
result.typeParameters = typeParameters;
}
if (ctor) {
result.ctor = ctor;
}
if (members) {
result.members = members;
}
if (indexType) {
result.indexType = indexType;
}
if (call) {
result.call = call;
}
simpleType = result;
}
// Handle "object" type
else if (isNonPrimitive(type, ts)) {
return enhance({
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 constraint = type.getConstraint();
const constraintSimpleType = constraint != null