Failed Conditions
Push — master ( cc39b3...a3ef1b )
by Šimon
10s
created

Printer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 422
Duplicated Lines 0 %

Test Coverage

Coverage 99.1%

Importance

Changes 0
Metric Value
wmc 31
eloc 212
dl 0
loc 422
ccs 219
cts 221
cp 0.991
rs 9.92
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A doPrint() 0 6 2
A length() 0 3 2
C printAST() 0 326 9
A wrap() 0 3 2
A join() 0 13 2
A manyList() 0 3 2
A indent() 0 3 2
A block() 0 5 3
A printBlockString() 0 7 5
A addDescription() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Language;
6
7
use GraphQL\Language\AST\ArgumentNode;
8
use GraphQL\Language\AST\BooleanValueNode;
9
use GraphQL\Language\AST\DirectiveDefinitionNode;
10
use GraphQL\Language\AST\DirectiveNode;
11
use GraphQL\Language\AST\DocumentNode;
12
use GraphQL\Language\AST\EnumTypeDefinitionNode;
13
use GraphQL\Language\AST\EnumTypeExtensionNode;
14
use GraphQL\Language\AST\EnumValueDefinitionNode;
15
use GraphQL\Language\AST\EnumValueNode;
16
use GraphQL\Language\AST\FieldDefinitionNode;
17
use GraphQL\Language\AST\FieldNode;
18
use GraphQL\Language\AST\FloatValueNode;
19
use GraphQL\Language\AST\FragmentDefinitionNode;
20
use GraphQL\Language\AST\FragmentSpreadNode;
21
use GraphQL\Language\AST\InlineFragmentNode;
22
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
23
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
24
use GraphQL\Language\AST\InputValueDefinitionNode;
25
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
26
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
27
use GraphQL\Language\AST\IntValueNode;
28
use GraphQL\Language\AST\ListTypeNode;
29
use GraphQL\Language\AST\ListValueNode;
30
use GraphQL\Language\AST\NamedTypeNode;
31
use GraphQL\Language\AST\Node;
32
use GraphQL\Language\AST\NodeKind;
33
use GraphQL\Language\AST\NonNullTypeNode;
34
use GraphQL\Language\AST\NullValueNode;
35
use GraphQL\Language\AST\ObjectFieldNode;
36
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
37
use GraphQL\Language\AST\ObjectTypeExtensionNode;
38
use GraphQL\Language\AST\ObjectValueNode;
39
use GraphQL\Language\AST\OperationDefinitionNode;
40
use GraphQL\Language\AST\OperationTypeDefinitionNode;
41
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
42
use GraphQL\Language\AST\ScalarTypeExtensionNode;
43
use GraphQL\Language\AST\SchemaDefinitionNode;
44
use GraphQL\Language\AST\SelectionSetNode;
45
use GraphQL\Language\AST\StringValueNode;
46
use GraphQL\Language\AST\UnionTypeDefinitionNode;
47
use GraphQL\Language\AST\UnionTypeExtensionNode;
48
use GraphQL\Language\AST\VariableDefinitionNode;
49
use GraphQL\Utils\Utils;
50
use function count;
51
use function implode;
52
use function json_encode;
53
use function preg_replace;
54
use function sprintf;
55
use function str_replace;
56
use function strpos;
57
58
/**
59
 * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
60
 * Useful for pretty-printing queries or printing back AST for logging, documentation, etc.
61
 *
62
 * Usage example:
63
 *
64
 * ```php
65
 * $query = 'query myQuery {someField}';
66
 * $ast = GraphQL\Language\Parser::parse($query);
67
 * $printed = GraphQL\Language\Printer::doPrint($ast);
68
 * ```
69
 */
