-
Notifications
You must be signed in to change notification settings - Fork 64
/
Copy pathGraphQLStatementsFormatter.ts
128 lines (112 loc) · 5.09 KB
/
GraphQLStatementsFormatter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import * as path from 'path';
import prettier, { BuiltInParserName } from 'prettier';
import {
interfaceNameFromOperation,
interfaceVariablesNameFromOperation,
} from '@aws-amplify/graphql-types-generator/lib/typescript/codeGeneration';
import type { GraphQLWithMeta } from '@aws-amplify/graphql-docs-generator';
const CODEGEN_WARNING = 'this is an auto generated file. This will be overwritten';
const LINE_DELIMITOR = '\n';
type Language = 'javascript' | 'graphql' | 'typescript' | 'flow' | 'angular';
/**
* Utility class to format the generated GraphQL statements based on frontend language type
*/
export class GraphQLStatementsFormatter {
private language: Language;
private lintOverrides: string[];
private headerComments: string[];
private opTypeName?: string;
private typesPath: string | null;
private includeTypeScriptTypes: boolean;
constructor(language: Language, operation: string, typesPath?: string) {
this.language = language || 'graphql';
this.opTypeName = {
queries: 'Query',
mutations: 'Mutation',
subscriptions: 'Subscription',
}[operation];
this.lintOverrides = [];
this.headerComments = [];
if (typesPath) {
const { dir, name } = path.parse(typesPath);
// ensure posix path separators are used
// Fallback to \ because path.win32 is not implemented by path-browserify
const typesPathWithoutExtension = path.join(dir, name).split(path.win32?.sep || '\\').join(path.posix.sep);
if (!typesPathWithoutExtension.startsWith('.')) {
// path.join will strip prefixed ./
this.typesPath = `./${typesPathWithoutExtension}`;
} else {
this.typesPath = typesPathWithoutExtension;
}
} else {
this.typesPath = null;
}
this.includeTypeScriptTypes = !!(this.language === 'typescript' && this.opTypeName && this.typesPath);
}
get typeDefs() {
if (!this.includeTypeScriptTypes) return '';
return [
`import * as APITypes from '${this.typesPath}';`,
`type Generated${this.opTypeName}<InputType, OutputType> = string & {`,
` __generated${this.opTypeName}Input: InputType;`,
` __generated${this.opTypeName}Output: OutputType;`,
`};`,
].join(LINE_DELIMITOR);
}
format(statements: Map<string, GraphQLWithMeta>): string {
switch (this.language) {
case 'javascript':
this.headerComments.push(CODEGEN_WARNING);
this.lintOverrides.push('/* eslint-disable */');
return this.prettify(this.formatJS(statements));
case 'typescript':
this.headerComments.push(CODEGEN_WARNING);
this.lintOverrides.push(...['/* tslint:disable */', '/* eslint-disable */']);
return this.prettify(this.formatJS(statements));
case 'flow':
this.headerComments.push('@flow', CODEGEN_WARNING);
return this.prettify(this.formatJS(statements));
default:
this.headerComments.push(CODEGEN_WARNING);
return this.prettify(this.formatGraphQL(statements));
}
}
formatGraphQL(statements: Map<string, GraphQLWithMeta>): string {
const headerBuffer = this.headerComments.map(comment => `# ${comment}`).join(LINE_DELIMITOR);
const statementsBuffer = statements ? [...statements.values()].map(s => s.graphql).join(LINE_DELIMITOR) : '';
const formattedOutput = [headerBuffer, LINE_DELIMITOR, statementsBuffer].join(LINE_DELIMITOR);
return formattedOutput;
}
formatJS(statements: Map<string, GraphQLWithMeta>): string {
const lintOverridesBuffer = this.lintOverrides.join(LINE_DELIMITOR);
const headerBuffer = this.headerComments.map(comment => `// ${comment}`).join(LINE_DELIMITOR);
const formattedStatements = [];
if (statements) {
for (const [key, { graphql, operationName, operationType }] of statements) {
const typeTag = this.buildTypeTag(operationName, operationType);
const formattedGraphQL = prettier.format(graphql, { parser: 'graphql' });
formattedStatements.push(`export const ${key} = /* GraphQL */ \`${formattedGraphQL}\`${typeTag}`);
}
}
const typeDefs = this.includeTypeScriptTypes ? [LINE_DELIMITOR, this.typeDefs] : [];
const formattedOutput = [lintOverridesBuffer, headerBuffer, ...typeDefs, LINE_DELIMITOR, ...formattedStatements].join(LINE_DELIMITOR);
return formattedOutput;
}
buildTypeTag(operationName?: string, operationType?: string): string {
if (!this.includeTypeScriptTypes || operationName === undefined || operationType === undefined) return '';
const operationDef = { operationName, operationType };
const resultTypeName = `APITypes.${interfaceNameFromOperation(operationDef)}`;
const variablesTypeName = `APITypes.${interfaceVariablesNameFromOperation(operationDef)}`;
return ` as Generated${this.opTypeName}<${variablesTypeName}, ${resultTypeName}>;`;
}
prettify(output: string): string {
const parserMap: { [key in Language]: BuiltInParserName } = {
javascript: 'babel',
graphql: 'graphql',
typescript: 'typescript',
flow: 'flow',
angular: 'graphql',
};
return prettier.format(output, { parser: parserMap[this.language || 'graphql'] });
}
}