Skip to content

Commit 8eab3b0

Browse files
authored
Merge pull request #109 from code0-tech/feat/#108
Undefined type results in generic schema
2 parents 6c3c18c + 49ae1f0 commit 8eab3b0

5 files changed

Lines changed: 5750 additions & 5541 deletions

File tree

src/util/schema.util.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,20 @@ export const getSchema = (
126126
functions: FunctionDefinition[],
127127
suggestions: boolean = true
128128
): Schema => {
129+
130+
if ((parameterType.flags & ts.TypeFlags.TypeParameter) !== 0) {
131+
const decl = parameterType.symbol?.declarations?.[0]
132+
if (decl && ts.isTypeParameterDeclaration(decl) && decl.constraint) {
133+
// getTypeFromTypeNode statt getBaseConstraintOfType → aliasSymbol bleibt erhalten
134+
const constraintType = checker.getTypeFromTypeNode(decl.constraint)
135+
return getSchema(checker, node, constraintType, functionDeclarations, functions, suggestions)
136+
}
137+
}
138+
129139
// Collect all available suggestions for this parameter
130140
const combinedSuggestions = suggestions ? {
131141
suggestions: [
132-
...getValues(parameterType),
142+
...getValues(parameterType, checker),
133143
...(node ? getReferences(
134144
checker,
135145
node,
@@ -151,6 +161,19 @@ export const getSchema = (
151161
],
152162
} : {};
153163

164+
// Strip undefined from unions (e.g. string | undefined → string).
165+
// Suggestions are collected above from the original type (preserving aliasSymbol literals),
166+
// the base schema is determined from the stripped type, then both are merged.
167+
if (parameterType.isUnion()) {
168+
const nonUndefined = parameterType.types.filter(
169+
(t) => (t.flags & ts.TypeFlags.Undefined) === 0
170+
)
171+
if (nonUndefined.length === 1) {
172+
const baseSchema = getSchema(checker, node, nonUndefined[0], functionDeclarations, functions, false)
173+
return {...baseSchema, ...combinedSuggestions}
174+
}
175+
}
176+
154177
// Check individual primitive types
155178
if (isBoolean(parameterType)) {
156179
return {input: "boolean", ...combinedSuggestions};

src/util/values.util.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,32 @@ import { LiteralValue } from "@code0-tech/sagittarius-graphql-types";
2121
* // { value: "42" }
2222
* // ]
2323
*/
24-
export const getValues = (type: ts.Type): LiteralValue[] => {
24+
const getLiteralsFromTypeNode = (node: ts.TypeNode): LiteralValue[] => {
25+
if (ts.isUnionTypeNode(node)) return node.types.flatMap(getLiteralsFromTypeNode)
26+
if (ts.isLiteralTypeNode(node)) {
27+
const lit = node.literal
28+
if (ts.isStringLiteral(lit)) return [{value: lit.text, __typename: "LiteralValue"}]
29+
if (ts.isNumericLiteral(lit)) return [{value: Number(lit.text)}]
30+
if (lit.kind === ts.SyntaxKind.TrueKeyword) return [{value: true, __typename: "LiteralValue"}]
31+
if (lit.kind === ts.SyntaxKind.FalseKeyword) return [{value: false, __typename: "LiteralValue"}]
32+
}
33+
return []
34+
}
35+
36+
export const getValues = (type: ts.Type, checker?: ts.TypeChecker): LiteralValue[] => {
37+
// When a type comes from an alias (e.g. HTTP_AUTH_TYPE), TypeScript simplifies
38+
// 'Bearer' | string → string, losing the literals. Read them directly from the
39+
// alias declaration's AST instead, which preserves the original union members.
40+
if (checker && type.aliasSymbol) {
41+
const decl = type.aliasSymbol.declarations?.[0]
42+
if (decl && ts.isTypeAliasDeclaration(decl)) {
43+
return getLiteralsFromTypeNode(decl.type)
44+
}
45+
}
46+
2547
// Handle union types by recursively extracting values from each constituent type
2648
if (type.isUnion()) {
27-
return type.types.flatMap(getValues);
49+
return type.types.flatMap(t => getValues(t, checker));
2850
}
2951

3052
// Extract string literal values

0 commit comments

Comments
 (0)