GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

SchemaPrinter::doPrint()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\Printer;
9
use GraphQL\Type\Definition\Directive;
10
use GraphQL\Type\Definition\EnumType;
11
use GraphQL\Type\Definition\InputObjectType;
12
use GraphQL\Type\Definition\InterfaceType;
13
use GraphQL\Type\Definition\ObjectType;
14
use GraphQL\Type\Definition\ScalarType;
15
use GraphQL\Type\Definition\Type;
16
use GraphQL\Type\Definition\UnionType;
17
use GraphQL\Type\Introspection;
18
use GraphQL\Type\Schema;
19
use function array_filter;
20
use function array_keys;
21
use function array_map;
22
use function array_merge;
23
use function array_values;
24
use function count;
25
use function explode;
26
use function implode;
27
use function ksort;
28
use function mb_strlen;
29
use function preg_match_all;
30
use function sprintf;
31
use function str_replace;
32
use function strlen;
33
use function substr;
34
35
/**
36
 * Given an instance of Schema, prints it in GraphQL type language.
37
 */
38
class SchemaPrinter
39
{
40
    /**
41
     * Accepts options as a second argument:
42
     *
43
     *    - commentDescriptions:
44
     *        Provide true to use preceding comments as the description.
45
     *
46
     * @param bool[] $options
47
     *
48
     * @api
49
     */
50 107
    public static function doPrint(Schema $schema, array $options = []) : string
51
    {
52 107
        return self::printFilteredSchema(
53 107
            $schema,
54
            static function ($type) {
55 107
                return ! Directive::isSpecifiedDirective($type);
56 107
            },
57
            static function ($type) {
58 107
                return ! Type::isBuiltInType($type);
59 107
            },
60 107
            $options
61
        );
62
    }
63
64
    /**
65
     * @param bool[] $options
66
     */
67 109
    private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter, $options) : string
68
    {
69 109
        $directives = array_filter(
70 109
            $schema->getDirectives(),
71
            static function ($directive) use ($directiveFilter) {
72 109
                return $directiveFilter($directive);
73 109
            }
74
        );
75
76 109
        $types = $schema->getTypeMap();
77 109
        ksort($types);
78 109
        $types = array_filter($types, $typeFilter);
79
80 109
        return sprintf(
81 109
            "%s\n",
82 109
            implode(
83 109
                "\n\n",
84 109
                array_filter(
85 109
                    array_merge(
86 109
                        [self::printSchemaDefinition($schema)],
87 109
                        array_map(
88
                            static function ($directive) use ($options) {
89 62
                                return self::printDirective($directive, $options);
90 109
                            },
91 109
                            $directives
92
                        ),
93 109
                        array_map(
94
                            static function ($type) use ($options) {
95 109
                                return self::printType($type, $options);
96 109
                            },
97 109
                            $types
98
                        )
99
                    )
100
                )
101
            )
102
        );
103
    }
104
105 109
    private static function printSchemaDefinition(Schema $schema) : string
106
    {
107 109
        if (self::isSchemaOfCommonNames($schema)) {
108 105
            return '';
109
        }
110
111 4
        $operationTypes = [];
112
113 4
        $queryType = $schema->getQueryType();
114 4
        if ($queryType !== null) {
115 4
            $operationTypes[] = sprintf('  query: %s', $queryType->name);
116
        }
117
118 4
        $mutationType = $schema->getMutationType();
119 4
        if ($mutationType !== null) {
120 1
            $operationTypes[] = sprintf('  mutation: %s', $mutationType->name);
121
        }
122
123 4
        $subscriptionType = $schema->getSubscriptionType();
124 4
        if ($subscriptionType !== null) {
125 1
            $operationTypes[] = sprintf('  subscription: %s', $subscriptionType->name);
126
        }
127
128 4
        return sprintf("schema {\n%s\n}", implode("\n", $operationTypes));
129
    }
