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

[gql_code_builder ] bugfix for reuse-fragments feature #417

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
80 changes: 66 additions & 14 deletions codegen/gql_code_builder/lib/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:gql/ast.dart";
import "package:gql_code_builder/src/common.dart";
import "package:gql_code_builder/src/config/data_class_config.dart";
import "package:gql_code_builder/src/config/when_extension_config.dart";
import "package:gql_code_builder/src/utils/possible_types.dart";

import "./source.dart";
import "./src/operation/data.dart";
Expand All @@ -26,8 +27,11 @@ Library buildDataLibrary(
),
]) {
final fragmentMap = _fragmentMap(docSource);
final possibleTypesMap = dataClassConfig.reuseFragments
? _possibleTypesMap(schemaSource)
: <String, Set<String>>{};
final dataClassAliasMap = dataClassConfig.reuseFragments
? _dataClassAliasMap(docSource, fragmentMap)
? _dataClassAliasMap(docSource, fragmentMap, possibleTypesMap)
: <String, Reference>{};

final operationDataClasses = docSource.document.definitions
Expand All @@ -40,6 +44,7 @@ Library buildDataLibrary(
typeOverrides,
whenExtensionConfig,
fragmentMap,
possibleTypesMap,
dataClassAliasMap,
),
)
Expand All @@ -55,6 +60,7 @@ Library buildDataLibrary(
typeOverrides,
whenExtensionConfig,
fragmentMap,
possibleTypesMap,
dataClassAliasMap,
),
)
Expand All @@ -80,16 +86,40 @@ Map<String, SourceSelections> _fragmentMap(SourceNode source) => {
for (final import in source.imports) ..._fragmentMap(import)
};

Map<String, Set<String>> _possibleTypesMap(SourceNode source,
[Set<String>? visitedSource, Map<String, Set<String>>? possibleTypesMap]) {
visitedSource ??= {};
possibleTypesMap ??= {};

source.imports.forEach((s) {
if (!visitedSource!.contains(source.url)) {
visitedSource.add(source.url);
_possibleTypesMap(s, visitedSource, possibleTypesMap);
}
});

source.document.possibleTypesMap().forEach((key, value) {
possibleTypesMap![key] ??= {};
possibleTypesMap[key]!.addAll(value);
});
dehypnosis marked this conversation as resolved.
Show resolved Hide resolved

return possibleTypesMap;
}

Map<String, Reference> _dataClassAliasMap(
SourceNode source, Map<String, SourceSelections> fragmentMap,
[Map<String, Reference>? aliasMap, Set<String>? visitedSource]) {
SourceNode source,
Map<String, SourceSelections> fragmentMap,
Map<String, Set<String>> possibleTypesMap,
[Map<String, Reference>? aliasMap,
Set<String>? visitedSource]) {
aliasMap ??= {};
visitedSource ??= {};

source.imports.forEach((s) {
if (!visitedSource!.contains(source.url)) {
visitedSource.add(source.url);
_dataClassAliasMap(s, fragmentMap, aliasMap);
_dataClassAliasMap(
s, fragmentMap, possibleTypesMap, aliasMap, visitedSource);
}
});

Expand All @@ -101,6 +131,7 @@ Map<String, Reference> _dataClassAliasMap(
selections: def.selectionSet.selections,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
);
}

Expand All @@ -112,13 +143,15 @@ Map<String, Reference> _dataClassAliasMap(
selections: def.selectionSet.selections,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
);
_dataClassAliasMapDFS(
typeRefPrefix: builtClassName("${def.name.value}Data"),
getAliasTypeName: (fragmentName) => "${builtClassName(fragmentName)}Data",
selections: def.selectionSet.selections,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
);
}

