Failed Conditions
Push — master ( 7ff3e9...e7de06 )
by Vladimir
04:33 queued 01:54
created

Printer::printBlockString()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.6111
c 0
b 0
f 0
cc 5
nc 12
nop 2
crap 5
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\SchemaTypeExtensionNode;
45
use GraphQL\Language\AST\SelectionSetNode;
46
use GraphQL\Language\AST\StringValueNode;
47
use GraphQL\Language\AST\UnionTypeDefinitionNode;
48
use GraphQL\Language\AST\UnionTypeExtensionNode;
49
use GraphQL\Language\AST\VariableDefinitionNode;
50
use GraphQL\Utils\Utils;
51
use function count;
52
use function implode;
53
use function json_encode;
54
use function preg_replace;
55
use function sprintf;
56
use function str_replace;
57
use function strpos;
58
59
/**
60
 * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
61
 * Useful for pretty-printing queries or printing back AST for logging, documentation, etc.
62
 *
63
 * Usage example:
64
 *
65
 * ```php
66
 * $query = 'query myQuery {someField}';
67
 * $ast = GraphQL\Language\Parser::parse($query);
68
 * $printed = GraphQL\Language\Printer::doPrint($ast);
69
 * ```
70
 */
71
class Printer
72
{
73
    /**
74
     * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
75
     *
76
     * @param Node $ast
77
     *
78
     * @return string
79
     *
80
     * @api
81
     */
82 138
    public static function doPrint($ast)
83
    {
84 138
        static $instance;
85 138
        $instance = $instance ?: new static();
86
87 138
        return $instance->printAST($ast);
88
    }
89
90 1
    protected function __construct()
91
    {
92 1
    }
93
94 138
    public function printAST($ast)
95
    {
96 138
        return Visitor::visit(
97 138
            $ast,
98
            [
99
                'leave' => [
100
                    NodeKind::NAME => static function (Node $node) {
101 73
                        return '' . $node->value;
102 138
                    },
103
104
                    NodeKind::VARIABLE => static function ($node) {
105 4
                        return '$' . $node->name;
106 138
                    },
107
108
                    NodeKind::DOCUMENT => function (DocumentNode $node) {
109 29
                        return $this->join($node->definitions, "\n\n") . "\n";
110 138
                    },
111
112
                    NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) {
113 8
                        $op           = $node->operation;
114 8
                        $name         = $node->name;
115 8
                        $varDefs      = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
116 8
                        $directives   = $this->join($node->directives, ' ');
117 8
                        $selectionSet = $node->selectionSet;
118
                        // Anonymous queries with no directives or variable definitions can use
119
                        // the query short form.
120 8
                        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...
121 8
                            ? $selectionSet
122 8
                            : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
123 138
                    },
124
125
                    NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) {
126 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

126
                        return $node->variable . ': ' . /** @scrutinizer ignore-type */ $node->type . $this->wrap(' = ', $node->defaultValue);
Loading history...
127 138
                    },
128
129
                    NodeKind::SELECTION_SET => function (SelectionSetNode $node) {
130 9
                        return $this->block($node->selections);
131 138
                    },
132
133
                    NodeKind::FIELD => function (FieldNode $node) {
134 10
                        return $this->join(
135
                            [
136 10
                                $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap(
137 10
                                    '(',
138 10
                                    $this->join($node->arguments, ', '),
139 10
                                    ')'
140
                                ),
141 10
                                $this->join($node->directives, ' '),
142 10
                                $node->selectionSet,
143
                            ],
144 10
                            ' '
145
                        );
146 138
                    },
147
148
                    NodeKind::ARGUMENT => static function (ArgumentNode $node) {
149 9
                        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

149
                        return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
150 138
                    },
151
152
                    NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) {
153 2
                        return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
154 138
                    },
155
156
                    NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) {
157 2
                        return $this->join(
158
                            [
159 2
                                '...',
160 2
                                $this->wrap('on ', $node->typeCondition),
161 2
                                $this->join($node->directives, ' '),
162 2
                                $node->selectionSet,
163
                            ],
164 2
                            ' '
165
                        );
166 138
                    },
167
168
                    NodeKind::FRAGMENT_DEFINITION => function (FragmentDefinitionNode $node) {
169
                        // Note: fragment variable definitions are experimental and may be changed or removed in the future.
170 3
                        return sprintf('fragment %s', $node->name)
171 3
                            . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
172 3
                            . sprintf(' on %s ', $node->typeCondition)
173 3
                            . $this->wrap('', $this->join($node->directives, ' '), ' ')
174 3
                            . $node->selectionSet;
175 138
                    },
176
177
                    NodeKind::INT => static function (IntValueNode $node) {
178 21
                        return $node->value;
179 138
                    },
180
181
                    NodeKind::FLOAT => static function (FloatValueNode $node) {
182 8
                        return $node->value;
183 138
                    },
184
185
                    NodeKind::STRING => function (StringValueNode $node, $key) {
186 29
                        if ($node->block) {
187 8
                            return $this->printBlockString($node->value, $key === 'description');
188
                        }
189
190 25
                        return json_encode($node->value);
191 138
                    },
192
193
                    NodeKind::BOOLEAN => static function (BooleanValueNode $node) {
194 12
                        return $node->value ? 'true' : 'false';
195 138
                    },
196
197
                    NodeKind::NULL => static 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

197
                    NodeKind::NULL => static 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...
198 8
                        return 'null';
199 138
                    },
