@gqlkit-ts/cli 0.6.0 → 0.7.1
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/dist/auto-type-generator/auto-type-generator.d.ts +7 -0
- package/dist/auto-type-generator/auto-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/auto-type-generator.js +375 -55
- package/dist/auto-type-generator/auto-type-generator.js.map +1 -1
- package/dist/auto-type-generator/discriminator-field-validator.d.ts +26 -0
- package/dist/auto-type-generator/discriminator-field-validator.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-field-validator.js +242 -0
- package/dist/auto-type-generator/discriminator-field-validator.js.map +1 -0
- package/dist/auto-type-generator/discriminator-naming.d.ts +11 -0
- package/dist/auto-type-generator/discriminator-naming.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-naming.js +15 -0
- package/dist/auto-type-generator/discriminator-naming.js.map +1 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts +44 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.d.ts.map +1 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.js +77 -0
- package/dist/auto-type-generator/discriminator-resolve-type-generator.js.map +1 -0
- package/dist/auto-type-generator/index.d.ts +3 -0
- package/dist/auto-type-generator/index.d.ts.map +1 -1
- package/dist/auto-type-generator/index.js +3 -0
- package/dist/auto-type-generator/index.js.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-enum-collector.js +14 -7
- package/dist/auto-type-generator/inline-enum-collector.js.map +1 -1
- package/dist/auto-type-generator/inline-object-converter.d.ts +12 -0
- package/dist/auto-type-generator/inline-object-converter.d.ts.map +1 -0
- package/dist/auto-type-generator/inline-object-converter.js +72 -0
- package/dist/auto-type-generator/inline-object-converter.js.map +1 -0
- package/dist/auto-type-generator/inline-object-traverser.d.ts +2 -1
- package/dist/auto-type-generator/inline-object-traverser.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-object-traverser.js +22 -4
- package/dist/auto-type-generator/inline-object-traverser.js.map +1 -1
- package/dist/auto-type-generator/inline-union-collector.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-union-collector.js +20 -6
- package/dist/auto-type-generator/inline-union-collector.js.map +1 -1
- package/dist/auto-type-generator/inline-union-types.d.ts +2 -0
- package/dist/auto-type-generator/inline-union-types.d.ts.map +1 -1
- package/dist/auto-type-generator/inline-union-validator.js +3 -3
- package/dist/auto-type-generator/inline-union-validator.js.map +1 -1
- package/dist/auto-type-generator/intersection-flattener.d.ts +44 -0
- package/dist/auto-type-generator/intersection-flattener.d.ts.map +1 -0
- package/dist/auto-type-generator/intersection-flattener.js +398 -0
- package/dist/auto-type-generator/intersection-flattener.js.map +1 -0
- package/dist/auto-type-generator/naming-convention.d.ts +21 -0
- package/dist/auto-type-generator/naming-convention.d.ts.map +1 -1
- package/dist/auto-type-generator/naming-convention.js +145 -1
- package/dist/auto-type-generator/naming-convention.js.map +1 -1
- package/dist/auto-type-generator/typename-extractor.d.ts +2 -0
- package/dist/auto-type-generator/typename-extractor.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-extractor.js +11 -3
- package/dist/auto-type-generator/typename-extractor.js.map +1 -1
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts +2 -0
- package/dist/auto-type-generator/typename-resolve-type-generator.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-resolve-type-generator.js +12 -84
- package/dist/auto-type-generator/typename-resolve-type-generator.js.map +1 -1
- package/dist/auto-type-generator/typename-types.d.ts +4 -0
- package/dist/auto-type-generator/typename-types.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-types.js +6 -0
- package/dist/auto-type-generator/typename-types.js.map +1 -1
- package/dist/auto-type-generator/typename-validator.d.ts.map +1 -1
- package/dist/auto-type-generator/typename-validator.js +4 -3
- package/dist/auto-type-generator/typename-validator.js.map +1 -1
- package/dist/commands/gen.d.ts.map +1 -1
- package/dist/commands/gen.js +2 -1
- package/dist/commands/gen.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config-loader/index.d.ts +1 -1
- package/dist/config-loader/index.d.ts.map +1 -1
- package/dist/config-loader/index.js.map +1 -1
- package/dist/config-loader/loader.d.ts +6 -0
- package/dist/config-loader/loader.d.ts.map +1 -1
- package/dist/config-loader/loader.js +1 -0
- package/dist/config-loader/loader.js.map +1 -1
- package/dist/config-loader/validator.d.ts.map +1 -1
- package/dist/config-loader/validator.js +84 -1
- package/dist/config-loader/validator.js.map +1 -1
- package/dist/gen-orchestrator/orchestrator.d.ts +2 -1
- package/dist/gen-orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/gen-orchestrator/orchestrator.js +15 -2
- package/dist/gen-orchestrator/orchestrator.js.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.d.ts.map +1 -1
- package/dist/resolver-extractor/extractor/define-api-extractor.js +4 -0
- package/dist/resolver-extractor/extractor/define-api-extractor.js.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts +2 -0
- package/dist/resolver-extractor/validator/abstract-resolver-validator.d.ts.map +1 -1
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js +16 -3
- package/dist/resolver-extractor/validator/abstract-resolver-validator.js.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.d.ts.map +1 -1
- package/dist/schema-generator/emitter/code-emitter.js +13 -1
- package/dist/schema-generator/emitter/code-emitter.js.map +1 -1
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts +18 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.d.ts.map +1 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js +89 -0
- package/dist/schema-generator/emitter/discriminator-resolve-type-emitter.js.map +1 -0
- package/dist/schema-generator/generate-schema.d.ts +2 -0
- package/dist/schema-generator/generate-schema.d.ts.map +1 -1
- package/dist/schema-generator/generate-schema.js +69 -10
- package/dist/schema-generator/generate-schema.js.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.d.ts +4 -0
- package/dist/schema-generator/integrator/result-integrator.d.ts.map +1 -1
- package/dist/schema-generator/integrator/result-integrator.js +18 -2
- package/dist/schema-generator/integrator/result-integrator.js.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts +2 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.d.ts.map +1 -1
- package/dist/schema-generator/resolver-collector/resolver-collector.js +4 -0
- package/dist/schema-generator/resolver-collector/resolver-collector.js.map +1 -1
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +14 -1
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/enum-prefix-detector.d.ts.map +1 -1
- package/dist/shared/enum-prefix-detector.js +78 -8
- package/dist/shared/enum-prefix-detector.js.map +1 -1
- package/dist/shared/inline-object-utils.js +1 -1
- package/dist/shared/inline-object-utils.js.map +1 -1
- package/dist/shared/type-converter.d.ts.map +1 -1
- package/dist/shared/type-converter.js +55 -0
- package/dist/shared/type-converter.js.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.d.ts.map +1 -1
- package/dist/type-extractor/converter/graphql-converter.js +11 -1
- package/dist/type-extractor/converter/graphql-converter.js.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.d.ts +18 -0
- package/dist/type-extractor/extractor/field-type-resolver.d.ts.map +1 -1
- package/dist/type-extractor/extractor/field-type-resolver.js +218 -16
- package/dist/type-extractor/extractor/field-type-resolver.js.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.d.ts +1 -0
- package/dist/type-extractor/extractor/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor/extractor/type-extractor.js +127 -14
- package/dist/type-extractor/extractor/type-extractor.js.map +1 -1
- package/dist/type-extractor/extractor/type-name-collector.d.ts.map +1 -1
- package/dist/type-extractor/extractor/type-name-collector.js +19 -4
- package/dist/type-extractor/extractor/type-name-collector.js.map +1 -1
- package/dist/type-extractor/types/diagnostics.d.ts +1 -1
- package/dist/type-extractor/types/diagnostics.d.ts.map +1 -1
- package/dist/type-extractor/types/index.d.ts +1 -1
- package/dist/type-extractor/types/index.d.ts.map +1 -1
- package/dist/type-extractor/types/index.js +1 -1
- package/dist/type-extractor/types/index.js.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts +7 -1
- package/dist/type-extractor/types/ts-type-reference-factory.d.ts.map +1 -1
- package/dist/type-extractor/types/ts-type-reference-factory.js +18 -3
- package/dist/type-extractor/types/ts-type-reference-factory.js.map +1 -1
- package/dist/type-extractor/types/typescript.d.ts +3 -1
- package/dist/type-extractor/types/typescript.d.ts.map +1 -1
- package/dist/type-extractor/validator/type-validator.d.ts.map +1 -1
- package/dist/type-extractor/validator/type-validator.js +6 -1
- package/dist/type-extractor/validator/type-validator.js.map +1 -1
- package/docs/configuration.md +19 -0
- package/docs/index.md +1 -0
- package/docs/integration/ai-sdk.md +189 -0
- package/docs/schema/unions.md +117 -0
- package/package.json +2 -2
- package/src/auto-type-generator/auto-type-generator.ts +576 -58
- package/src/auto-type-generator/discriminator-field-validator.ts +368 -0
- package/src/auto-type-generator/discriminator-naming.ts +24 -0
- package/src/auto-type-generator/discriminator-resolve-type-generator.ts +136 -0
- package/src/auto-type-generator/index.ts +17 -0
- package/src/auto-type-generator/inline-enum-collector.ts +19 -4
- package/src/auto-type-generator/inline-object-converter.ts +100 -0
- package/src/auto-type-generator/inline-object-traverser.ts +33 -7
- package/src/auto-type-generator/inline-union-collector.ts +26 -4
- package/src/auto-type-generator/inline-union-types.ts +2 -0
- package/src/auto-type-generator/inline-union-validator.ts +3 -3
- package/src/auto-type-generator/intersection-flattener.ts +554 -0
- package/src/auto-type-generator/naming-convention.ts +205 -1
- package/src/auto-type-generator/typename-extractor.ts +17 -3
- package/src/auto-type-generator/typename-resolve-type-generator.ts +19 -108
- package/src/auto-type-generator/typename-types.ts +7 -0
- package/src/auto-type-generator/typename-validator.ts +4 -3
- package/src/commands/gen.ts +9 -2
- package/src/config/types.ts +10 -0
- package/src/config-loader/index.ts +1 -0
- package/src/config-loader/loader.ts +11 -0
- package/src/config-loader/validator.ts +100 -1
- package/src/gen-orchestrator/orchestrator.ts +19 -2
- package/src/resolver-extractor/extractor/define-api-extractor.ts +4 -0
- package/src/resolver-extractor/validator/abstract-resolver-validator.ts +20 -6
- package/src/schema-generator/emitter/code-emitter.ts +26 -1
- package/src/schema-generator/emitter/discriminator-resolve-type-emitter.ts +125 -0
- package/src/schema-generator/generate-schema.ts +100 -13
- package/src/schema-generator/integrator/result-integrator.ts +25 -1
- package/src/schema-generator/resolver-collector/resolver-collector.ts +7 -0
- package/src/shared/constants.ts +15 -1
- package/src/shared/enum-prefix-detector.ts +96 -8
- package/src/shared/inline-object-utils.ts +1 -1
- package/src/shared/type-converter.ts +63 -0
- package/src/type-extractor/converter/graphql-converter.ts +17 -1
- package/src/type-extractor/extractor/field-type-resolver.ts +266 -16
- package/src/type-extractor/extractor/type-extractor.ts +148 -11
- package/src/type-extractor/extractor/type-name-collector.ts +19 -4
- package/src/type-extractor/types/diagnostics.ts +10 -1
- package/src/type-extractor/types/index.ts +2 -1
- package/src/type-extractor/types/ts-type-reference-factory.ts +24 -3
- package/src/type-extractor/types/typescript.ts +6 -2
- package/src/type-extractor/validator/type-validator.ts +6 -1
|
@@ -32,6 +32,86 @@ export function buildEnumPrefixCandidate(enumName: string): string {
|
|
|
32
32
|
return `${toUpperSnakeCase(enumName)}_`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const IRREGULAR_PLURAL_SEGMENTS = new Map([
|
|
36
|
+
["ALIAS", "ALIASES"],
|
|
37
|
+
["ANALYSIS", "ANALYSES"],
|
|
38
|
+
["CHILD", "CHILDREN"],
|
|
39
|
+
["COOKIE", "COOKIES"],
|
|
40
|
+
["CRISIS", "CRISES"],
|
|
41
|
+
["DIAGNOSIS", "DIAGNOSES"],
|
|
42
|
+
["FOOT", "FEET"],
|
|
43
|
+
["GOOSE", "GEESE"],
|
|
44
|
+
["MAN", "MEN"],
|
|
45
|
+
["MOUSE", "MICE"],
|
|
46
|
+
["MOVIE", "MOVIES"],
|
|
47
|
+
["PERSON", "PEOPLE"],
|
|
48
|
+
["THESIS", "THESES"],
|
|
49
|
+
["TOOTH", "TEETH"],
|
|
50
|
+
["WOMAN", "WOMEN"],
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
function isConsonant(char: string): boolean {
|
|
54
|
+
return /^[BCDFGHJKLMNPQRSTVWXYZ]$/.test(char);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function pluralizeUpperSnakeSegment(segment: string): string {
|
|
58
|
+
const irregularPlural = IRREGULAR_PLURAL_SEGMENTS.get(segment);
|
|
59
|
+
if (irregularPlural) {
|
|
60
|
+
return irregularPlural;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
segment.endsWith("Y") &&
|
|
65
|
+
segment.length > 1 &&
|
|
66
|
+
isConsonant(segment.at(-2) ?? "")
|
|
67
|
+
) {
|
|
68
|
+
return `${segment.slice(0, -1)}IES`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
segment.endsWith("S") ||
|
|
73
|
+
segment.endsWith("SH") ||
|
|
74
|
+
segment.endsWith("CH") ||
|
|
75
|
+
segment.endsWith("X") ||
|
|
76
|
+
segment.endsWith("Z")
|
|
77
|
+
) {
|
|
78
|
+
return `${segment}ES`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return `${segment}S`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildEnumPrefixCandidates(enumName: string): string[] {
|
|
85
|
+
const upperSnakeName = toUpperSnakeCase(enumName);
|
|
86
|
+
const baseCandidate = `${upperSnakeName}_`;
|
|
87
|
+
const segments = upperSnakeName.split("_");
|
|
88
|
+
const candidateSegmentSets: string[][] = [segments];
|
|
89
|
+
|
|
90
|
+
if (segments.length === 0) {
|
|
91
|
+
return [baseCandidate];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [index, segment] of segments.entries()) {
|
|
95
|
+
const pluralizedSegment = pluralizeUpperSnakeSegment(segment);
|
|
96
|
+
if (pluralizedSegment === segment) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const existingCandidates = [...candidateSegmentSets];
|
|
101
|
+
for (const candidateSegments of existingCandidates) {
|
|
102
|
+
const nextCandidateSegments = [...candidateSegments];
|
|
103
|
+
nextCandidateSegments[index] = pluralizedSegment;
|
|
104
|
+
candidateSegmentSets.push(nextCandidateSegments);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return [
|
|
109
|
+
...new Set(
|
|
110
|
+
candidateSegmentSets.map((candidate) => `${candidate.join("_")}_`),
|
|
111
|
+
),
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
35
115
|
export interface DetectEnumPrefixParams {
|
|
36
116
|
readonly enumName: string;
|
|
37
117
|
readonly memberValues: ReadonlyArray<string>;
|
|
@@ -71,20 +151,28 @@ export function detectEnumPrefix(
|
|
|
71
151
|
return { shouldStrip: false, prefix: null };
|
|
72
152
|
}
|
|
73
153
|
|
|
74
|
-
const prefixCandidate
|
|
154
|
+
for (const prefixCandidate of buildEnumPrefixCandidates(enumName)) {
|
|
155
|
+
let matches = true;
|
|
156
|
+
|
|
157
|
+
for (const value of memberValues) {
|
|
158
|
+
if (!value.startsWith(prefixCandidate)) {
|
|
159
|
+
matches = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
75
162
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
163
|
+
const stripped = value.slice(prefixCandidate.length);
|
|
164
|
+
if (stripped === "") {
|
|
165
|
+
matches = false;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
79
168
|
}
|
|
80
169
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return { shouldStrip: false, prefix: null };
|
|
170
|
+
if (matches) {
|
|
171
|
+
return { shouldStrip: true, prefix: prefixCandidate };
|
|
84
172
|
}
|
|
85
173
|
}
|
|
86
174
|
|
|
87
|
-
return { shouldStrip:
|
|
175
|
+
return { shouldStrip: false, prefix: null };
|
|
88
176
|
}
|
|
89
177
|
|
|
90
178
|
/**
|
|
@@ -12,7 +12,7 @@ export function isInlineObjectType(type: ts.Type): boolean {
|
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
14
|
const symbolName = type.symbol.getName();
|
|
15
|
-
if (symbolName !== "__type") {
|
|
15
|
+
if (symbolName !== "__type" && symbolName !== "__object") {
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
18
|
if (!(type.flags & ts.TypeFlags.Object)) {
|
|
@@ -4,6 +4,21 @@ import type {
|
|
|
4
4
|
} from "../type-extractor/types/index.js";
|
|
5
5
|
import { PRIMITIVE_TYPE_MAP } from "./constants.js";
|
|
6
6
|
|
|
7
|
+
const GRAPHQL_INT_MIN = -(2 ** 31);
|
|
8
|
+
const GRAPHQL_INT_MAX = 2 ** 31 - 1;
|
|
9
|
+
|
|
10
|
+
function numericLiteralToGraphQLScalar(name: string | null): string {
|
|
11
|
+
const num = Number(name);
|
|
12
|
+
if (
|
|
13
|
+
Number.isInteger(num) &&
|
|
14
|
+
num >= GRAPHQL_INT_MIN &&
|
|
15
|
+
num <= GRAPHQL_INT_MAX
|
|
16
|
+
) {
|
|
17
|
+
return "Int";
|
|
18
|
+
}
|
|
19
|
+
return "Float";
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
function convertElementTypeName(elementType: TSTypeReference): string {
|
|
8
23
|
if (elementType.kind === "scalar") {
|
|
9
24
|
return elementType.scalarInfo?.scalarName ?? elementType.name ?? "String";
|
|
@@ -20,6 +35,18 @@ function convertElementTypeName(elementType: TSTypeReference): string {
|
|
|
20
35
|
if (elementType.kind === "inlineEnum") {
|
|
21
36
|
return "__INLINE_ENUM__";
|
|
22
37
|
}
|
|
38
|
+
if (elementType.kind === "union") {
|
|
39
|
+
return "__INLINE_UNION__";
|
|
40
|
+
}
|
|
41
|
+
if (elementType.kind === "stringLiteral") {
|
|
42
|
+
return "String";
|
|
43
|
+
}
|
|
44
|
+
if (elementType.kind === "numericLiteral") {
|
|
45
|
+
return numericLiteralToGraphQLScalar(elementType.name);
|
|
46
|
+
}
|
|
47
|
+
if (elementType.kind === "never") {
|
|
48
|
+
return "__NEVER__";
|
|
49
|
+
}
|
|
23
50
|
return elementType.name ?? "String";
|
|
24
51
|
}
|
|
25
52
|
|
|
@@ -90,6 +117,42 @@ export function convertTsTypeToGraphQLType(
|
|
|
90
117
|
};
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
if (tsType.kind === "union") {
|
|
121
|
+
return {
|
|
122
|
+
typeName: "__INLINE_UNION__",
|
|
123
|
+
nullable,
|
|
124
|
+
list: false,
|
|
125
|
+
listItemNullable: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (tsType.kind === "stringLiteral") {
|
|
130
|
+
return {
|
|
131
|
+
typeName: "String",
|
|
132
|
+
nullable,
|
|
133
|
+
list: false,
|
|
134
|
+
listItemNullable: null,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (tsType.kind === "numericLiteral") {
|
|
139
|
+
return {
|
|
140
|
+
typeName: numericLiteralToGraphQLScalar(tsType.name),
|
|
141
|
+
nullable,
|
|
142
|
+
list: false,
|
|
143
|
+
listItemNullable: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (tsType.kind === "never") {
|
|
148
|
+
return {
|
|
149
|
+
typeName: "__NEVER__",
|
|
150
|
+
nullable,
|
|
151
|
+
list: false,
|
|
152
|
+
listItemNullable: null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
93
156
|
return {
|
|
94
157
|
typeName: tsType.name ?? "String",
|
|
95
158
|
nullable,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTypenameFieldName } from "../../auto-type-generator/typename-types.js";
|
|
1
2
|
import {
|
|
2
3
|
BUILT_IN_SCALARS,
|
|
3
4
|
isBuiltInScalar,
|
|
@@ -152,6 +153,11 @@ function convertFields(
|
|
|
152
153
|
const diagnostics: Diagnostic[] = [];
|
|
153
154
|
|
|
154
155
|
for (const field of extracted.fields) {
|
|
156
|
+
// Typename discrimination fields are silently excluded from the schema
|
|
157
|
+
if (isTypenameFieldName(field.name)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
155
161
|
const eligibility = isEligibleField({
|
|
156
162
|
fieldName: field.name,
|
|
157
163
|
kind: isInput ? "input" : "object",
|
|
@@ -171,9 +177,19 @@ function convertFields(
|
|
|
171
177
|
continue;
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
const graphqlType = convertTsTypeToGraphQLType(
|
|
181
|
+
field.tsType,
|
|
182
|
+
field.optional,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Skip fields with never type — they represent impossible values
|
|
186
|
+
if (graphqlType.typeName === "__NEVER__") {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
174
190
|
fields.push({
|
|
175
191
|
name: field.name,
|
|
176
|
-
type:
|
|
192
|
+
type: graphqlType,
|
|
177
193
|
description: field.description,
|
|
178
194
|
deprecated: field.deprecated,
|
|
179
195
|
directives: field.directives,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
1
2
|
import ts from "typescript";
|
|
2
3
|
import {
|
|
3
4
|
detectBrandedType,
|
|
@@ -7,11 +8,16 @@ import { isInternalTypeSymbol } from "../../shared/constants.js";
|
|
|
7
8
|
import { extractInlineObjectProperties as extractInlineObjectPropertiesShared } from "../../shared/inline-object-extractor.js";
|
|
8
9
|
import { isInlineObjectType } from "../../shared/inline-object-utils.js";
|
|
9
10
|
import { detectScalarMetadata } from "../../shared/metadata-detector.js";
|
|
11
|
+
import {
|
|
12
|
+
getSourceLocationFromNode,
|
|
13
|
+
type SourceLocation,
|
|
14
|
+
} from "../../shared/source-location.js";
|
|
10
15
|
import {
|
|
11
16
|
type DeprecationInfo,
|
|
12
17
|
extractTsDocFromSymbol,
|
|
13
18
|
} from "../../shared/tsdoc-parser.js";
|
|
14
19
|
import {
|
|
20
|
+
filterNonNullTypeNodes,
|
|
15
21
|
findEnumParentSymbol,
|
|
16
22
|
findNonNullTypeNode,
|
|
17
23
|
getNonNullableTypes,
|
|
@@ -25,14 +31,17 @@ import type {
|
|
|
25
31
|
ScalarMappingContext,
|
|
26
32
|
} from "../mapper/scalar-base-type-mapper.js";
|
|
27
33
|
import { lookupScalarMapping } from "../mapper/scalar-base-type-mapper.js";
|
|
34
|
+
import type { DiagnosticCode } from "../types/diagnostics.js";
|
|
28
35
|
import {
|
|
29
36
|
createArrayType,
|
|
30
37
|
createInlineEnumType,
|
|
31
38
|
createInlineObjectType,
|
|
32
|
-
|
|
39
|
+
createNeverType,
|
|
40
|
+
createNumericLiteralType,
|
|
33
41
|
createPrimitiveType,
|
|
34
42
|
createReferenceType,
|
|
35
43
|
createScalarType,
|
|
44
|
+
createStringLiteralType,
|
|
36
45
|
createUnionType,
|
|
37
46
|
} from "../types/ts-type-reference-factory.js";
|
|
38
47
|
import type {
|
|
@@ -41,6 +50,20 @@ import type {
|
|
|
41
50
|
} from "../types/typescript.js";
|
|
42
51
|
import type { GlobalTypeMapping } from "./type-extractor.js";
|
|
43
52
|
|
|
53
|
+
export interface DiscoveredTypeEntry {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly tsType: ts.Type;
|
|
56
|
+
readonly tsSymbol: ts.Symbol;
|
|
57
|
+
readonly sourceFile: string;
|
|
58
|
+
readonly sourceLocation: SourceLocation;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface FieldTypeResolverDiagnostic {
|
|
62
|
+
readonly code: DiagnosticCode;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
readonly severity: "error" | "warning";
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
export interface FieldTypeResolverContext {
|
|
45
68
|
readonly checker: ts.TypeChecker;
|
|
46
69
|
readonly knownTypeNames: ReadonlySet<string>;
|
|
@@ -52,6 +75,10 @@ export interface FieldTypeResolverContext {
|
|
|
52
75
|
readonly scalarMappingTable: ScalarBaseTypeMappingTable | null;
|
|
53
76
|
/** Current resolution context for scalar mapping (input or output) */
|
|
54
77
|
readonly scalarMappingContext: ScalarMappingContext;
|
|
78
|
+
/** Mutable map for collecting transitively discovered types */
|
|
79
|
+
readonly discoveredTypes: Map<string, DiscoveredTypeEntry> | null;
|
|
80
|
+
/** Mutable array for collecting diagnostics during field type resolution */
|
|
81
|
+
readonly diagnostics: FieldTypeResolverDiagnostic[];
|
|
55
82
|
}
|
|
56
83
|
|
|
57
84
|
/**
|
|
@@ -141,6 +168,30 @@ function resolveFieldTypeInternal(
|
|
|
141
168
|
}
|
|
142
169
|
}
|
|
143
170
|
|
|
171
|
+
// Handle `T | null` where T is a known union type alias.
|
|
172
|
+
// TypeScript flattens `T | null` when T is itself a union, losing aliasSymbol.
|
|
173
|
+
// Recover the alias by inspecting the UnionTypeNode children for a TypeReferenceNode.
|
|
174
|
+
// Only applies when there is exactly one non-null type node (e.g., `Item | null`),
|
|
175
|
+
// not when there are multiple (e.g., `User | Post | null` which is an inline union).
|
|
176
|
+
if (nullable && typeNode && ts.isUnionTypeNode(typeNode)) {
|
|
177
|
+
const nonNullTypeNodes = filterNonNullTypeNodes(typeNode);
|
|
178
|
+
if (nonNullTypeNodes.length === 1) {
|
|
179
|
+
const singleTypeNode = nonNullTypeNodes[0]!;
|
|
180
|
+
if (ts.isTypeReferenceNode(singleTypeNode)) {
|
|
181
|
+
const typeName = getTypeNameFromNode(singleTypeNode);
|
|
182
|
+
const nodeSymbol = checker.getSymbolAtLocation(
|
|
183
|
+
singleTypeNode.typeName,
|
|
184
|
+
);
|
|
185
|
+
if (
|
|
186
|
+
typeName &&
|
|
187
|
+
isKnownSchemaType(typeName, nodeSymbol ?? undefined, ctx)
|
|
188
|
+
) {
|
|
189
|
+
return createReferenceType({ name: typeName, nullable });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
144
195
|
const nonNullTypes = getNonNullableTypes(type);
|
|
145
196
|
|
|
146
197
|
// Check if all non-null types belong to the same enum
|
|
@@ -207,11 +258,28 @@ function resolveFieldTypeInternal(
|
|
|
207
258
|
return { ...innerResult, nullable };
|
|
208
259
|
}
|
|
209
260
|
|
|
210
|
-
const memberResults = nonNullTypes.map((t) =>
|
|
211
|
-
resolveFieldTypeInternal(t, undefined, ctx)
|
|
212
|
-
|
|
261
|
+
const memberResults = nonNullTypes.map((t) => {
|
|
262
|
+
const result = resolveFieldTypeInternal(t, undefined, ctx);
|
|
263
|
+
// If the result is an unresolvable reference and the original type is an
|
|
264
|
+
// object type with properties, try to expand it as an inline object.
|
|
265
|
+
// This handles external library types used as union members.
|
|
266
|
+
// When type discovery is active, discovered types are kept as references
|
|
267
|
+
// so they can be registered with their original names later.
|
|
268
|
+
if (
|
|
269
|
+
result.kind === "reference" &&
|
|
270
|
+
result.name !== null &&
|
|
271
|
+
!ctx.knownTypeNames.has(result.name) &&
|
|
272
|
+
t.flags & ts.TypeFlags.Object &&
|
|
273
|
+
t.getProperties().length > 0 &&
|
|
274
|
+
!ctx.discoveredTypes?.has(result.name)
|
|
275
|
+
) {
|
|
276
|
+
return tryExtractAsInlineObject(t, ctx, result.name);
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
});
|
|
213
280
|
|
|
214
|
-
|
|
281
|
+
const aliasName = type.aliasSymbol?.getName() ?? null;
|
|
282
|
+
return createUnionType({ members: memberResults, nullable, aliasName });
|
|
215
283
|
}
|
|
216
284
|
|
|
217
285
|
// Array type handling
|
|
@@ -231,6 +299,27 @@ function resolveFieldTypeInternal(
|
|
|
231
299
|
return createArrayType(elementResult);
|
|
232
300
|
}
|
|
233
301
|
|
|
302
|
+
// Never type — represents an impossible value, skip this field
|
|
303
|
+
// Also handles `undefined` which results from `field?: never` (never | undefined simplifies to undefined)
|
|
304
|
+
if (type.flags & ts.TypeFlags.Never || type.flags & ts.TypeFlags.Undefined) {
|
|
305
|
+
return createNeverType();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Unknown type — represents arbitrary values, map to JSON scalar (graphql-scalars)
|
|
309
|
+
if (type.flags & ts.TypeFlags.Unknown) {
|
|
310
|
+
return createScalarType({
|
|
311
|
+
name: "JSON",
|
|
312
|
+
scalarInfo: {
|
|
313
|
+
scalarName: "JSON",
|
|
314
|
+
typeName: "unknown",
|
|
315
|
+
baseType: undefined,
|
|
316
|
+
isCustom: true,
|
|
317
|
+
only: null,
|
|
318
|
+
},
|
|
319
|
+
nullable: false,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
234
323
|
// Primitive types
|
|
235
324
|
const typeString = checker.typeToString(type);
|
|
236
325
|
|
|
@@ -247,10 +336,14 @@ function resolveFieldTypeInternal(
|
|
|
247
336
|
return createPrimitiveType({ name: "boolean", nullable: false });
|
|
248
337
|
}
|
|
249
338
|
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
250
|
-
return
|
|
339
|
+
return createStringLiteralType(typeString.replace(/"/g, ""));
|
|
251
340
|
}
|
|
252
341
|
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
253
|
-
return
|
|
342
|
+
return createNumericLiteralType(typeString);
|
|
343
|
+
}
|
|
344
|
+
// Template literal types (e.g., `prefix-${string}`) represent string subsets
|
|
345
|
+
if (type.flags & ts.TypeFlags.TemplateLiteral) {
|
|
346
|
+
return createPrimitiveType({ name: "string", nullable: false });
|
|
254
347
|
}
|
|
255
348
|
|
|
256
349
|
// Intersection types in field context
|
|
@@ -291,12 +384,69 @@ function resolveFieldTypeInternal(
|
|
|
291
384
|
});
|
|
292
385
|
}
|
|
293
386
|
|
|
294
|
-
// 4.
|
|
295
|
-
|
|
387
|
+
// 4. Register in discoveredTypes if applicable (same logic as alias expansion)
|
|
388
|
+
// This handles non-exported recursive intersection types that would otherwise
|
|
389
|
+
// cause cycle detection failures in tryExtractAsInlineObject.
|
|
390
|
+
if (type.aliasSymbol) {
|
|
391
|
+
const aliasName = type.aliasSymbol.getName();
|
|
392
|
+
if (!knownTypeNames.has(aliasName)) {
|
|
393
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(aliasName)) {
|
|
394
|
+
const resolvedAliasSymbol = resolveOriginalSymbol(
|
|
395
|
+
type.aliasSymbol,
|
|
396
|
+
checker,
|
|
397
|
+
);
|
|
398
|
+
const declarations = resolvedAliasSymbol.getDeclarations();
|
|
399
|
+
const decl = declarations?.[0];
|
|
400
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
401
|
+
const properties = type.getProperties();
|
|
402
|
+
if (properties.length > 0) {
|
|
403
|
+
const declSourceFile = decl.getSourceFile();
|
|
404
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
405
|
+
file: declSourceFile.fileName,
|
|
406
|
+
line: 1,
|
|
407
|
+
column: 1,
|
|
408
|
+
};
|
|
409
|
+
ctx.discoveredTypes.set(aliasName, {
|
|
410
|
+
name: aliasName,
|
|
411
|
+
tsType: type,
|
|
412
|
+
tsSymbol: resolvedAliasSymbol,
|
|
413
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
414
|
+
sourceLocation: location,
|
|
415
|
+
});
|
|
416
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (ctx.discoveredTypes?.has(aliasName)) {
|
|
421
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 5. Otherwise, treat as inline object
|
|
427
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
296
428
|
}
|
|
297
429
|
|
|
298
430
|
// Inline object type handling
|
|
299
431
|
if (isInlineObjectType(type)) {
|
|
432
|
+
// Index signature types (Record<string, T>, { [key: string]: T })
|
|
433
|
+
// These have no named properties, only index signatures — map to JSONObject (graphql-scalars)
|
|
434
|
+
const hasStringIndex =
|
|
435
|
+
checker.getIndexTypeOfType(type, ts.IndexKind.String) !== undefined;
|
|
436
|
+
if (hasStringIndex && type.getProperties().length === 0) {
|
|
437
|
+
return createScalarType({
|
|
438
|
+
name: "JSONObject",
|
|
439
|
+
scalarInfo: {
|
|
440
|
+
scalarName: "JSONObject",
|
|
441
|
+
typeName: "Record",
|
|
442
|
+
baseType: undefined,
|
|
443
|
+
isCustom: true,
|
|
444
|
+
only: null,
|
|
445
|
+
},
|
|
446
|
+
nullable: false,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
300
450
|
// Check if typeNode references a known type
|
|
301
451
|
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
302
452
|
const typeName = getTypeNameFromNode(typeNode);
|
|
@@ -309,13 +459,30 @@ function resolveFieldTypeInternal(
|
|
|
309
459
|
}
|
|
310
460
|
}
|
|
311
461
|
|
|
312
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
462
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
313
463
|
}
|
|
314
464
|
|
|
315
465
|
// Mapped types (utility types like Omit, Pick, user-defined utilities)
|
|
316
466
|
if (type.flags & ts.TypeFlags.Object) {
|
|
317
467
|
const objectType = type as ts.ObjectType;
|
|
318
468
|
if (objectType.objectFlags & ts.ObjectFlags.Mapped) {
|
|
469
|
+
// Index signature mapped types (Record<string, T>) — map to JSONObject (graphql-scalars)
|
|
470
|
+
const hasMappedStringIndex =
|
|
471
|
+
checker.getIndexTypeOfType(type, ts.IndexKind.String) !== undefined;
|
|
472
|
+
if (hasMappedStringIndex && type.getProperties().length === 0) {
|
|
473
|
+
return createScalarType({
|
|
474
|
+
name: "JSONObject",
|
|
475
|
+
scalarInfo: {
|
|
476
|
+
scalarName: "JSONObject",
|
|
477
|
+
typeName: "Record",
|
|
478
|
+
baseType: undefined,
|
|
479
|
+
isCustom: true,
|
|
480
|
+
only: null,
|
|
481
|
+
},
|
|
482
|
+
nullable: false,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
319
486
|
// Check if typeNode references a known type (schema-defined type)
|
|
320
487
|
if (typeNode && ts.isTypeReferenceNode(typeNode)) {
|
|
321
488
|
const typeName = getTypeNameFromNode(typeNode);
|
|
@@ -328,7 +495,7 @@ function resolveFieldTypeInternal(
|
|
|
328
495
|
}
|
|
329
496
|
}
|
|
330
497
|
// Not a known type - treat as inline object
|
|
331
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
498
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
332
499
|
}
|
|
333
500
|
}
|
|
334
501
|
|
|
@@ -348,8 +515,42 @@ function resolveFieldTypeInternal(
|
|
|
348
515
|
((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0;
|
|
349
516
|
|
|
350
517
|
if (isAnonymousObject) {
|
|
518
|
+
// Check if this type alias qualifies for transitive discovery.
|
|
519
|
+
// Type aliases to object literals used as union members should be
|
|
520
|
+
// discovered with their original alias name, not expanded as inline
|
|
521
|
+
// objects with auto-generated names. (#202)
|
|
522
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(aliasName)) {
|
|
523
|
+
const resolvedAliasSymbol = resolveOriginalSymbol(
|
|
524
|
+
type.aliasSymbol,
|
|
525
|
+
checker,
|
|
526
|
+
);
|
|
527
|
+
const declarations = resolvedAliasSymbol.getDeclarations();
|
|
528
|
+
const decl = declarations?.[0];
|
|
529
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
530
|
+
const properties = type.getProperties();
|
|
531
|
+
if (properties.length > 0) {
|
|
532
|
+
const declSourceFile = decl.getSourceFile();
|
|
533
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
534
|
+
file: declSourceFile.fileName,
|
|
535
|
+
line: 1,
|
|
536
|
+
column: 1,
|
|
537
|
+
};
|
|
538
|
+
ctx.discoveredTypes.set(aliasName, {
|
|
539
|
+
name: aliasName,
|
|
540
|
+
tsType: type,
|
|
541
|
+
tsSymbol: resolvedAliasSymbol,
|
|
542
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
543
|
+
sourceLocation: location,
|
|
544
|
+
});
|
|
545
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (ctx.discoveredTypes?.has(aliasName)) {
|
|
550
|
+
return createReferenceType({ name: aliasName, nullable: false });
|
|
551
|
+
}
|
|
351
552
|
// Not a known schema type and is an anonymous object - expand to generate Payload type
|
|
352
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
553
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
353
554
|
}
|
|
354
555
|
}
|
|
355
556
|
}
|
|
@@ -411,7 +612,7 @@ function resolveFieldTypeInternal(
|
|
|
411
612
|
return createReferenceType({ name: symbolName, nullable: false });
|
|
412
613
|
}
|
|
413
614
|
// Type from outside schema files - expand as inline object
|
|
414
|
-
return tryExtractAsInlineObject(type, ctx);
|
|
615
|
+
return tryExtractAsInlineObject(type, ctx, null);
|
|
415
616
|
}
|
|
416
617
|
|
|
417
618
|
// Check for scalar base type mapping
|
|
@@ -461,6 +662,30 @@ function resolveFieldTypeInternal(
|
|
|
461
662
|
}
|
|
462
663
|
}
|
|
463
664
|
|
|
665
|
+
// Discover extractable named types for transitive type registration
|
|
666
|
+
if (ctx.discoveredTypes && !ctx.discoveredTypes.has(symbolName)) {
|
|
667
|
+
const declarations = resolvedSymbol.getDeclarations();
|
|
668
|
+
const decl = declarations?.[0];
|
|
669
|
+
if (decl && !decl.getSourceFile().isDeclarationFile) {
|
|
670
|
+
const properties = type.getProperties();
|
|
671
|
+
if (properties.length > 0) {
|
|
672
|
+
const declSourceFile = decl.getSourceFile();
|
|
673
|
+
const location = getSourceLocationFromNode(decl) ?? {
|
|
674
|
+
file: declSourceFile.fileName,
|
|
675
|
+
line: 1,
|
|
676
|
+
column: 1,
|
|
677
|
+
};
|
|
678
|
+
ctx.discoveredTypes.set(symbolName, {
|
|
679
|
+
name: symbolName,
|
|
680
|
+
tsType: type,
|
|
681
|
+
tsSymbol: resolvedSymbol,
|
|
682
|
+
sourceFile: relative(process.cwd(), declSourceFile.fileName),
|
|
683
|
+
sourceLocation: location,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
464
689
|
// Unknown type - still return reference but it will likely cause validation error later
|
|
465
690
|
return createReferenceType({ name: symbolName, nullable: false });
|
|
466
691
|
}
|
|
@@ -472,12 +697,28 @@ function resolveFieldTypeInternal(
|
|
|
472
697
|
function tryExtractAsInlineObject(
|
|
473
698
|
type: ts.Type,
|
|
474
699
|
ctx: InternalFieldTypeContext,
|
|
700
|
+
hintName: string | null,
|
|
475
701
|
): TSTypeReference {
|
|
476
702
|
const { visitedTypes, checker } = ctx;
|
|
477
703
|
if (visitedTypes.has(type)) {
|
|
478
|
-
//
|
|
479
|
-
const
|
|
480
|
-
|
|
704
|
+
// Prefer aliasSymbol (type alias name) over type.symbol (which may be __type for anonymous objects)
|
|
705
|
+
const candidateName = type.aliasSymbol?.getName() ?? type.symbol?.getName();
|
|
706
|
+
if (
|
|
707
|
+
candidateName &&
|
|
708
|
+
!isInternalTypeSymbol(candidateName) &&
|
|
709
|
+
(ctx.knownTypeNames.has(candidateName) ||
|
|
710
|
+
ctx.discoveredTypes?.has(candidateName))
|
|
711
|
+
) {
|
|
712
|
+
// Valid reference target exists in the schema
|
|
713
|
+
return createReferenceType({ name: candidateName, nullable: false });
|
|
714
|
+
}
|
|
715
|
+
// No valid reference target — emit warning and skip field
|
|
716
|
+
ctx.diagnostics.push({
|
|
717
|
+
code: "CYCLE_DETECTED",
|
|
718
|
+
message: "Cycle detected in type resolution; field will be skipped",
|
|
719
|
+
severity: "warning",
|
|
720
|
+
});
|
|
721
|
+
return createNeverType();
|
|
481
722
|
}
|
|
482
723
|
|
|
483
724
|
visitedTypes.add(type);
|
|
@@ -488,6 +729,14 @@ function tryExtractAsInlineObject(
|
|
|
488
729
|
(propType) => resolveFieldTypeInternal(propType, undefined, ctx),
|
|
489
730
|
);
|
|
490
731
|
|
|
732
|
+
// Allow this type to be visited again in sibling union members.
|
|
733
|
+
// Cycle detection still works because real cycles are caught during the
|
|
734
|
+
// recursive extractInlineObjectPropertiesShared call above (while the
|
|
735
|
+
// type is still in visitedTypes). Removing it afterward prevents
|
|
736
|
+
// false-positive cycle detection when TypeScript shares the same type
|
|
737
|
+
// object across multiple union members (common with generic .d.ts types).
|
|
738
|
+
visitedTypes.delete(type);
|
|
739
|
+
|
|
491
740
|
// Extract type-level TSDoc from the alias symbol if present (Requirement 7.2)
|
|
492
741
|
// Only extract from user-defined types, not built-in TypeScript utility types
|
|
493
742
|
let description: string | null = null;
|
|
@@ -511,6 +760,7 @@ function tryExtractAsInlineObject(
|
|
|
511
760
|
properties: inlineProperties,
|
|
512
761
|
description,
|
|
513
762
|
deprecated,
|
|
763
|
+
hintName,
|
|
514
764
|
});
|
|
515
765
|
}
|
|
516
766
|
|