@trapi/metadata
Version:
Generate REST-API metadata scheme from TypeScript Decorators.
1,217 lines • 103 kB
JavaScript
import { BaseError, isBaseError } from "@ebec/core";
import { NodeBuilderFlags, NodeFlags, SymbolFlags, SyntaxKind, canHaveDecorators, canHaveModifiers, convertCompilerOptionsFromJson, createProgram, displayPartsToString, factory, forEachChild, getDecorators, getModifiers, isArrayLiteralExpression, isArrayTypeNode, isCallExpression, isClassDeclaration, isConditionalTypeNode, isConstructorDeclaration, isEnumDeclaration, isEnumMember, isExpressionWithTypeArguments, isFunctionTypeNode, isIdentifier, isImportSpecifier, isIndexSignatureDeclaration, isIndexedAccessTypeNode, isInterfaceDeclaration, isIntersectionTypeNode, isJSDocParameterTag, isJSDocPropertyTag, isJSDocReturnTag, isJSDocThisTag, isJSDocTypeTag, isLiteralTypeNode, isMappedTypeNode, isMethodDeclaration, isMethodSignature, isModuleBlock, isModuleDeclaration, isNamedTupleMember, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParameter, isParenthesizedTypeNode, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyDeclaration, isPropertySignature, isQualifiedName, isStringLiteral, isTupleTypeNode, isTypeAliasDeclaration, isTypeLiteralNode, isTypeOperatorNode, isTypeReferenceNode, isUnionTypeNode } from "typescript";
import path, { normalize } from "node:path";
import { CollectionFormat, NumericKind, ParamKind, ParameterSource, TypeName, UtilityTypeName, ValidatorName, createRegistry, isArrayType, isDeprecatedMarker, isExtensionMarker, isNestedObjectLiteralType, isRefAliasType, isRefEnumType, isRefObjectType, isStringType, isUnionType, isVoidType, loadRegistry, loadRegistryByName, matches, matchesJsDoc, mergeRegistries, namesForMarker, newControllerDraft, newMethodDraft, newParameterDraft, numericMarkerKind, resolvePresetByName, tagsForMarker } from "@trapi/core";
import { buildFilePath, isObject, load, locateMany } from "locter";
import process from "node:process";
import { parse, stringify } from "flatted";
import crypto from "node:crypto";
import fs from "node:fs";
import { tmpdir } from "node:os";
import { minimatch } from "minimatch";
//#region src/core/error/base.ts
var MetadataError = class extends BaseError {};
//#endregion
//#region src/core/error/config.ts
var ConfigError = class extends MetadataError {};
//#endregion
//#region src/core/error/config-codes.ts
const ConfigErrorCode = {
TSCONFIG_MALFORMED: "CONFIG_TSCONFIG_MALFORMED",
PRESET_NOT_FOUND: "CONFIG_PRESET_NOT_FOUND",
PRESET_MISSING: "CONFIG_PRESET_MISSING"
};
//#endregion
//#region src/core/error/generator.ts
var GeneratorError = class extends MetadataError {};
function isGeneratorError(input) {
if (!isBaseError(input)) return false;
return typeof input.code === "string";
}
//#endregion
//#region src/core/error/generator-codes.ts
const GeneratorErrorCode = {
CONTROLLER_NO_SOURCE_FILE: "GENERATOR_CONTROLLER_NO_SOURCE_FILE",
CONTROLLER_NO_NAME: "GENERATOR_CONTROLLER_NO_NAME",
PARAMETER_GENERATION_FAILED: "GENERATOR_PARAMETER_GENERATION_FAILED",
BODY_PARAMETER_DUPLICATE: "GENERATOR_BODY_PARAMETER_DUPLICATE",
BODY_FORM_CONFLICT: "GENERATOR_BODY_FORM_CONFLICT",
STRICT_UNMATCHED_DECORATORS: "GENERATOR_STRICT_UNMATCHED_DECORATORS"
};
//#endregion
//#region src/core/error/parameter-codes.ts
const ParameterErrorCode = {
TYPE_UNSUPPORTED: "PARAMETER_TYPE_UNSUPPORTED",
METHOD_UNSUPPORTED: "PARAMETER_METHOD_UNSUPPORTED",
PATH_MISMATCH: "PARAMETER_PATH_MISMATCH",
SCOPE_REQUIRED: "PARAMETER_SCOPE_REQUIRED",
INVALID_EXAMPLE: "PARAMETER_INVALID_EXAMPLE"
};
//#endregion
//#region src/core/error/parameter.ts
var ParameterError = class ParameterError extends MetadataError {
static typeUnsupported(context) {
const location = context.node ? ParameterError.getCurrentLocation(context.node) : void 0;
return new ParameterError({
message: `@${context.decoratorName}('${context.propertyName}') does not support '${context.type.typeName}' type${location ? ` at ${location}` : ""}.`,
code: ParameterErrorCode.TYPE_UNSUPPORTED
});
}
static methodUnsupported(context) {
const location = context.node ? ParameterError.getCurrentLocation(context.node) : void 0;
return new ParameterError({
message: `@${context.decoratorName}('${context.propertyName}') does not support method '${context.method}'${location ? ` at ${location}` : ""}.`,
code: ParameterErrorCode.METHOD_UNSUPPORTED
});
}
static invalidPathMatch(context) {
const location = context.node ? ParameterError.getCurrentLocation(context.node) : void 0;
return new ParameterError({
message: `@${context.decoratorName}('${context.propertyName}') does not exist in path '${context.path}'${location ? ` at ${location}` : ""}.`,
code: ParameterErrorCode.PATH_MISMATCH
});
}
static scopeRequired(context) {
const location = context.node ? ParameterError.getCurrentLocation(context.node) : void 0;
return new ParameterError({
message: `@${context.decoratorName}() requires a scope argument${location ? ` at ${location}` : ""}.`,
code: ParameterErrorCode.SCOPE_REQUIRED
});
}
static invalidExampleSchema() {
return new ParameterError({
message: "The @example JSDoc tag contains invalid JSON.",
code: ParameterErrorCode.INVALID_EXAMPLE
});
}
static getCurrentLocation(node) {
const parts = [];
if (isMethodDeclaration(node.parent)) {
parts.push(node.parent.name.getText());
if (isClassDeclaration(node.parent.parent) && node.parent.parent.name) parts.unshift(node.parent.parent.name.text);
}
return parts.join(".");
}
};
//#endregion
//#region src/core/error/resolver.ts
var ResolverError = class extends MetadataError {
file;
line;
constructor(message, node, options) {
const opts = typeof options === "boolean" ? { onlyCurrent: options } : options;
const onlyCurrent = opts?.onlyCurrent ?? false;
const parts = [message];
let file;
let line;
if (node) {
const location = prettyLocationOfNode(node);
if (location) {
parts.push(location.text);
file = location.file;
line = location.line;
}
parts.push(prettyTroubleCause(node, onlyCurrent));
}
super({
message: parts.join("\n"),
cause: opts?.cause
});
this.file = file;
this.line = line;
}
};
function isResolverError(input) {
if (!isBaseError(input)) return false;
return "file" in input && "line" in input;
}
function prettyLocationOfNode(node) {
try {
const sourceFile = node.getSourceFile();
if (!sourceFile) return void 0;
const token = node.getFirstToken() || node.parent?.getFirstToken();
const start = token ? sourceFile.getLineAndCharacterOfPosition(token.getStart()).line + 1 : void 0;
const end = token ? sourceFile.getLineAndCharacterOfPosition(token.getEnd()).line + 1 : void 0;
return {
text: `At: ${normalize(sourceFile.fileName)}${start ? `:${start}` : ""}${end ? `:${end}` : ""}.`,
file: sourceFile.fileName,
line: start
};
} catch {
return;
}
}
function prettyTroubleCause(node, onlyCurrent = false) {
try {
let name;
if (onlyCurrent || !node.parent) name = node.pos !== -1 ? node.getText() : node.name.text;
else name = node.parent.pos !== -1 ? node.parent.getText() : node.parent.name.text;
return `This was caused by '${name}'`;
} catch {
return "This was caused by an unknown node";
}
}
//#endregion
//#region src/core/error/validator.ts
var ValidatorError = class extends MetadataError {};
//#endregion
//#region src/core/error/validator-codes.ts
const ValidatorErrorCode = {
EXPECTED_NUMBER: "VALIDATOR_EXPECTED_NUMBER",
EXPECTED_DATE: "VALIDATOR_EXPECTED_DATE",
EXPECTED_STRING: "VALIDATOR_EXPECTED_STRING"
};
//#endregion
//#region src/adapters/typescript/js-doc/constants.ts
let JSDocTagName = /* @__PURE__ */ function(JSDocTagName) {
JSDocTagName["ABSTRACT"] = "abstract";
JSDocTagName["ACCESS"] = "access";
JSDocTagName["ALIAS"] = "alias";
JSDocTagName["ASYNC"] = "async";
/**
* Alias for @extends
*/
JSDocTagName["AUGMENTS"] = "augments";
JSDocTagName["AUTHOR"] = "author";
JSDocTagName["DEFAULT"] = "default";
JSDocTagName["DEPRECATED"] = "deprecated";
JSDocTagName["DESCRIPTION"] = "description";
JSDocTagName["EXAMPLE"] = "example";
JSDocTagName["FORMAT"] = "format";
JSDocTagName["IGNORE"] = "ignore";
JSDocTagName["SUMMARY"] = "summary";
return JSDocTagName;
}({});
//#endregion
//#region src/core/utils/array.ts
function isStringArray(input) {
if (!Array.isArray(input)) return false;
for (const element of input) if (typeof element !== "string") return false;
return true;
}
//#endregion
//#region src/core/utils/object.ts
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
//#endregion
//#region src/core/utils/path-normalize.ts
function normalizePath(str) {
str = str.replace(/^[/\\\s]+|[/\\\s]+$/g, "");
str = str.replace(/([^:]\/)\/+/g, "$1");
return str;
}
//#endregion
//#region src/adapters/typescript/js-doc/utils.ts
function transformJSDocComment(input) {
if (typeof input === "string") return input;
if (!input || input.length === 0) return;
const comment = input[0];
if (typeof comment === "string") return comment;
if (isObject(comment) && typeof comment.text === "string") return comment.text;
}
//#endregion
//#region src/adapters/typescript/js-doc/module.ts
function getJSDocDescription(node, index) {
const jsDoc = getJSDoc(node, index);
if (!jsDoc) return;
return transformJSDocComment(jsDoc.comment);
}
function getJSDoc(node, index) {
if (!hasOwnProperty(node, "jsDoc")) return;
const jsDoc = node.jsDoc;
if (!jsDoc || !Array.isArray(jsDoc) || !jsDoc.length) return;
index = index ?? 0;
return jsDoc.length > index && index >= 0 ? jsDoc[index] : void 0;
}
function getJSDocTags(node, isMatching) {
const jsDoc = getJSDoc(node);
if (typeof jsDoc === "undefined") return [];
const jsDocTags = jsDoc.tags;
if (typeof jsDocTags === "undefined") return [];
if (typeof isMatching === "undefined") return jsDocTags;
if (typeof isMatching === "function") return jsDocTags.filter(isMatching);
const tagNames = Array.isArray(isMatching) ? isMatching : [isMatching];
return jsDocTags.filter((tag) => tagNames.includes(tag.tagName.text));
}
function hasJSDocTag(node, tagName) {
const tags = getJSDocTags(node, tagName);
return !(!tags || !tags.length);
}
function getJSDocTagComment(node, tagName) {
const first = getJSDocTags(node, tagName)[0];
if (!first || typeof first.comment !== "string") return;
return first.comment;
}
function getJSDocTagNames(node, requireTagName = false) {
let tags;
/* istanbul ignore next */
if (node.kind === SyntaxKind.Parameter) {
const parameterName = node.name.text;
tags = getJSDocTags(node.parent, (tag) => {
if (isJSDocParameterTag(tag)) return false;
if (tag.comment === void 0) throw new MetadataError(`Orphan tag: @${String(tag.tagName.text || tag.tagName.escapedText)} must be followed by a parameter name.`);
return typeof tag.comment === "string" ? tag.comment.startsWith(parameterName) : false;
});
} else tags = getJSDocTags(node, (tag) => requireTagName ? tag.comment !== void 0 : true);
return tags.map((tag) => tag.tagName.text);
}
//#endregion
//#region src/adapters/typescript/initializer.ts
function getInitializerValue(initializer, typeChecker, type) {
if (!initializer) return;
switch (initializer.kind) {
case SyntaxKind.ArrayLiteralExpression: return initializer.elements.map((element) => getInitializerValue(element, typeChecker));
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral: return initializer.text;
case SyntaxKind.TrueKeyword: return true;
case SyntaxKind.FalseKeyword: return false;
case SyntaxKind.PrefixUnaryExpression: {
const prefixUnary = initializer;
switch (prefixUnary.operator) {
case SyntaxKind.PlusToken: return Number(prefixUnary.operand.text);
case SyntaxKind.MinusToken: return Number(`-${prefixUnary.operand.text}`);
default: throw new MetadataError(`Unsupported prefix operator token: ${prefixUnary.operator}`);
}
}
case SyntaxKind.NumberKeyword:
case SyntaxKind.FirstLiteralToken: return Number(initializer.text);
case SyntaxKind.NewExpression: {
const newExpression = initializer;
if (newExpression.expression.text === "Date") {
let date = /* @__PURE__ */ new Date();
if (newExpression.arguments) {
const argsValue = newExpression.arguments.filter((args) => args.kind !== void 0).map((args) => getInitializerValue(args, typeChecker));
if (argsValue.length > 0) date = new Date(argsValue);
}
const dateString = date.toISOString();
if (type && type.typeName === "date") return dateString.split("T")[0];
return dateString;
}
return;
}
case SyntaxKind.NullKeyword: return null;
case SyntaxKind.ObjectLiteralExpression: {
const objectLiteral = initializer;
const nestedObject = {};
objectLiteral.properties.forEach((p) => {
nestedObject[p.name.text] = getInitializerValue(p.initializer, typeChecker);
});
return nestedObject;
}
case SyntaxKind.ImportSpecifier: {
if (typeof typeChecker === "undefined") return;
const importSpecifier = initializer;
const importSymbol = typeChecker.getSymbolAtLocation(importSpecifier.name);
if (!importSymbol) return;
const declarations = typeChecker.getAliasedSymbol(importSymbol).getDeclarations();
return getInitializerValue(extractInitializer(declarations && declarations.length > 0 ? declarations[0] : void 0), typeChecker);
}
default: {
if (typeof initializer === "undefined") return;
if (typeof initializer.parent === "undefined" || typeof typeChecker === "undefined") {
if (hasOwnProperty(initializer, "text")) return initializer.text;
return;
}
const symbol = typeChecker.getSymbolAtLocation(initializer);
if (!symbol) return;
return getInitializerValue(extractInitializer(symbol.valueDeclaration) || extractInitializer(extractImportSpecifier(symbol)), typeChecker);
}
}
}
const hasInitializer = (node) => Object.prototype.hasOwnProperty.call(node, "initializer");
const extractInitializer = (valueDeclaration) => valueDeclaration && hasInitializer(valueDeclaration) && valueDeclaration.initializer || void 0;
const extractImportSpecifier = (symbol) => {
const declaration = symbol?.declarations?.[0];
return declaration && isImportSpecifier(declaration) ? declaration : void 0;
};
//#endregion
//#region src/adapters/decorator/typescript/utils.ts
/**
* Enumerate decorators on a TS node and classify their argument values without
* going through the registry. Used by read-side consumers (type resolver,
* extension extraction) that only need decorator names + argument values.
*/
function readNodeDecorators(node, typeChecker) {
if (!canHaveDecorators(node)) return [];
const decorators = getDecorators(node);
if (!decorators || decorators.length === 0) return [];
const output = [];
for (const decorator of decorators) {
const { expression } = decorator;
let name;
let argumentExpressions = [];
if (isCallExpression(expression)) {
argumentExpressions = expression.arguments;
name = readDecoratorName$1(expression.expression);
} else name = readDecoratorName$1(expression);
if (!name) continue;
output.push({
name,
arguments: argumentExpressions.map((a) => buildDecoratorArgument(a, typeChecker))
});
}
return output;
}
function findDecoratorByName(node, name, typeChecker) {
return readNodeDecorators(node, typeChecker).find((d) => d.name === name);
}
function findDecoratorsByName(node, name, typeChecker) {
return readNodeDecorators(node, typeChecker).filter((d) => d.name === name);
}
function hasDecoratorNamed(node, name, typeChecker) {
return readNodeDecorators(node, typeChecker).some((d) => d.name === name);
}
function readDecoratorName$1(expression) {
if (isIdentifier(expression)) return expression.text;
if (isPropertyAccessExpression(expression)) return expression.name.text;
}
function buildDecoratorArgument(expr, typeChecker) {
if (isStringLiteral(expr) || isNumericLiteral(expr) || isNoSubstitutionTemplateLiteral(expr)) return {
raw: getInitializerValue(expr, typeChecker),
kind: "literal"
};
if (expr.kind === SyntaxKind.TrueKeyword) return {
raw: true,
kind: "literal"
};
if (expr.kind === SyntaxKind.FalseKeyword) return {
raw: false,
kind: "literal"
};
if (expr.kind === SyntaxKind.NullKeyword) return {
raw: null,
kind: "literal"
};
if (isPrefixUnaryExpression(expr) && (expr.operator === SyntaxKind.PlusToken || expr.operator === SyntaxKind.MinusToken) && isNumericLiteral(expr.operand)) return {
raw: getInitializerValue(expr, typeChecker),
kind: "literal"
};
if (isObjectLiteralExpression(expr)) return {
raw: getInitializerValue(expr, typeChecker),
kind: "object"
};
if (isArrayLiteralExpression(expr)) return {
raw: getInitializerValue(expr, typeChecker),
kind: "array"
};
if (isIdentifier(expr) || isPropertyAccessExpression(expr)) {
const value = getInitializerValue(expr, typeChecker);
if (typeof value !== "undefined") return {
raw: value,
kind: "identifier"
};
return {
raw: void 0,
kind: "unresolvable"
};
}
return {
raw: void 0,
kind: "unresolvable"
};
}
//#endregion
//#region src/adapters/decorator/typescript/module.ts
function buildDecoratorSources(node, options) {
if (!canHaveDecorators(node)) return [];
const decorators = getDecorators(node);
if (!decorators || decorators.length === 0) return [];
const output = [];
for (const decorator of decorators) {
const source = buildDecoratorSource(decorator, options);
if (source) output.push(source);
}
return output;
}
function buildDecoratorSource(decorator, options) {
const { expression } = decorator;
let name;
let argumentExpressions = [];
let typeArgumentNodes = [];
if (isCallExpression(expression)) {
argumentExpressions = expression.arguments;
typeArgumentNodes = expression.typeArguments ?? [];
name = readDecoratorName(expression.expression);
} else name = readDecoratorName(expression);
if (!name) return;
const decoratorArguments = argumentExpressions.map((arg) => buildDecoratorArgument(arg, options.typeChecker));
const decoratorTypeArguments = typeArgumentNodes.map((typeNode) => ({ resolve: () => options.resolveTypeNode(typeNode) }));
const sourceFile = decorator.getSourceFile();
const location = sourceFile ? {
file: sourceFile.fileName,
line: sourceFile.getLineAndCharacterOfPosition(decorator.getStart()).line + 1
} : void 0;
return {
name,
arguments: decoratorArguments,
typeArguments: decoratorTypeArguments,
target: options.target,
host: options.host,
location
};
}
function readDecoratorName(expression) {
if (isIdentifier(expression)) return expression.text;
if (isPropertyAccessExpression(expression)) return expression.name.text;
}
function buildJsDocSources(node, options) {
const tags = getJSDocTags(node);
if (tags.length === 0) return [];
const output = [];
for (const tag of tags) output.push(buildJsDocSource(tag, options));
return output;
}
function buildJsDocSource(tag, options) {
const tagName = tag.tagName.text;
const text = transformJSDocComment(tag.comment);
let parameterName;
let typeNode;
if (isJSDocParameterTag(tag) || isJSDocPropertyTag(tag)) {
if (tag.name) parameterName = readEntityName(tag.name);
typeNode = tag.typeExpression?.type;
} else if (isJSDocReturnTag(tag) || isJSDocTypeTag(tag) || isJSDocThisTag(tag)) typeNode = tag.typeExpression?.type;
const source = {
tag: tagName,
target: options.target,
host: options.host
};
if (typeof text !== "undefined") source.text = text;
if (typeof parameterName !== "undefined") source.parameterName = parameterName;
if (typeNode) {
const capturedTypeNode = typeNode;
source.typeExpression = { resolve: () => options.resolveTypeNode(capturedTypeNode) };
}
return source;
}
function readEntityName(name) {
if (isIdentifier(name)) return name.text;
if (isQualifiedName(name)) {
const left = readEntityName(name.left);
return left ? `${left}.${name.right.text}` : name.right.text;
}
}
//#endregion
//#region src/adapters/decorator/orchestrator/module.ts
function buildHandlerContext(source, options) {
return {
host: source.host,
argument: (i) => source.arguments[i],
arguments: () => source.arguments,
typeArgument: (i) => source.typeArguments[i],
typeArguments: () => source.typeArguments,
parameterType: options.parameterType ?? (() => void 0)
};
}
function buildJsDocHandlerContext(source, options) {
return {
host: source.host,
source,
parameterType: options.parameterType ?? (() => void 0)
};
}
function applyDecoratorHandlers(node, handlers, draft, options) {
if (handlers.length === 0 && !options.onUnmatchedDecorator) return;
const sources = buildDecoratorSources(node, options);
if (sources.length === 0) return;
for (const source of sources) {
let matched = 0;
for (const handler of handlers) if (matches(handler.match, source)) {
handler.apply(buildHandlerContext(source, options), draft);
matched += 1;
}
if (matched === 0 && options.onUnmatchedDecorator) {
let file;
let line;
if (source.location) {
file = source.location.file;
line = source.location.line;
} else {
const sourceFile = node.getSourceFile();
file = sourceFile.fileName;
line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
}
options.onUnmatchedDecorator({
name: source.name,
target: source.target,
host: source.host,
file,
line
}, source);
}
}
}
function applyJsDocHandlers(node, handlers, draft, options) {
if (handlers.length === 0) return;
const sources = buildJsDocSources(node, options);
if (sources.length === 0) return;
for (const source of sources) for (const handler of handlers) if (matchesJsDoc(handler.match, source)) handler.apply(buildJsDocHandlerContext(source, options), draft);
}
//#endregion
//#region src/adapters/typescript/validator/module.ts
function getDeclarationValidators(declaration, name) {
if (!declaration.parent) return {};
const getCommentValue = (comment) => comment && comment.split(" ")[0];
const parameterTags = getSupportedParameterTags();
const tags = getJSDocTags(declaration.parent, (tag) => {
const { comment } = tag;
if (!comment) return false;
const commentValue = getCommentValue(transformJSDocComment(comment));
return parameterTags.some((value) => {
if (value !== tag.tagName.text) return false;
return !(name && name !== commentValue);
});
});
function getErrorMsg(comment, isValue = true) {
if (!comment) return;
if (isValue) {
const indexOf = comment.indexOf(" ");
if (indexOf > 0) return comment.substring(indexOf + 1);
return;
}
return comment;
}
const validators = {};
for (const tag of tags) {
if (!tag.comment) continue;
const name = tag.tagName.text;
const rawComment = transformJSDocComment(tag.comment);
if (!rawComment) continue;
const comment = rawComment.substring(rawComment.indexOf(" ") + 1).trim();
const value = getCommentValue(comment);
switch (name) {
case ValidatorName.UNIQUE_ITEMS:
validators[name] = {
message: getErrorMsg(comment, false),
value: void 0
};
break;
case ValidatorName.MINIMUM:
case ValidatorName.MAXIMUM:
case ValidatorName.MIN_ITEMS:
case ValidatorName.MAX_ITEMS:
case ValidatorName.MIN_LENGTH:
case ValidatorName.MAX_LENGTH:
{
const parsed = Number(value);
if (!Number.isFinite(parsed)) throw new ValidatorError({
message: `@${name} validator expects a numeric value, got '${value}'.`,
code: ValidatorErrorCode.EXPECTED_NUMBER
});
validators[name] = {
message: getErrorMsg(comment),
value: parsed
};
}
break;
case ValidatorName.MIN_DATE:
case ValidatorName.MAX_DATE:
if (typeof value !== "string") throw new ValidatorError({
message: `@${name} validator expects a date string, got '${typeof value}'.`,
code: ValidatorErrorCode.EXPECTED_DATE
});
validators[name] = {
message: getErrorMsg(comment),
value
};
break;
case ValidatorName.PATTERN:
if (typeof value !== "string") throw new ValidatorError({
message: `@${name} validator expects a string pattern, got '${value}'.`,
code: ValidatorErrorCode.EXPECTED_STRING
});
validators[name] = {
message: getErrorMsg(comment),
value: removeSurroundingQuotes(value)
};
break;
default:
if (name.toLowerCase().startsWith("is")) {
const errorMsg = getErrorMsg(comment, false);
if (errorMsg) validators[name] = {
message: errorMsg,
value: void 0
};
}
break;
}
}
return validators;
}
function getSupportedParameterTags() {
return [
"isString",
"isBoolean",
"isInt",
"isLong",
"isFloat",
"isDouble",
"isDate",
"isDateTime",
"minItems",
"maxItems",
"uniqueItems",
"minLength",
"maxLength",
"pattern",
"minimum",
"maximum",
"minDate",
"maxDate"
];
}
function removeSurroundingQuotes(str) {
if (str.startsWith("`") && str.endsWith("`")) return str.substring(1, str.length - 1);
if (str.startsWith("```") && str.endsWith("```")) return str.substring(3, str.length - 3);
return str;
}
//#endregion
//#region src/adapters/typescript/resolver/extension/module.ts
function getNodeExtensions(node, registry) {
const names = namesForMarker(registry, isExtensionMarker);
if (names.size === 0) return [];
const output = [];
for (const name of names) {
const decorators = findDecoratorsByName(node, name);
for (const decorator of decorators) {
const keyArg = decorator.arguments[0];
const valueArg = decorator.arguments[1];
if (!keyArg || keyArg.kind !== "literal" || typeof keyArg.raw !== "string") continue;
if (!valueArg || valueArg.kind === "unresolvable" || typeof valueArg.raw === "undefined") continue;
output.push({
key: keyArg.raw,
value: valueArg.raw
});
}
}
return output;
}
//#endregion
//#region src/adapters/typescript/resolver/sub/array.ts
function resolveArrayType(typeNode, ctx) {
if (!isArrayTypeNode(typeNode)) return;
return {
typeName: TypeName.ARRAY,
elementType: ctx.resolveType(typeNode.elementType, ctx.parentNode, ctx.context)
};
}
//#endregion
//#region src/adapters/typescript/resolver/sub/base.ts
var ResolverBase = class {
hasPublicModifier(node) {
if (!canHaveModifiers(node)) return true;
const modifiers = getModifiers(node);
if (!modifiers) return true;
return modifiers.every((modifier) => modifier.kind !== SyntaxKind.ProtectedKeyword && modifier.kind !== SyntaxKind.PrivateKeyword);
}
hasStaticModifier(node) {
if (!canHaveModifiers(node)) return false;
const modifiers = getModifiers(node);
return modifiers && modifiers.some((modifier) => modifier.kind === SyntaxKind.StaticKeyword);
}
isAccessibleParameter(node) {
if (!canHaveModifiers(node)) return false;
const modifiers = getModifiers(node);
if (!modifiers) return false;
if (modifiers.some((modifier) => modifier.kind === SyntaxKind.PublicKeyword)) return true;
const isReadonly = modifiers.some((modifier) => modifier.kind === SyntaxKind.ReadonlyKeyword);
const isProtectedOrPrivate = modifiers.some((modifier) => modifier.kind === SyntaxKind.ProtectedKeyword || modifier.kind === SyntaxKind.PrivateKeyword);
return isReadonly && !isProtectedOrPrivate;
}
};
//#endregion
//#region src/adapters/typescript/resolver/utils.ts
function getNodeDescription(node, typeChecker) {
if (!hasOwnProperty(node, "name")) return;
const symbol = typeChecker.getSymbolAtLocation(node.name);
if (!symbol) return;
/**
* TODO: Workaround for what seems like a bug in the compiler
* Warrants more investigation and possibly a PR against typescript
*/
if (node.kind === SyntaxKind.Parameter) symbol.flags = 0;
const comments = symbol.getDocumentationComment(typeChecker);
if (comments.length) return displayPartsToString(comments);
}
function toTypeNodeOrFail(typeChecker, ...args) {
const output = typeChecker.typeToTypeNode(...args);
if (typeof output === "undefined") throw new ResolverError("Type could not be transformed to TypeNode.");
return output;
}
//#endregion
//#region src/adapters/typescript/resolver/sub/indexed-access.ts
function resolveIndexedAccessType(typeNode, ctx) {
if (!isIndexedAccessTypeNode(typeNode)) return;
if (typeNode.indexType.kind === SyntaxKind.NumberKeyword || typeNode.indexType.kind === SyntaxKind.StringKeyword) {
const numberIndexType = typeNode.indexType.kind === SyntaxKind.NumberKeyword;
const objectType = ctx.typeChecker.getTypeFromTypeNode(typeNode.objectType);
const type = numberIndexType ? objectType.getNumberIndexType() : objectType.getStringIndexType();
if (type === void 0) throw new ResolverError(`Could not determine ${numberIndexType ? "number" : "string"} index on ${ctx.typeChecker.typeToString(objectType)}`, typeNode);
return ctx.resolveType(toTypeNodeOrFail(ctx.typeChecker, type, void 0, void 0), typeNode, ctx.context, ctx.referencer);
}
if (isLiteralTypeNode(typeNode.indexType) && (isStringLiteral(typeNode.indexType.literal) || isNumericLiteral(typeNode.indexType.literal))) {
const hasType = (node) => node !== void 0 && Object.prototype.hasOwnProperty.call(node, "type");
const symbol = ctx.typeChecker.getPropertyOfType(ctx.typeChecker.getTypeFromTypeNode(typeNode.objectType), typeNode.indexType.literal.text);
if (symbol === void 0) throw new ResolverError(`Could not determine the keys on ${ctx.typeChecker.typeToString(ctx.typeChecker.getTypeFromTypeNode(typeNode.objectType))}`, typeNode);
if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) return ctx.resolveType(symbol.valueDeclaration.type, typeNode, ctx.context, ctx.referencer);
const declaration = ctx.typeChecker.getTypeOfSymbolAtLocation(symbol, typeNode.objectType);
try {
return ctx.resolveType(toTypeNodeOrFail(ctx.typeChecker, declaration, void 0, void 0), typeNode, ctx.context, ctx.referencer);
} catch (err) {
throw new ResolverError(`Could not determine the keys on ${ctx.typeChecker.typeToString(declaration)}`, typeNode, { cause: err });
}
}
}
//#endregion
//#region src/adapters/typescript/resolver/sub/intersection.ts
function resolveIntersectionType(typeNode, ctx) {
if (!isIntersectionTypeNode(typeNode)) return;
const members = typeNode.types.map((type) => ctx.resolveType(type, ctx.parentNode, ctx.context));
return {
typeName: TypeName.INTERSECTION,
members
};
}
//#endregion
//#region src/adapters/typescript/resolver/sub/literal.ts
function resolveLiteralType(typeNode, ctx) {
if (typeNode.kind === SyntaxKind.NullKeyword) return {
typeName: TypeName.ENUM,
members: [null]
};
if (typeNode.kind === SyntaxKind.AnyKeyword || typeNode.kind === SyntaxKind.UnknownKeyword) return { typeName: TypeName.ANY };
if (isLiteralTypeNode(typeNode)) return {
typeName: TypeName.ENUM,
members: [getLiteralValue(typeNode)]
};
if (typeNode.kind === SyntaxKind.TemplateLiteralType) {
const type = ctx.typeChecker.getTypeFromTypeNode(ctx.referencer || typeNode);
if (type.isUnion() && type.types.every((t) => t.isStringLiteral())) return {
typeName: TypeName.ENUM,
members: type.types.map((t) => t.value)
};
throw new ResolverError(`Could not resolve type: ${ctx.typeChecker.typeToString(ctx.typeChecker.getTypeFromTypeNode(typeNode), typeNode)}`, typeNode);
}
if (isParenthesizedTypeNode(typeNode)) return ctx.resolveType(typeNode.type, typeNode, ctx.context, ctx.referencer);
if (typeNode.kind === SyntaxKind.ObjectKeyword || isFunctionTypeNode(typeNode)) return { typeName: TypeName.OBJECT };
}
function getLiteralValue(typeNode) {
let value;
switch (typeNode.literal.kind) {
case SyntaxKind.TrueKeyword:
value = true;
break;
case SyntaxKind.FalseKeyword:
value = false;
break;
case SyntaxKind.StringLiteral:
value = typeNode.literal.text;
break;
case SyntaxKind.NumericLiteral:
value = Number.parseFloat(typeNode.literal.text);
break;
case SyntaxKind.NullKeyword:
value = null;
break;
default: if (Object.prototype.hasOwnProperty.call(typeNode.literal, "text")) value = typeNode.literal.text;
else throw new ResolverError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`, typeNode.literal);
}
return value;
}
//#endregion
//#region src/adapters/typescript/resolver/sub/mapped.ts
function resolveMappedType(typeNode, ctx) {
if (!isMappedTypeNode(typeNode) || !ctx.referencer) return;
const type = ctx.typeChecker.getTypeFromTypeNode(ctx.referencer);
const mappedTypeNode = typeNode;
const { typeChecker } = ctx;
const getDeclaration = (prop) => prop.declarations && prop.declarations[0];
const isIgnored = (prop) => {
const declaration = getDeclaration(prop);
if (prop.getJsDocTags().findIndex((tag) => tag.name === "ignore") >= 0) return true;
return !!declaration && !isPropertyDeclaration(declaration) && !isPropertySignature(declaration) && !isParameter(declaration);
};
const properties = type.getProperties().filter((property) => !isIgnored(property)).map((property) => {
const declaration = getDeclaration(property);
const overrideToken = mappedTypeNode.questionToken?.kind === SyntaxKind.PlusToken ? factory.createToken(SyntaxKind.QuestionToken) : mappedTypeNode.questionToken;
if (declaration && isPropertySignature(declaration)) return {
...ctx.propertyFromSignature(declaration, overrideToken),
name: property.getName()
};
if (declaration && (isPropertyDeclaration(declaration) || isParameter(declaration))) return {
...ctx.propertyFromDeclaration(declaration, overrideToken),
name: property.getName()
};
let required = (property.flags & SymbolFlags.Optional) === 0;
const typeNode2 = toTypeNodeOrFail(typeChecker, typeChecker.getTypeOfSymbolAtLocation(property, typeNode), void 0, NodeBuilderFlags.NoTruncation);
if (mappedTypeNode.questionToken && mappedTypeNode.questionToken.kind === SyntaxKind.MinusToken) required = true;
else if (mappedTypeNode.questionToken && (mappedTypeNode.questionToken.kind === SyntaxKind.QuestionToken || mappedTypeNode.questionToken.kind === SyntaxKind.PlusToken)) required = false;
return {
name: property.getName(),
required,
deprecated: false,
type: ctx.resolveType(typeNode2, typeNode, ctx.context, ctx.referencer),
validators: {}
};
});
return {
typeName: TypeName.NESTED_OBJECT_LITERAL,
properties
};
}
//#endregion
//#region src/adapters/typescript/resolver/sub/object-literal.ts
function resolveObjectLiteralType(typeNode, ctx) {
if (!isTypeLiteralNode(typeNode)) return;
const properties = typeNode.members.filter((member) => isPropertySignature(member)).reduce((res, propertySignature) => {
if (!propertySignature.type) throw new ResolverError("No valid type found for property declaration.", propertySignature);
const type = ctx.resolveType(propertySignature.type, propertySignature, ctx.context);
return [{
deprecated: hasJSDocTag(propertySignature, "deprecated"),
example: ctx.getNodeExample(propertySignature),
extensions: ctx.getNodeExtensions(propertySignature),
default: getJSDocTagComment(propertySignature, "default"),
description: ctx.getNodeDescription(propertySignature),
format: getNodeFormat(propertySignature),
name: getPropertyName(propertySignature),
required: !propertySignature.questionToken,
type,
validators: getDeclarationValidators(propertySignature) || {}
}, ...res];
}, []);
const indexMember = typeNode.members.find((member) => isIndexSignatureDeclaration(member));
let additionalType;
if (indexMember) {
const indexSignatureDeclaration = indexMember;
if (!isStringType(ctx.resolveType(indexSignatureDeclaration.parameters[0].type, ctx.parentNode, ctx.context))) throw new ResolverError("Only string indexes are supported.", typeNode);
additionalType = ctx.resolveType(indexSignatureDeclaration.type, ctx.parentNode, ctx.context);
}
return {
additionalProperties: indexMember && additionalType,
typeName: TypeName.NESTED_OBJECT_LITERAL,
properties
};
}
function getNodeFormat(node) {
return getJSDocTagComment(node, "format");
}
function getPropertyName(node) {
if (isIdentifier(node.name)) return node.name.text;
if (isStringLiteral(node.name) || isNumericLiteral(node.name)) return node.name.text;
return node.name.getText();
}
//#endregion
//#region src/adapters/typescript/resolver/sub/primitive.ts
const NUMERIC_KIND_TO_TYPE_NAME = {
[NumericKind.Int]: TypeName.INTEGER,
[NumericKind.Long]: TypeName.LONG,
[NumericKind.Float]: TypeName.FLOAT,
[NumericKind.Double]: TypeName.DOUBLE
};
var PrimitiveResolver = class {
registry;
constructor(registry) {
this.registry = registry;
}
resolve(node, parentNode) {
const resolved = this.resolveSyntaxKind(node.kind);
if (resolved) {
if (resolved === "string") return { typeName: TypeName.STRING };
if (resolved === "void") return { typeName: TypeName.VOID };
if (resolved === "boolean") return { typeName: TypeName.BOOLEAN };
if (resolved === "undefined") return { typeName: TypeName.UNDEFINED };
if (resolved === "null") return;
if (resolved === "never") return { typeName: TypeName.NEVER };
if (resolved === "bigint") return { typeName: TypeName.BIGINT };
if (resolved === "number") {
if (!parentNode) return { typeName: TypeName.DOUBLE };
const presentJsDocTags = new Set(getJSDocTagNames(parentNode).map((tag) => tag.toLowerCase()));
for (const kind of [
NumericKind.Int,
NumericKind.Long,
NumericKind.Float,
NumericKind.Double
]) {
const matchKind = (m) => numericMarkerKind(m) === kind;
const decoratorNames = namesForMarker(this.registry, matchKind);
for (const name of decoratorNames) if (hasDecoratorNamed(parentNode, name)) return { typeName: NUMERIC_KIND_TO_TYPE_NAME[kind] };
const jsDocTags = tagsForMarker(this.registry, matchKind);
for (const tag of jsDocTags) if (presentJsDocTags.has(tag.toLowerCase())) return { typeName: NUMERIC_KIND_TO_TYPE_NAME[kind] };
}
return { typeName: TypeName.DOUBLE };
}
}
}
resolveSyntaxKind(syntaxKind) {
switch (syntaxKind) {
case SyntaxKind.StringKeyword: return "string";
case SyntaxKind.BooleanKeyword: return "boolean";
case SyntaxKind.VoidKeyword: return "void";
case SyntaxKind.UndefinedKeyword: return "undefined";
case SyntaxKind.NullKeyword: return "null";
case SyntaxKind.NumberKeyword: return "number";
case SyntaxKind.BigIntKeyword: return "bigint";
case SyntaxKind.NeverKeyword: return "never";
default: return;
}
}
};
//#endregion
//#region src/adapters/typescript/resolver/sub/reference.ts
var ReferenceResolver = class extends ResolverBase {
typeChecker;
constructor(typeChecker) {
super();
this.typeChecker = typeChecker;
}
merge(referenceTypes) {
if (referenceTypes.length === 1) return referenceTypes[0];
if (referenceTypes.every((refType) => refType.refName === TypeName.REF_ENUM)) return this.mergeManyRefEnums(referenceTypes);
if (referenceTypes.every((refType) => refType.refName === TypeName.REF_OBJECT)) return this.mergeManyRefObjects(referenceTypes);
throw new ResolverError(`These resolved type merge rules are not defined: ${JSON.stringify(referenceTypes)}`);
}
mergeManyRefEnums(many) {
let merged = this.mergeRefEnums(many[0], many[1]);
for (let i = 2; i < many.length; ++i) merged = this.mergeRefEnums(merged, many[i]);
return merged;
}
mergeRefEnums(first, second) {
let description;
if (first.description || second.description) if (!first.description) description = second.description;
else if (!second.description) description = first.description;
else description = `${first.description}\n${second.description}`;
return {
typeName: TypeName.REF_ENUM,
example: first.example || second.example,
description,
members: [...first.members || [], ...second.members || []],
memberNames: [...first.memberNames || [], ...second.memberNames || []],
refName: first.refName,
deprecated: first.deprecated || second.deprecated
};
}
mergeManyRefObjects(many) {
let merged = this.mergeRefObject(many[0], many[1]);
for (let i = 2; i < many.length; ++i) merged = this.mergeRefObject(merged, many[i]);
return merged;
}
mergeRefObject(first, second) {
let description;
if (first.description || second.description) if (!first.description) description = second.description;
else if (!second.description) description = first.description;
else description = `${first.description}\n${second.description}`;
const properties = [...first.properties, ...second.properties.filter((prop) => first.properties.every((firstProp) => firstProp.name !== prop.name))];
let additionalProperties;
if (first.additionalProperties || second.additionalProperties) if (!first.additionalProperties) additionalProperties = second.additionalProperties;
else if (!second.additionalProperties) additionalProperties = first.additionalProperties;
else additionalProperties = {
typeName: TypeName.UNION,
members: [first.additionalProperties, second.additionalProperties]
};
return {
typeName: TypeName.REF_OBJECT,
description,
properties,
additionalProperties,
refName: first.refName,
deprecated: first.deprecated || second.deprecated,
example: first.example || second.example
};
}
transformEnum(declaration, enumName) {
const isNotUndefined = (item) => item !== void 0;
const enums = declaration.members.map(this.typeChecker.getConstantValue.bind(this.typeChecker)).filter(isNotUndefined);
const enumNames = declaration.members.map((e) => e.name.getText()).filter(isNotUndefined);
return {
typeName: TypeName.REF_ENUM,
description: getNodeDescription(declaration, this.typeChecker),
members: enums,
memberNames: enumNames,
refName: enumName,
deprecated: hasJSDocTag(declaration, "deprecated")
};
}
transformEnumMember(declaration, enumName) {
return {
typeName: TypeName.REF_ENUM,
refName: enumName,
members: [this.typeChecker.getConstantValue(declaration)],
memberNames: [declaration.name.getText()],
deprecated: hasJSDocTag(declaration, "deprecated")
};
}
};
//#endregion
//#region src/adapters/typescript/resolver/sub/tuple.ts
function resolveTupleType(typeNode, ctx) {
if (!isTupleTypeNode(typeNode)) return;
const elements = typeNode.elements.map((element) => {
const isNamed = isNamedTupleMember(element);
const actualType = isNamed ? element.type : element;
return {
type: ctx.resolveType(actualType, ctx.parentNode, ctx.context),
...isNamed && { name: element.name.text }
};
});
return {
typeName: TypeName.TUPLE,
elements
};
}
//#endregion
//#region src/adapters/typescript/resolver/sub/type-operator.ts
function resolveTypeOperatorType(typeNode, ctx) {
if (!isTypeOperatorNode(typeNode)) return;
if (typeNode.operator === SyntaxKind.KeyOfKeyword) {
const type = ctx.typeChecker.getTypeFromTypeNode(typeNode);
try {
return ctx.resolveType(toTypeNodeOrFail(ctx.typeChecker, type, void 0, NodeBuilderFlags.NoTruncation), typeNode, ctx.context, ctx.referencer);
} catch (err) {
throw new ResolverError(`Could not determine the keys on ${ctx.typeChecker.typeToString(ctx.typeChecker.getTypeFromTypeNode(typeNode.type))}`, typeNode, { cause: err });
}
}
if (typeNode.operator === SyntaxKind.ReadonlyKeyword) return ctx.resolveType(typeNode.type, typeNode, ctx.context, ctx.referencer);
}
//#endregion
//#region src/adapters/typescript/resolver/sub/union.ts
function resolveUnionType(typeNode, ctx) {
if (!isUnionTypeNode(typeNode)) return;
const members = typeNode.types.map((type) => ctx.resolveType(type, ctx.parentNode, ctx.context));
return {
typeName: TypeName.UNION,
members
};
}
//#endregion
//#region src/adapters/typescript/resolver/module.ts
var TypeNodeResolver = class TypeNodeResolver extends ResolverBase {
static MAX_DEPTH = 50;
typeNode;
current;
parentNode;
context;
referencer;
depth;
primitiveResolver;
referenceResolver;
constructor(typeNode, current, parentNode, context, referencer, depth) {
super();
this.typeNode = typeNode;
this.current = current;
this.parentNode = parentNode;
this.context = context || {};
this.referencer = referencer;
this.depth = depth ?? 0;
this.primitiveResolver = new PrimitiveResolver(current.registry);
this.referenceResolver = new ReferenceResolver(current.typeChecker);
}
/**
* @deprecated Use resolverCache.clear() on the context instead.
* Kept for backward compatibility — no-ops since cache is now instance-scoped.
*/
static clearCache() {}
resolve() {
if (this.depth > TypeNodeResolver.MAX_DEPTH) throw new ResolverError(`Type resolution exceeded maximum depth of ${TypeNodeResolver.MAX_DEPTH}. This usually indicates deeply nested or circular generics.`, this.typeNode);
const ctx = this.createSubResolverContext();
const result = this.primitiveResolver.resolve(this.typeNode, this.parentNode) ?? resolveLiteralType(this.typeNode, ctx) ?? resolveArrayType(this.typeNode, ctx) ?? resolveUnionType(this.typeNode, ctx) ?? resolveIntersectionType(this.typeNode, ctx) ?? resolveObjectLiteralType(this.typeNode, ctx) ?? resolveTupleType(this.typeNode, ctx) ?? resolveMappedType(this.typeNode, ctx) ?? this.resolveConditionalType() ?? resolveTypeOperatorType(this.typeNode, ctx) ?? resolveIndexedAccessType(this.typeNode, ctx) ?? this.resolveTypeReference();
if (!result) this.throwUnknownType();
return result;
}
createSubResolverContext() {
return {
typeChecker: this.current.typeChecker,
current: this.current,
parentNode: this.parentNode,
context: this.context,
referencer: this.referencer,
resolveType: (typeNode, parentNode, context, referencer) => this.resolveNestedType(typeNode, parentNode, context, referencer),
propertyFromSignature: (sig, overrideToken) => this.propertyFromSignature(sig, overrideToken),
propertyFromDeclaration: (decl, overrideToken) => this.propertyFromDeclaration(decl, overrideToken),
getNodeDescription: (node) => this.getNodeDescription(node),
getNodeExample: (node) => this.getNodeExample(node),
getNodeExtensions: (node) => this.getNodeExtensions(node)
};
}
resolveNestedType(typeNode, parentNode, context, referencer) {
return new TypeNodeResolver(typeNode, this.current, parentNode, context, referencer, this.depth + 1).resolve();
}
throwUnknownType() {
throw new ResolverError(`Unknown type: ${SyntaxKind[this.typeNode.kind]}`, this.typeNode);
}
resolveConditionalType() {
if (!isConditionalTypeNode(this.typeNode)) return;
if (Object.keys(this.context).length > 0 && this.referencer) return this.resolveTypeViaChecker(this.referencer);
return this.resolveTypeViaChecker(this.typeNode);
}
resolveTypeReference() {
if (this.typeNode.kind !== SyntaxKind.TypeReference) return;
const typeReference = this.typeNode;
if (typeReference.typeName.kind === SyntaxKind.Identifier) {
if (typeReference.typeName.text === "Record" && typeReference.typeArguments && typeReference.typeArguments[1]) return {
additionalProperties: this.resolveNestedType(typeReference.typeArguments[1], this.parentNode, this.context),
typeName: TypeName.NESTED_OBJECT_LITERAL,
properties: []
};
const specialReference = TypeNodeResolver.resolveSpecialReference(typeReference.typeName);
if (typeof specialReference !== "undefined") return specialReference;
if (typeReference.typeName.text === "Date") return this.getDateType(this.parentNode);
if (typeReference.typeName.text === "Buffer" || typeReference.typeName.text === "Readable") return { typeName: TypeName.BUFFER };
if (typeReference.typeName.text === "Array" && typeReference.typeArguments?.[0]) return {
typeName: TypeName.ARRAY,
elementType: this.resolveNestedType(typeReference.typeArguments[0], this.parentNode, this.context)
};
if (typeReference.typeName.text === "Promise" && typeReference.typeArguments?.length === 1 && typeReference.typeArguments[0]) return this.resolveNestedType(typeReference.typeArguments[0], this.parentNode, this.context);
if (typeReference.typeName.text === "String") return { typeName: TypeName.STRING };
const contextual = this.context[typeReference.typeName.text];
if (contextual) return this.resolveNestedType(contextual, this.parentNode, this.context);
if (TypeNodeResolver.isCheckerResolvableUtilityType(typeReference.typeName.text)) return this.resolveUtilityTypeViaChecker(typeReference);
}
try {
const referenceType = this.getReferenceType(typeReference);
this.current.addReferenceType(referenceType);
return referenceType;
} catch (err) {
try {
return this.resolveTypeViaChecker(typeReference);
} catch {
throw err;
}
}
}
static CHECKER_RESOLVABLE_UTILITY_TYPES = new Set([
UtilityTypeName.NON_NULLABLE,
UtilityTypeName.OMIT,
UtilityTypeName.PARTIAL,
UtilityTypeName.READONLY,
UtilityTypeName.REQUIRED,
UtilityTypeName.PICK,
UtilityTypeName.EXTRACT,
UtilityTypeName.EXCLUDE,
UtilityTypeName.RETURN_TYPE,
UtilityTypeName.PARAMETERS,
UtilityTypeName.AWAITED,
UtilityTypeName.INSTANCE_TYPE,
UtilityTypeName.CONSTRUCTOR_PARAMETERS
]);
static isCheckerResolvableUtilityType(name) {
return TypeNodeResolver.CHECKER_RESOLVABLE_UTILITY_TYPES.has(name);
}
resolveUtilityTypeViaChecker(typeReference) {
if (this.hasUnboundContextArgs(typeReference) && this.referencer) return this.resolveTypeViaChecker(this.referencer);
return this.resolveTypeViaChecker(typeReference);
}
/**
* Shared checker delegation: resolves a type node by letting the TS
* type checker evaluate it, converting back to a TypeNode, and
* recursively resolving the result.
*/
resolveTypeViaChecker(typeNode) {
const type = this.current.typeChecker.getTypeFromTypeNode(typeNode);
const resolvedTypeNode = this.current.typeChecker.typeToTypeNode(type, void 0, NodeBuilderFlags.NoTruncation | NodeBuilderFlags.InTypeAlias);
if (!resolvedTypeNode) return {
typeName: TypeName.TUPLE,
elements: []
};
return this.resolveNestedType(resolvedTypeNode, this.parentNode, this.context);
}
/