70
class Printer
71
{
72
    /**
73
     * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
74
     *
75
     * @api
76
     * @param Node $ast
77
     * @return string
78
     */
79 83
    public static function doPrint($ast)
80
    {
81 83
        static $instance;
82 83
        $instance = $instance ?: new static();
83
84 83
        return $instance->printAST($ast);
85
    }
86
87 1
    protected function __construct()
88
    {
89 1
    }
90
91 83
    public function printAST($ast)
92
    {
93 83
        return Visitor::visit(
94 83
            $ast,
95
            [
96
                'leave' => [
97
                    NodeKind::NAME => function (Node $node) {
98 19
                        return '' . $node->value;
99 83
                    },
100
101
                    NodeKind::VARIABLE => function ($node) {
102 4
                        return '$' . $node->name;
103 83
                    },
104
105
                    NodeKind::DOCUMENT => function (DocumentNode $node) {
106 10
                        return $this->join($node->definitions, "\n\n") . "\n";
107 83
                    },
108
109
                    NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) {
110 7
                        $op           = $node->operation;
111 7
                        $name         = $node->name;
112 7
                        $varDefs      = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
113 7
                        $directives   = $this->join($node->directives, ' ');
114 7
                        $selectionSet = $node->selectionSet;
115
                        // Anonymous queries with no directives or variable definitions can use
116
                        // the query short form.
117 7
                        return ! $name && ! $directives && ! $varDefs && $op === 'query'
0 ignored issues
show
introduced by
$name is of type GraphQL\Language\AST\NameNode, thus it always evaluated to true. If $name can have other possible types, add them to src/Language/AST/OperationDefinitionNode.php:12
Loading history...
118 7
                            ? $selectionSet
119 7
                            : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
120 83
                    },
121
122
                    NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) {
123 4
                        return $node->variable . ': ' . $node->type . $this->wrap(' = ', $node->defaultValue);
0 ignored issues
show
Bug introduced by
Are you sure $node->type of type GraphQL\Language\AST\TypeNode can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

123
                        return $node->variable . ': ' . /** @scrutinizer ignore-type */ $node->type . $this->wrap(' = ', $node->defaultValue);
Loading history...
124 83
                    },
125
126
                    NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
127 8
                        return $this->block($node->selections);
128 83
                    },
129
130
                    NodeKind::FIELD => function (FieldNode $node) {
131 9
                        return $this->join(
132
                            [
133 9
                                $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap(
134 9
                                    '(',
135 9
                                    $this->join($node->arguments, ', '),
136 9
                                    ')'
137
                                ),
138 9
                                $this->join($node->directives, ' '),
139 9
                                $node->selectionSet,
140
                            ],
141 9
                            ' '
142
                        );
143 83
                    },
144
145
                    NodeKind::ARGUMENT => function (ArgumentNode $node) {
146 8
                        return $node->name . ': ' . $node->value;
0 ignored issues
show
Bug introduced by
Are you sure $node->value of type GraphQL\Language\AST\ValueNode can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
                        return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
147 83
                    },
148
149
                    NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) {
150 2
                        return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
151 83
                    },
152
153
                    NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) {
154 2
                        return $this->join(
155
                            [
156 2
                                '...',
157 2
                                $this->wrap('on ', $node->typeCondition),
158 2
                                $this->join($node->directives, ' '),
159 2
                                $node->selectionSet,
160
                            ],
161 2
                            ' '
162
                        );
163 83
                    },
164
165
                    NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
166
                        // Note: fragment variable definitions are experimental and may be changed or removed in the future.
167 3
                        return sprintf('fragment %s', $node->name)
168 3
                            . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
169 3
                            . sprintf(' on %s ', $node->typeCondition)
170 3
                            . $this->wrap('', $this->join($node->directives, ' '), ' ')
171 3
                            . $node->selectionSet;
172 83
                    },
173
174
                    NodeKind::INT => function (IntValueNode $node) {
175 21
                        return $node->value;
176 83
                    },
177
178
                    NodeKind::FLOAT => function (FloatValueNode $node) {
179 8
                        return $node->value;
180 83
                    },
181
182
                    NodeKind::STRING => function (StringValueNode $node, $key) {
183 27
                        if ($node->block) {
184 7
                            return $this->printBlockString($node->value, $key === 'description');
185
                        }
186
187 24
                        return json_encode($node->value);
188 83
                    },
189
190
                    NodeKind::BOOLEAN => function (BooleanValueNode $node) {
191 12
                        return $node->value ? 'true' : 'false';
192 83
                    },
193
194
                    NodeKind::NULL => function (NullValueNode $node) {
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

194
                    NodeKind::NULL => function (/** @scrutinizer ignore-unused */ NullValueNode $node) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
195 8
                        return 'null';
196 83
                    },
197
198
                    NodeKind::ENUM => function (EnumValueNode $node) {
199 18
                        return $node->value;
200 83
                    },
201
202
                    NodeKind::LST => function (ListValueNode $node) {
203 5
                        return '[' . $this->join($node->values, ', ') . ']';
204 83
                    },
205
206
                    NodeKind::OBJECT => function (ObjectValueNode $node) {
207 4
                        return '{' . $this->join($node->fields, ', ') . '}';
208 83
                    },
209
210
                    NodeKind::OBJECT_FIELD => function (ObjectFieldNode $node) {
211 4
                        return $node->name . ': ' . $node->value;
0 ignored issues
show
Bug introduced by
Are you sure $node->value of type GraphQL\Language\AST\ValueNode can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
                        return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
212 83
                    },
213
214
                    NodeKind::DIRECTIVE => function (DirectiveNode $node) {
215 5
                        return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
216 83
                    },
