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

ASTDefinitionBuilder   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 438
Duplicated Lines 0 %

Test Coverage

Coverage 88.48%

Importance

Changes 0
Metric Value
wmc 66
eloc 204
dl 0
loc 438
rs 3.12
c 0
b 0
f 0
ccs 192
cts 217
cp 0.8848

22 Methods

Rating   Name   Duplication   Size   Complexity  
B makeSchemaDef() 0 20 8
A getNamedTypeNode() 0 8 3
A makeImplementedInterfaces() 0 15 2
A buildField() 0 11 2
A makeInputObjectDef() 0 11 2
A makeScalarDef() 0 8 1
A makeFieldDefMap() 0 13 2
A internalBuildWrappedType() 0 5 1
A __construct() 0 12 1
A buildType() 0 7 2
A buildDirective() 0 13 2
B makeSchemaDefFromConfig() 0 20 8
A makeEnumDef() 0 21 2
A getDescription() 0 13 4
A makeTypeDef() 0 14 1
A makeInterfaceDef() 0 11 1
A makeInputValues() 0 23 2
B getLeadingCommentBlock() 0 20 9
A makeUnionDef() 0 17 2
A getDeprecationReason() 0 5 1
B internalBuildType() 0 39 7
A buildWrappedType() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like ASTDefinitionBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ASTDefinitionBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Executor\Values;
9
use GraphQL\Language\AST\DirectiveDefinitionNode;
10
use GraphQL\Language\AST\EnumTypeDefinitionNode;
11
use GraphQL\Language\AST\EnumTypeExtensionNode;
12
use GraphQL\Language\AST\EnumValueDefinitionNode;
13
use GraphQL\Language\AST\FieldDefinitionNode;
14
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
15
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
16
use GraphQL\Language\AST\ListTypeNode;
17
use GraphQL\Language\AST\NamedTypeNode;
18
use GraphQL\Language\AST\Node;
19
use GraphQL\Language\AST\NodeKind;
20
use GraphQL\Language\AST\NonNullTypeNode;
21
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
22
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
23
use GraphQL\Language\AST\TypeNode;
24
use GraphQL\Language\AST\UnionTypeDefinitionNode;
25
use GraphQL\Language\Token;
26
use GraphQL\Type\Definition\CustomScalarType;
27
use GraphQL\Type\Definition\Directive;
28
use GraphQL\Type\Definition\EnumType;
29
use GraphQL\Type\Definition\FieldArgument;
30
use GraphQL\Type\Definition\InputObjectType;
31
use GraphQL\Type\Definition\InputType;
32
use GraphQL\Type\Definition\InterfaceType;
33
use GraphQL\Type\Definition\NonNull;
34
use GraphQL\Type\Definition\ObjectType;
35
use GraphQL\Type\Definition\Type;
36
use GraphQL\Type\Definition\UnionType;
37
use function array_reverse;
38
use function implode;
39
use function is_array;
40
use function is_string;
41
use function sprintf;
42
43
class ASTDefinitionBuilder
44
{
45
    /** @var Node[] */
46
    private $typeDefintionsMap;
47
48
    /** @var callable */
49
    private $typeConfigDecorator;
50
51
    /** @var bool[] */
52
    private $options;
53
54
    /** @var callable */
55
    private $resolveType;
56
57
    /** @var Type[] */
58
    private $cache;
59
60
    /**
61
     * @param Node[] $typeDefintionsMap
62
     * @param bool[] $options
63
     */
64 79
    public function __construct(
65
        array $typeDefintionsMap,
66
        $options,
67
        callable $resolveType,
68
        ?callable $typeConfigDecorator = null
69
    ) {
70 79
        $this->typeDefintionsMap   = $typeDefintionsMap;
71 79
        $this->typeConfigDecorator = $typeConfigDecorator;
72 79
        $this->options             = $options;
73 79
        $this->resolveType         = $resolveType;
74
75 79
        $this->cache = Type::getAllBuiltInTypes();
76 79
    }
77
78
    /**
79
     * @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
80
     * @return Type
81
     */
82 74
    private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
83
    {
84 74
        if ($inputTypeNode->kind === NodeKind::LIST_TYPE) {
0 ignored issues
show
Bug introduced by
Accessing kind on the interface GraphQL\Language\AST\TypeNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
85 7
            return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
0 ignored issues
show
Bug introduced by
Accessing type on the interface GraphQL\Language\AST\TypeNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
86
        }
87 74
        if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) {
88 6
            $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
89
90 6
            return Type::nonNull(NonNull::assertNullableType($wrappedType));
91
        }
92
93 74
        return $innerType;
94
    }
