UNPKG

7.66 kBJavaScriptView Raw
1import { isEnumType, isNonNullType, concatAST, Kind, visit } from 'graphql';
2import { BaseSelectionSetProcessor, indent, BaseDocumentsVisitor, getConfigValue, wrapTypeWithModifiers, PreResolveTypesProcessor, generateFragmentImportStatement, SelectionSetToObject, optimizeOperations } from '@graphql-codegen/visitor-plugin-common';
3import { FlowOperationVariablesToObject } from '@graphql-codegen/flow';
4import autoBind from 'auto-bind';
5
6class 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
81class 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}
89class 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 && !this.config.inlineFragmentTypes
128 ? this.config.fragmentImports
129 // In flow, all non ` * as x` imports must be type imports
130 .map(fragmentImport => ({ ...fragmentImport, typesImport: true }))
131 .map(fragmentImport => generateFragmentImportStatement(fragmentImport, 'type'))
132 : [];
133 }
134}
135
136const 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
164export { plugin };