130
131
    /**
132
     * GraphQL schema define root types for each type of operation. These types are
133
     * the same as any other type and can be named in any manner, however there is
134
     * a common naming convention:
135
     *
136
     *   schema {
137
     *     query: Query
138
     *     mutation: Mutation
139
     *   }
140
     *
141
     * When using this naming convention, the schema description can be omitted.
142
     */
143 109
    private static function isSchemaOfCommonNames(Schema $schema) : bool
144
    {
145 109
        $queryType = $schema->getQueryType();
146 109
        if ($queryType !== null && $queryType->name !== 'Query') {
147 4
            return false;
148
        }
149
150 105
        $mutationType = $schema->getMutationType();
151 105
        if ($mutationType !== null && $mutationType->name !== 'Mutation') {
152
            return false;
153
        }
154
155 105
        $subscriptionType = $schema->getSubscriptionType();
156
157 105
        return $subscriptionType === null || $subscriptionType->name === 'Subscription';
158
    }
159
160 62
    private static function printDirective($directive, $options) : string
161
    {
162 62
        return self::printDescription($options, $directive) .
163 62
            'directive @' . $directive->name . self::printArgs($options, $directive->args) .
164 62
            ' on ' . implode(' | ', $directive->locations);
165
    }
166
167 109
    private static function printDescription($options, $def, $indentation = '', $firstInBlock = true) : string
168
    {
169 109
        if (! $def->description) {
170 109
            return '';
171
        }
172 7
        $lines = self::descriptionLines($def->description, 120 - strlen($indentation));
173 7
        if (isset($options['commentDescriptions'])) {
174 2
            return self::printDescriptionWithComments($lines, $indentation, $firstInBlock);
175
        }
176
177 5
        $description = $indentation && ! $firstInBlock
178 2
            ? "\n" . $indentation . '"""'
179 5
            : $indentation . '"""';
180
181
        // In some circumstances, a single line can be used for the description.
182 5
        if (count($lines) === 1 &&
183 5
            mb_strlen($lines[0]) < 70 &&
184 5
            substr($lines[0], -1) !== '"'
185
        ) {
186 3
            return $description . self::escapeQuote($lines[0]) . "\"\"\"\n";
187
        }
188
189
        // Format a multi-line block quote to account for leading space.
190 3
        $hasLeadingSpace = isset($lines[0]) &&
191
            (
192 3
                substr($lines[0], 0, 1) === ' ' ||
193 3
                substr($lines[0], 0, 1) === '\t'
194
            );
195 3
        if (! $hasLeadingSpace) {
196 2
            $description .= "\n";
197
        }
198
199 3
        $lineLength = count($lines);
200 3
        for ($i = 0; $i < $lineLength; $i++) {
201 3
            if ($i !== 0 || ! $hasLeadingSpace) {
202 2
                $description .= $indentation;
203
            }
204 3
            $description .= self::escapeQuote($lines[$i]) . "\n";
205
        }
206 3
        $description .= $indentation . "\"\"\"\n";
207
208 3
        return $description;
209
    }
210
211
    /**
212
     * @return string[]
213
     */
214 7
    private static function descriptionLines(string $description, int $maxLen) : array
215
    {
216 7
        $lines    = [];
217 7
        $rawLines = explode("\n", $description);
218 7
        foreach ($rawLines as $line) {
219 7
            if ($line === '') {
220 2
                $lines[] = $line;
221
            } else {
222
                // For > 120 character long lines, cut at space boundaries into sublines
223
                // of ~80 chars.
224 7
                $sublines = self::breakLine($line, $maxLen);
225 7
                foreach ($sublines as $subline) {
226 7
                    $lines[] = $subline;
227
                }
228
            }
229
        }
230
231 7
        return $lines;
232
    }
233
234
    /**
235
     * @return string[]
236
     */
237 7
    private static function breakLine(string $line, int $maxLen) : array