95
96
    /**
97
     * @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
98
     * @return TypeNode
99
     */
100 76
    private function getNamedTypeNode(TypeNode $typeNode)
101
    {
102 76
        $namedType = $typeNode;
103 76
        while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) {
0 ignored issues
show
Bug introduced by
Accessing kind on the interface GraphQL\Language\AST\TypeNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
104 10
            $namedType = $namedType->type;
0 ignored issues
show
Bug introduced by
Accessing type on the interface GraphQL\Language\AST\TypeNode suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
105
        }
106
107 76
        return $namedType;
108
    }
109
110
    /**
111
     * @param string             $typeName
112
     * @param NamedTypeNode|null $typeNode
113
     * @return Type
114
     * @throws Error
115
     */
116 79
    private function internalBuildType($typeName, $typeNode = null)
117
    {
118 79
        if (! isset($this->cache[$typeName])) {
119 79
            if (isset($this->typeDefintionsMap[$typeName])) {
120 79
                $type = $this->makeSchemaDef($this->typeDefintionsMap[$typeName]);
121 79
                if ($this->typeConfigDecorator) {
122 2
                    $fn = $this->typeConfigDecorator;
123
                    try {
124 2
                        $config = $fn($type->config, $this->typeDefintionsMap[$typeName], $this->typeDefintionsMap);
125
                    } catch (\Throwable $e) {
126
                        throw new Error(
127
                            sprintf('Type config decorator passed to %s threw an error ', static::class) .
128
                            sprintf('when building %s type: %s', $typeName, $e->getMessage()),
129
                            null,
130
                            null,
131
                            null,
132
                            null,
133
                            $e
134
                        );
135
                    }
136 2
                    if (! is_array($config) || isset($config[0])) {
137
                        throw new Error(
138
                            sprintf(
139
                                'Type config decorator passed to %s is expected to return an array, but got %s',
140
                                static::class,
141
                                Utils::getVariableType($config)
142
                            )
143
                        );
144
                    }
145 2
                    $type = $this->makeSchemaDefFromConfig($this->typeDefintionsMap[$typeName], $config);
146
                }
147 79
                $this->cache[$typeName] = $type;
148
            } else {
149 3
                $fn                     = $this->resolveType;
150 3
                $this->cache[$typeName] = $fn($typeName, $typeNode);
151
            }
152
        }
153
154 79
        return $this->cache[$typeName];
155
    }
156
157
    /**
158
     * @param string|NamedTypeNode $ref
159
     * @return Type
160
     * @throws Error
161
     */
162 79
    public function buildType($ref)
163
    {
164 79
        if (is_string($ref)) {
165 79
            return $this->internalBuildType($ref);
166
        }
167
168 77
        return $this->internalBuildType($ref->name->value, $ref);
169
    }
170
171
    /**
172
     * @return Type|InputType
173
     * @throws Error
174
     */
175 76
    private function internalBuildWrappedType(TypeNode $typeNode)
176
    {
177 76
        $typeDef = $this->buildType($this->getNamedTypeNode($typeNode));
178
179 74
        return $this->buildWrappedType($typeDef, $typeNode);
180
    }
181
182 5
    public function buildDirective(DirectiveDefinitionNode $directiveNode)
183
    {
184 5
        return new Directive([
185 5
            'name'        => $directiveNode->name->value,
186 5
            'description' => $this->getDescription($directiveNode),
187 5
            'locations'   => Utils::map(
188 5
                $directiveNode->locations,
189
                function ($node) {
190 5
                    return $node->value;
191 5
                }
192
            ),
193 5
            'args'        => $directiveNode->arguments ? FieldArgument::createMap($this->makeInputValues($directiveNode->arguments)) : null,
194 5
            'astNode'     => $directiveNode,
195
        ]);
196
    }
197
198 75
    public function buildField(FieldDefinitionNode $field)
199
    {
200
        return [
201
            // Note: While this could make assertions to get the correctly typed
202
            // value, that would throw immediately while type system validation
203
            // with validateSchema() will produce more actionable results.
204 75
            'type'              => $this->internalBuildWrappedType($field->type),
205 73
            'description'       => $this->getDescription($field),
206 73
            'args'              => $field->arguments ? $this->makeInputValues($field->arguments) : null,
207 73
            'deprecationReason' => $this->getDeprecationReason($field),
208 73
            'astNode'           => $field,
209
        ];
210
    }
