Mirror: The spec-compliant minimum of client-side GraphQL.
1import type {
2 ASTNode,
3 NameNode,
4 DocumentNode,
5 VariableNode,
6 SelectionSetNode,
7 FieldNode,
8 ArgumentNode,
9 FragmentSpreadNode,
10 InlineFragmentNode,
11 VariableDefinitionNode,
12 OperationDefinitionNode,
13 FragmentDefinitionNode,
14 IntValueNode,
15 FloatValueNode,
16 StringValueNode,
17 BooleanValueNode,
18 NullValueNode,
19 EnumValueNode,
20 ListValueNode,
21 ObjectValueNode,
22 ObjectFieldNode,
23 DirectiveNode,
24 NamedTypeNode,
25 ListTypeNode,
26 NonNullTypeNode,
27} from './ast';
28
29function mapJoin<T>(value: readonly T[], joiner: string, mapper: (value: T) => string): string {
30 let out = '';
31 for (let index = 0; index < value.length; index++) {
32 if (index) out += joiner;
33 out += mapper(value[index]);
34 }
35 return out;
36}
37
38function printString(string: string): string {
39 return JSON.stringify(string);
40}
41
42function printBlockString(string: string): string {
43 return '"""\n' + string.replace(/"""/g, '\\"""') + '\n"""';
44}
45
46const MAX_LINE_LENGTH = 80;
47
48let LF = '\n';
49
50const nodes = {
51 OperationDefinition(node: OperationDefinitionNode): string {
52 let out: string = '';
53 if (node.description) {
54 out += nodes.StringValue(node.description) + '\n';
55 }
56 out += node.operation;
57 if (node.name) out += ' ' + node.name.value;
58 if (node.variableDefinitions && node.variableDefinitions.length) {
59 if (!node.name) out += ' ';
60 out += '(' + mapJoin(node.variableDefinitions, ', ', nodes.VariableDefinition) + ')';
61 }
62 if (node.directives && node.directives.length)
63 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
64 const selectionSet = nodes.SelectionSet(node.selectionSet);
65 return out !== 'query' ? out + ' ' + selectionSet : selectionSet;
66 },
67 VariableDefinition(node: VariableDefinitionNode): string {
68 let out = '';
69 if (node.description) {
70 out += nodes.StringValue(node.description) + ' ';
71 }
72 out += nodes.Variable!(node.variable) + ': ' + _print(node.type);
73 if (node.defaultValue) out += ' = ' + _print(node.defaultValue);
74 if (node.directives && node.directives.length)
75 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
76 return out;
77 },
78 Field(node: FieldNode): string {
79 let out = node.alias ? node.alias.value + ': ' + node.name.value : node.name.value;
80 if (node.arguments && node.arguments.length) {
81 const args = mapJoin(node.arguments, ', ', nodes.Argument);
82 if (out.length + args.length + 2 > MAX_LINE_LENGTH) {
83 out +=
84 '(' +
85 (LF += ' ') +
86 mapJoin(node.arguments, LF, nodes.Argument) +
87 (LF = LF.slice(0, -2)) +
88 ')';
89 } else {
90 out += '(' + args + ')';
91 }
92 }
93 if (node.directives && node.directives.length)
94 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
95 if (node.selectionSet && node.selectionSet.selections.length) {
96 out += ' ' + nodes.SelectionSet(node.selectionSet);
97 }
98 return out;
99 },
100 StringValue(node: StringValueNode): string {
101 if (node.block) {
102 return printBlockString(node.value).replace(/\n/g, LF);
103 } else {
104 return printString(node.value);
105 }
106 },
107 BooleanValue(node: BooleanValueNode): string {
108 return '' + node.value;
109 },
110 NullValue(_node: NullValueNode): string {
111 return 'null';
112 },
113 IntValue(node: IntValueNode): string {
114 return node.value;
115 },
116 FloatValue(node: FloatValueNode): string {
117 return node.value;
118 },
119 EnumValue(node: EnumValueNode): string {
120 return node.value;
121 },
122 Name(node: NameNode): string {
123 return node.value;
124 },
125 Variable(node: VariableNode): string {
126 return '$' + node.name.value;
127 },
128 ListValue(node: ListValueNode): string {
129 return '[' + mapJoin(node.values, ', ', _print) + ']';
130 },
131 ObjectValue(node: ObjectValueNode): string {
132 return '{' + mapJoin(node.fields, ', ', nodes.ObjectField) + '}';
133 },
134 ObjectField(node: ObjectFieldNode): string {
135 return node.name.value + ': ' + _print(node.value);
136 },
137 Document(node: DocumentNode): string {
138 if (!node.definitions || !node.definitions.length) return '';
139 return mapJoin(node.definitions, '\n\n', _print);
140 },
141 SelectionSet(node: SelectionSetNode): string {
142 return '{' + (LF += ' ') + mapJoin(node.selections, LF, _print) + (LF = LF.slice(0, -2)) + '}';
143 },
144 Argument(node: ArgumentNode): string {
145 return node.name.value + ': ' + _print(node.value);
146 },
147 FragmentSpread(node: FragmentSpreadNode): string {
148 let out = '...' + node.name.value;
149 if (node.directives && node.directives.length)
150 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
151 return out;
152 },
153 InlineFragment(node: InlineFragmentNode): string {
154 let out = '...';
155 if (node.typeCondition) out += ' on ' + node.typeCondition.name.value;
156 if (node.directives && node.directives.length)
157 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
158 out += ' ' + nodes.SelectionSet(node.selectionSet);
159 return out;
160 },
161 FragmentDefinition(node: FragmentDefinitionNode): string {
162 let out = '';
163 if (node.description) {
164 out += nodes.StringValue(node.description) + '\n';
165 }
166 out += 'fragment ' + node.name.value;
167 out += ' on ' + node.typeCondition.name.value;
168 if (node.directives && node.directives.length)
169 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive);
170 return out + ' ' + nodes.SelectionSet(node.selectionSet);
171 },
172 Directive(node: DirectiveNode): string {
173 let out = '@' + node.name.value;
174 if (node.arguments && node.arguments.length)
175 out += '(' + mapJoin(node.arguments, ', ', nodes.Argument) + ')';
176 return out;
177 },
178 NamedType(node: NamedTypeNode): string {
179 return node.name.value;
180 },
181 ListType(node: ListTypeNode): string {
182 return '[' + _print(node.type) + ']';
183 },
184 NonNullType(node: NonNullTypeNode): string {
185 return _print(node.type) + '!';
186 },
187} as const;
188
189const _print = (node: ASTNode): string => nodes[node.kind](node);
190
191function print(node: ASTNode): string {
192 LF = '\n';
193 return nodes[node.kind] ? nodes[node.kind](node) : '';
194}
195
196export { print, printString, printBlockString };