Failed Conditions
Push — master ( ea13c9...49ec89 )
by Vladimir
07:25
created

Printer::addDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
namespace GraphQL\Language;
3
4
use GraphQL\Language\AST\ArgumentNode;
5
use GraphQL\Language\AST\DirectiveDefinitionNode;
6
use GraphQL\Language\AST\EnumTypeDefinitionNode;
7
use GraphQL\Language\AST\EnumTypeExtensionNode;
8
use GraphQL\Language\AST\EnumValueDefinitionNode;
9
use GraphQL\Language\AST\FieldDefinitionNode;
10
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
11
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
12
use GraphQL\Language\AST\InputValueDefinitionNode;
13
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
14
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
15
use GraphQL\Language\AST\ListValueNode;
16
use GraphQL\Language\AST\BooleanValueNode;
17
use GraphQL\Language\AST\DirectiveNode;
18
use GraphQL\Language\AST\DocumentNode;
19
use GraphQL\Language\AST\EnumValueNode;
20
use GraphQL\Language\AST\FieldNode;
21
use GraphQL\Language\AST\FloatValueNode;
22
use GraphQL\Language\AST\FragmentDefinitionNode;
23
use GraphQL\Language\AST\FragmentSpreadNode;
24
use GraphQL\Language\AST\InlineFragmentNode;
25
use GraphQL\Language\AST\IntValueNode;
26
use GraphQL\Language\AST\ListTypeNode;
27
use GraphQL\Language\AST\NamedTypeNode;
28
use GraphQL\Language\AST\Node;
29
use GraphQL\Language\AST\NodeKind;
30
use GraphQL\Language\AST\NonNullTypeNode;
31
use GraphQL\Language\AST\NullValueNode;
32
use GraphQL\Language\AST\ObjectFieldNode;
33
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
34
use GraphQL\Language\AST\ObjectValueNode;
35
use GraphQL\Language\AST\OperationDefinitionNode;
36
use GraphQL\Language\AST\OperationTypeDefinitionNode;
37
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
38
use GraphQL\Language\AST\ScalarTypeExtensionNode;
39
use GraphQL\Language\AST\SchemaDefinitionNode;
40
use GraphQL\Language\AST\SelectionSetNode;
41
use GraphQL\Language\AST\StringValueNode;
42
use GraphQL\Language\AST\ObjectTypeExtensionNode;
43
use GraphQL\Language\AST\UnionTypeDefinitionNode;
44
use GraphQL\Language\AST\UnionTypeExtensionNode;
45
use GraphQL\Language\AST\VariableDefinitionNode;
46
use GraphQL\Utils\Utils;
47
48
/**
49
 * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
50
 * Useful for pretty-printing queries or printing back AST for logging, documentation, etc.
51
 *
52
 * Usage example:
53
 *
54
 * ```php
55
 * $query = 'query myQuery {someField}';
56
 * $ast = GraphQL\Language\Parser::parse($query);
57
 * $printed = GraphQL\Language\Printer::doPrint($ast);
58
 * ```
59
 */