211
212
    /**
213
     * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode|UnionTypeDefinitionNode $def
214
     * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
215
     * @throws Error
216
     */
217 79
    private function makeSchemaDef($def)
218
    {
219 79
        if (! $def) {
220
            throw new Error('def must be defined.');
221
        }
222 79
        switch ($def->kind) {
223 79
            case NodeKind::OBJECT_TYPE_DEFINITION:
224 78
                return $this->makeTypeDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\InterfaceTypeDefinitionNode and GraphQL\Language\AST\EnumTypeDefinitionNode and GraphQL\Language\AST\ScalarTypeDefinitionNode and GraphQL\Language\AST\InputObjectTypeDefinitionNode and GraphQL\Language\AST\UnionTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinitionBuilder::makeTypeDef() does only seem to accept GraphQL\Language\AST\ObjectTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

224
                return $this->makeTypeDef(/** @scrutinizer ignore-type */ $def);
Loading history...
225 53
            case NodeKind::INTERFACE_TYPE_DEFINITION:
226 25
                return $this->makeInterfaceDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\EnumTypeDefinitionNode and GraphQL\Language\AST\ScalarTypeDefinitionNode and GraphQL\Language\AST\InputObjectTypeDefinitionNode and GraphQL\Language\AST\UnionTypeDefinitionNode and GraphQL\Language\AST\ObjectTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinit...der::makeInterfaceDef() does only seem to accept GraphQL\Language\AST\InterfaceTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

226
                return $this->makeInterfaceDef(/** @scrutinizer ignore-type */ $def);
Loading history...
227 33
            case NodeKind::ENUM_TYPE_DEFINITION:
228 10
                return $this->makeEnumDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\InterfaceTypeDefinitionNode and GraphQL\Language\AST\ScalarTypeDefinitionNode and GraphQL\Language\AST\InputObjectTypeDefinitionNode and GraphQL\Language\AST\UnionTypeDefinitionNode and GraphQL\Language\AST\ObjectTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinitionBuilder::makeEnumDef() does only seem to accept GraphQL\Language\AST\EnumTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

228
                return $this->makeEnumDef(/** @scrutinizer ignore-type */ $def);
Loading history...
229 24
            case NodeKind::UNION_TYPE_DEFINITION:
230 12
                return $this->makeUnionDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\InterfaceTypeDefinitionNode and GraphQL\Language\AST\EnumTypeDefinitionNode and GraphQL\Language\AST\ScalarTypeDefinitionNode and GraphQL\Language\AST\InputObjectTypeDefinitionNode and GraphQL\Language\AST\ObjectTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinitionBuilder::makeUnionDef() does only seem to accept GraphQL\Language\AST\UnionTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

230
                return $this->makeUnionDef(/** @scrutinizer ignore-type */ $def);
Loading history...
231 14
            case NodeKind::SCALAR_TYPE_DEFINITION:
232 2
                return $this->makeScalarDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\InterfaceTypeDefinitionNode and GraphQL\Language\AST\EnumTypeDefinitionNode and GraphQL\Language\AST\InputObjectTypeDefinitionNode and GraphQL\Language\AST\UnionTypeDefinitionNode and GraphQL\Language\AST\ObjectTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinit...uilder::makeScalarDef() does only seem to accept GraphQL\Language\AST\ScalarTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

232
                return $this->makeScalarDef(/** @scrutinizer ignore-type */ $def);
Loading history...
233 12
            case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
234 12
                return $this->makeInputObjectDef($def);
0 ignored issues
show
Bug introduced by
It seems like $def can also be of type GraphQL\Language\AST\InterfaceTypeDefinitionNode and GraphQL\Language\AST\EnumTypeDefinitionNode and GraphQL\Language\AST\ScalarTypeDefinitionNode and GraphQL\Language\AST\UnionTypeDefinitionNode and GraphQL\Language\AST\ObjectTypeDefinitionNode; however, parameter $def of GraphQL\Utils\ASTDefinit...r::makeInputObjectDef() does only seem to accept GraphQL\Language\AST\InputObjectTypeDefinitionNode, maybe add an additional type check? ( Ignorable by Annotation )

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

234
                return $this->makeInputObjectDef(/** @scrutinizer ignore-type */ $def);
Loading history...
235
            default:
236
                throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
237
        }
238
    }
