Skip to content
This repository was archived by the owner on Sep 27, 2023. It is now read-only.

Commit fe0f9ed

Browse files
kastermesteralloy
authored andcommitted
Report selectionsToBabel -> selectionsToAST to fixup union type decls
See the changes in the tests. This should allow much easier work with types that can take many different shapes. I assume this will fix #120.
1 parent 7bd8683 commit fe0f9ed

File tree

2 files changed

+188
-384
lines changed

2 files changed

+188
-384
lines changed

src/TypeScriptGenerator.ts

Lines changed: 98 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ function nullthrows<T>(obj: T | null | undefined): T {
6161
function makeProp(
6262
selection: Selection,
6363
state: State,
64+
unmasked: boolean,
6465
concreteType?: string
6566
): ts.PropertySignature {
6667
let { value } = selection;
@@ -69,7 +70,11 @@ function makeProp(
6970
value = transformScalarType(
7071
nodeType,
7172
state,
72-
selectionsToAST([Array.from(nullthrows(nodeSelections).values())], state)
73+
selectionsToAST(
74+
[Array.from(nullthrows(nodeSelections).values())],
75+
state,
76+
unmasked
77+
)
7378
);
7479
}
7580
if (schemaName === "__typename" && concreteType) {
@@ -88,6 +93,7 @@ const onlySelectsTypename = (selections: Selection[]) =>
8893
function selectionsToAST(
8994
selections: Selection[][],
9095
state: State,
96+
unmasked: boolean,
9197
refTypeName?: string
9298
): ts.TypeNode {
9399
const baseFields = new Map<string, Selection>();
@@ -109,77 +115,95 @@ function selectionsToAST(
109115
});
110116

111117
const types: ts.PropertySignature[][] = [];
112-
const discriminators = Array.from(baseFields.values()).filter(
113-
isTypenameSelection
114-
);
115-
for (const concreteType in byConcreteType) {
116-
types.push(
117-
groupRefs([...discriminators, ...byConcreteType[concreteType]]).map(
118-
selection => makeProp(selection, state, concreteType)
119-
)
120-
);
121-
}
122-
123-
if (types.length) {
124-
// It might be some other type than the listed concrete types.
125-
// Ideally, we would set the type to Exclude<string, set of listed concrete types>,
126-
// but this doesn't work with TypeScript's discriminated unions.
127-
const otherProp = readOnlyObjectTypeProperty(
128-
"__typename",
129-
ts.createLiteralTypeNode(ts.createLiteral("%other"))
130-
);
131-
const otherPropWithComment = ts.addSyntheticLeadingComment(
132-
otherProp,
133-
ts.SyntaxKind.MultiLineCommentTrivia,
134-
"This will never be '% other', but we need some\n" +
135-
"value in case none of the concrete values match.",
136-
true
137-
);
138-
types.push([otherPropWithComment]);
139-
}
118+
if (
119+
Object.keys(byConcreteType).length > 0 &&
120+
onlySelectsTypename(Array.from(baseFields.values())) &&
121+
(hasTypenameSelection(Array.from(baseFields.values())) ||
122+
Object.keys(byConcreteType).every(type =>
123+
hasTypenameSelection(byConcreteType[type])
124+
))
125+
) {
126+
const typenameAliases = new Set<string>();
127+
for (const concreteType in byConcreteType) {
128+
types.push(
129+
groupRefs([
130+
...Array.from(baseFields.values()),
131+
...byConcreteType[concreteType]
132+
]).map(selection => {
133+
if (selection.schemaName === "__typename") {
134+
typenameAliases.add(selection.key);
135+
}
136+
return makeProp(selection, state, unmasked, concreteType);
137+
})
138+
);
139+
}
140140

141-
let selectionMap = selectionsToMap(Array.from(baseFields.values()));
142-
for (const concreteType in byConcreteType) {
143-
selectionMap = mergeSelections(
144-
selectionMap,
145-
selectionsToMap(
146-
byConcreteType[concreteType].map(sel => ({
147-
...sel,
148-
conditional: true
149-
}))
150-
)
141+
types.push(
142+
Array.from(typenameAliases).map(typenameAlias => {
143+
const otherProp = readOnlyObjectTypeProperty(
144+
typenameAlias,
145+
ts.createLiteralTypeNode(ts.createLiteral("%other"))
146+
);
147+
const otherPropWithComment = ts.addSyntheticLeadingComment(
148+
otherProp,
149+
ts.SyntaxKind.MultiLineCommentTrivia,
150+
"This will never be '%other', but we need some\n" +
151+
"value in case none of the concrete values match.",
152+
true
153+
);
154+
return otherPropWithComment;
155+
})
151156
);
152-
}
153-
const baseProps: ts.PropertySignature[] = groupRefs(
154-
Array.from(selectionMap.values())
155-
).map(sel =>
156-
isTypenameSelection(sel) && sel.concreteType
157-
? makeProp({ ...sel, conditional: false }, state, sel.concreteType)
158-
: makeProp(sel, state)
159-
);
160-
161-
if (refTypeName) {
162-
baseProps.push(
163-
readOnlyObjectTypeProperty(
164-
REF_TYPE,
165-
ts.createTypeReferenceNode(ts.createIdentifier(refTypeName), undefined)
166-
)
157+
} else {
158+
let selectionMap = selectionsToMap(Array.from(baseFields.values()));
159+
for (const concreteType in byConcreteType) {
160+
selectionMap = mergeSelections(
161+
selectionMap,
162+
selectionsToMap(
163+
byConcreteType[concreteType].map(sel => ({
164+
...sel,
165+
conditional: true
166+
}))
167+
)
168+
);
169+
}
170+
const selectionMapValues = groupRefs(Array.from(selectionMap.values())).map(
171+
sel =>
172+
isTypenameSelection(sel) && sel.concreteType
173+
? makeProp(
174+
{
175+
...sel,
176+
conditional: false
177+
},
178+
state,
179+
unmasked,
180+
sel.concreteType
181+
)
182+
: makeProp(sel, state, unmasked)
167183
);
184+
types.push(selectionMapValues);
168185
}
169186

170-
if (types.length > 0) {
171-
const unionType = ts.createUnionTypeNode(
172-
types.map(props => {
173-
return exactObjectTypeAnnotation(props);
174-
})
175-
);
176-
return ts.createIntersectionTypeNode([
177-
exactObjectTypeAnnotation(baseProps),
178-
unionType
179-
]);
180-
} else {
181-
return exactObjectTypeAnnotation(baseProps);
187+
const typeElements = types.map(props => {
188+
if (refTypeName) {
189+
props.push(
190+
readOnlyObjectTypeProperty(
191+
REF_TYPE,
192+
ts.createTypeReferenceNode(
193+
ts.createIdentifier(refTypeName),
194+
undefined
195+
)
196+
)
197+
);
198+
}
199+
return unmasked
200+
? ts.createTypeLiteralNode(props)
201+
: exactObjectTypeAnnotation(props);
202+
});
203+
if (typeElements.length === 1) {
204+
return typeElements[0];
182205
}
206+
return ts.createUnionTypeNode(typeElements);
183207
}
184208

185209
// We don't have exact object types in typescript.
@@ -290,7 +314,7 @@ function createVisitor(options: TypeGeneratorOptions) {
290314
const inputObjectTypes = generateInputObjectTypes(state);
291315
const responseType = exportType(
292316
`${node.name}Response`,
293-
selectionsToAST(node.selections, state)
317+
selectionsToAST(node.selections, state, false)
294318
);
295319
const operationType = exportType(
296320
node.name,
@@ -368,7 +392,13 @@ function createVisitor(options: TypeGeneratorOptions) {
368392
);
369393
refTypeNodes.push(refType);
370394
}
371-
const baseType = selectionsToAST(selections, state, refTypeName);
395+
const unmasked = node.metadata != null && node.metadata.mask === false;
396+
const baseType = selectionsToAST(
397+
selections,
398+
state,
399+
unmasked,
400+
refTypeName
401+
);
372402
const type = isPlural(node)
373403
? ts.createTypeReferenceNode(ts.createIdentifier("ReadonlyArray"), [
374404
baseType

0 commit comments

Comments
 (0)