200
201
                    NodeKind::ENUM => static function (EnumValueNode $node) {
202 18
                        return $node->value;
203 138
                    },
204
205
                    NodeKind::LST => function (ListValueNode $node) {
206 5
                        return '[' . $this->join($node->values, ', ') . ']';
207 138
                    },
208
209
                    NodeKind::OBJECT => function (ObjectValueNode $node) {
210 4
                        return '{' . $this->join($node->fields, ', ') . '}';
211 138
                    },
212
213
                    NodeKind::OBJECT_FIELD => static function (ObjectFieldNode $node) {
214 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

214
                        return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
215 138
                    },
216
217
                    NodeKind::DIRECTIVE => function (DirectiveNode $node) {
218 7
                        return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
219 138
                    },
220
221
                    NodeKind::NAMED_TYPE => static function (NamedTypeNode $node) {
222 66
                        return $node->name;
223 138
                    },
224
225
                    NodeKind::LIST_TYPE => static function (ListTypeNode $node) {
226 56
                        return '[' . $node->type . ']';
227 138
                    },
228
229
                    NodeKind::NON_NULL_TYPE => static function (NonNullTypeNode $node) {
230 58
                        return $node->type . '!';
231 138
                    },
232
233
                    NodeKind::SCHEMA_DEFINITION => function (SchemaDefinitionNode $def) {
234 3
                        return $this->join(
235
                            [
236 3
                                'schema',
237 3
                                $this->join($def->directives, ' '),
238 3
                                $this->block($def->operationTypes),
239
                            ],
240 3
                            ' '
241
                        );
242 138
                    },
243
244
                    NodeKind::OPERATION_TYPE_DEFINITION => static function (OperationTypeDefinitionNode $def) {
245 4
                        return $def->operation . ': ' . $def->type;
246 138
                    },
247
248
                    NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function (ScalarTypeDefinitionNode $def) {
249 57
                        return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
250 138
                    }),
251
252
                    NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function (ObjectTypeDefinitionNode $def) {
253 56
                        return $this->join(
254
                            [
255 56
                                'type',
256 56
                                $def->name,
257 56
                                $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
258 56
                                $this->join($def->directives, ' '),
259 56
                                $this->block($def->fields),
260
                            ],
261 56
                            ' '
262
                        );
263 138
                    }),
264
265
                    NodeKind::FIELD_DEFINITION => $this->addDescription(function (FieldDefinitionNode $def) {
266 56
                        return $def->name
267 56
                            . $this->wrap('(', $this->join($def->arguments, ', '), ')')
268 56
                            . ': ' . $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

268
                            . ': ' . /** @scrutinizer ignore-type */ $def->type
Loading history...
269 56
                            . $this->wrap(' ', $this->join($def->directives, ' '));
270 138
                    }),
271
272
                    NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function (InputValueDefinitionNode $def) {
273 56
                        return $this->join(
274
                            [
275 56
                                $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

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