Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split large unions over multiple lines #10219

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1071,54 +1071,58 @@ export class BaseResolversVisitor<
memberTypes: readonly GraphQLObjectType[] | GraphQLObjectType[];
isTypenameNonOptional: boolean;
}): string {
const result =
memberTypes
.map(type => {
const isTypeMapped = this.config.mappers[type.name];
// 1. If mapped without placehoder, just use it without doing extra checks
if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) {
return { typename: type.name, typeValue: isTypeMapped.type };
}
const members = memberTypes
.map(type => {
const isTypeMapped = this.config.mappers[type.name];
// 1. If mapped without placehoder, just use it without doing extra checks
if (isTypeMapped && !hasPlaceholder(isTypeMapped.type)) {
return { typename: type.name, typeValue: isTypeMapped.type };
}

// 2. Work out value for type
// 2a. By default, use the typescript type
let typeValue = this.convertName(type.name, {}, true);
// 2. Work out value for type
// 2a. By default, use the typescript type
let typeValue = this.convertName(type.name, {}, true);

// 2b. Find fields to Omit if needed.
// - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
// - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
const fieldsToOmit = this.getRelevantFieldsToOmit({
schemaType: type,
getTypeToUse: baseType => `_RefType['${baseType}']`,
});
if (fieldsToOmit.length > 0) {
typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit);
}
// 2b. Find fields to Omit if needed.
// - If no field to Omit, "type with maybe Omit" is typescript type i.e. no Omit
// - If there are fields to Omit, keep track of these "type with maybe Omit" to replace in original unionMemberValue
const fieldsToOmit = this.getRelevantFieldsToOmit({
schemaType: type,
getTypeToUse: baseType => `_RefType['${baseType}']`,
});
if (fieldsToOmit.length > 0) {
typeValue = this.replaceFieldsInType(typeValue, fieldsToOmit);
}

// 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T}
if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) {
return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) };
}
// 2c. If type is mapped with placeholder, use the "type with maybe Omit" as {T}
if (isTypeMapped && hasPlaceholder(isTypeMapped.type)) {
return { typename: type.name, typeValue: replacePlaceholder(isTypeMapped.type, typeValue) };
}

// 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T}
const hasDefaultMapper = !!this.config.defaultMapper?.type;
const isScalar = this.config.scalars[typeName];
if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
const finalTypename = isScalar ? this._getScalar(typeName) : typeValue;
return {
typename: type.name,
typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename),
};
}
// 2d. If has default mapper with placeholder, use the "type with maybe Omit" as {T}
const hasDefaultMapper = !!this.config.defaultMapper?.type;
const isScalar = this.config.scalars[typeName];
if (hasDefaultMapper && hasPlaceholder(this.config.defaultMapper.type)) {
const finalTypename = isScalar ? this._getScalar(typeName) : typeValue;
return {
typename: type.name,
typeValue: replacePlaceholder(this.config.defaultMapper.type, finalTypename),
};
}

return { typename: type.name, typeValue };
})
.map(({ typename, typeValue }) => {
const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : '';
return { typename: type.name, typeValue };
})
.map(({ typename, typeValue }) => {
const nonOptionalTypenameModifier = isTypenameNonOptional ? ` & { __typename: '${typename}' }` : '';

return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them
})
.join(' | ') || 'never';
return `( ${typeValue}${nonOptionalTypenameModifier} )`; // Must wrap every type in explicit "( )" to separate them
});
const result =
members.length === 0
? 'never'
: this.config.printFieldsOnNewLines && members.length > 1
? `\n | ${members.join('\n | ')}`
: members.join(' | ');
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
this.getEmptyObjectTypeString(mustAddEmptyObject),
].filter(Boolean);

const content = typeParts.join(' | ');
const content = formatUnion(this._config, typeParts);

if (typeParts.length > 1 && this._config.extractAllFieldsToTypes) {
return {
Expand Down Expand Up @@ -933,7 +933,12 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
.export()
.asKind('type')
.withName(mergedTypeString)
.withContent(subTypes.map(t => t.name).join(' | ')).string,
.withContent(
formatUnion(
this._config,
subTypes.map(t => t.name)
)
).string,
].join('\n');
}

