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

Commit 4c2fbf8

Browse files
committed
Generate a discriminated union whenever possible
This commit forces generating the discriminated union if there are any fragments on concrete types. It also always adds the "optional base fields" types, so it shouldn't break users that depend on them.
1 parent bb0b6a1 commit 4c2fbf8

File tree

3 files changed

+398
-234
lines changed

3 files changed

+398
-234
lines changed

src/TypeScriptGenerator.ts

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function selectionsToAST(
9393
state: State,
9494
refTypeName?: string
9595
): ts.TypeNode {
96-
const baseFields = new Map();
96+
const baseFields = new Map<string, Selection>();
9797
const byConcreteType: { [type: string]: Selection[] } = {};
9898

9999
flattenArray(selections).forEach(selection => {
@@ -112,26 +112,21 @@ function selectionsToAST(
112112
});
113113

114114
const types: ts.PropertySignature[][] = [];
115+
const discriminators = Array.from(baseFields.values()).filter(
116+
isTypenameSelection
117+
);
118+
for (const concreteType in byConcreteType) {
119+
types.push(
120+
groupRefs([...discriminators, ...byConcreteType[concreteType]]).map(
121+
selection => makeProp(selection, state, concreteType)
122+
)
123+
);
124+
}
115125

116-
if (
117-
Object.keys(byConcreteType).length &&
118-
onlySelectsTypename(Array.from(baseFields.values())) &&
119-
(hasTypenameSelection(Array.from(baseFields.values())) ||
120-
Object.keys(byConcreteType).every(type =>
121-
hasTypenameSelection(byConcreteType[type])
122-
))
123-
) {
124-
for (const concreteType in byConcreteType) {
125-
types.push(
126-
groupRefs([
127-
...Array.from(baseFields.values()),
128-
...byConcreteType[concreteType]
129-
]).map(selection => makeProp(selection, state, concreteType))
130-
);
131-
}
132-
// It might be some other type than the listed concrete types. Ideally, we
133-
// would set the type to diff(string, set of listed concrete types), but
134-
// this doesn't exist in Flow at the time.
126+
if (types.length) {
127+
// It might be some other type than the listed concrete types.
128+
// Ideally, we would set the type to Exclude<string, set of listed concrete types>,
129+
// but this doesn't work with TypeScript's discriminated unions.
135130
const otherProp = readOnlyObjectTypeProperty(
136131
"__typename",
137132
ts.createLiteralTypeNode(ts.createLiteral("%other"))
@@ -144,44 +139,51 @@ function selectionsToAST(
144139
true
145140
);
146141
types.push([otherPropWithComment]);
147-
} else {
148-
let selectionMap = selectionsToMap(Array.from(baseFields.values()));
149-
for (const concreteType in byConcreteType) {
150-
selectionMap = mergeSelections(
151-
selectionMap,
152-
selectionsToMap(
153-
byConcreteType[concreteType].map(sel => ({
154-
...sel,
155-
conditional: true
156-
}))
157-
)
158-
);
159-
}
160-
const selectionMapValues = groupRefs(Array.from(selectionMap.values())).map(
161-
sel =>
162-
isTypenameSelection(sel) && sel.concreteType
163-
? makeProp({ ...sel, conditional: false }, state, sel.concreteType)
164-
: makeProp(sel, state)
165-
);
166-
types.push(selectionMapValues);
167142
}
168143

169-
return ts.createUnionTypeNode(
170-
types.map(props => {
171-
if (refTypeName) {
172-
props.push(
173-
readOnlyObjectTypeProperty(
174-
REF_TYPE,
175-
ts.createTypeReferenceNode(
176-
ts.createIdentifier(refTypeName),
177-
undefined
178-
)
179-
)
180-
);
181-
}
182-
return exactObjectTypeAnnotation(props);
183-
})
144+
let selectionMap = selectionsToMap(Array.from(baseFields.values()));
145+
for (const concreteType in byConcreteType) {
146+
selectionMap = mergeSelections(
147+
selectionMap,
148+
selectionsToMap(
149+
byConcreteType[concreteType].map(sel => ({
150+
...sel,
151+
conditional: true
152+
}))
153+
)
154+
);
155+
}
156+
const baseProps: ts.PropertySignature[] = groupRefs(
157+
Array.from(selectionMap.values())
158+
).map(
159+
sel =>
160+
isTypenameSelection(sel) && sel.concreteType
161+
? makeProp({ ...sel, conditional: false }, state, sel.concreteType)
162+
: makeProp(sel, state)
184163
);
164+
165+
if (refTypeName) {
166+
baseProps.push(
167+
readOnlyObjectTypeProperty(
168+
REF_TYPE,
169+
ts.createTypeReferenceNode(ts.createIdentifier(refTypeName), undefined)
170+
)
171+
);
172+
}
173+
174+
if (types.length > 0) {
175+
const unionType = ts.createUnionTypeNode(
176+
types.map(props => {
177+
return exactObjectTypeAnnotation(props);
178+
})
179+
);
180+
return ts.createIntersectionTypeNode([
181+
exactObjectTypeAnnotation(baseProps),
182+
unionType
183+
]);
184+
} else {
185+
return exactObjectTypeAnnotation(baseProps);
186+
}
185187
}
186188

187189
// We don't have exact object types in typescript.

0 commit comments

Comments
 (0)