60
class Printer
61
{
62
    /**
63
     * Prints AST to string. Capable of printing GraphQL queries and Type definition language.
64
     *
65
     * @api
66
     * @param Node $ast
67
     * @return string
68
     */
69 83
    public static function doPrint($ast)
70
    {
71 83
        static $instance;
72 83
        $instance = $instance ?: new static();
73 83
        return $instance->printAST($ast);
74
    }
75
76 1
    protected function __construct()
77 1
    {}
78
79 83
    public function printAST($ast)
80
    {
81 83
        return Visitor::visit($ast, [
82
            'leave' => [
83
                NodeKind::NAME => function(Node $node) {
84 19
                    return '' . $node->value;
85 83
                },
86
                NodeKind::VARIABLE => function($node) {
87 4
                    return '$' . $node->name;
88 83
                },
89
                NodeKind::DOCUMENT => function(DocumentNode $node) {
90 10
                    return $this->join($node->definitions, "\n\n") . "\n";
91 83
                },
92
                NodeKind::OPERATION_DEFINITION => function(OperationDefinitionNode $node) {
93 7
                    $op = $node->operation;
94 7
                    $name = $node->name;
95 7
                    $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
96 7
                    $directives = $this->join($node->directives, ' ');
97 7
                    $selectionSet = $node->selectionSet;
98
                    // Anonymous queries with no directives or variable definitions can use
99
                    // the query short form.
100 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...
101 7
                        ? $selectionSet
102 7
                        : $this->join([$op, $this->join([$name, $varDefs]), $directives, $selectionSet], ' ');
103 83
                },
104
                NodeKind::VARIABLE_DEFINITION => function(VariableDefinitionNode $node) {
105 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

105
                    return $node->variable . ': ' . /** @scrutinizer ignore-type */ $node->type . $this->wrap(' = ', $node->defaultValue);
Loading history...
106 83
                },
107
                NodeKind::SELECTION_SET => function(SelectionSetNode $node) {
108 8
                    return $this->block($node->selections);
109 83
                },
110
                NodeKind::FIELD => function(FieldNode $node) {
111 9
                    return $this->join([
112 9
                        $this->wrap('', $node->alias, ': ') . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')'),
113 9
                        $this->join($node->directives, ' '),
114 9
                        $node->selectionSet
115 9
                    ], ' ');
116 83
                },
117
                NodeKind::ARGUMENT => function(ArgumentNode $node) {
118 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

118
                    return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
119 83
                },
120
121
                // Fragments
122
                NodeKind::FRAGMENT_SPREAD => function(FragmentSpreadNode $node) {
123 2
                    return '...' . $node->name . $this->wrap(' ', $this->join($node->directives, ' '));
124 83
                },
125
                NodeKind::INLINE_FRAGMENT => function(InlineFragmentNode $node) {
126 2
                    return $this->join([
127 2
                        "...",
128 2
                        $this->wrap('on ', $node->typeCondition),
129 2
                        $this->join($node->directives, ' '),
130 2
                        $node->selectionSet
131 2
                    ], ' ');
132 83
                },
133
                NodeKind::FRAGMENT_DEFINITION => function(FragmentDefinitionNode $node) {
134
                    // Note: fragment variable definitions are experimental and may be changed
135
                    // or removed in the future.
136 3
                    return "fragment {$node->name}"
137 3
                        . $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')')
138 3
                        . " on {$node->typeCondition} "
139 3
                        . $this->wrap('', $this->join($node->directives, ' '), ' ')
140 3
                        . $node->selectionSet;
141 83
                },
142
143
                // Value
144
                NodeKind::INT => function(IntValueNode  $node) {
145 21
                    return $node->value;
146 83
                },
147
                NodeKind::FLOAT => function(FloatValueNode $node) {
148 8
                    return $node->value;
149 83
                },
150
                NodeKind::STRING => function(StringValueNode $node, $key) {
151 27
                    if ($node->block) {
152 7
                       return $this->printBlockString($node->value, $key === 'description');
153
                    }
154 24
                    return json_encode($node->value);
155 83
                },
156
                NodeKind::BOOLEAN => function(BooleanValueNode $node) {
157 12
                    return $node->value ? 'true' : 'false';
158 83
                },
159
                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

159
                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...
160 8
                    return 'null';
161 83
                },
162
                NodeKind::ENUM => function(EnumValueNode $node) {
163 18
                    return $node->value;
164 83
                },
165
                NodeKind::LST => function(ListValueNode $node) {
166 5
                    return '[' . $this->join($node->values, ', ') . ']';
167 83
                },
168
                NodeKind::OBJECT => function(ObjectValueNode $node) {
169 4
                    return '{' . $this->join($node->fields, ', ') . '}';
170 83
                },
171
                NodeKind::OBJECT_FIELD => function(ObjectFieldNode $node) {
172 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

172
                    return $node->name . ': ' . /** @scrutinizer ignore-type */ $node->value;
Loading history...
173 83
                },
174
175
                // DirectiveNode
176
                NodeKind::DIRECTIVE => function(DirectiveNode $node) {
177 5
                    return '@' . $node->name . $this->wrap('(', $this->join($node->arguments, ', '), ')');
178 83
                },
179
180
                // Type
181
                NodeKind::NAMED_TYPE => function(NamedTypeNode $node) {
182 13
                    return $node->name;
183 83
                },
184
                NodeKind::LIST_TYPE => function(ListTypeNode $node) {
185 3
                    return '[' . $node->type . ']';
186 83
                },
187
                NodeKind::NON_NULL_TYPE => function(NonNullTypeNode $node) {
188 5
                    return $node->type . '!';
189 83
                },
190
191
                // Type System Definitions
192
                NodeKind::SCHEMA_DEFINITION => function(SchemaDefinitionNode $def) {
193 3
                    return $this->join([
194 3
                        'schema',
195 3
                        $this->join($def->directives, ' '),
196 3
                        $this->block($def->operationTypes)
197 3
                    ], ' ');
198 83
                },
199
                NodeKind::OPERATION_TYPE_DEFINITION => function(OperationTypeDefinitionNode $def) {
200 3
                    return $def->operation . ': ' . $def->type;
201 83
                },
202
203
                NodeKind::SCALAR_TYPE_DEFINITION => $this->addDescription(function(ScalarTypeDefinitionNode $def) {
204 3
                    return $this->join(['scalar', $def->name, $this->join($def->directives, ' ')], ' ');
205 83
                }),
206
                NodeKind::OBJECT_TYPE_DEFINITION => $this->addDescription(function(ObjectTypeDefinitionNode $def) {
207 3
                    return $this->join([
208 3
                        'type',
209 3
                        $def->name,
210 3
                        $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
211 3
                        $this->join($def->directives, ' '),
212 3
                        $this->block($def->fields)
213 3
                    ], ' ');
214 83
                }),
215
                NodeKind::FIELD_DEFINITION => $this->addDescription(function(FieldDefinitionNode $def) {
216 3
                    return $def->name
217 3
                     . $this->wrap('(', $this->join($def->arguments, ', '), ')')
218 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

218
                     . ': ' . /** @scrutinizer ignore-type */ $def->type
Loading history...
219 3
                     . $this->wrap(' ', $this->join($def->directives, ' '));
220 83
                }),
221
                NodeKind::INPUT_VALUE_DEFINITION => $this->addDescription(function(InputValueDefinitionNode $def) {
222 3
                    return $this->join([
223 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

223
                        $def->name . ': ' . /** @scrutinizer ignore-type */ $def->type,
Loading history...
224 3
                        $this->wrap('= ', $def->defaultValue),
225 3
                        $this->join($def->directives, ' ')
226 3
                    ], ' ');
227 83
                }),
228
                NodeKind::INTERFACE_TYPE_DEFINITION => $this->addDescription(function(InterfaceTypeDefinitionNode $def) {
229 3
                    return $this->join([
230 3
                        'interface',
231 3
                        $def->name,
232 3
                        $this->join($def->directives, ' '),
233 3
                        $this->block($def->fields)
234 3
                    ], ' ');
235 83
                }),
236
                NodeKind::UNION_TYPE_DEFINITION => $this->addDescription(function(UnionTypeDefinitionNode $def) {
237 3
                    return $this->join([
238 3
                        'union',
239 3
                        $def->name,
240 3
                        $this->join($def->directives, ' '),
241 3
                        $def->types
242 3
                            ? '= ' . $this->join($def->types, ' | ')
243 3
                            : ''
244 3
                    ], ' ');
245 83
                }),
246
                NodeKind::ENUM_TYPE_DEFINITION => $this->addDescription(function(EnumTypeDefinitionNode $def) {
247 3
                    return $this->join([
248 3
                        'enum',
249 3
                        $def->name,
250 3
                        $this->join($def->directives, ' '),
251 3
                        $this->block($def->values)
252 3
                    ], ' ');
253 83
                }),
254
                NodeKind::ENUM_VALUE_DEFINITION => $this->addDescription(function(EnumValueDefinitionNode $def) {
255 3
                    return $this->join([$def->name, $this->join($def->directives, ' ')], ' ');
256 83
                }),
257
                NodeKind::INPUT_OBJECT_TYPE_DEFINITION => $this->addDescription(function(InputObjectTypeDefinitionNode $def) {
258 3
                    return $this->join([
259 3
                        'input',
260 3
                        $def->name,
261 3
                        $this->join($def->directives, ' '),
262 3
                        $this->block($def->fields)
263 3
                    ], ' ');
264 83
                }),
265
                NodeKind::SCALAR_TYPE_EXTENSION => function(ScalarTypeExtensionNode $def) {
266 2
                    return $this->join([
267 2
                        'extend scalar',
268 2
                        $def->name,
269 2
                        $this->join($def->directives, ' '),
270 2
                    ], ' ');
271 83
                },
272
                NodeKind::OBJECT_TYPE_EXTENSION => function(ObjectTypeExtensionNode $def) {
273 2
                    return $this->join([
274 2
                        'extend type',
275 2
                        $def->name,
276 2
                        $this->wrap('implements ', $this->join($def->interfaces, ' & ')),
277 2
                        $this->join($def->directives, ' '),
278 2
                        $this->block($def->fields),
279 2
                    ], ' ');
280 83
                },
281
                NodeKind::INTERFACE_TYPE_EXTENSION => function(InterfaceTypeExtensionNode $def) {
282 2
                    return $this->join([
283 2
                        'extend interface',
284 2
                        $def->name,
285 2
                        $this->join($def->directives, ' '),
286 2
                        $this->block($def->fields),
287 2
                    ], ' ');
288 83
                },
289
                NodeKind::UNION_TYPE_EXTENSION => function(UnionTypeExtensionNode $def) {
290 2
                    return $this->join([
291 2
                        'extend union',
292 2
                        $def->name,
293 2
                        $this->join($def->directives, ' '),
294 2
                        $def->types
295 2
                            ? '= ' . $this->join($def->types, ' | ')
296 2
                            : ''
297 2
                    ], ' ');
298 83
                },
299
                NodeKind::ENUM_TYPE_EXTENSION => function(EnumTypeExtensionNode $def) {
300 2
                    return $this->join([
301 2
                        'extend enum',
302 2
                        $def->name,
303 2
                        $this->join($def->directives, ' '),
304 2
                        $this->block($def->values),
305 2
                    ], ' ');
306 83
                },
307
                NodeKind::INPUT_OBJECT_TYPE_EXTENSION => function(InputObjectTypeExtensionNode $def) {
308 2
                    return $this->join([
309 2
                        'extend input',
310 2
                        $def->name,
311 2
                        $this->join($def->directives, ' '),
312 2
                        $this->block($def->fields),
313 2
                    ], ' ');
314 83
                },
315
                NodeKind::DIRECTIVE_DEFINITION => $this->addDescription(function(DirectiveDefinitionNode $def) {
316
                    return 'directive @'
317 3
                        . $def->name
318 3
                        . $this->wrap('(', $this->join($def->arguments, ', '), ')')
319 3
                        . ' on ' . $this->join($def->locations, ' | ');
320 83
                })
321
            ]
322
        ]);
