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.

PossibleFragmentSpreads   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 34
eloc 60
dl 0
loc 137
ccs 64
cts 64
cp 1
rs 9.68
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
D doTypesOverlap() 0 66 22
A typeIncompatibleAnonSpreadMessage() 0 6 1
A getFragmentType() 0 11 3
A typeIncompatibleSpreadMessage() 0 7 1
B getVisitor() 0 33 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Validator\Rules;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\AST\FragmentSpreadNode;
9
use GraphQL\Language\AST\InlineFragmentNode;
10
use GraphQL\Language\AST\NodeKind;
11
use GraphQL\Type\Definition\AbstractType;
12
use GraphQL\Type\Definition\CompositeType;
13
use GraphQL\Type\Definition\InterfaceType;
14
use GraphQL\Type\Definition\ObjectType;
15
use GraphQL\Type\Definition\UnionType;
16
use GraphQL\Type\Schema;
17
use GraphQL\Utils\TypeInfo;
18
use GraphQL\Validator\ValidationContext;
19
use function sprintf;
20
21
class PossibleFragmentSpreads extends ValidationRule
22
{
23 142
    public function getVisitor(ValidationContext $context)
24
    {
25
        return [
26
            NodeKind::INLINE_FRAGMENT => function (InlineFragmentNode $node) use ($context) {
27 26
                $fragType   = $context->getType();
28 26
                $parentType = $context->getParentType();
29
30 26
                if (! ($fragType instanceof CompositeType) ||
31 26
                    ! ($parentType instanceof CompositeType) ||
32 26
                    $this->doTypesOverlap($context->getSchema(), $fragType, $parentType)) {
33 25
                    return;
34
                }
35
36 1
                $context->reportError(new Error(
37 1
                    self::typeIncompatibleAnonSpreadMessage($parentType, $fragType),
38 1
                    [$node]
39
                ));
40 142
            },
41
            NodeKind::FRAGMENT_SPREAD => function (FragmentSpreadNode $node) use ($context) {
42 32
                $fragName   = $node->name->value;
43 32
                $fragType   = $this->getFragmentType($context, $fragName);
44 32
                $parentType = $context->getParentType();
45
46 32
                if (! $fragType ||
47 31
                    ! $parentType ||
48 32
                    $this->doTypesOverlap($context->getSchema(), $fragType, $parentType)
49
                ) {
50 24
                    return;
51
                }
52
53 8
                $context->reportError(new Error(
54 8
                    self::typeIncompatibleSpreadMessage($fragName, $parentType, $fragType),
55 8
                    [$node]
56
                ));
57 142
            },
58
        ];
59
    }
60
61 51
    private function doTypesOverlap(Schema $schema, CompositeType $fragType, CompositeType $parentType)
62
    {
63
        // Checking in the order of the most frequently used scenarios:
64
        // Parent type === fragment type
65 51
        if ($parentType === $fragType) {
66 12
            return true;
67
        }
68
69
        // Parent type is interface or union, fragment type is object type
70 39
        if ($parentType instanceof AbstractType && $fragType instanceof ObjectType) {
71 23
            return $schema->isPossibleType($parentType, $fragType);
72
        }
73
74
        // Parent type is object type, fragment type is interface (or rather rare - union)
75 16
        if ($parentType instanceof ObjectType && $fragType instanceof AbstractType) {
76 4
            return $schema->isPossibleType($fragType, $parentType);
77
        }
78
79
        // Both are object types:
80 12
        if ($parentType instanceof ObjectType && $fragType instanceof ObjectType) {
81 2
            return $parentType === $fragType;
82
        }
83
84
        // Both are interfaces
85
        // This case may be assumed valid only when implementations of two interfaces intersect
86
        // But we don't have information about all implementations at runtime
87
        // (getting this information via $schema->getPossibleTypes() requires scanning through whole schema
88
        // which is very costly to do at each request due to PHP "shared nothing" architecture)
89
        //
90
        // So in this case we just make it pass - invalid fragment spreads will be simply ignored during execution
91
        // See also https://github.com/webonyx/graphql-php/issues/69#issuecomment-283954602
92 10
        if ($parentType instanceof InterfaceType && $fragType instanceof InterfaceType) {
93 4
            return true;
94
95
            // Note that there is one case when we do have information about all implementations:
96
            // When schema descriptor is defined ($schema->hasDescriptor())
97
            // BUT we must avoid situation when some query that worked in development had suddenly stopped
98
            // working in production. So staying consistent and always validate.
99
        }
100
101
        // Interface within union
102 6
        if ($parentType instanceof UnionType && $fragType instanceof InterfaceType) {
103 2
            foreach ($parentType->getTypes() as $type) {
104 2
                if ($type->implementsInterface($fragType)) {
105 2
                    return true;
106
                }
107
            }
108
        }
109
110 5
        if ($parentType instanceof InterfaceType && $fragType instanceof UnionType) {
111 2
            foreach ($fragType->getTypes() as $type) {
112 2
                if ($type->implementsInterface($parentType)) {
113 2
                    return true;
114
                }
115
            }
116
        }
117
118 4
        if ($parentType instanceof UnionType && $fragType instanceof UnionType) {
119 2
            foreach ($fragType->getTypes() as $type) {
120 2
                if ($parentType->isPossibleType($type)) {
121 2
                    return true;
122
                }
123
            }
124
        }
125
126 3
        return false;
127
    }
128
129 1
    public static function typeIncompatibleAnonSpreadMessage($parentType, $fragType)
130
    {
131 1
        return sprintf(
132 1
            'Fragment cannot be spread here as objects of type "%s" can never be of type "%s".',
133 1
            $parentType,
134 1
            $fragType
135
        );
136
    }
137
138 32
    private function getFragmentType(ValidationContext $context, $name)
139
    {
140 32
        $frag = $context->getFragment($name);
141 32
        if ($frag) {
142 32
            $type = TypeInfo::typeFromAST($context->getSchema(), $frag->typeCondition);
143 32
            if ($type instanceof CompositeType) {
144 31
                return $type;
145
            }
146
        }
147
148 1
        return null;
149
    }
150
151 8
    public static function typeIncompatibleSpreadMessage($fragName, $parentType, $fragType)
152
    {
153 8
        return sprintf(
154 8
            'Fragment "%s" cannot be spread here as objects of type "%s" can never be of type "%s".',
155 8
            $fragName,
156 8
            $parentType,
157 8
            $fragType
158
        );
159
    }
160
}
161