Failed Conditions
Pull Request — master (#360)
by
unknown
04:02
created

Printer::indent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 1
nc 2
nop 1
crap 2
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
     * @api
77
     * @param Node $ast
78
     * @return string
79
     */
80 85
    public static function doPrint($ast)
81
    {
82 85
        static $instance;
83 85
        $instance = $instance ?: new static();
84
85 85
        return $instance->printAST($ast);
86
    }
87
88 1
    protected function __construct()
89
    {
90 1
    }
91
92 85
    public function printAST($ast)
93
    {
94 85
        return Visitor::visit(
95 85
            $ast,
96
            [
97
                'leave' => [
98
                    NodeKind::NAME => function (Node $node) {
99 20
                        return '' . $node->value;
100 85
                    },
101
102
                    NodeKind::VARIABLE => function ($node) {
103 4
                        return '$' . $node->name;
104 85
                    },
105
106
                    NodeKind::DOCUMENT => function (DocumentNode $node) {
107 11
                        return $this->join($node->definitions, "\n\n") . "\n";
108 85
                    },
109
110
                    NodeKind::OPERATION_DEFINITION => function (OperationDefinitionNode $node) {
111 8
                        $op           = $node->operation;
112 8
                        $name         = $node->name;
113 8
                        $varDefs      = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
114 8
                        $directives   = $this->join($node->directives, ' ');
115 8
                        $selectionSet = $node->selectionSet;
116
                        // Anonymous queries with no directives or variable definitions can use
117
                        // the query short form.
118 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...
119 8
                            ? $selectionSet
120 8
                            : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
121 85
                    },
122
123
                    NodeKind::VARIABLE_DEFINITION => function (VariableDefinitionNode $node) {
124 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

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

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

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

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

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

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