1 | import { isEnumType, isNonNullType, concatAST, Kind, visit } from 'graphql';
|
2 | import { BaseSelectionSetProcessor, indent, BaseDocumentsVisitor, getConfigValue, wrapTypeWithModifiers, PreResolveTypesProcessor, generateFragmentImportStatement, SelectionSetToObject, optimizeOperations } from '@graphql-codegen/visitor-plugin-common';
|
3 | import { FlowOperationVariablesToObject } from '@graphql-codegen/flow';
|
4 | import autoBind from 'auto-bind';
|
5 |
|
6 | class FlowWithPickSelectionSetProcessor extends BaseSelectionSetProcessor {
|
7 | transformAliasesPrimitiveFields(schemaType, fields) {
|
8 | if (fields.length === 0) {
|
9 | return [];
|
10 | }
|
11 | const useFlowExactObject = this.config.useFlowExactObjects;
|
12 | const formatNamedField = this.config.formatNamedField;
|
13 | const fieldObj = schemaType.getFields();
|
14 | const parentName = (this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
|
15 | this.config.convertName(schemaType.name, {
|
16 | useTypesPrefix: true,
|
17 | });
|
18 | return [
|
19 | `{${useFlowExactObject ? '|' : ''} ${fields
|
20 | .map(aliasedField => `${formatNamedField(aliasedField.alias, fieldObj[aliasedField.fieldName].type)}: $ElementType<${parentName}, '${aliasedField.fieldName}'>`)
|
21 | .join(', ')} ${useFlowExactObject ? '|' : ''}}`,
|
22 | ];
|
23 | }
|
24 | buildFieldsIntoObject(allObjectsMerged) {
|
25 | return `...{ ${allObjectsMerged.join(', ')} }`;
|
26 | }
|
27 | buildSelectionSetFromStrings(pieces) {
|
28 | if (pieces.length === 0) {
|
29 | return null;
|
30 | }
|
31 | else if (pieces.length === 1) {
|
32 | return pieces[0];
|
33 | }
|
34 | else {
|
35 | return `({\n ${pieces.map(t => indent(`...${t}`)).join(`,\n`)}\n})`;
|
36 | }
|
37 | }
|
38 | transformLinkFields(fields) {
|
39 | if (fields.length === 0) {
|
40 | return [];
|
41 | }
|
42 | const useFlowExactObject = this.config.useFlowExactObjects;
|
43 | return [
|
44 | `{${useFlowExactObject ? '|' : ''} ${fields
|
45 | .map(field => `${field.alias || field.name}: ${field.selectionSet}`)
|
46 | .join(', ')} ${useFlowExactObject ? '|' : ''}}`,
|
47 | ];
|
48 | }
|
49 | transformPrimitiveFields(schemaType, fields) {
|
50 | if (fields.length === 0) {
|
51 | return [];
|
52 | }
|
53 | const useFlowExactObject = this.config.useFlowExactObjects;
|
54 | const formatNamedField = this.config.formatNamedField;
|
55 | const parentName = (this.config.namespacedImportName ? `${this.config.namespacedImportName}.` : '') +
|
56 | this.config.convertName(schemaType.name, {
|
57 | useTypesPrefix: true,
|
58 | });
|
59 | const fieldObj = schemaType.getFields();
|
60 | let hasConditionals = false;
|
61 | const conditilnalsList = [];
|
62 | let resString = `$Pick<${parentName}, {${useFlowExactObject ? '|' : ''} ${fields
|
63 | .map(field => {
|
64 | if (field.isConditional) {
|
65 | hasConditionals = true;
|
66 | conditilnalsList.push(field.fieldName);
|
67 | }
|
68 | return `${formatNamedField(field.fieldName, fieldObj[field.fieldName].type)}: *`;
|
69 | })
|
70 | .join(', ')} ${useFlowExactObject ? '|' : ''}}>`;
|
71 | if (hasConditionals) {
|
72 | resString = `$MakeOptional<${resString}, ${conditilnalsList.map(field => `{ ${field}: * }`).join(' | ')}>`;
|
73 | }
|
74 | return [resString];
|
75 | }
|
76 | transformTypenameField(type, name) {
|
77 | return [`{ ${name}: ${type} }`];
|
78 | }
|
79 | }
|
80 |
|
81 | class FlowSelectionSetToObject extends SelectionSetToObject {
|
82 | getUnknownType() {
|
83 | return 'any';
|
84 | }
|
85 | createNext(parentSchemaType, selectionSet) {
|
86 | return new FlowSelectionSetToObject(this._processor, this._scalars, this._schema, this._convertName.bind(this), this._getFragmentSuffix.bind(this), this._loadedFragments, this._config, parentSchemaType, selectionSet);
|
87 | }
|
88 | }
|
89 | class FlowDocumentsVisitor extends BaseDocumentsVisitor {
|
90 | constructor(schema, config, allFragments) {
|
91 | super(config, {
|
92 | useFlowExactObjects: getConfigValue(config.useFlowExactObjects, true),
|
93 | useFlowReadOnlyTypes: getConfigValue(config.useFlowReadOnlyTypes, false),
|
94 | }, schema);
|
95 | autoBind(this);
|
96 | const wrapArray = (type) => `${this.config.useFlowReadOnlyTypes ? '$ReadOnlyArray' : 'Array'}<${type}>`;
|
97 | const wrapOptional = (type) => `?${type}`;
|
98 | const useFlowReadOnlyTypes = this.config.useFlowReadOnlyTypes;
|
99 | const formatNamedField = (name, type, isConditional = false) => {
|
100 | const optional = (!!type && !isNonNullType(type)) || isConditional;
|
101 | return `${useFlowReadOnlyTypes ? '+' : ''}${name}${optional ? '?' : ''}`;
|
102 | };
|
103 | const processorConfig = {
|
104 | namespacedImportName: this.config.namespacedImportName,
|
105 | convertName: this.convertName.bind(this),
|
106 | enumPrefix: this.config.enumPrefix,
|
107 | scalars: this.scalars,
|
108 | formatNamedField,
|
109 | wrapTypeWithModifiers(baseType, type) {
|
110 | return wrapTypeWithModifiers(baseType, type, { wrapOptional, wrapArray });
|
111 | },
|
112 | };
|
113 | const processor = config.preResolveTypes
|
114 | ? new PreResolveTypesProcessor(processorConfig)
|
115 | : new FlowWithPickSelectionSetProcessor({
|
116 | ...processorConfig,
|
117 | useFlowExactObjects: this.config.useFlowExactObjects,
|
118 | });
|
119 | const enumsNames = Object.keys(schema.getTypeMap()).filter(typeName => isEnumType(schema.getType(typeName)));
|
120 | this.setSelectionSetHandler(new FlowSelectionSetToObject(processor, this.scalars, this.schema, this.convertName.bind(this), this.getFragmentSuffix.bind(this), allFragments, this.config));
|
121 | this.setVariablesTransformer(new FlowOperationVariablesToObject(this.scalars, this.convertName.bind(this), this.config.namespacedImportName, enumsNames, this.config.enumPrefix, {}, true));
|
122 | }
|
123 | getPunctuation(declarationKind) {
|
124 | return declarationKind === 'type' ? ',' : ';';
|
125 | }
|
126 | getImports() {
|
127 | return !this.config.globalNamespace
|
128 | ? this.config.fragmentImports
|
129 |
|
130 | .map(fragmentImport => ({ ...fragmentImport, typesImport: true }))
|
131 | .map(fragmentImport => generateFragmentImportStatement(fragmentImport, 'type'))
|
132 | : [];
|
133 | }
|
134 | }
|
135 |
|
136 | const plugin = (schema, rawDocuments, config) => {
|
137 | const documents = config.flattenGeneratedTypes
|
138 | ? optimizeOperations(schema, rawDocuments, { includeFragments: true })
|
139 | : rawDocuments;
|
140 | const prefix = config.preResolveTypes
|
141 | ? ''
|
142 | : `type $Pick<Origin: Object, Keys: Object> = $ObjMapi<Keys, <Key>(k: Key) => $ElementType<Origin, Key>>;\n`;
|
143 | const allAst = concatAST(documents.map(v => v.document));
|
144 | const includedFragments = allAst.definitions.filter(d => d.kind === Kind.FRAGMENT_DEFINITION);
|
145 | const allFragments = [
|
146 | ...includedFragments.map(fragmentDef => ({
|
147 | node: fragmentDef,
|
148 | name: fragmentDef.name.value,
|
149 | onType: fragmentDef.typeCondition.name.value,
|
150 | isExternal: false,
|
151 | })),
|
152 | ...(config.externalFragments || []),
|
153 | ];
|
154 | const visitor = new FlowDocumentsVisitor(schema, config, allFragments);
|
155 | const visitorResult = visit(allAst, {
|
156 | leave: visitor,
|
157 | });
|
158 | return {
|
159 | prepend: ['// @flow\n', ...visitor.getImports()],
|
160 | content: [prefix, ...visitorResult.definitions].join('\n'),
|
161 | };
|
162 | };
|
163 |
|
164 | export { plugin };
|
165 |
|