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