238
    {
239 7
        if (strlen($line) < $maxLen + 5) {
240 7
            return [$line];
241
        }
242 2
        preg_match_all('/((?: |^).{15,' . ($maxLen - 40) . '}(?= |$))/', $line, $parts);
243 2
        $parts = $parts[0];
244
245 2
        return array_map('trim', $parts);
246
    }
247
248 2
    private static function printDescriptionWithComments($lines, $indentation, $firstInBlock) : string
249
    {
250 2
        $description = $indentation && ! $firstInBlock ? "\n" : '';
251 2
        foreach ($lines as $line) {
252 2
            if ($line === '') {
253 1
                $description .= $indentation . "#\n";
254
            } else {
255 2
                $description .= $indentation . '# ' . $line . "\n";
256
            }
257
        }
258
259 2
        return $description;
260
    }
261
262 5
    private static function escapeQuote($line) : string
263
    {
264 5
        return str_replace('"""', '\\"""', $line);
265
    }
266
267 109
    private static function printArgs($options, $args, $indentation = '') : string
268
    {
269 109
        if (! $args) {
270 93
            return '';
271
        }
272
273
        // If every arg does not have a description, print them on one line.
274 79
        if (Utils::every(
275 79
            $args,
276
            static function ($arg) {
277 79
                return empty($arg->description);
278 79
            }
279
        )) {
280 77
            return '(' . implode(', ', array_map('self::printInputValue', $args)) . ')';
281
        }
282
283 4
        return sprintf(
284 4
            "(\n%s\n%s)",
285 4
            implode(
286 4
                "\n",
287 4
                array_map(
288
                    static function ($arg, $i) use ($indentation, $options) {
289 4
                        return self::printDescription($options, $arg, '  ' . $indentation, ! $i) . '  ' . $indentation .
290 4
                            self::printInputValue($arg);
291 4
                    },
292 4
                    $args,
293 4
                    array_keys($args)
294
                )
295
            ),
296 4
            $indentation
297
        );
298
    }
299
300 79
    private static function printInputValue($arg) : string
301
    {
302 79
        $argDecl = $arg->name . ': ' . (string) $arg->getType();
303 79
        if ($arg->defaultValueExists()) {
304 10
            $argDecl .= ' = ' . Printer::doPrint(AST::astFromValue($arg->defaultValue, $arg->getType()));
305
        }
306
307 79
        return $argDecl;
308
    }
309
310
    /**
311
     * @param bool[] $options
312
     */
313 109
    public static function printType(Type $type, array $options = []) : string
314
    {
315 109
        if ($type instanceof ScalarType) {
316 59
            return self::printScalar($type, $options);
317
        }
318
319 109
        if ($type instanceof ObjectType) {
320 109
            return self::printObject($type, $options);
321
        }
322
323 75
        if ($type instanceof InterfaceType) {
324 60
            return self::printInterface($type, $options);
325
        }
326
327 71
        if ($type instanceof UnionType) {
328 60
            return self::printUnion($type, $options);
329
        }
330
331 67
        if ($type instanceof EnumType) {
332 65
            return self::printEnum($type, $options);
333
        }
334
335 58
        if ($type instanceof InputObjectType) {
336 58
            return self::printInputObject($type, $options);
337
        }
338
339
        throw new Error(sprintf('Unknown type: %s.', Utils::printSafe($type)));
340
    }
341
342
    /**
343
     * @param bool[] $options
344
     */
345 59
    private static function printScalar(ScalarType $type, array $options) : string
346
    {
347 59
        return sprintf('%sscalar %s', self::printDescription($options, $type), $type->name);
348
    }
349
350
    /**
351
     * @param bool[] $options
352
     */
353 109
    private static function printObject(ObjectType $type, array $options) : string
354
    {
355 109
        $interfaces            = $type->getInterfaces();
356 109
        $implementedInterfaces = ! empty($interfaces)
357 60
            ? ' implements ' . implode(
358 60
                ' & ',
359 60
                array_map(
360
                    static function ($i) {
361 60
                        return $i->name;
362 60
                    },
363 60
                    $interfaces
364
                )
365
            )
366 109
            : '';
367
368 109
        return self::printDescription($options, $type) .
369 109
            sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));
