persona-community-5/.pnpm-store/v3/files/d9/365e5464ca7df7615f643f49b4572f9f4fcacd993b83c9bf79e2d8048de35fb06ea32440f9e107b2927d8fed2323e027e14da24388451a6e2be56851ad45f3
rdev-worker a1d0d1bf1c
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /implement-feature community-ui --requirements 'Build the React commu...
2026-02-24 08:22:30 +00:00

738 lines
27 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { parseRef } from "@redocly/openapi-core/lib/ref-utils.js";
import ts from "typescript";
import {
addJSDocComment,
BOOLEAN,
NEVER,
NULL,
NUMBER,
oapiRef,
QUESTION_TOKEN,
STRING,
tsArrayLiteralExpression,
tsEnum,
tsIntersection,
tsIsPrimitive,
tsLiteral,
tsModifiers,
tsNullable,
tsOmit,
tsPropertyIndex,
tsRecord,
tsUnion,
tsWithRequired,
UNDEFINED,
UNKNOWN,
} from "../lib/ts.js";
import { createDiscriminatorProperty, createRef, getEntries } from "../lib/utils.js";
import type { ReferenceObject, SchemaObject, TransformNodeOptions } from "../types.js";
/**
* Transform SchemaObject nodes (4.8.24)
* @see https://spec.openapis.org/oas/v3.1.0#schema-object
*/
export default function transformSchemaObject(
schemaObject: SchemaObject | ReferenceObject,
options: TransformNodeOptions,
fromAdditionalProperties = false,
): ts.TypeNode {
const type = transformSchemaObjectWithComposition(schemaObject, options, fromAdditionalProperties);
if (typeof options.ctx.postTransform === "function") {
const postTransformResult = options.ctx.postTransform(type, options);
if (postTransformResult) {
return postTransformResult;
}
}
return type;
}
/**
* Transform SchemaObjects
*/
export function transformSchemaObjectWithComposition(
schemaObject: SchemaObject | ReferenceObject,
options: TransformNodeOptions,
fromAdditionalProperties = false,
): ts.TypeNode {
/**
* Unexpected types & edge cases
*/
// missing/falsy type returns `never`
if (!schemaObject) {
return NEVER;
}
// `true` returns `unknown` (this exists, but is untyped)
if ((schemaObject as unknown) === true) {
return UNKNOWN;
}
// for any other unexpected type, throw error
if (Array.isArray(schemaObject) || typeof schemaObject !== "object") {
throw new Error(
`Expected SchemaObject, received ${Array.isArray(schemaObject) ? "Array" : typeof schemaObject} at ${options.path}`,
);
}
/**
* ReferenceObject
*/
if ("$ref" in schemaObject) {
return oapiRef(schemaObject.$ref);
}
/**
* const (valid for any type)
*/
if (schemaObject.const !== null && schemaObject.const !== undefined) {
return tsLiteral(schemaObject.const);
}
/**
* enum (non-objects)
* note: enum is valid for any type, but for objects, handle in oneOf below
*/
if (
Array.isArray(schemaObject.enum) &&
(!("type" in schemaObject) || schemaObject.type !== "object") &&
!("properties" in schemaObject) &&
!("additionalProperties" in schemaObject)
) {
// hoist enum to top level if string/number enum and option is enabled
if (shouldTransformToTsEnum(options, schemaObject)) {
let enumName = parseRef(options.path ?? "").pointer.join("/");
// allow #/components/schemas to have simpler names
enumName = enumName.replace("components/schemas", "");
const metadata = schemaObject.enum.map((_, i) => ({
name: schemaObject["x-enum-varnames"]?.[i] ?? schemaObject["x-enumNames"]?.[i],
description: schemaObject["x-enum-descriptions"]?.[i] ?? schemaObject["x-enumDescriptions"]?.[i],
}));
// enums can contain null values, but dont want to output them
let hasNull = false;
const validSchemaEnums = schemaObject.enum.filter((enumValue) => {
if (enumValue === null) {
hasNull = true;
return false;
}
return true;
});
const enumType = tsEnum(enumName, validSchemaEnums as (string | number)[], metadata, {
shouldCache: options.ctx.dedupeEnums,
export: true,
// readonly: TS enum do not support the readonly modifier
});
if (!options.ctx.injectFooter.includes(enumType)) {
options.ctx.injectFooter.push(enumType);
}
const ref = ts.factory.createTypeReferenceNode(enumType.name);
return hasNull ? tsUnion([ref, NULL]) : ref;
}
const enumType = schemaObject.enum.map(tsLiteral);
if ((Array.isArray(schemaObject.type) && schemaObject.type.includes("null")) || schemaObject.nullable) {
enumType.push(NULL);
}
const unionType = tsUnion(enumType);
// hoist array with valid enum values to top level if string/number enum and option is enabled
if (options.ctx.enumValues && schemaObject.enum.every((v) => typeof v === "string" || typeof v === "number")) {
const parsed = parseRef(options.path ?? "");
let enumValuesVariableName = parsed.pointer.join("/");
// allow #/components/schemas to have simpler names
enumValuesVariableName = enumValuesVariableName.replace("components/schemas", "");
enumValuesVariableName = `${enumValuesVariableName}Values`;
// build a ref path for the type that ignores union indices (anyOf/oneOf) so
// type references remain stable even when names include union positions
const cleanedPointer: string[] = [];
// Track ALL properties after a oneOf/anyOf that need Extract<> narrowing.
// We apply Extract<> before EVERY property access after a union index because:
// - When the property exists on ALL variants, Extract<> is a no-op (returns same type)
// - When the property only exists on SOME variants, it correctly narrows the union
// - When both variants have same property name but different inner schemas,
// we still narrow at each level to handle nested unions correctly
// This robust approach handles both simple and complex union structures.
const extractProperties: string[] = [];
for (let i = 0; i < parsed.pointer.length; i++) {
// Example: #/paths/analytics/data/get/responses/400/content/application/json/anyOf/0/message
const segment = parsed.pointer[i];
if ((segment === "anyOf" || segment === "oneOf") && i < parsed.pointer.length - 1) {
const next = parsed.pointer[i + 1];
if (/^\d+$/.test(next)) {
// If we encounter something like "anyOf/0", we want to skip that part of the path
i++;
// Collect ALL remaining segments after the union index.
// Each one will be wrapped with Extract<> to safely narrow the type
// at each level, handling both top-level and nested union variants.
const remainingSegments = parsed.pointer.slice(i + 1);
for (const seg of remainingSegments) {
// Skip union keywords and indices, only add actual property names
if (seg !== "anyOf" && seg !== "oneOf" && !/^\d+$/.test(seg)) {
extractProperties.push(seg);
}
}
continue;
}
}
cleanedPointer.push(segment);
}
const cleanedRefPath = createRef(cleanedPointer);
const enumValuesArray = tsArrayLiteralExpression(
enumValuesVariableName,
// If fromAdditionalProperties is true we are dealing with a record type and we should append [string] to the generated type
fromAdditionalProperties
? ts.factory.createIndexedAccessTypeNode(
oapiRef(cleanedRefPath, undefined, { deep: true, extractProperties }),
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("string")),
)
: oapiRef(cleanedRefPath, undefined, { deep: true, extractProperties }),
schemaObject.enum as (string | number)[],
{
export: true,
readonly: true,
injectFooter: options.ctx.injectFooter,
},
);
options.ctx.injectFooter.push(enumValuesArray);
}
return unionType;
}
/**
* Object + composition (anyOf/allOf/oneOf) types
*/
/** Collect oneOf/anyOf */
function collectUnionCompositions(items: (SchemaObject | ReferenceObject)[], unionKey: "anyOf" | "oneOf") {
const output: ts.TypeNode[] = [];
for (const [index, item] of items.entries()) {
output.push(
transformSchemaObject(item, {
...options,
// include index in path so generated names from nested enums/enumValues are unique
path: createRef([options.path, unionKey, String(index)]),
}),
);
}
return output;
}
/** Collect allOf with Omit<> for discriminators */
function collectAllOfCompositions(items: (SchemaObject | ReferenceObject)[], required?: string[]): ts.TypeNode[] {
const output: ts.TypeNode[] = [];
for (const item of items) {
let itemType: ts.TypeNode;
// if this is a $ref, use WithRequired<X, Y> if parent specifies required properties
// (but only for valid keys)
if ("$ref" in item) {
itemType = transformSchemaObject(item, options);
const resolved = options.ctx.resolve<SchemaObject>(item.$ref);
// make keys required, if necessary
if (
resolved &&
typeof resolved === "object" &&
"properties" in resolved &&
// we have already handled this item (discriminator property was already added as required)
!options.ctx.discriminators.refsHandled.includes(item.$ref)
) {
// add WithRequired<X, Y> if necessary
const validRequired = (required ?? []).filter((key) => !!resolved.properties?.[key]);
if (validRequired.length) {
itemType = tsWithRequired(itemType, validRequired, options.ctx.injectFooter);
}
}
}
// otherwise, if this is a schema object, combine parent `required[]` with its own, if any
else {
const itemRequired = [...(required ?? [])];
if (typeof item === "object" && Array.isArray(item.required)) {
itemRequired.push(...item.required);
}
itemType = transformSchemaObject({ ...item, required: itemRequired }, options);
}
const discriminator =
("$ref" in item && options.ctx.discriminators.objects[item.$ref]) || (item as any).discriminator;
if (discriminator) {
output.push(tsOmit(itemType, [discriminator.propertyName]));
} else {
output.push(itemType);
}
}
return output;
}
// compile final type
let finalType: ts.TypeNode | undefined;
// core + allOf: intersect
const coreObjectType = transformSchemaObjectCore(schemaObject, options);
const allOfType = collectAllOfCompositions(schemaObject.allOf ?? [], schemaObject.required);
if (coreObjectType || allOfType.length) {
const allOf: ts.TypeNode | undefined = allOfType.length ? tsIntersection(allOfType) : undefined;
finalType = tsIntersection([...(coreObjectType ? [coreObjectType] : []), ...(allOf ? [allOf] : [])]);
}
// anyOf: union
// (note: this may seem counterintuitive, but as TypeScripts unions are not true XORs, they mimic behavior closer to anyOf than oneOf)
const anyOfType = collectUnionCompositions(schemaObject.anyOf ?? [], "anyOf");
if (anyOfType.length) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...anyOfType]);
}
// oneOf: union (within intersection with other types, if any)
const oneOfType = collectUnionCompositions(
schemaObject.oneOf ||
("type" in schemaObject &&
schemaObject.type === "object" &&
(schemaObject.enum as (SchemaObject | ReferenceObject)[])) ||
[],
"oneOf",
);
if (oneOfType.length) {
// note: oneOf is the only type that may include primitives
if (oneOfType.every(tsIsPrimitive)) {
finalType = tsUnion([...(finalType ? [finalType] : []), ...oneOfType]);
} else {
finalType = tsIntersection([...(finalType ? [finalType] : []), tsUnion(oneOfType)]);
}
}
// When no final type can be generated, fall back to unknown type (or related variants)
if (!finalType) {
if ("type" in schemaObject) {
finalType = tsRecord(STRING, options.ctx.emptyObjectsUnknown ? UNKNOWN : NEVER);
} else {
finalType = UNKNOWN;
}
}
if (finalType !== UNKNOWN && schemaObject.nullable) {
finalType = tsNullable([finalType]);
}
return finalType;
}
/**
* Check if the given OAPI enum should be transformed to a TypeScript enum
*/
function shouldTransformToTsEnum(options: TransformNodeOptions, schemaObject: SchemaObject): boolean {
// Enum conversion not enabled or no enum present
if (!options.ctx.enum || !schemaObject.enum) {
return false;
}
// Enum must have string, number or null values
if (!schemaObject.enum.every((v) => ["string", "number", null].includes(typeof v))) {
return false;
}
// If conditionalEnums is enabled, only convert if x-enum-* metadata is present
if (options.ctx.conditionalEnums) {
const hasEnumMetadata =
Array.isArray(schemaObject["x-enum-varnames"]) ||
Array.isArray(schemaObject["x-enumNames"]) ||
Array.isArray(schemaObject["x-enum-descriptions"]) ||
Array.isArray(schemaObject["x-enumDescriptions"]);
if (!hasEnumMetadata) {
return false;
}
}
return true;
}
/**
* Handle SchemaObject minus composition (anyOf/allOf/oneOf)
*/
function transformSchemaObjectCore(schemaObject: SchemaObject, options: TransformNodeOptions): ts.TypeNode | undefined {
if ("type" in schemaObject && schemaObject.type) {
if (typeof options.ctx.transform === "function") {
const result = options.ctx.transform(schemaObject, options);
if (result && typeof result === "object") {
if ("schema" in result) {
if (result.questionToken) {
return ts.factory.createUnionTypeNode([result.schema, UNDEFINED]);
} else {
return result.schema;
}
} else {
return result;
}
}
}
// primitives
// type: null
if (schemaObject.type === "null") {
return NULL;
}
// type: string
if (schemaObject.type === "string") {
return STRING;
}
// type: number / type: integer
if (schemaObject.type === "number" || schemaObject.type === "integer") {
return NUMBER;
}
// type: boolean
if (schemaObject.type === "boolean") {
return BOOLEAN;
}
// type: array (with support for tuples)
if (schemaObject.type === "array") {
// default to `unknown[]`
let itemType: ts.TypeNode = UNKNOWN;
// tuple type
if (schemaObject.prefixItems || Array.isArray(schemaObject.items)) {
const prefixItems = schemaObject.prefixItems ?? (schemaObject.items as (SchemaObject | ReferenceObject)[]);
itemType = ts.factory.createTupleTypeNode(prefixItems.map((item) => transformSchemaObject(item, options)));
}
// standard array type
else if (schemaObject.items) {
if (hasKey(schemaObject.items, "type") && schemaObject.items.type === "array") {
itemType = ts.factory.createArrayTypeNode(transformSchemaObject(schemaObject.items, options));
} else {
itemType = transformSchemaObject(schemaObject.items, options);
}
}
const min: number =
typeof schemaObject.minItems === "number" && schemaObject.minItems >= 0 ? schemaObject.minItems : 0;
const max: number | undefined =
typeof schemaObject.maxItems === "number" && schemaObject.maxItems >= 0 && min <= schemaObject.maxItems
? schemaObject.maxItems
: undefined;
const estimateCodeSize = typeof max !== "number" ? min : (max * (max + 1) - min * (min - 1)) / 2;
if (
options.ctx.arrayLength &&
(min !== 0 || max !== undefined) &&
estimateCodeSize < 30 // "30" is an arbitrary number but roughly around when TS starts to struggle with tuple inference in practice
) {
if (min === max) {
const elements: ts.TypeNode[] = [];
for (let i = 0; i < min; i++) {
elements.push(itemType);
}
return tsUnion([ts.factory.createTupleTypeNode(elements)]);
} else if ((schemaObject.maxItems as number) > 0) {
// if maxItems is set, then return a union of all permutations of possible tuple types
const members: ts.TypeNode[] = [];
// populate 1 short of min …
for (let i = 0; i <= (max ?? 0) - min; i++) {
const elements: ts.TypeNode[] = [];
for (let j = min; j < i + min; j++) {
elements.push(itemType);
}
members.push(ts.factory.createTupleTypeNode(elements));
}
return tsUnion(members);
}
// if maxItems not set, then return a simple tuple type the length of `min`
else {
const elements: ts.TypeNode[] = [];
for (let i = 0; i < min; i++) {
elements.push(itemType);
}
elements.push(ts.factory.createRestTypeNode(ts.factory.createArrayTypeNode(itemType)));
return ts.factory.createTupleTypeNode(elements);
}
}
const finalType =
ts.isTupleTypeNode(itemType) || ts.isArrayTypeNode(itemType)
? itemType
: ts.factory.createArrayTypeNode(itemType); // wrap itemType in array type, but only if not a tuple or array already
return options.ctx.immutable
? ts.factory.createTypeOperatorNode(ts.SyntaxKind.ReadonlyKeyword, finalType)
: finalType;
}
// polymorphic, or 3.1 nullable
if (Array.isArray(schemaObject.type) && !Array.isArray(schemaObject)) {
// skip any primitive types that appear in oneOf as well
const uniqueTypes: ts.TypeNode[] = [];
if (Array.isArray(schemaObject.oneOf)) {
for (const t of schemaObject.type) {
if (
(t === "boolean" || t === "string" || t === "number" || t === "integer" || t === "null") &&
schemaObject.oneOf.find((o) => typeof o === "object" && "type" in o && o.type === t)
) {
continue;
}
uniqueTypes.push(
t === "null" || t === null
? NULL
: transformSchemaObject(
{ ...schemaObject, type: t, oneOf: undefined } as SchemaObject, // dont stack oneOf transforms
options,
),
);
}
} else {
for (const t of schemaObject.type) {
if (t === "null" || t === null) {
uniqueTypes.push(NULL);
} else {
uniqueTypes.push(transformSchemaObject({ ...schemaObject, type: t } as SchemaObject, options));
}
}
}
return tsUnion(uniqueTypes);
}
}
// type: object
const coreObjectType: ts.TypeElement[] = [];
// discriminators: explicit mapping on schema object
for (const k of ["allOf", "anyOf"] as const) {
if (!schemaObject[k]) {
continue;
}
// for all magic inheritance, we will have already gathered it into
// ctx.discriminators. But stop objects from referencing their own
// discriminator meant for children (!schemaObject.discriminator)
// and don't add discriminator properties if we already added/patched
// them (options.ctx.discriminators.refsHandled.includes(options.path!).
const discriminator =
!schemaObject.discriminator &&
!options.ctx.discriminators.refsHandled.includes(options.path ?? "") &&
options.ctx.discriminators.objects[options.path ?? ""];
if (discriminator) {
coreObjectType.unshift(
createDiscriminatorProperty(discriminator, {
path: options.path ?? "",
readonly: options.ctx.immutable,
}),
);
break;
}
}
if (
("properties" in schemaObject && schemaObject.properties && Object.keys(schemaObject.properties).length) ||
("additionalProperties" in schemaObject && schemaObject.additionalProperties) ||
("patternProperties" in schemaObject && schemaObject.patternProperties) ||
("$defs" in schemaObject && schemaObject.$defs)
) {
// properties
if (Object.keys(schemaObject.properties ?? {}).length) {
for (const [k, v] of getEntries(schemaObject.properties ?? {}, options.ctx)) {
if ((typeof v !== "object" && typeof v !== "boolean") || Array.isArray(v)) {
throw new Error(
`${options.path}: invalid property ${k}. Expected Schema Object or boolean, got ${
Array.isArray(v) ? "Array" : typeof v
}`,
);
}
const { $ref, readOnly, writeOnly, hasDefault } =
typeof v === "object"
? {
$ref: "$ref" in v && v.$ref,
readOnly: "readOnly" in v && v.readOnly,
writeOnly: "writeOnly" in v && v.writeOnly,
hasDefault: "default" in v && v.default !== undefined,
}
: {};
// handle excludeDeprecated option
if (options.ctx.excludeDeprecated) {
const resolved = $ref ? options.ctx.resolve<SchemaObject>($ref) : v;
if ((resolved as SchemaObject)?.deprecated) {
continue;
}
}
let optional =
schemaObject.required?.includes(k) ||
(schemaObject.required === undefined && options.ctx.propertiesRequiredByDefault) ||
(hasDefault &&
options.ctx.defaultNonNullable &&
!options.path?.includes("parameters") &&
!options.path?.includes("requestBody") &&
!options.path?.includes("requestBodies")) // cant be required, even with defaults
? undefined
: QUESTION_TOKEN;
let type = $ref
? oapiRef($ref)
: transformSchemaObject(v, {
...options,
path: createRef([options.path, k]),
});
if (typeof options.ctx.transform === "function") {
const result = options.ctx.transform(v as SchemaObject, options);
if (result && typeof result === "object") {
if ("schema" in result) {
type = result.schema;
optional = result.questionToken ? QUESTION_TOKEN : optional;
} else {
type = result;
}
}
}
type = wrapWithReadWriteMarker(type, !!readOnly, !!writeOnly, options.ctx);
let property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly: options.ctx.immutable || (!options.ctx.readWriteMarkers && readOnly),
}),
/* name */ tsPropertyIndex(k),
/* questionToken */ optional,
/* type */ type,
);
// Apply transformProperty hook if available
if (typeof options.ctx.transformProperty === "function") {
const result = options.ctx.transformProperty(property, v as SchemaObject, {
...options,
path: createRef([options.path, k]),
});
if (result) {
property = result;
}
}
addJSDocComment(v, property);
coreObjectType.push(property);
}
}
// $defs
if (schemaObject.$defs && typeof schemaObject.$defs === "object" && Object.keys(schemaObject.$defs).length) {
const defKeys: ts.TypeElement[] = [];
for (const [k, v] of Object.entries(schemaObject.$defs)) {
const defReadOnly = "readOnly" in v && !!v.readOnly;
const defWriteOnly = "writeOnly" in v && !!v.writeOnly;
const defType = wrapWithReadWriteMarker(
transformSchemaObject(v, { ...options, path: createRef([options.path, "$defs", k]) }),
defReadOnly,
defWriteOnly,
options.ctx,
);
let property = ts.factory.createPropertySignature(
/* modifiers */ tsModifiers({
readonly: options.ctx.immutable || (!options.ctx.readWriteMarkers && defReadOnly),
}),
/* name */ tsPropertyIndex(k),
/* questionToken */ undefined,
/* type */ defType,
);
// Apply transformProperty hook if available
if (typeof options.ctx.transformProperty === "function") {
const result = options.ctx.transformProperty(property, v as SchemaObject, {
...options,
path: createRef([options.path, "$defs", k]),
});
if (result) {
property = result;
}
}
addJSDocComment(v, property);
defKeys.push(property);
}
coreObjectType.push(
ts.factory.createPropertySignature(
/* modifiers */ undefined,
/* name */ tsPropertyIndex("$defs"),
/* questionToken */ undefined,
/* type */ ts.factory.createTypeLiteralNode(defKeys),
),
);
}
// additionalProperties / patternProperties
const hasExplicitAdditionalProperties =
typeof schemaObject.additionalProperties === "object" && Object.keys(schemaObject.additionalProperties).length;
const hasImplicitAdditionalProperties =
schemaObject.additionalProperties === true ||
(typeof schemaObject.additionalProperties === "object" &&
Object.keys(schemaObject.additionalProperties).length === 0);
const hasExplicitPatternProperties =
typeof schemaObject.patternProperties === "object" && Object.keys(schemaObject.patternProperties).length;
const stringIndexTypes = [];
if (hasExplicitAdditionalProperties) {
stringIndexTypes.push(transformSchemaObject(schemaObject.additionalProperties as SchemaObject, options, true));
}
if (hasImplicitAdditionalProperties || (!schemaObject.additionalProperties && options.ctx.additionalProperties)) {
stringIndexTypes.push(UNKNOWN);
}
if (hasExplicitPatternProperties) {
for (const [_, v] of getEntries(schemaObject.patternProperties ?? {}, options.ctx)) {
stringIndexTypes.push(transformSchemaObject(v, options));
}
}
if (stringIndexTypes.length === 0) {
return coreObjectType.length ? ts.factory.createTypeLiteralNode(coreObjectType) : undefined;
}
const stringIndexType = tsUnion(stringIndexTypes);
return tsIntersection([
...(coreObjectType.length ? [ts.factory.createTypeLiteralNode(coreObjectType)] : []),
ts.factory.createTypeLiteralNode([
ts.factory.createIndexSignature(
/* modifiers */ tsModifiers({
readonly: options.ctx.immutable,
}),
/* parameters */ [
ts.factory.createParameterDeclaration(
/* modifiers */ undefined,
/* dotDotDotToken */ undefined,
/* name */ ts.factory.createIdentifier("key"),
/* questionToken */ undefined,
/* type */ STRING,
),
],
/* type */ stringIndexType,
),
]),
]);
}
return coreObjectType.length ? ts.factory.createTypeLiteralNode(coreObjectType) : undefined;
}
/**
* Check if an object has a key
* @param possibleObject - The object to check
* @param key - The key to check for
* @returns True if the object has the key, false otherwise
*/
function hasKey<K extends string>(possibleObject: unknown, key: K): possibleObject is { [key in K]: unknown } {
return typeof possibleObject === "object" && possibleObject !== null && key in possibleObject;
}
/** Wrap type with $Read or $Write marker when readWriteMarkers flag is enabled */
function wrapWithReadWriteMarker(
type: ts.TypeNode,
readOnly: boolean,
writeOnly: boolean,
ctx: { readWriteMarkers: boolean },
): ts.TypeNode {
if (!ctx.readWriteMarkers || (readOnly && writeOnly)) {
return type;
}
if (readOnly) {
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("$Read"), [type]);
}
if (writeOnly) {
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("$Write"), [type]);
}
return type;
}