239
240
    /**
241
     * @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def
242
     * @param mixed[]                                                                                                                           $config
243
     * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
244
     * @throws Error
245
     */
246 2
    private function makeSchemaDefFromConfig($def, array $config)
247
    {
248 2
        if (! $def) {
249
            throw new Error('def must be defined.');
250
        }
251 2
        switch ($def->kind) {
252 2
            case NodeKind::OBJECT_TYPE_DEFINITION:
253 2
                return new ObjectType($config);
254 2
            case NodeKind::INTERFACE_TYPE_DEFINITION:
255 2
                return new InterfaceType($config);
256 2
            case NodeKind::ENUM_TYPE_DEFINITION:
257 2
                return new EnumType($config);
258
            case NodeKind::UNION_TYPE_DEFINITION:
259
                return new UnionType($config);
260
            case NodeKind::SCALAR_TYPE_DEFINITION:
261
                return new CustomScalarType($config);
262
            case NodeKind::INPUT_OBJECT_TYPE_DEFINITION:
263
                return new InputObjectType($config);
264
            default:
265
                throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
266
        }
267
    }
268
269 78
    private function makeTypeDef(ObjectTypeDefinitionNode $def)
270
    {
271 78
        $typeName = $def->name->value;
272
273 78
        return new ObjectType([
274 78
            'name'        => $typeName,
275 78
            'description' => $this->getDescription($def),
276
            'fields'      => function () use ($def) {
277 75
                return $this->makeFieldDefMap($def);
278 78
            },
279
            'interfaces'  => function () use ($def) {
280 73
                return $this->makeImplementedInterfaces($def);
281 78
            },
282 78
            'astNode'     => $def,
283
        ]);
284
    }
285
286 75
    private function makeFieldDefMap($def)
287
    {
288 75
        return $def->fields
289 75
            ? Utils::keyValMap(
290 75
                $def->fields,
291
                function ($field) {
292 75
                    return $field->name->value;
293 75
                },
294
                function ($field) {
295 75
                    return $this->buildField($field);
296 75
                }
297
            )
298 73
            : [];
299
    }
300
301 73
    private function makeImplementedInterfaces(ObjectTypeDefinitionNode $def)
302
    {
303 73
        if ($def->interfaces) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $def->interfaces of type GraphQL\Language\AST\NamedTypeNode[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
304
            // Note: While this could make early assertions to get the correctly
305
            // typed values, that would throw immediately while type system
306
            // validation with validateSchema() will produce more actionable results.
307 25
            return Utils::map(
308 25
                $def->interfaces,
309
                function ($iface) {
310 25
                    return $this->buildType($iface);
311 25
                }
312
            );
313
        }
314
315 70
        return null;
316
    }
317
318 75
    private function makeInputValues($values)
319
    {
320 75
        return Utils::keyValMap(
321 75
            $values,
322
            function ($value) {
323 32
                return $value->name->value;
324 75
            },
325
            function ($value) {
326
                // Note: While this could make assertions to get the correctly typed
327
                // value, that would throw immediately while type system validation
328
                // with validateSchema() will produce more actionable results.
329 32
                $type   = $this->internalBuildWrappedType($value->type);
330
                $config = [
331 32
                    'name'        => $value->name->value,
332 32
                    'type'        => $type,
333 32
                    'description' => $this->getDescription($value),
334 32
                    'astNode'     => $value,
335
                ];
336 32
                if (isset($value->defaultValue)) {
337 2
                    $config['defaultValue'] = AST::valueFromAST($value->defaultValue, $type);
0 ignored issues
show
Bug introduced by
$type of type GraphQL\Type\Definition\Type is incompatible with the type GraphQL\Type\Definition\InputType expected by parameter $type of GraphQL\Utils\AST::valueFromAST(). ( Ignorable by Annotation )

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

337
                    $config['defaultValue'] = AST::valueFromAST($value->defaultValue, /** @scrutinizer ignore-type */ $type);
Loading history...
338
                }
339
340 32
                return $config;
341 75
            }
342
        );
343
    }
344
345 25
    private function makeInterfaceDef(InterfaceTypeDefinitionNode $def)
