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.
- package/README.md +12 -5
- package/dist/adapters/node-deprecations/node-deprecations.cjs +14 -0
- package/dist/adapters/node-deprecations/node-deprecations.cjs.map +1 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs +12 -0
- package/dist/adapters/node-deprecations/node-deprecations.mjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
- package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
- package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
- package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
- package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
- package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
- package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
- package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
- package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
- package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
- package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
- package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
- package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
- package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
- package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
- package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
- package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
- package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
- package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
- package/dist/api/default-ports.cjs +14 -0
- package/dist/api/default-ports.cjs.map +1 -1
- package/dist/api/default-ports.mjs +12 -1
- package/dist/api/default-ports.mjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs +1 -1
- package/dist/api/discriminator-conflict-warnings.cjs.map +1 -1
- package/dist/api/discriminator-conflict-warnings.mjs +2 -2
- package/dist/api/discriminator-conflict-warnings.mjs.map +1 -1
- package/dist/api/discriminator-patch.cjs +8 -4
- package/dist/api/discriminator-patch.cjs.map +1 -1
- package/dist/api/discriminator-patch.mjs +9 -5
- package/dist/api/discriminator-patch.mjs.map +1 -1
- package/dist/api/discriminator-scan.cjs +2 -2
- package/dist/api/discriminator-scan.cjs.map +1 -1
- package/dist/api/discriminator-scan.mjs +3 -3
- package/dist/api/discriminator-scan.mjs.map +1 -1
- package/dist/api/document-refs.cjs +15 -12
- package/dist/api/document-refs.cjs.map +1 -1
- package/dist/api/document-refs.mjs +15 -12
- package/dist/api/document-refs.mjs.map +1 -1
- package/dist/api/document-run.cjs +6 -3
- package/dist/api/document-run.cjs.map +1 -1
- package/dist/api/document-run.mjs +6 -3
- package/dist/api/document-run.mjs.map +1 -1
- package/dist/api/footer-sink.cjs +25 -2
- package/dist/api/footer-sink.cjs.map +1 -1
- package/dist/api/footer-sink.mjs +25 -2
- package/dist/api/footer-sink.mjs.map +1 -1
- package/dist/api/legacy-callback-warning.cjs +7 -1
- package/dist/api/legacy-callback-warning.cjs.map +1 -1
- package/dist/api/legacy-callback-warning.mjs +8 -2
- package/dist/api/legacy-callback-warning.mjs.map +1 -1
- package/dist/api/legacy-ts-helpers.cjs.map +1 -1
- package/dist/api/legacy-ts-helpers.mjs.map +1 -1
- package/dist/api/legacy-utils.cjs.map +1 -1
- package/dist/api/legacy-utils.mjs.map +1 -1
- package/dist/api/openapi-ts.cjs +3 -2
- package/dist/api/openapi-ts.cjs.map +1 -1
- package/dist/api/openapi-ts.mjs +4 -3
- package/dist/api/openapi-ts.mjs.map +1 -1
- package/dist/api/string-to-ast.cjs +7 -9
- package/dist/api/string-to-ast.cjs.map +1 -1
- package/dist/api/string-to-ast.mjs +8 -9
- package/dist/api/string-to-ast.mjs.map +1 -1
- package/dist/api/transform-context.cjs +1 -1
- package/dist/api/transform-context.cjs.map +1 -1
- package/dist/api/transform-context.mjs +1 -1
- package/dist/api/transform-context.mjs.map +1 -1
- package/dist/api/transform-schema.cjs +2 -2
- package/dist/api/transform-schema.cjs.map +1 -1
- package/dist/api/transform-schema.mjs +3 -3
- package/dist/api/transform-schema.mjs.map +1 -1
- package/dist/application/print/type-printer.cjs +2 -1
- package/dist/application/print/type-printer.cjs.map +1 -1
- package/dist/application/print/type-printer.mjs +2 -1
- package/dist/application/print/type-printer.mjs.map +1 -1
- package/dist/application/transform/document/components.cjs +1 -1
- package/dist/application/transform/document/components.cjs.map +1 -1
- package/dist/application/transform/document/components.mjs +2 -2
- package/dist/application/transform/document/components.mjs.map +1 -1
- package/dist/application/transform/document/parameters.cjs +2 -2
- package/dist/application/transform/document/parameters.cjs.map +1 -1
- package/dist/application/transform/document/parameters.mjs +3 -3
- package/dist/application/transform/document/parameters.mjs.map +1 -1
- package/dist/application/transform/document/paths.cjs +2 -2
- package/dist/application/transform/document/paths.cjs.map +1 -1
- package/dist/application/transform/document/paths.mjs +3 -3
- package/dist/application/transform/document/paths.mjs.map +1 -1
- package/dist/application/transform/footer/evaluation.cjs +9 -0
- package/dist/application/transform/footer/evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/evaluation.mjs +8 -1
- package/dist/application/transform/footer/evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.cjs +4 -112
- package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
- package/dist/application/transform/footer/type-evaluation.mjs +4 -112
- package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
- package/dist/application/transform/footer/type-indexing.cjs +107 -0
- package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
- package/dist/application/transform/footer/type-indexing.mjs +105 -0
- package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
- package/dist/application/transform/ref-pointer/pointer-type.cjs +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
- package/dist/application/transform/ref-pointer/pointer-type.mjs +2 -2
- package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
- package/dist/application/transform/schema/access-flags.cjs +1 -1
- package/dist/application/transform/schema/access-flags.cjs.map +1 -1
- package/dist/application/transform/schema/access-flags.mjs +2 -2
- package/dist/application/transform/schema/access-flags.mjs.map +1 -1
- package/dist/application/transform/schema/array-type.cjs +2 -2
- package/dist/application/transform/schema/array-type.cjs.map +1 -1
- package/dist/application/transform/schema/array-type.mjs +3 -3
- package/dist/application/transform/schema/array-type.mjs.map +1 -1
- package/dist/application/transform/schema/composition-type.cjs +3 -3
- package/dist/application/transform/schema/composition-type.cjs.map +1 -1
- package/dist/application/transform/schema/composition-type.mjs +4 -4
- package/dist/application/transform/schema/composition-type.mjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.cjs +3 -5
- package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
- package/dist/application/transform/schema/enum-rule.mjs +4 -6
- package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
- package/dist/application/transform/schema/presence-members.cjs +1 -1
- package/dist/application/transform/schema/presence-members.cjs.map +1 -1
- package/dist/application/transform/schema/presence-members.mjs +2 -2
- package/dist/application/transform/schema/presence-members.mjs.map +1 -1
- package/dist/application/transform/schema/required-variants.cjs +2 -2
- package/dist/application/transform/schema/required-variants.cjs.map +1 -1
- package/dist/application/transform/schema/required-variants.mjs +3 -3
- package/dist/application/transform/schema/required-variants.mjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs +1 -1
- package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
- package/dist/application/transform/schema/unevaluated-properties.mjs +2 -2
- package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
- package/dist/application/transform/schema-comment.cjs +1 -1
- package/dist/application/transform/schema-comment.cjs.map +1 -1
- package/dist/application/transform/schema-comment.mjs +2 -2
- package/dist/application/transform/schema-comment.mjs.map +1 -1
- package/dist/cli/api-settings.cjs +78 -0
- package/dist/cli/api-settings.cjs.map +1 -0
- package/dist/cli/api-settings.mjs +73 -0
- package/dist/cli/api-settings.mjs.map +1 -0
- package/dist/cli/cli-arguments.cjs +1 -0
- package/dist/cli/cli-arguments.cjs.map +1 -1
- package/dist/cli/cli-arguments.mjs +1 -1
- package/dist/cli/cli-arguments.mjs.map +1 -1
- package/dist/cli/help-text.cjs +3 -3
- package/dist/cli/help-text.cjs.map +1 -1
- package/dist/cli/help-text.mjs +3 -3
- package/dist/cli/help-text.mjs.map +1 -1
- package/dist/cli/redocly-apis.cjs +8 -4
- package/dist/cli/redocly-apis.cjs.map +1 -1
- package/dist/cli/redocly-apis.mjs +7 -3
- package/dist/cli/redocly-apis.mjs.map +1 -1
- package/dist/cli/run-cli.cjs +5 -1
- package/dist/cli/run-cli.cjs.map +1 -1
- package/dist/cli/run-cli.mjs +5 -1
- package/dist/cli/run-cli.mjs.map +1 -1
- package/dist/domain/deprecations.cjs +11 -0
- package/dist/domain/deprecations.cjs.map +1 -0
- package/dist/domain/deprecations.mjs +9 -0
- package/dist/domain/deprecations.mjs.map +1 -0
- package/dist/domain/errors.cjs +4 -1
- package/dist/domain/errors.cjs.map +1 -1
- package/dist/domain/errors.mjs +4 -1
- package/dist/domain/errors.mjs.map +1 -1
- package/dist/domain/ir/constructors.cjs +2 -0
- package/dist/domain/ir/constructors.cjs.map +1 -1
- package/dist/domain/ir/constructors.mjs +2 -1
- package/dist/domain/ir/constructors.mjs.map +1 -1
- package/dist/domain/openapi/path-templates.cjs +1 -1
- package/dist/domain/openapi/path-templates.cjs.map +1 -1
- package/dist/domain/openapi/path-templates.mjs +1 -1
- package/dist/domain/openapi/path-templates.mjs.map +1 -1
- package/dist/domain/output-fixes.cjs +11 -14
- package/dist/domain/output-fixes.cjs.map +1 -1
- package/dist/domain/output-fixes.mjs +11 -13
- package/dist/domain/output-fixes.mjs.map +1 -1
- package/dist/domain/url-credentials.cjs +10 -0
- package/dist/domain/url-credentials.cjs.map +1 -0
- package/dist/domain/url-credentials.mjs +8 -0
- package/dist/domain/url-credentials.mjs.map +1 -0
- package/dist/domain/warnings.cjs +8 -1
- package/dist/domain/warnings.cjs.map +1 -1
- package/dist/domain/warnings.mjs +8 -1
- package/dist/domain/warnings.mjs.map +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1185 -59
- package/dist/index.d.mts +1185 -59
- package/dist/index.d.ts +1185 -59
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/transform/path-item-object.cjs.map +1 -1
- package/dist/transform/path-item-object.mjs.map +1 -1
- package/package.json +5 -4
- package/src/adapters/node-deprecations/node-deprecations.ts +14 -0
- package/src/adapters/redocly-loader/configured-headers.ts +65 -0
- package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
- package/src/adapters/redocly-loader/ref-access.ts +60 -5
- package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
- package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
- package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
- package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
- package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
- package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
- package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
- package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
- package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
- package/src/api/default-ports.ts +23 -0
- package/src/api/discriminator-conflict-warnings.ts +2 -2
- package/src/api/discriminator-patch.ts +11 -7
- package/src/api/discriminator-scan.ts +3 -3
- package/src/api/document-refs.ts +19 -15
- package/src/api/document-run.ts +14 -6
- package/src/api/footer-sink.ts +34 -2
- package/src/api/legacy-callback-warning.ts +16 -5
- package/src/api/legacy-ts-helpers.ts +7 -5
- package/src/api/legacy-utils.ts +1 -1
- package/src/api/openapi-ts.ts +8 -2
- package/src/api/run-context.ts +37 -0
- package/src/api/string-to-ast.ts +11 -15
- package/src/api/transform-context.ts +10 -8
- package/src/api/transform-schema.ts +9 -8
- package/src/application/ports/deprecation-reporter.ts +9 -0
- package/src/application/print/type-printer.ts +2 -1
- package/src/application/transform/document/components.ts +2 -2
- package/src/application/transform/document/parameters.ts +4 -3
- package/src/application/transform/document/paths.ts +4 -3
- package/src/application/transform/footer/evaluation.ts +10 -0
- package/src/application/transform/footer/type-evaluation.ts +6 -133
- package/src/application/transform/footer/type-indexing.ts +152 -0
- package/src/application/transform/ref-pointer/pointer-type.ts +2 -4
- package/src/application/transform/schema/access-flags.ts +2 -2
- package/src/application/transform/schema/array-type.ts +3 -5
- package/src/application/transform/schema/composition-type.ts +4 -4
- package/src/application/transform/schema/enum-rule.ts +4 -6
- package/src/application/transform/schema/presence-members.ts +2 -2
- package/src/application/transform/schema/required-variants.ts +3 -3
- package/src/application/transform/schema/unevaluated-properties.ts +2 -2
- package/src/application/transform/schema-comment.ts +2 -2
- package/src/cli/api-settings.ts +109 -0
- package/src/cli/cli-arguments.ts +5 -2
- package/src/cli/help-text.ts +4 -4
- package/src/cli/redocly-apis.ts +13 -7
- package/src/cli/run-cli.ts +5 -1
- package/src/domain/deprecations.ts +15 -0
- package/src/domain/errors.ts +16 -4
- package/src/domain/ir/constructors.ts +3 -0
- package/src/domain/ir/nodes.ts +9 -6
- package/src/domain/openapi/path-templates.ts +2 -1
- package/src/domain/output-fixes.ts +16 -16
- package/src/domain/url-credentials.ts +18 -0
- package/src/domain/warnings.ts +7 -0
- package/src/index.ts +3 -6
- package/src/transform/path-item-object.ts +6 -2
- package/src/types/generator-options.ts +12 -32
- package/src/types/openapi-common.ts +1 -0
- package/src/types/openapi-request.ts +3 -0
- package/src/types/openapi-schema.ts +2 -0
- 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,
|
|
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
|
-
|
|
7
|
-
|
|
6
|
+
intersectionType,
|
|
7
|
+
isNullish,
|
|
8
8
|
OPAQUE,
|
|
9
9
|
propertyKey,
|
|
10
10
|
unionType,
|
|
11
11
|
} from "./evaluation.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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
|
+
}
|
package/src/cli/cli-arguments.ts
CHANGED
|
@@ -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
|
-
/**
|
|
126
|
-
|
|
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
|
}
|