217
218
                    NodeKind::NAMED_TYPE => function (NamedTypeNode $node) {
219 13
                        return $node->name;
220 83
                    },
221
222
                    NodeKind::LIST_TYPE => function (ListTypeNode $node) {
223 3
                        return '[' . $node->type . ']';
224 83
                    },
225
226
                    NodeKind::NON_NULL_TYPE => function (NonNullTypeNode $node) {
227 5
                        return $node->type . '!';
228 83
                    },
229
230
                    NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) {
231 3
                        return $this->join(
232
                            [
233 3
                                'schema',
234 3
                                $this->join($def->directives, ' '),
235 3
                                $this->block($def->operationTypes),
236
                            ],
237 3
                            ' '
238
                        );
239 83
                    },
240
241
                    NodeKind::OPERATION_TYPE_DEFINITION => function (OperationTypeDefinitionNode $def) {
242 3
                        return $def->operation . ': ' . $def->type;
243 83
                    },
244
245
                    NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) {
246 3
                        return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
247 83
                    }),
248
249
                    NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) {
250 3
                        return $this->join(
251
                            [
252 3
                                'type',
253 3
                                $def->name,
254 3
                                $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
255 3
                                $this->join($def->directives, ' '),
256 3
                                $this->block($def->fields),
257
                            ],
258 3
                            ' '
259
                        );
260 83
                    }),
261
262
                    NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) {
263 3
                        return $def->name
264 3
                            . $this->wrap('(', $this->join($def->arguments, ', '), ')')
265 3
                            . ': ' . $def->type
0 ignored issues
show
Bug introduced by
Are you sure $def->type of type GraphQL\Language\AST\TypeNode can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
                            . ': ' . /** @scrutinizer ignore-type */ $def->type
Loading history...
266 3
                            . $this->wrap(' ', $this->join($def->directives, ' '));
267 83
                    }),
268
269
                    NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) {
270 3
                        return $this->join(
271
                            [
272 3
                                $def->name . ': ' . $def->type,
0 ignored issues
show
Bug introduced by
Are you sure $def->type of type GraphQL\Language\AST\TypeNode can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

272
                                $def->name . ': ' . /** @scrutinizer ignore-type */ $def->type,
Loading history...
273 3
                                $this->wrap('= ', $def->defaultValue),
274 3
                                $this->join($def->directives, ' '),
275
                            ],
276 3
                            ' '
277
                        );
278 83
                    }),
279
280 83
                    NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(
281
                        function (InterfaceTypeDefinitionNode $def) {
282 3
                            return $this->join(
283
                                [
284 3
                                    'interface',
285 3
                                    $def->name,
286 3
                                    $this->join($def->directives, ' '),
287 3
                                    $this->block($def->fields),
288
                                ],
289 3
                                ' '
290
                            );
291 83
                        }
292
                    ),
293
294
                    NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function (UnionTypeDefinitionNode $def) {
295 3
                        return $this->join(
296
                            [
297 3
                                'union',
298 3
                                $def->name,
299 3
                                $this->join($def->directives, ' '),
300 3
                                $def->types
301 3
                                    ? '= ' . $this->join($def->types, ' | ')
302 3
                                    : '',
303
                            ],
304 3
                            ' '
305
                        );
306 83
                    }),
307
308
                    NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function (EnumTypeDefinitionNode $def) {
309 3
                        return $this->join(
310
                            [
311 3
                                'enum',
312 3
                                $def->name,
313 3
                                $this->join($def->directives, ' '),
314 3
                                $this->block($def->values),
315
                            ],
316 3
                            ' '
317
                        );
318 83
                    }),
319
320
                    NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function (EnumValueDefinitionNode $def) {
321 3
                        return $this->join([$def->name, $this->join($def->directives, ' ')], ' ');
322 83
                    }),
323
324
                    NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function (
325
                        InputObjectTypeDefinitionNode $def
326
                    ) {
327 3
                        return $this->join(
328
                            [
329 3
                                'input',
330 3
                                $def->name,
331 3
                                $this->join($def->directives, ' '),
332 3
                                $this->block($def->fields),
333
                            ],
334 3
                            ' '
335
                        );
336 83
                    }),
337
338
                    NodeKind::SCALAR_TYPE_EXTENSION => function (ScalarTypeExtensionNode $def) {
339 2
                        return $this->join(
340
                            [
341 2
                                'extend scalar',
342 2
                                $def->name,
343 2
                                $this->join($def->directives, ' '),
344
                            ],
345 2
                            ' '
346
                        );
347 83
                    },
