openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +12 -5
  2. package/dist/adapters/node-deprecations/node-deprecations.cjs +14 -0
  3. package/dist/adapters/node-deprecations/node-deprecations.cjs.map +1 -0
  4. package/dist/adapters/node-deprecations/node-deprecations.mjs +12 -0
  5. package/dist/adapters/node-deprecations/node-deprecations.mjs.map +1 -0
  6. package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
  7. package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
  8. package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
  9. package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
  10. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
  11. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
  13. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
  15. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  16. package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
  17. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  18. package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
  19. package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
  21. package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
  23. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
  24. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
  25. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
  26. package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
  27. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
  29. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
  31. package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
  33. package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
  34. package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
  35. package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
  36. package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
  37. package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
  38. package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
  39. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  40. package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
  41. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  42. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
  43. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
  44. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
  45. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
  46. package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
  47. package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
  48. package/dist/api/default-ports.cjs +14 -0
  49. package/dist/api/default-ports.cjs.map +1 -1
  50. package/dist/api/default-ports.mjs +12 -1
  51. package/dist/api/default-ports.mjs.map +1 -1
  52. package/dist/api/discriminator-conflict-warnings.cjs +1 -1
  53. package/dist/api/discriminator-conflict-warnings.cjs.map +1 -1
  54. package/dist/api/discriminator-conflict-warnings.mjs +2 -2
  55. package/dist/api/discriminator-conflict-warnings.mjs.map +1 -1
  56. package/dist/api/discriminator-patch.cjs +8 -4
  57. package/dist/api/discriminator-patch.cjs.map +1 -1
  58. package/dist/api/discriminator-patch.mjs +9 -5
  59. package/dist/api/discriminator-patch.mjs.map +1 -1
  60. package/dist/api/discriminator-scan.cjs +2 -2
  61. package/dist/api/discriminator-scan.cjs.map +1 -1
  62. package/dist/api/discriminator-scan.mjs +3 -3
  63. package/dist/api/discriminator-scan.mjs.map +1 -1
  64. package/dist/api/document-refs.cjs +15 -12
  65. package/dist/api/document-refs.cjs.map +1 -1
  66. package/dist/api/document-refs.mjs +15 -12
  67. package/dist/api/document-refs.mjs.map +1 -1
  68. package/dist/api/document-run.cjs +6 -3
  69. package/dist/api/document-run.cjs.map +1 -1
  70. package/dist/api/document-run.mjs +6 -3
  71. package/dist/api/document-run.mjs.map +1 -1
  72. package/dist/api/footer-sink.cjs +25 -2
  73. package/dist/api/footer-sink.cjs.map +1 -1
  74. package/dist/api/footer-sink.mjs +25 -2
  75. package/dist/api/footer-sink.mjs.map +1 -1
  76. package/dist/api/legacy-callback-warning.cjs +7 -1
  77. package/dist/api/legacy-callback-warning.cjs.map +1 -1
  78. package/dist/api/legacy-callback-warning.mjs +8 -2
  79. package/dist/api/legacy-callback-warning.mjs.map +1 -1
  80. package/dist/api/legacy-ts-helpers.cjs.map +1 -1
  81. package/dist/api/legacy-ts-helpers.mjs.map +1 -1
  82. package/dist/api/legacy-utils.cjs.map +1 -1
  83. package/dist/api/legacy-utils.mjs.map +1 -1
  84. package/dist/api/openapi-ts.cjs +3 -2
  85. package/dist/api/openapi-ts.cjs.map +1 -1
  86. package/dist/api/openapi-ts.mjs +4 -3
  87. package/dist/api/openapi-ts.mjs.map +1 -1
  88. package/dist/api/string-to-ast.cjs +7 -9
  89. package/dist/api/string-to-ast.cjs.map +1 -1
  90. package/dist/api/string-to-ast.mjs +8 -9
  91. package/dist/api/string-to-ast.mjs.map +1 -1
  92. package/dist/api/transform-context.cjs +1 -1
  93. package/dist/api/transform-context.cjs.map +1 -1
  94. package/dist/api/transform-context.mjs +1 -1
  95. package/dist/api/transform-context.mjs.map +1 -1
  96. package/dist/api/transform-schema.cjs +2 -2
  97. package/dist/api/transform-schema.cjs.map +1 -1
  98. package/dist/api/transform-schema.mjs +3 -3
  99. package/dist/api/transform-schema.mjs.map +1 -1
  100. package/dist/application/print/type-printer.cjs +2 -1
  101. package/dist/application/print/type-printer.cjs.map +1 -1
  102. package/dist/application/print/type-printer.mjs +2 -1
  103. package/dist/application/print/type-printer.mjs.map +1 -1
  104. package/dist/application/transform/document/components.cjs +1 -1
  105. package/dist/application/transform/document/components.cjs.map +1 -1
  106. package/dist/application/transform/document/components.mjs +2 -2
  107. package/dist/application/transform/document/components.mjs.map +1 -1
  108. package/dist/application/transform/document/parameters.cjs +2 -2
  109. package/dist/application/transform/document/parameters.cjs.map +1 -1
  110. package/dist/application/transform/document/parameters.mjs +3 -3
  111. package/dist/application/transform/document/parameters.mjs.map +1 -1
  112. package/dist/application/transform/document/paths.cjs +2 -2
  113. package/dist/application/transform/document/paths.cjs.map +1 -1
  114. package/dist/application/transform/document/paths.mjs +3 -3
  115. package/dist/application/transform/document/paths.mjs.map +1 -1
  116. package/dist/application/transform/footer/evaluation.cjs +9 -0
  117. package/dist/application/transform/footer/evaluation.cjs.map +1 -1
  118. package/dist/application/transform/footer/evaluation.mjs +8 -1
  119. package/dist/application/transform/footer/evaluation.mjs.map +1 -1
  120. package/dist/application/transform/footer/type-evaluation.cjs +4 -112
  121. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
  122. package/dist/application/transform/footer/type-evaluation.mjs +4 -112
  123. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
  124. package/dist/application/transform/footer/type-indexing.cjs +107 -0
  125. package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
  126. package/dist/application/transform/footer/type-indexing.mjs +105 -0
  127. package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
  128. package/dist/application/transform/ref-pointer/pointer-type.cjs +1 -1
  129. package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
  130. package/dist/application/transform/ref-pointer/pointer-type.mjs +2 -2
  131. package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
  132. package/dist/application/transform/schema/access-flags.cjs +1 -1
  133. package/dist/application/transform/schema/access-flags.cjs.map +1 -1
  134. package/dist/application/transform/schema/access-flags.mjs +2 -2
  135. package/dist/application/transform/schema/access-flags.mjs.map +1 -1
  136. package/dist/application/transform/schema/array-type.cjs +2 -2
  137. package/dist/application/transform/schema/array-type.cjs.map +1 -1
  138. package/dist/application/transform/schema/array-type.mjs +3 -3
  139. package/dist/application/transform/schema/array-type.mjs.map +1 -1
  140. package/dist/application/transform/schema/composition-type.cjs +3 -3
  141. package/dist/application/transform/schema/composition-type.cjs.map +1 -1
  142. package/dist/application/transform/schema/composition-type.mjs +4 -4
  143. package/dist/application/transform/schema/composition-type.mjs.map +1 -1
  144. package/dist/application/transform/schema/enum-rule.cjs +3 -5
  145. package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
  146. package/dist/application/transform/schema/enum-rule.mjs +4 -6
  147. package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
  148. package/dist/application/transform/schema/presence-members.cjs +1 -1
  149. package/dist/application/transform/schema/presence-members.cjs.map +1 -1
  150. package/dist/application/transform/schema/presence-members.mjs +2 -2
  151. package/dist/application/transform/schema/presence-members.mjs.map +1 -1
  152. package/dist/application/transform/schema/required-variants.cjs +2 -2
  153. package/dist/application/transform/schema/required-variants.cjs.map +1 -1
  154. package/dist/application/transform/schema/required-variants.mjs +3 -3
  155. package/dist/application/transform/schema/required-variants.mjs.map +1 -1
  156. package/dist/application/transform/schema/unevaluated-properties.cjs +1 -1
  157. package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
  158. package/dist/application/transform/schema/unevaluated-properties.mjs +2 -2
  159. package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
  160. package/dist/application/transform/schema-comment.cjs +1 -1
  161. package/dist/application/transform/schema-comment.cjs.map +1 -1
  162. package/dist/application/transform/schema-comment.mjs +2 -2
  163. package/dist/application/transform/schema-comment.mjs.map +1 -1
  164. package/dist/cli/api-settings.cjs +78 -0
  165. package/dist/cli/api-settings.cjs.map +1 -0
  166. package/dist/cli/api-settings.mjs +73 -0
  167. package/dist/cli/api-settings.mjs.map +1 -0
  168. package/dist/cli/cli-arguments.cjs +1 -0
  169. package/dist/cli/cli-arguments.cjs.map +1 -1
  170. package/dist/cli/cli-arguments.mjs +1 -1
  171. package/dist/cli/cli-arguments.mjs.map +1 -1
  172. package/dist/cli/help-text.cjs +3 -3
  173. package/dist/cli/help-text.cjs.map +1 -1
  174. package/dist/cli/help-text.mjs +3 -3
  175. package/dist/cli/help-text.mjs.map +1 -1
  176. package/dist/cli/redocly-apis.cjs +8 -4
  177. package/dist/cli/redocly-apis.cjs.map +1 -1
  178. package/dist/cli/redocly-apis.mjs +7 -3
  179. package/dist/cli/redocly-apis.mjs.map +1 -1
  180. package/dist/cli/run-cli.cjs +5 -1
  181. package/dist/cli/run-cli.cjs.map +1 -1
  182. package/dist/cli/run-cli.mjs +5 -1
  183. package/dist/cli/run-cli.mjs.map +1 -1
  184. package/dist/domain/deprecations.cjs +11 -0
  185. package/dist/domain/deprecations.cjs.map +1 -0
  186. package/dist/domain/deprecations.mjs +9 -0
  187. package/dist/domain/deprecations.mjs.map +1 -0
  188. package/dist/domain/errors.cjs +4 -1
  189. package/dist/domain/errors.cjs.map +1 -1
  190. package/dist/domain/errors.mjs +4 -1
  191. package/dist/domain/errors.mjs.map +1 -1
  192. package/dist/domain/ir/constructors.cjs +2 -0
  193. package/dist/domain/ir/constructors.cjs.map +1 -1
  194. package/dist/domain/ir/constructors.mjs +2 -1
  195. package/dist/domain/ir/constructors.mjs.map +1 -1
  196. package/dist/domain/openapi/path-templates.cjs +1 -1
  197. package/dist/domain/openapi/path-templates.cjs.map +1 -1
  198. package/dist/domain/openapi/path-templates.mjs +1 -1
  199. package/dist/domain/openapi/path-templates.mjs.map +1 -1
  200. package/dist/domain/output-fixes.cjs +11 -14
  201. package/dist/domain/output-fixes.cjs.map +1 -1
  202. package/dist/domain/output-fixes.mjs +11 -13
  203. package/dist/domain/output-fixes.mjs.map +1 -1
  204. package/dist/domain/url-credentials.cjs +10 -0
  205. package/dist/domain/url-credentials.cjs.map +1 -0
  206. package/dist/domain/url-credentials.mjs +8 -0
  207. package/dist/domain/url-credentials.mjs.map +1 -0
  208. package/dist/domain/warnings.cjs +8 -1
  209. package/dist/domain/warnings.cjs.map +1 -1
  210. package/dist/domain/warnings.mjs +8 -1
  211. package/dist/domain/warnings.mjs.map +1 -1
  212. package/dist/index.cjs +4 -3
  213. package/dist/index.cjs.map +1 -1
  214. package/dist/index.d.cts +1185 -59
  215. package/dist/index.d.mts +1185 -59
  216. package/dist/index.d.ts +1185 -59
  217. package/dist/index.mjs +4 -2
  218. package/dist/index.mjs.map +1 -1
  219. package/dist/transform/path-item-object.cjs.map +1 -1
  220. package/dist/transform/path-item-object.mjs.map +1 -1
  221. package/package.json +5 -4
  222. package/src/adapters/node-deprecations/node-deprecations.ts +14 -0
  223. package/src/adapters/redocly-loader/configured-headers.ts +65 -0
  224. package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
  225. package/src/adapters/redocly-loader/ref-access.ts +60 -5
  226. package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
  227. package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
  228. package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
  229. package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
  230. package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
  231. package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
  232. package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
  233. package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
  234. package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
  235. package/src/api/default-ports.ts +23 -0
  236. package/src/api/discriminator-conflict-warnings.ts +2 -2
  237. package/src/api/discriminator-patch.ts +11 -7
  238. package/src/api/discriminator-scan.ts +3 -3
  239. package/src/api/document-refs.ts +19 -15
  240. package/src/api/document-run.ts +14 -6
  241. package/src/api/footer-sink.ts +34 -2
  242. package/src/api/legacy-callback-warning.ts +16 -5
  243. package/src/api/legacy-ts-helpers.ts +7 -5
  244. package/src/api/legacy-utils.ts +1 -1
  245. package/src/api/openapi-ts.ts +8 -2
  246. package/src/api/run-context.ts +37 -0
  247. package/src/api/string-to-ast.ts +11 -15
  248. package/src/api/transform-context.ts +10 -8
  249. package/src/api/transform-schema.ts +9 -8
  250. package/src/application/ports/deprecation-reporter.ts +9 -0
  251. package/src/application/print/type-printer.ts +2 -1
  252. package/src/application/transform/document/components.ts +2 -2
  253. package/src/application/transform/document/parameters.ts +4 -3
  254. package/src/application/transform/document/paths.ts +4 -3
  255. package/src/application/transform/footer/evaluation.ts +10 -0
  256. package/src/application/transform/footer/type-evaluation.ts +6 -133
  257. package/src/application/transform/footer/type-indexing.ts +152 -0
  258. package/src/application/transform/ref-pointer/pointer-type.ts +2 -4
  259. package/src/application/transform/schema/access-flags.ts +2 -2
  260. package/src/application/transform/schema/array-type.ts +3 -5
  261. package/src/application/transform/schema/composition-type.ts +4 -4
  262. package/src/application/transform/schema/enum-rule.ts +4 -6
  263. package/src/application/transform/schema/presence-members.ts +2 -2
  264. package/src/application/transform/schema/required-variants.ts +3 -3
  265. package/src/application/transform/schema/unevaluated-properties.ts +2 -2
  266. package/src/application/transform/schema-comment.ts +2 -2
  267. package/src/cli/api-settings.ts +109 -0
  268. package/src/cli/cli-arguments.ts +5 -2
  269. package/src/cli/help-text.ts +4 -4
  270. package/src/cli/redocly-apis.ts +13 -7
  271. package/src/cli/run-cli.ts +5 -1
  272. package/src/domain/deprecations.ts +15 -0
  273. package/src/domain/errors.ts +16 -4
  274. package/src/domain/ir/constructors.ts +3 -0
  275. package/src/domain/ir/nodes.ts +9 -6
  276. package/src/domain/openapi/path-templates.ts +2 -1
  277. package/src/domain/output-fixes.ts +16 -16
  278. package/src/domain/url-credentials.ts +18 -0
  279. package/src/domain/warnings.ts +7 -0
  280. package/src/index.ts +3 -6
  281. package/src/transform/path-item-object.ts +6 -2
  282. package/src/types/generator-options.ts +12 -32
  283. package/src/types/openapi-common.ts +1 -0
  284. package/src/types/openapi-request.ts +3 -0
  285. package/src/types/openapi-schema.ts +2 -0
  286. package/src/types/openapi-security.ts +1 -0