346
    {
347 25
        $typeName = $def->name->value;
348
349 25
        return new InterfaceType([
350 25
            'name'        => $typeName,
351 25
            'description' => $this->getDescription($def),
352
            'fields'      => function () use ($def) {
353 25
                return $this->makeFieldDefMap($def);
354 25
            },
355 25
            'astNode'     => $def,
356
        ]);
357
    }
358
359 10
    private function makeEnumDef(EnumTypeDefinitionNode $def)
360
    {
361 10
        return new EnumType([
362 10
            'name'        => $def->name->value,
363 10
            'description' => $this->getDescription($def),
364 10
            'values'      => $def->values
365 10
                ? Utils::keyValMap(
366 10
                    $def->values,
367
                    function ($enumValue) {
368 9
                        return $enumValue->name->value;
369 10
                    },
370
                    function ($enumValue) {
371
                        return [
372 9
                            'description'       => $this->getDescription($enumValue),
373 9
                            'deprecationReason' => $this->getDeprecationReason($enumValue),
374 9
                            'astNode'           => $enumValue,
375
                        ];
376 10
                    }
377
                )
378
                : [],
379 10
            'astNode'     => $def,
380
        ]);
381
    }
382
383 12
    private function makeUnionDef(UnionTypeDefinitionNode $def)
384
    {
385 12
        return new UnionType([
386 12
            'name'        => $def->name->value,
387 12
            'description' => $this->getDescription($def),
388
            // Note: While this could make assertions to get the correctly typed
389
            // values below, that would throw immediately while type system
390
            // validation with validateSchema() will produce more actionable results.
391 12
            'types'       => $def->types
392 11
                ? Utils::map(
393 11
                    $def->types,
394
                    function ($typeNode) {
395 11
                        return $this->buildType($typeNode);
396 11
                    }
397
                ) :
398
                [],
399 11
            'astNode'     => $def,
400
        ]);
401
    }
402
403 2
    private function makeScalarDef(ScalarTypeDefinitionNode $def)
404
    {
405 2
        return new CustomScalarType([
406 2
            'name'        => $def->name->value,
407 2
            'description' => $this->getDescription($def),
408 2
            'astNode'     => $def,
409
            'serialize'   => function ($value) {
410 1
                return $value;
411 2
            },
412
        ]);
413
    }
414
415 12
    private function makeInputObjectDef(InputObjectTypeDefinitionNode $def)
416
    {
417 12
        return new InputObjectType([
418 12
            'name'        => $def->name->value,
419 12
            'description' => $this->getDescription($def),
420
            'fields'      => function () use ($def) {
421 12
                return $def->fields
422 12
                    ? $this->makeInputValues($def->fields)
423 12
                    : [];
424 12
            },
425 12
            'astNode'     => $def,
426
        ]);
427
    }
428
429
    /**
430
     * Given a collection of directives, returns the string value for the
431
     * deprecation reason.
432
     *
433
     * @param EnumValueDefinitionNode | FieldDefinitionNode $node
434
     * @return string
435
     */
436 73
    private function getDeprecationReason($node)
437
    {
438 73
        $deprecated = Values::getDirectiveValues(Directive::deprecatedDirective(), $node);
439
440 73
        return $deprecated['reason'] ?? null;
441
    }
442
443
    /**
444
     * Given an ast node, returns its string description.
445
     */
446 79
    private function getDescription($node)
447
    {
448 79
        if ($node->description) {
449 4
            return $node->description->value;
450
        }
451 79
        if (isset($this->options['commentDescriptions'])) {
452 1
            $rawValue = $this->getLeadingCommentBlock($node);
453 1
            if ($rawValue !== null) {
454 1
                return BlockString::value("\n" . $rawValue);
455
            }
456
        }
457
458 78
        return null;
459
    }
460
461 1
    private function getLeadingCommentBlock($node)
462
    {
463 1
        $loc = $node->loc;
464 1
        if (! $loc || ! $loc->startToken) {
465
            return null;
466
        }
467 1
        $comments = [];
468 1
        $token    = $loc->startToken->prev;
469 1
        while ($token &&
470 1
            $token->kind === Token::COMMENT &&
471 1
            $token->next && $token->prev &&
472 1
            $token->line + 1 === $token->next->line &&
473 1
            $token->line !== $token->prev->line
474
        ) {
475 1
            $value      = $token->value;
476 1
            $comments[] = $value;
477 1
            $token      = $token->prev;
478
        }
479
480 1
        return implode("\n", array_reverse($comments));
481
    }
482
}
483