323
    }
324
325 83
    public function addDescription(\Closure $cb)
326
    {
327
        return function ($node) use ($cb) {
328 4
            return $this->join([$node->description, $cb($node)], "\n");
329 83
        };
330
    }
331
332
    /**
333
     * If maybeString is not null or empty, then wrap with start and end, otherwise
334
     * print an empty string.
335
     */
336 12
    public function wrap($start, $maybeString, $end = '')
337
    {
338 12
        return $maybeString ? ($start . $maybeString . $end) : '';
339
    }
340
341
    /**
342
     * Given array, print each item on its own line, wrapped in an
343
     * indented "{ }" block.
344
     */
345 11
    public function block($array)
346
    {
347 11
        return ($array && $this->length($array))
348 11
            ? "{\n" . $this->indent($this->join($array, "\n")) . "\n}"
349 11
            : '';
350
    }
351
352 11
    public function indent($maybeString)
353
    {
354 11
        return $maybeString ? '  ' . str_replace("\n", "\n  ", $maybeString) : '';
355
    }
356
357
    public function manyList($start, $list, $separator, $end)
358
    {
359
        return $this->length($list) === 0 ? null : ($start . $this->join($list, $separator) . $end);
360
    }
361
362 11
    public function length($maybeArray)
363
    {
364 11
        return $maybeArray ? count($maybeArray) : 0;
365
    }
366
367 14
    public function join($maybeArray, $separator = '')
368
    {
369 14
        return $maybeArray
370 14
            ? implode(
371 14
                $separator,
372 14
                Utils::filter(
373 14
                    $maybeArray,
374
                    function($x) { return !!$x;}
375
                )
376
            )
377 14
            : '';
378
    }
379
380
    /**
381
     * Print a block string in the indented block form by adding a leading and
382
     * trailing blank line. However, if a block string starts with whitespace and is
383
     * a single-line, adding a leading blank line would strip that whitespace.
384
     */
385 7
    private function printBlockString($value, $isDescription) {
386 7
        $escaped = str_replace('"""', '\\"""', $value);
387 7
        return (($value[0] === ' ' || $value[0] === "\t") && strpos($value, "\n") === false)
388 2
            ? ('"""' . preg_replace('/"$/', "\"\n", $escaped) . '"""')
389 7
            : ("\"\"\"\n" . ($isDescription ? $escaped : $this->indent($escaped)) . "\n\"\"\"");
390
    }
391
}
392