@@ -23,6 +23,16 @@ export function unionType(types: readonly IrType[]): IrType {
23
23
  return types.length === 0 ? NEVER : { kind: "union", types };
24
24
  }
25
25
 
26
+ export function intersectionType(types: readonly IrType[]): IrType {
27
+ return types.length === 1 ? (types[0] as IrType) : { kind: "intersection", types };
28
+ }
29
+
30
+ const NULLISH: ReadonlySet<string> = new Set(["null", "undefined"]);
31
+
32
+ export function isNullish(type: IrType): boolean {
33
+ return type.kind === "keyword" && NULLISH.has(type.keyword);
34
+ }
35
+
26
36
  export function propertyKey(name: IrPropertyName): string {
27
37
  return name.kind === "string" ? name.value : name.text;
28
38
  }
@@ -1,21 +1,16 @@
1
- import type { IrDeclaration, IrMember, IrType } from "../../../domain/ir/nodes.js";
1
+ import type { IrDeclaration, IrType } from "../../../domain/ir/nodes.js";
2
2
  import {
3
3
  absorbed,
4
4
  type Evaluated,
5
5
  type Evaluation,
6
- MISSING,
7
- NEVER,
6
+ intersectionType,
7
+ isNullish,
8
8
  OPAQUE,
9
9
  propertyKey,
10
10
  unionType,
11
11
  } from "./evaluation.js";