370
    }
371
372
    /**
373
     * @param bool[] $options
374
     */
375 109
    private static function printFields($options, $type) : string
376
    {
377 109
        $fields = array_values($type->getFields());
378
379 109
        return implode(
380 109
            "\n",
381 109
            array_map(
382
                static function ($f, $i) use ($options) {
383 109
                    return self::printDescription($options, $f, '  ', ! $i) . '  ' .
384 109
                        $f->name . self::printArgs($options, $f->args, '  ') . ': ' .
385 109
                        (string) $f->getType() . self::printDeprecated($f);
386 109
                },
387 109
                $fields,
388 109
                array_keys($fields)
389
            )
390
        );
391
    }
392
393 109
    private static function printDeprecated($fieldOrEnumVal) : string
394
    {
395 109
        $reason = $fieldOrEnumVal->deprecationReason;
396 109
        if (empty($reason)) {
397 109
            return '';
398
        }
399 1
        if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) {
400 1
            return ' @deprecated';
401
        }
402
403
        return ' @deprecated(reason: ' .
404 1
            Printer::doPrint(AST::astFromValue($reason, Type::string())) . ')';
405
    }
406
407
    /**
408
     * @param bool[] $options
409
     */
410 60
    private static function printInterface(InterfaceType $type, array $options) : string
411
    {
412 60
        return self::printDescription($options, $type) .
413 60
            sprintf("interface %s {\n%s\n}", $type->name, self::printFields($options, $type));
414
    }
415
416
    /**
417
     * @param bool[] $options
418
     */
419 60
    private static function printUnion(UnionType $type, array $options) : string
420
    {
421 60
        return self::printDescription($options, $type) .
422 60
            sprintf('union %s = %s', $type->name, implode(' | ', $type->getTypes()));
423
    }
424
425
    /**
426
     * @param bool[] $options
427
     */
428 65
    private static function printEnum(EnumType $type, array $options) : string
429
    {
430 65
        return self::printDescription($options, $type) .
431 65
            sprintf("enum %s {\n%s\n}", $type->name, self::printEnumValues($type->getValues(), $options));
432
    }
433
434
    /**
435
     * @param bool[] $options
436
     */
437 65
    private static function printEnumValues($values, $options) : string
438
    {
439 65
        return implode(
440 65
            "\n",
441 65
            array_map(
442
                static function ($value, $i) use ($options) {
443 65
                    return self::printDescription($options, $value, '  ', ! $i) . '  ' .
444 65
                        $value->name . self::printDeprecated($value);
445 65
                },
446 65
                $values,
447 65
                array_keys($values)
448
            )
449
        );
450
    }
451
452
    /**
453
     * @param bool[] $options
454
     */
455 58
    private static function printInputObject(InputObjectType $type, array $options) : string
456
    {
457 58
        $fields = array_values($type->getFields());
458
459 58
        return self::printDescription($options, $type) .
460 58
            sprintf(
461 58
                "input %s {\n%s\n}",
462 58
                $type->name,
463 58
                implode(
464 58
                    "\n",
465 58
                    array_map(
466
                        static function ($f, $i) use ($options) {
467 58
                            return self::printDescription($options, $f, '  ', ! $i) . '  ' . self::printInputValue($f);
468 58
                        },
469 58
                        $fields,
470 58
                        array_keys($fields)
471
                    )
472
                )
473
            );
474
    }
475
476
    /**
477
     * @param bool[] $options
478
     *
479
     * @api
480
     */
481 2
    public static function printIntrospectionSchema(Schema $schema, array $options = []) : string
482
    {
483 2
        return self::printFilteredSchema(
484 2
            $schema,
485 2
            [Directive::class, 'isSpecifiedDirective'],
486 2
            [Introspection::class, 'isIntrospectionType'],
487 2
            $options
488
        );
489
    }
490
}
491