Expand All @@ -131,25 +164,27 @@ void _dataClassAliasMapDFS({
required List<SelectionNode> selections,
required Map<String, SourceSelections> fragmentMap,
required Map<String, Reference> aliasMap,
required Map<String, Set<String>> possibleTypesMap,
bool shouldAlwaysGenerate = false,
}) {
if (selections.isEmpty) return;

// flatten selections to extract untouched fragments while visiting children.
// shrink selections to extract untouched fragments while visiting children.
final shrunkenSelections =
shrinkSelections(mergeSelections(selections, fragmentMap), fragmentMap);

// alias single fragment and finish
final selectionsWithoutTypename = shrunkenSelections
.where((s) => !(s is FieldNode && s.name.value == "__typename"));
if (selectionsWithoutTypename.length == 1 &&
if (!shouldAlwaysGenerate &&
selectionsWithoutTypename.length == 1 &&
selectionsWithoutTypename.first is FragmentSpreadNode) {
final node = selectionsWithoutTypename.first as FragmentSpreadNode;
final fragment = fragmentMap[node.name.value];
final fragmentTypeName = getAliasTypeName(node.name.value);
aliasMap[typeRefPrefix] =
refer(fragmentTypeName, "${fragment!.url ?? ""}#data");
// print("alias $typeRefPrefix => $fragmentTypeName");
return;
}

for (final node in selectionsWithoutTypename) {
Expand Down Expand Up @@ -179,20 +214,36 @@ void _dataClassAliasMapDFS({
selections: exclusiveFragmentSelections,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
);
} else if (node is InlineFragmentNode) {
/// TODO: Handle inline fragments without a type condition
if (node.typeCondition != null) {
/// TODO: Handle inline fragments without a type condition
final currentType = node.typeCondition!.on.name.value;
final interfaces = possibleTypesMap.entries
.where((e) => e.value.contains(currentType))
.map((e) => e.key)
.toSet();

final selectionsIncludingInterfaces = [
...selections.where((s) => !(s is InlineFragmentNode)),
// spread all interfaces which current type is belongs to
...selections
.whereType<InlineFragmentNode>()
.where((s) =>
s == node ||
interfaces.contains(s.typeCondition?.on.name.value))
.expand((s) => s.selectionSet.selections),
];

_dataClassAliasMapDFS(
typeRefPrefix:
"${typeRefPrefix}__as${node.typeCondition!.on.name.value}",
typeRefPrefix: "${typeRefPrefix}__as${currentType}",
getAliasTypeName: getAliasTypeName,
selections: [
...selections.where((s) => s != node),
...node.selectionSet.selections,
],
selections: selectionsIncludingInterfaces,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
shouldAlwaysGenerate: true,
);
}
} else if (node is FieldNode && node.selectionSet != null) {
Expand All @@ -202,6 +253,7 @@ void _dataClassAliasMapDFS({
selections: node.selectionSet!.selections,
fragmentMap: fragmentMap,
aliasMap: aliasMap,
possibleTypesMap: possibleTypesMap,
);
}
}
Expand Down
48 changes: 29 additions & 19 deletions codegen/gql_code_builder/lib/src/inline_fragment_classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ List<Spec> buildInlineFragmentClasses({
required String type,
required Map<String, Reference> typeOverrides,
required Map<String, SourceSelections> fragmentMap,
required Map<String, Set<String>> possibleTypesMap,
required Map<String, Reference> dataClassAliasMap,
required Map<String, SourceSelections> superclassSelections,
required List<InlineFragmentNode> inlineFragments,
Expand All @@ -32,6 +33,7 @@ List<Spec> buildInlineFragmentClasses({
baseTypeName: name,
inlineFragments: inlineFragments,
config: whenExtensionConfig,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
);
return [
Expand Down Expand Up @@ -71,6 +73,7 @@ List<Spec> buildInlineFragmentClasses({
fragmentMap,
),
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
schemaSource: schemaSource,
type: type,
Expand All @@ -94,28 +97,35 @@ List<Spec> buildInlineFragmentClasses({
// print("alias $typeName => ${dataClassAliasMap[typeName]!.symbol}");
return false;
}
// is it okay to inlcude interfaces?
return true;
}).expand(
(inlineFragment) => buildSelectionSetDataClasses(
name: "${name}__as${inlineFragment.typeCondition!.on.name.value}",
selections: mergeSelections(
[
...selections.whereType<FieldNode>(),
...selections.whereType<FragmentSpreadNode>(),
...inlineFragment.selectionSet.selections,
],
fragmentMap,
),
fragmentMap: fragmentMap,
dataClassAliasMap: dataClassAliasMap,
schemaSource: schemaSource,
type: inlineFragment.typeCondition!.on.name.value,
typeOverrides: typeOverrides,
superclassSelections: {
name: SourceSelections(url: null, selections: selections)
},
built: built,
whenExtensionConfig: whenExtensionConfig),
name: "${name}__as${inlineFragment.typeCondition!.on.name.value}",
selections: mergeSelections(
[
...selections.whereType<FieldNode>(),
...selections.whereType<FragmentSpreadNode>(),
...inlineFragment.selectionSet.selections,
],
fragmentMap,
),
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
schemaSource: schemaSource,
type: inlineFragment.typeCondition!.on.name.value,
typeOverrides: typeOverrides,
superclassSelections: {
name: SourceSelections(url: null, selections: selections),
if (dataClassAliasMap.isNotEmpty)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this condition is not correct.
Inline Fragments can add new fields in selections which can cause conflicts in extending the fragments.

It also causes the build error in the end to end test package.

When Removing this, it works again

...Map.fromEntries(superclassSelections.entries.map((e) => MapEntry(
"${e.key}__as${inlineFragment.typeCondition!.on.name.value}",
e.value))),
},
built: built,
whenExtensionConfig: whenExtensionConfig,
),
),
];
}
Expand Down
34 changes: 27 additions & 7 deletions codegen/gql_code_builder/lib/src/operation/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ List<Spec> buildOperationDataClasses(
Map<String, Reference> typeOverrides,
InlineFragmentSpreadWhenExtensionConfig whenExtensionConfig,
Map<String, SourceSelections> fragmentMap,
Map<String, Set<String>> possibleTypesMap,
Map<String, Reference> dataClassAliasMap,
) {
if (op.name == null) {
Expand All @@ -34,6 +35,7 @@ List<Spec> buildOperationDataClasses(
),
typeOverrides: typeOverrides,
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
superclassSelections: {},
whenExtensionConfig: whenExtensionConfig,
Expand All @@ -47,6 +49,7 @@ List<Spec> buildFragmentDataClasses(
Map<String, Reference> typeOverrides,
InlineFragmentSpreadWhenExtensionConfig whenExtensionConfig,
Map<String, SourceSelections> fragmentMap,
Map<String, Set<String>> possibleTypesMap,
Map<String, Reference> dataClassAliasMap,
) {
final selections = mergeSelections(
Expand All @@ -62,6 +65,7 @@ List<Spec> buildFragmentDataClasses(
type: frag.typeCondition.on.name.value,
typeOverrides: typeOverrides,
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
superclassSelections: {},
built: false,
Expand All @@ -75,6 +79,7 @@ List<Spec> buildFragmentDataClasses(
type: frag.typeCondition.on.name.value,
typeOverrides: typeOverrides,
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
superclassSelections: {
frag.name.value: SourceSelections(
Expand Down Expand Up @@ -120,6 +125,7 @@ List<Spec> buildSelectionSetDataClasses({
required String type,
required Map<String, Reference> typeOverrides,
required Map<String, SourceSelections> fragmentMap,
required Map<String, Set<String>> possibleTypesMap,
required Map<String, Reference> dataClassAliasMap,
required Map<String, SourceSelections> superclassSelections,
bool built = true,
Expand Down Expand Up @@ -180,6 +186,7 @@ List<Spec> buildSelectionSetDataClasses({
type: type,
typeOverrides: typeOverrides,
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
superclassSelections: superclassSelections,
inlineFragments: inlineFragments,
Expand Down Expand Up @@ -236,6 +243,7 @@ List<Spec> buildSelectionSetDataClasses({
name: "${name}_${field.alias?.value ?? field.name.value}",
selections: field.selectionSet!.selections,
fragmentMap: fragmentMap,
possibleTypesMap: possibleTypesMap,
dataClassAliasMap: dataClassAliasMap,
schemaSource: schemaSource,
type: unwrapTypeNode(
Expand Down Expand Up @@ -292,20 +300,32 @@ List<SelectionNode> shrinkSelections(
}
}

final duplicateIndices = <int>{};
for (final node in unmerged.whereType<FragmentSpreadNode>().toList()) {
if (!fragmentMap.containsKey(node.name.value)) {
throw "Cannot find a fragment ${node.name.value} from current file.";
}
final fragment = fragmentMap[node.name.value]!;
final spreadIndex = unmerged.indexOf(node);
final duplicateIndexList = <int>[];
final fragmentExpanded = {
...fragment.selections,
..._expandFragmentSpreads(fragment.selections, fragmentMap)
};
final fragmentSpreadIndex = unmerged.indexOf(node);
unmerged.forEachIndexed((selectionIndex, selection) {
if (selectionIndex > spreadIndex &&
fragment.selections.any((s) => s.hashCode == selection.hashCode)) {
duplicateIndexList.add(selectionIndex);
if (selectionIndex != fragmentSpreadIndex &&
!(selection is FieldNode && selection.name.value == "__typename") &&
fragmentExpanded.any((s) => s.hashCode == selection.hashCode)) {
duplicateIndices.add(selectionIndex);
}
});
duplicateIndexList.reversed.forEach(unmerged.removeAt);
}

return unmerged;
return unmerged
.asMap()
.entries
.where((e) => !duplicateIndices.contains(e.key))
.map((e) => e.value)
.toList();
}

/// Deeply merges field nodes
Expand Down
11 changes: 8 additions & 3 deletions codegen/gql_code_builder/lib/src/when_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ Extension? inlineFragmentWhenExtension(
{required String baseTypeName,
required List<InlineFragmentNode> inlineFragments,
required InlineFragmentSpreadWhenExtensionConfig config,
required Map<String, Set<String>> possibleTypesMap,
required Map<String, Reference> dataClassAliasMap}) {
final inlineFragmentsWithTypConditions = inlineFragments
.where((inlineFragment) => inlineFragment.typeCondition != null)
.where((inlineFragment) =>
inlineFragment.typeCondition != null
// except interfaces on when extension
&&
!possibleTypesMap
.containsKey(inlineFragment.typeCondition!.on.name.value))
dehypnosis marked this conversation as resolved.
Show resolved Hide resolved
.toList();

final nothingToDo = inlineFragmentsWithTypConditions.isEmpty ||
Expand Down Expand Up @@ -187,8 +193,7 @@ Extension? inlineFragmentWhenExtension(
[
_switchTypeName,
_curlyBracketOpen,
...inlineFragments
.where((element) => element.typeCondition != null)
...inlineFragmentsWithTypConditions
.map((inlineFragment) => Block.of([
_caseStatement(inlineFragment),
maybeWhenCaseBody(inlineFragment),
Expand Down
Loading