12
- import {
13
- declaredFirst,
14
- extracted,
15
- isNeverIntersection,
16
- markerObject,
17
- omitted,
18
- } from "./type-rules.js";
12
+ import { indexType } from "./type-indexing.js";
13
+ import { extracted, markerObject, omitted } from "./type-rules.js";
19
14
 
20
15
  /**
21
16
  * A small evaluator of the generated types, as TypeScript reads them, for the `--enum-values`
@@ -30,7 +25,6 @@ const MAX_EXPANSIONS = 200;
30
25
  const DEEP_PREFIX = "FlattenedDeepRequired<";
31
26
  /** How many members distributing an intersection may give; more is opaque. */
32
27
  const MAX_DISTRIBUTED = 64;
33
- const NULLISH: ReadonlySet<string> = new Set(["null", "undefined"]);
34
28
 
35
29
  export class TypeEvaluator {
36
30
  private readonly declared = new Map<string, IrType>();
@@ -168,127 +162,6 @@ export class TypeEvaluator {
168
162
 
169
163
  /** `value[index]` for a string literal, `string` or `number` index. */
170
164
  index(value: Evaluated, index: IrType): Evaluation {
171
- const owner = this.expand(value);
172
- if (owner.kind !== "type") {
173
- return owner;
174
- }
175
- if (this.membersOf(owner).length > 1) {
176
- return this.unionIndex(owner, index);
177
- }
178
- const { type } = owner;
179
- switch (type.kind) {
180
- case "object":
181
- return this.memberType(owner, type.members, index);
182
- case "intersection":
183
- return this.partsIndex(owner, type.types, index);
184
- case "union":
185
- return this.unionIndex(owner, index);
186
- case "array":
187
- return isNumberIndex(index) ? this.flattened(owner, type.element) : builtInIndex(index);
188
- case "keyword":
189
- return type.keyword === "never" ? owner : builtInIndex(index);
190
- default:
191
- return builtInIndex(index);
192
- }
193
- }
194
-
195
- private memberType(owner: Evaluated, members: readonly IrMember[], index: IrType): Evaluation {
196
- const key = indexKey(index);
197
- const property = members.find(
198
- (member) => member.kind === "property" && key !== null && propertyKey(member.name) === key,
199
- );
200
- if (property !== undefined) {
201
- return this.flattened(owner, property.type);
202
- }
203
- const signature = members.find((member) => member.kind === "indexSignature");
204
- if (signature !== undefined) {
205
- return this.flattened(owner, signature.type);
206
- }
207
- return key === null || isObjectBuiltIn(key) ? OPAQUE : MISSING;
208
- }
209
-
210
- private partsIndex(owner: Evaluated, parts: readonly IrType[], index: IrType): Evaluation {
211
- if (isNeverIntersection(this, owner, parts)) {
212
- return { ...owner, type: NEVER };
213
- }
214
- const found = parts.map((type) => this.index({ ...owner, type }, index));
215
- if (found.some((part) => part.kind === "opaque")) {
216
- return OPAQUE;
217
- }
218
- const read = parts.map((type, at) => ({ type, found: found[at] as Evaluation }));
219
- const types = declaredFirst(this, owner, indexKey(index), read).flatMap((part) =>
220
- part.kind === "type" ? [part.type] : [],
221
- );
222
- return types.length === 0 ? MISSING : { ...owner, type: intersectionType(types) };
165
+ return indexType(this, value, index);
223
166
  }
224
-
225
- private unionIndex(owner: Evaluated, index: IrType): Evaluation {
226
- const found = this.membersOf(owner).map((member) =>
227
- member.kind === "type" ? this.index(member, index) : member,
228
- );
229
- if (found.some((member) => member.kind === "missing")) {
230
- return MISSING;
231
- }
232
- if (found.some((member) => member.kind === "opaque")) {
233
- return OPAQUE;
234
- }
235
- return { ...owner, type: unionType((found as Evaluated[]).map((member) => member.type)) };
236
- }
237
-
238
- /**
239
- * A member read through `FlattenedDeepRequired`: a value that is only arrays (and `undefined` or
240
- * `null`) is replaced by its elements, anything else loses `undefined` and `null`.
241
- */
242
- private flattened(owner: Evaluated, type: IrType): Evaluation {
243
- const member: Evaluated = { ...owner, type };
244
- if (!owner.deep) {
245
- return this.expand(member);
246
- }
247
- const members = this.membersOf(member);
248
- if (members.some((candidate) => candidate.kind !== "type")) {
249
- return OPAQUE;
250
- }
251
- const present = (members as Evaluated[]).filter((candidate) => !isNullish(candidate.type));
252
- const elements = present.map((candidate) => arrayElement(candidate.type));
253
- if (present.length > 0 && elements.every((element) => element !== null)) {
254
- return this.expand({ ...owner, type: unionType(elements as IrType[]) });
255
- }
256
- return { ...owner, type: unionType(present.map((candidate) => candidate.type)) };
257
- }
258
- }
259
-
260
- function isNullish(type: IrType): boolean {
261
- return type.kind === "keyword" && NULLISH.has(type.keyword);
262
- }
263
-
264
- /** The element of an expanded member; expanding already dropped `readonly`. */
265
- function arrayElement(type: IrType): IrType | null {
266
- return type.kind === "array" ? type.element : null;
267
- }
268
-
269
- function intersectionType(types: readonly IrType[]): IrType {
270
- return types.length === 1 ? (types[0] as IrType) : { kind: "intersection", types };
271
- }
272
-
273
- function indexKey(index: IrType): string | null {
274
- if (index.kind === "stringLiteral") {
275
- return index.value;
276
- }
277
- return index.kind === "numberLiteral" ? String(index.value) : null;
278
- }
279
-
280
- function isNumberIndex(index: IrType): boolean {
281
- const key = indexKey(index);
282
- return (index.kind === "keyword" && index.keyword === "number") || /^\d+$/.test(key ?? "");
283
- }
284
-
285
- function isObjectBuiltIn(key: string): boolean {
286
- return key in {};
287
- }
288
-
289
- /** A key of a primitive or an array: a built-in member is opaque, any other key is missing. */
290
- function builtInIndex(index: IrType): Evaluation {
291
- const key = indexKey(index);
292
- const isBuiltIn = key === null || key in String.prototype || key in Array.prototype;
293
- return isBuiltIn || key in Number.prototype ? OPAQUE : MISSING;
294
167
  }
@@ -0,0 +1,152 @@
1
+ import type { IrMember, IrType } from "../../../domain/ir/nodes.js";
2
+ import {
3
+ type Evaluated,
4
+ type Evaluation,
5
+ intersectionType,
6
+ isNullish,
7
+ MISSING,
8
+ NEVER,
9
+ OPAQUE,
10
+ propertyKey,
11
+ unionType,
12
+ } from "./evaluation.js";
13
+ import { declaredFirst, isNeverIntersection, type TypeExpansion } from "./type-rules.js";
14
+
15
+ /**
16
+ * Indexed access (`T["key"]`, `T[number]`) on the generated types, as TypeScript reads it, for
17
+ * the evaluator of the `--enum-values` check (ADR-027).
18
+ */
19
+
20
+ /** The part of the evaluator that indexed access uses. */
21
+ export interface IndexedTypes extends TypeExpansion {
22
+ expand(value: Evaluated): Evaluation;
23
+ index(value: Evaluated, index: IrType): Evaluation;
24
+ }
25
+
26
+ /** `value[index]` for a string literal, `string` or `number` index. */
27
+ export function indexType(types: IndexedTypes, value: Evaluated, index: IrType): Evaluation {
28
+ const owner = types.expand(value);
29
+ if (owner.kind !== "type") {
30
+ return owner;
31
+ }
32
+ if (types.membersOf(owner).length > 1) {
33
+ return unionIndex(types, owner, index);
34
+ }
35
+ const { type } = owner;
36
+ switch (type.kind) {
37
+ case "object":
38
+ return memberType(types, owner, type.members, index);
39
+ case "intersection":
40
+ return partsIndex(types, owner, type.types, index);
41
+ case "union":
42
+ return unionIndex(types, owner, index);
43
+ case "array":
44
+ return isNumberIndex(index) ? flattened(types, owner, type.element) : builtInIndex(index);
45
+ case "keyword":
46
+ return type.keyword === "never" ? owner : builtInIndex(index);
47
+ default:
48
+ return builtInIndex(index);
49
+ }
50
+ }
51
+
52
+ function memberType(
53
+ types: IndexedTypes,
54
+ owner: Evaluated,
55
+ members: readonly IrMember[],
56
+ index: IrType,
57
+ ): Evaluation {
58
+ const key = indexKey(index);
59
+ const property = members.find(
60
+ (member) => member.kind === "property" && key !== null && propertyKey(member.name) === key,
61
+ );
62
+ if (property !== undefined) {
63
+ return flattened(types, owner, property.type);
64
+ }
65
+ const signature = members.find((member) => member.kind === "indexSignature");
66
+ if (signature !== undefined) {
67
+ return flattened(types, owner, signature.type);
68
+ }
69
+ return key === null || isObjectBuiltIn(key) ? OPAQUE : MISSING;
70
+ }
71
+
72
+ function partsIndex(
73
+ types: IndexedTypes,
74
+ owner: Evaluated,
75
+ parts: readonly IrType[],
76
+ index: IrType,
77
+ ): Evaluation {
78
+ if (isNeverIntersection(types, owner, parts)) {
79
+ return { ...owner, type: NEVER };
80
+ }
81
+ const found = parts.map((type) => types.index({ ...owner, type }, index));
82
+ if (found.some((part) => part.kind === "opaque")) {
83
+ return OPAQUE;
84
+ }
85
+ const read = parts.map((type, at) => ({ type, found: found[at] as Evaluation }));
86
+ const kept = declaredFirst(types, owner, indexKey(index), read).flatMap((part) =>
87
+ part.kind === "type" ? [part.type] : [],
88
+ );
89
+ return kept.length === 0 ? MISSING : { ...owner, type: intersectionType(kept) };
90
+ }
91
+
92
+ function unionIndex(types: IndexedTypes, owner: Evaluated, index: IrType): Evaluation {
93
+ const found = types
94
+ .membersOf(owner)
95
+ .map((member) => (member.kind === "type" ? types.index(member, index) : member));
96
+ if (found.some((member) => member.kind === "missing")) {
97
+ return MISSING;
98
+ }
99
+ if (found.some((member) => member.kind === "opaque")) {
100
+ return OPAQUE;
101
+ }
102
+ return { ...owner, type: unionType((found as Evaluated[]).map((member) => member.type)) };
103
+ }
104
+
105
+ /**
106
+ * A member read through `FlattenedDeepRequired`: a value that is only arrays (and `undefined` or
107
+ * `null`) is replaced by its elements, anything else loses `undefined` and `null`.
108
+ */
109
+ function flattened(types: IndexedTypes, owner: Evaluated, type: IrType): Evaluation {
110
+ const member: Evaluated = { ...owner, type };
111
+ if (!owner.deep) {
112
+ return types.expand(member);
113
+ }
114
+ const members = types.membersOf(member);
115
+ if (members.some((candidate) => candidate.kind !== "type")) {
116
+ return OPAQUE;
117
+ }
118
+ const present = (members as Evaluated[]).filter((candidate) => !isNullish(candidate.type));
119
+ const elements = present.map((candidate) => arrayElement(candidate.type));
120
+ if (present.length > 0 && elements.every((element) => element !== null)) {
121
+ return types.expand({ ...owner, type: unionType(elements as IrType[]) });
122
+ }
123
+ return { ...owner, type: unionType(present.map((candidate) => candidate.type)) };
124
+ }
125
+
126
+ /** The element of an expanded member; expanding already dropped `readonly`. */
127
+ function arrayElement(type: IrType): IrType | null {
128
+ return type.kind === "array" ? type.element : null;
129
+ }
130
+
131
+ function indexKey(index: IrType): string | null {
132
+ if (index.kind === "stringLiteral") {
133
+ return index.value;
134
+ }
135
+ return index.kind === "numberLiteral" ? String(index.value) : null;
136
+ }
137
+
138
+ function isNumberIndex(index: IrType): boolean {
139
+ const key = indexKey(index);
140
+ return (index.kind === "keyword" && index.keyword === "number") || /^\d+$/.test(key ?? "");
141
+ }
142
+
143
+ function isObjectBuiltIn(key: string): boolean {
144
+ return key in {};
145
+ }
146
+
147
+ /** A key of a primitive or an array: a built-in member is opaque, any other key is missing. */
148
+ function builtInIndex(index: IrType): Evaluation {
149
+ const key = indexKey(index);
150
+ const isBuiltIn = key === null || key in String.prototype || key in Array.prototype;
151
+ return isBuiltIn || key in Number.prototype ? OPAQUE : MISSING;
152
+ }
@@ -1,6 +1,6 @@
1
1
  import { irReference } from "../../../domain/ir/constructors.js";
2
2
  import type { IrType } from "../../../domain/ir/nodes.js";
3
- import { OutputFix } from "../../../domain/output-fixes.js";
3
+ import { FIX } from "../../../domain/output-fixes.js";
4
4
  import { InvalidRefError, refType } from "../ref-type.js";
5
5
  import type { SchemaRequest } from "../schema/schema-rule.js";
6
6
  import { generatedLocationOf } from "./generated-location.js";
@@ -62,9 +62,7 @@ function correctedType(
62
62
 
63
63
  function isCorrected(pointer: RefPointer, request: SchemaRequest): boolean {
64
64
  const { options, refs } = request.context;
65
- return (
66
- options.fixes.has(OutputFix.REF_POINTERS) || originalIndexFails({ pointer, options, refs })
67
- );
65
+ return options.fixes.has(FIX.REF_POINTERS) || originalIndexFails({ pointer, options, refs });
68
66
  }
69
67
 
70
68
  /** The type of a schema `$ref`. Throws InvalidRefError when the `$ref` has no pointer. */
@@ -1,5 +1,5 @@
1
1
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
2
- import { OutputFix } from "../../../domain/output-fixes.js";
2
+ import { FIX } from "../../../domain/output-fixes.js";
3
3
  import type { SchemaContext } from "./schema-context.js";
4
4
 
5
5
  /** Whether a property is `readOnly` or `writeOnly` (JSON Schema 2020-12 Validation §9.4). */
@@ -56,7 +56,7 @@ export function propertyAccessFlags(value: Schema, context: SchemaContext): Acce
56
56
  if (typeof value !== "object") {
57
57
  return NO_FLAGS;
58
58
  }
59
- if (!context.options.fixes.has(OutputFix.READ_WRITE_REFS)) {
59
+ if (!context.options.fixes.has(FIX.READ_WRITE_REFS)) {
60
60
  return ownFlags(value as SchemaObject);
61
61
  }
62
62
  return appliedFlags(value, context, new Set());
@@ -7,7 +7,7 @@ import {
7
7
  } from "../../../domain/ir/constructors.js";
8
8
  import type { IrTupleElement, IrType } from "../../../domain/ir/nodes.js";
9
9
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
10
- import { OutputFix } from "../../../domain/output-fixes.js";
10
+ import { FIX } from "../../../domain/output-fixes.js";
11
11
  import { UNKNOWN_TYPE } from "../shared-types.js";
12
12
  import { unionOf } from "../type-combinators.js";
13
13
  import type { SchemaRequest } from "./schema-rule.js";
@@ -79,9 +79,7 @@ function prefixItemsTupleType(
79
79
  }
80
80
 
81
81
  function isPrefixItemsFixed(schema: SchemaObject, request: SchemaRequest): boolean {
82
- return (
83
- schema.prefixItems !== undefined && request.context.options.fixes.has(OutputFix.PREFIX_ITEMS)
84
- );
82
+ return schema.prefixItems !== undefined && request.context.options.fixes.has(FIX.PREFIX_ITEMS);
85
83
  }
86
84
 
87
85
  /** The type of one item; `unknown` without `items`. */
@@ -193,7 +191,7 @@ function fixedArrayType(schema: SchemaObject, request: SchemaRequest): IrType {
193
191
 
194
192
  function isArrayLengthFixed(request: SchemaRequest): boolean {
195
193
  const { arrayLength, fixes } = request.context.options;
196
- return arrayLength && fixes.has(OutputFix.ARRAY_LENGTH_TUPLES);
194
+ return arrayLength && fixes.has(FIX.ARRAY_LENGTH_TUPLES);
197
195
  }
198
196
 
199
197
  /** `type: array`, including tuples (`prefixItems`, array `items`) and `--array-length`. */
@@ -2,7 +2,7 @@ import { irKeyword, irRecord } from "../../../domain/ir/constructors.js";
2
2
  import type { IrType } from "../../../domain/ir/nodes.js";
3
3
  import { createRef } from "../../../domain/openapi/json-pointer.js";
4
4
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
5
- import { OutputFix } from "../../../domain/output-fixes.js";
5
+ import { FIX } from "../../../domain/output-fixes.js";
6
6
  import { requirableRefType } from "../ref-pointer/pointer-type.js";
7
7
  import { UNKNOWN_TYPE } from "../shared-types.js";
8
8
  import {
@@ -58,7 +58,7 @@ function keysToRequire(
58
58
  if (!request.context.discriminators.isHandled(ref)) {
59
59
  return defined;
60
60
  }
61
- if (!request.context.options.fixes.has(OutputFix.DISCRIMINATOR_MEMBER_REQUIRED)) {
61
+ if (!request.context.options.fixes.has(FIX.DISCRIMINATOR_MEMBER_REQUIRED)) {
62
62
  return [];
63
63
  }
64
64
  return defined.filter((key) => !target.required?.includes(key));
@@ -131,7 +131,7 @@ function allOfItemType(
131
131
  const isUnion =
132
132
  unionItem !== undefined &&
133
133
  !unionItem.isMapped &&
134
- request.context.options.fixes.has(OutputFix.ALLOF_DISCRIMINATED_UNION);
134
+ request.context.options.fixes.has(FIX.ALLOF_DISCRIMINATED_UNION);
135
135
  if (!propertyName || isUnion) {
136
136
  return type;
137
137
  }
@@ -173,7 +173,7 @@ function anyOfType(
173
173
  if (variants.length === 0 || previous === null) {
174
174
  return variants.length === 0 ? previous : unionOf(variants);
175
175
  }
176
- if (!request.context.options.fixes.has(OutputFix.ANYOF_PROPERTIES)) {
176
+ if (!request.context.options.fixes.has(FIX.ANYOF_PROPERTIES)) {
177
177
  return unionOf([previous, ...variants]);
178
178
  }
179
179
  return Array.isArray(schema.type) ? previous : intersectionOf([previous, unionOf(variants)]);
@@ -8,7 +8,7 @@ import {
8
8
  import type { IrType } from "../../../domain/ir/nodes.js";
9
9
  import { createRef, parseRefPointer } from "../../../domain/openapi/json-pointer.js";
10
10
  import type { SchemaObject } from "../../../domain/openapi/schema-object.js";
11
- import { OutputFix } from "../../../domain/output-fixes.js";
11
+ import { FIX } from "../../../domain/output-fixes.js";
12
12
  import { literalType } from "../literal-type.js";
13
13
  import { refType } from "../ref-type.js";
14
14
  import { intersectionOf, unionOf } from "../type-combinators.js";
@@ -55,7 +55,7 @@ function hasEnumMetadata(schema: SchemaObject): boolean {
55
55
  */
56
56
  function tsEnumEntries(schema: SchemaObject, options: SchemaOptions): [unknown, number][] {
57
57
  const entries = (schema.enum ?? []).map((value, index): [unknown, number] => [value, index]);
58
- return options.fixes.has(OutputFix.NULLABLE_ENUMS)
58
+ return options.fixes.has(FIX.NULLABLE_ENUMS)
59
59
  ? entries.filter(([value]) => value !== null)
60
60
  : entries;
61
61
  }
@@ -147,9 +147,7 @@ export function ownerEnumMembersType(
147
147
  request: SchemaRequest,
148
148
  ): IrType | null {
149
149
  const { options, discriminators, footer } = request.context;
150
- const parent: DiscriminatorEnumParent | undefined = options.fixes.has(
151
- OutputFix.DISCRIMINATOR_VALUES,
152
- )
150
+ const parent: DiscriminatorEnumParent | undefined = options.fixes.has(FIX.DISCRIMINATOR_VALUES)
153
151
  ? discriminators.enumParentOf(key)
154
152
  : undefined;
155
153
  if (parent === undefined || !isTsEnum(parent.schema, options)) {
@@ -175,7 +173,7 @@ function tsEnumType(schema: SchemaObject, request: SchemaRequest): IrType {
175
173
  irReference(footer.addEnum(enumRequest(schema, request.location.path, options))),
176
174
  schema,
177
175
  );
178
- return options.fixes.has(OutputFix.NULLABLE_ENUMS) && isNullAllowed(schema)
176
+ return options.fixes.has(FIX.NULLABLE_ENUMS) && isNullAllowed(schema)
179
177
  ? unionOf([type, irKeyword("null")])
180
178
  : type;
181
179
  }
@@ -1,5 +1,5 @@
1
1
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
2
- import { OutputFix } from "../../../domain/output-fixes.js";
2
+ import { FIX } from "../../../domain/output-fixes.js";
3
3
  import type { SchemaRequest } from "./schema-rule.js";
4
4
 
5
5
  /**
@@ -81,7 +81,7 @@ export interface AllOfMembers {
81
81
  */
82
82
  export function allOfMembers(members: readonly Schema[], request: SchemaRequest): AllOfMembers {
83
83
  const unchanged = { members, required: [] };
84
- if (!request.context.options.fixes.has(OutputFix.ALLOF_REQUIRED)) {
84
+ if (!request.context.options.fixes.has(FIX.ALLOF_REQUIRED)) {
85
85
  return unchanged;
86
86
  }
87
87
  const schemas = members.map((member) => memberSchema(member, request));
@@ -1,6 +1,6 @@
1
1
  import type { IrType } from "../../../domain/ir/nodes.js";
2
2
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
3
- import { OutputFix } from "../../../domain/output-fixes.js";
3
+ import { FIX } from "../../../domain/output-fixes.js";
4
4
  import { unionOf, withRequiredKeys } from "../type-combinators.js";
5
5
  import { isKeptProperty } from "./object-type.js";
6
6
  import type { SchemaRequest } from "./schema-rule.js";
@@ -55,7 +55,7 @@ export function hasRequiredVariants(
55
55
  request: SchemaRequest,
56
56
  ): boolean {
57
57
  return (
58
- request.context.options.fixes.has(OutputFix.ANYOF_ONEOF_REQUIRED) &&
58
+ request.context.options.fixes.has(FIX.ANYOF_ONEOF_REQUIRED) &&
59
59
  isObjectTyped(schema) &&
60
60
  requiredKeyLists(schema, variants, request) !== null
61
61
  );
@@ -73,7 +73,7 @@ export function requiredVariantsType(
73
73
  variants: readonly Schema[],
74
74
  request: SchemaRequest,
75
75
  ): IrType | null {
76
- if (!request.context.options.fixes.has(OutputFix.ANYOF_ONEOF_REQUIRED) || previous === null) {
76
+ if (!request.context.options.fixes.has(FIX.ANYOF_ONEOF_REQUIRED) || previous === null) {
77
77
  return null;
78
78
  }
79
79
  const keyLists = isObjectTyped(schema) ? requiredKeyLists(schema, variants, request) : null;
@@ -6,7 +6,7 @@ import {
6
6
  } from "../../../domain/ir/constructors.js";
7
7
  import type { IrMember, IrType } from "../../../domain/ir/nodes.js";
8
8
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
9
- import { OutputFix } from "../../../domain/output-fixes.js";
9
+ import { FIX } from "../../../domain/output-fixes.js";
10
10
  import { HELPER_TYPES } from "../helper-types.js";
11
11
  import { UNKNOWN_TYPE } from "../shared-types.js";
12
12
  import { unionOf } from "../type-combinators.js";
@@ -32,7 +32,7 @@ import type { SchemaRequest } from "./schema-rule.js";
32
32
  function unevaluatedSchemaOf(schema: SchemaObject, request: SchemaRequest): Schema | null {
33
33
  const { unevaluatedProperties } = schema;
34
34
  if (
35
- !request.context.options.fixes.has(OutputFix.UNEVALUATED_PROPERTIES) ||
35
+ !request.context.options.fixes.has(FIX.UNEVALUATED_PROPERTIES) ||
36
36
  unevaluatedProperties === undefined ||
37
37
  unevaluatedProperties === false ||
38
38
  schema.additionalProperties !== undefined
@@ -1,5 +1,5 @@
1
1
  import type { IrComment, IrPropertySignature } from "../../domain/ir/nodes.js";
2
- import { OutputFix } from "../../domain/output-fixes.js";
2
+ import { FIX, type OutputFix } from "../../domain/output-fixes.js";
3
3
 
4
4
  /** Schema fields that produce JSDoc lines (the original `AnnotatedSchemaObject`). */
5
5
  export interface AnnotatedSchema {
@@ -44,7 +44,7 @@ export const ORIGINAL_SCHEMA_COMMENT: SchemaCommentOptions = { hasMemberDescript
44
44
 
45
45
  /** How the fixes turned on write a schema's JSDoc. */
46
46
  export function schemaCommentOptionsOf(fixes: ReadonlySet<OutputFix>): SchemaCommentOptions {
47
- return { hasMemberDescriptions: fixes.has(OutputFix.ANYOF_ONEOF_DESCRIPTIONS) };
47
+ return { hasMemberDescriptions: fixes.has(FIX.ANYOF_ONEOF_DESCRIPTIONS) };
48
48
  }
49
49
 
50
50
  function formatValue(value: unknown): string {
@@ -0,0 +1,109 @@
1
+ import { kebabCase } from "scule";
2
+ import { WARNING_CODES, warningText } from "../domain/warnings.js";
3
+ import { GENERATOR_FLAGS, hostList } from "./cli-arguments.js";
4
+ import { cliError } from "./cli-errors.js";
5
+ import { resolveLocationUrl } from "./cli-output.js";
6
+ import type { RefLimitOptions } from "./ref-limits.js";
7
+
8
+ /**
9
+ * The settings of one API in `redocly.yaml` (`apis.<name>.x-openapi-ts`). A setting has the name
10
+ * of its CLI flag, in kebab-case or camelCase (ADR-021, decision 1); a setting the CLI does not
11
+ * read gets a warning, as an unknown flag gets a notice.
12
+ */
13
+
14
+ export type ApiSettings = Readonly<Record<string, unknown>>;
15
+
16
+ /** The setting that names the output file. */
17
+ export const OUTPUT_SETTING = "output";
18
+
19
+ /** The setting that lists the API's output fixes. */
20
+ export const FIX_SETTING = "fix";
21
+
22
+ const LOCAL_REF_ROOT_SETTING = "localRefRoot";
23
+ const REMOTE_REF_HOSTS_SETTING = "remoteRefHosts";
24
+
25
+ /** `--content-never` is accepted and has no effect, as on the command line. */
26
+ const IGNORED_SETTINGS = ["contentNever"] as const;
27
+
28
+ const CAMEL_CASE_SETTINGS = [
29
+ OUTPUT_SETTING,
30
+ FIX_SETTING,
31
+ LOCAL_REF_ROOT_SETTING,
32
+ REMOTE_REF_HOSTS_SETTING,
33
+ ...GENERATOR_FLAGS,
34
+ ...IGNORED_SETTINGS,
35
+ ];
36
+
37
+ const KNOWN_SETTINGS = new Set<string>([
38
+ ...CAMEL_CASE_SETTINGS,
39
+ ...CAMEL_CASE_SETTINGS.map((name) => kebabCase(name)),
40
+ ]);
41
+
42
+ /** The warning for each setting of an API that no code reads. */
43
+ export function unknownSettingWarnings(apiName: string, settings: ApiSettings): string[] {
44
+ return Object.keys(settings)
45
+ .filter((key) => !KNOWN_SETTINGS.has(key))
46
+ .map((key) =>
47
+ warningText(
48
+ `API ${apiName}: the setting \`${key}\` is not an openapi-typesafe option and is ignored`,
49
+ WARNING_CODES.CONFIG_SETTING_UNKNOWN,
50
+ ),
51
+ );
52
+ }
53
+
54
+ /** The value of a setting in camelCase, else in kebab-case; `undefined` when it is absent. */
55
+ function settingValue(settings: ApiSettings, name: string): unknown {
56
+ return name in settings ? settings[name] : settings[kebabCase(name)];
57
+ }
58
+
59
+ function hasSetting(settings: ApiSettings, name: string): boolean {
60
+ return name in settings || kebabCase(name) in settings;
61
+ }
62
+
63
+ function invalidSetting(apiName: string, name: string, expected: string): Error {
64
+ return cliError(
65
+ "CLI_CONFIG_INVALID",
66
+ `API ${apiName}: \`${kebabCase(name)}\` in x-openapi-ts must be ${expected}`,
67
+ );
68
+ }
69
+
70
+ /** `local-ref-root: dir`, relative to the config file; empty means the config file's directory. */
71
+ function localRefRootOf(apiName: string, settings: ApiSettings, configRoot: URL): RefLimitOptions {
72
+ if (!hasSetting(settings, LOCAL_REF_ROOT_SETTING)) {
73
+ return {};
74
+ }
75
+ const value = settingValue(settings, LOCAL_REF_ROOT_SETTING) ?? "";
76
+ if (typeof value !== "string") {
77
+ throw invalidSetting(apiName, LOCAL_REF_ROOT_SETTING, "a directory path");
78
+ }
79
+ return { localRefRoot: resolveLocationUrl(value === "" ? "." : value, configRoot) };
80
+ }
81
+
82
+ /** `remote-ref-hosts: [a, b]` or `a,b`; an empty list or value allows no host. */
83
+ function remoteRefHostsOf(apiName: string, settings: ApiSettings): RefLimitOptions {
84
+ if (!hasSetting(settings, REMOTE_REF_HOSTS_SETTING)) {
85
+ return {};
86
+ }
87
+ const value = settingValue(settings, REMOTE_REF_HOSTS_SETTING) ?? [];
88
+ const entries = [value].flat();
89
+ if (entries.some((entry) => typeof entry !== "string")) {
90
+ throw invalidSetting(apiName, REMOTE_REF_HOSTS_SETTING, "a host list");
91
+ }
92
+ return { remoteRefHosts: hostList(entries) ?? [] };
93
+ }
94
+
95
+ /**
96
+ * The `$ref` limits an API sets (threat model R1, R2). Each one replaces the CLI flag of the same
97
+ * name for this API, as its boolean settings and `fix` do; fails with `CLI_CONFIG_INVALID` for a
98
+ * value of another type.
99
+ */
100
+ export function apiRefLimitOptions(
101
+ apiName: string,
102
+ settings: ApiSettings,
103
+ configRoot: URL,
104
+ ): RefLimitOptions {
105
+ return {
106
+ ...localRefRootOf(apiName, settings, configRoot),
107
+ ...remoteRefHostsOf(apiName, settings),
108
+ };
109
+ }
@@ -122,8 +122,11 @@ function lastString(value: unknown): string | undefined {
122
122
  return optionalString(Array.isArray(value) ? value.at(-1) : value);
123
123
  }
124
124
 
125
- /** `--remote-ref-hosts a.example,b.example`, repeatable; an empty value allows no host. */
126
- function hostList(value: unknown): string[] | undefined {
125
+ /**
126
+ * `--remote-ref-hosts a.example,b.example`, repeatable; an empty value allows no host. The
127
+ * `remote-ref-hosts` setting of a `redocly.yaml` API is read the same way.
128
+ */
129
+ export function hostList(value: unknown): string[] | undefined {
127
130
  if (value === undefined) {
128
131
  return undefined;
129
132
  }