348
349
                    NodeKind::OBJECT_TYPE_EXTENSION => function (ObjectTypeExtensionNode $def) {
350 2
                        return $this->join(
351
                            [
352 2
                                'extend type',
353 2
                                $def->name,
354 2
                                $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
355 2
                                $this->join($def->directives, ' '),
356 2
                                $this->block($def->fields),
357
                            ],
358 2
                            ' '
359
                        );
360 83
                    },
361
362
                    NodeKind::INTERFACE_TYPE_EXTENSION => function (InterfaceTypeExtensionNode $def) {
363 2
                        return $this->join(
364
                            [
365 2
                                'extend interface',
366 2
                                $def->name,
367 2
                                $this->join($def->directives, ' '),
368 2
                                $this->block($def->fields),
369
                            ],
370 2
                            ' '
371
                        );
372 83
                    },
373
374
                    NodeKind::UNION_TYPE_EXTENSION => function (UnionTypeExtensionNode $def) {
375 2
                        return $this->join(
376
                            [
377 2
                                'extend union',
378 2
                                $def->name,
379 2
                                $this->join($def->directives, ' '),
380 2
                                $def->types
381 2
                                    ? '= ' . $this->join($def->types, ' | ')
382 2
                                    : '',
383
                            ],
384 2
                            ' '
385
                        );
386 83
                    },
387
388
                    NodeKind::ENUM_TYPE_EXTENSION => function (EnumTypeExtensionNode $def) {
389 2
                        return $this->join(
390
                            [
391 2
                                'extend enum',
392 2
                                $def->name,
393 2
                                $this->join($def->directives, ' '),
394 2
                                $this->block($def->values),
395
                            ],
396 2
                            ' '
397
                        );
398 83
                    },
399
400
                    NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function (InputObjectTypeExtensionNode $def) {
401 2
                        return $this->join(
402
                            [
403 2
                                'extend input',
404 2
                                $def->name,
405 2
                                $this->join($def->directives, ' '),
406 2
                                $this->block($def->fields),
407
                            ],
408 2
                            ' '
409
                        );
410 83
                    },
411
412
                    NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function (DirectiveDefinitionNode $def) {
413
                        return 'directive @'
414 3
                            . $def->name
415 3
                            . $this->wrap('(', $this->join($def->arguments, ', '), ')')
416 3
                            . ' on ' . $this->join($def->locations, ' | ');
417 83
                    }),
418
                ],
419
            ]
420
        );
421
    }
422
423 83
    public function addDescription(\Closure $cb)
424
    {
425
        return function ($node) use ($cb) {
426 4
            return $this->join([$node->description, $cb($node)], "\n");
427 83
        };
428
    }
429
430
    /**
431
     * If maybeString is not null or empty, then wrap with start and end, otherwise
432
     * print an empty string.
433
     */
434 12
    public function wrap($start, $maybeString, $end = '')
435
    {
436 12
        return $maybeString ? ($start . $maybeString . $end) : '';
437
    }
438
439
    /**
440
     * Given array, print each item on its own line, wrapped in an
441
     * indented "{ }" block.
442
     */
443 11
    public function block($array)
444
    {
445 11
        return ($array && $this->length($array))
446 11
            ? "{\n" . $this->indent($this->join($array, "\n")) . "\n}"
447 11
            : '';
448
    }
449
450 11
    public function indent($maybeString)
451
    {
452 11
        return $maybeString ? '  ' . str_replace("\n", "\n  ", $maybeString) : '';
453
    }
454
455
    public function manyList($start, $list, $separator, $end)
456
    {
457
        return $this->length($list) === 0 ? null : ($start . $this->join($list, $separator) . $end);
458
    }
459
460 11
    public function length($maybeArray)
461
    {
462 11
        return $maybeArray ? count($maybeArray) : 0;
463
    }
464
465 14
    public function join($maybeArray, $separator = '')
466
    {
467 14
        return $maybeArray
468 14
            ? implode(
469 14
                $separator,
470 14
                Utils::filter(
471 14
                    $maybeArray,
472
                    function ($x) {
473 14
                        return ! ! $x;
474 14
                    }
475
                )
476
            )
477 14
            : '';
478
    }
479
480
    /**
481
     * Print a block string in the indented block form by adding a leading and
482
     * trailing blank line. However, if a block string starts with whitespace and is
483
     * a single-line, adding a leading blank line would strip that whitespace.
484
     */
485 7
    private function printBlockString($value, $isDescription)
486
    {
487 7
        $escaped = str_replace('"""', '\\"""', $value);
488
489 7
        return (($value[0] === ' ' || $value[0] === "\t") && strpos($value, "\n") === false)
490 2
            ? ('"""' . preg_replace('/"$/', "\"\n", $escaped) . '"""')
491 7
            : ("\"\"\"\n" . ($isDescription ? $escaped : $this->indent($escaped)) . "\n\"\"\"");
492
    }
493
}
494