Expand All @@ -950,3 +955,10 @@ export class SelectionSetToObject<Config extends ParsedDocumentsConfig = ParsedD
return operationTypes.includes(typeName) ? parentName : `${parentName}_${typeName}`;
}
}

function formatUnion(config: ParsedDocumentsConfig, members: string[]): string {
if (config.printFieldsOnNewLines && members.length > 1) {
return `\n | ${members.join('\n | ')}`;
}
return members.join(' | ');
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor<S
});

if (unsetTypes) {
return [`MakeEmpty<${parentName}, ${fields.map(field => `'${field.fieldName}'`).join(' | ')}>`];
const escapedFieldNames = fields.map(field => `'${field.fieldName}'`);
return [formattedUnionTransform(this.config, 'MakeEmpty', parentName, escapedFieldNames)];
}

let hasConditionals = false;
const conditilnalsList: string[] = [];
let resString = `Pick<${parentName}, ${fields
.map(field => {
if (field.isConditional) {
hasConditionals = true;
conditilnalsList.push(field.fieldName);
}
return `'${field.fieldName}'`;
})
.join(' | ')}>`;
const escapedConditionalsList: string[] = [];
const escapedFieldNames = fields.map(field => {
if (field.isConditional) {
hasConditionals = true;
escapedConditionalsList.push(`'${field.fieldName}'`);
}
return `'${field.fieldName}'`;
});
let resString = formattedUnionTransform(this.config, 'Pick', parentName, escapedFieldNames);

if (hasConditionals) {
const avoidOptional =
Expand All @@ -52,7 +52,7 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor<S
const transform = avoidOptional ? 'MakeMaybe' : 'MakeOptional';
resString = `${
this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : ''
}${transform}<${resString}, ${conditilnalsList.map(field => `'${field}'`).join(' | ')}>`;
}${formattedUnionTransform(this.config, transform, resString, escapedConditionalsList)}`;
}
return [resString];
}
Expand All @@ -75,25 +75,43 @@ export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor<S
useTypesPrefix: true,
});

return [
`{ ${fields
.map(aliasedField => {
const value =
aliasedField.fieldName === '__typename'
? `'${schemaType.name}'`
: `${parentName}['${aliasedField.fieldName}']`;

return `${aliasedField.alias}: ${value}`;
})
.join(', ')} }`,
];
const selections = fields.map(aliasedField => {
const value =
aliasedField.fieldName === '__typename' ? `'${schemaType.name}'` : `${parentName}['${aliasedField.fieldName}']`;

return `${aliasedField.alias}: ${value}`;
});
return [formatSelections(this.config, selections)];
}

transformLinkFields(fields: LinkField[]): ProcessResult {
if (fields.length === 0) {
return [];
}

return [`{ ${fields.map(field => `${field.alias || field.name}: ${field.selectionSet}`).join(', ')} }`];
const selections = fields.map(field => `${field.alias || field.name}: ${field.selectionSet}`);

return [formatSelections(this.config, selections)];
}
}

/** Equivalent to `${transformName}<${target}, ${unionElements.join(' | ')}>`, but with line feeds if necessary */
function formattedUnionTransform(
config: SelectionSetProcessorConfig,
transformName: string,
target: string,
unionElements: string[]
): string {
if (config.printFieldsOnNewLines && unionElements.length > 3) {
return `${transformName}<\n ${target},\n | ${unionElements.join('\n | ')}\n >`;
}
return `${transformName}<${target}, ${unionElements.join(' | ')}>`;
}

/** Equivalent to `{ ${selections.join(', ')} }`, but with line feeds if necessary */
function formatSelections(config: SelectionSetProcessorConfig, selections: string[]): string {
if (config.printFieldsOnNewLines && selections.length > 1) {
return `{\n ${selections.join(',\n ')},\n }`;
}
return `{ ${selections.join(', ')} }`;
}
Loading
Loading