UNPKG

@jitl/ts-simple-type

Version:

Static analysis and compiler framework for TypeScript types

1,257 lines (1,243 loc) 158 kB
'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