Completed
Pull Request — master (#45)
by Christoffer
02:23
created

getSuggestedTypeNames()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 3
nop 3
dl 0
loc 40
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Validation\Rule;
4
5
use Digia\GraphQL\Error\GraphQLError;
6
use Digia\GraphQL\Language\AST\Node\FieldNode;
7
use Digia\GraphQL\Language\AST\Node\InterfacesTrait;
8
use Digia\GraphQL\Language\AST\Node\NodeInterface;
9
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
10
use Digia\GraphQL\Type\Definition\FieldsTrait;
11
use Digia\GraphQL\Type\Definition\InterfaceType;
12
use Digia\GraphQL\Type\Definition\NameTrait;
13
use Digia\GraphQL\Type\Definition\ObjectType;
14
use Digia\GraphQL\Type\Definition\OutputTypeInterface;
15
use Digia\GraphQL\Type\Definition\TypeInterface;
16
use Digia\GraphQL\Type\SchemaInterface;
17
use function Digia\GraphQL\Util\suggestionList;
18
19
class FieldOnCorrectTypeRule extends AbstractRule
20
{
21
    /**
22
     * @inheritdoc
23
     */
24
    public function enterNode(
25
        NodeInterface $node,
26
        $key = null,
27
        ?NodeInterface $parent = null,
28
        array $path = []
29
    ): ?NodeInterface {
30
        if ($node instanceof FieldNode) {
31
            $type = $this->context->getParentType();
32
33
            if (null !== $type) {
34
                $fieldDefinition = $this->context->getFieldDefinition();
35
36
                if (null === $fieldDefinition) {
37
                    $schema              = $this->context->getSchema();
38
                    $fieldName           = $node->getNameValue();
39
                    $suggestedTypeNames  = getSuggestedTypeNames($schema, $type, $fieldName);
40
                    $suggestedFieldNames = count($suggestedTypeNames) !== 0
41
                        ? []
42
                        : getSuggestedFieldNames($type, $fieldName);
0 ignored issues
show
Bug introduced by
$type of type Digia\GraphQL\Type\Definition\TypeInterface is incompatible with the type Digia\GraphQL\Type\Definition\OutputTypeInterface expected by parameter $type of Digia\GraphQL\Validation...etSuggestedFieldNames(). ( Ignorable by Annotation )

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

42
                        : getSuggestedFieldNames(/** @scrutinizer ignore-type */ $type, $fieldName);
Loading history...
43
44
                    $this->context->reportError(
45
                        new GraphQLError(
46
                            undefinedFieldMessage(
47
                                $fieldName,
48
                                $type->getName(),
0 ignored issues
show
Bug introduced by
The method getName() does not exist on Digia\GraphQL\Type\Definition\TypeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Type\Defin...n\AbstractTypeInterface or Digia\GraphQL\Type\Defin...n\WrappingTypeInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

48
                                $type->/** @scrutinizer ignore-call */ 
49
                                       getName(),
Loading history...
49
                                $suggestedTypeNames,
50
                                $suggestedFieldNames
51
                            ),
52
                            [$node]
53
                        )
54
                    );
55
                }
56
            }
57
        }
58
59
        return $node;
60
    }
61
}
62
63
/**
64
 * Go through all of the implementations of type, as well as the interfaces
65
 * that they implement. If any of those types include the provided field,
66
 * suggest them, sorted by how often the type is referenced,  starting
67
 * with Interfaces.
68
 *
69
 * @param SchemaInterface $schema
70
 * @param TypeInterface   $type
71
 * @param string          $fieldName
72
 * @return array
73
 * @throws \Exception
74
 */
75
function getSuggestedTypeNames(SchemaInterface $schema, TypeInterface $type, string $fieldName): array
76
{
77
    if (!$type instanceof AbstractTypeInterface) {
78
        // Otherwise, must be an Object type, which does not have possible fields.
79
        return [];
80
    }
81
82
    $suggestedObjectTypes = [];
83
    $interfaceUsageCount  = [];
84
85
    /** @var FieldsTrait|NameTrait|InterfacesTrait $possibleType */
86
    foreach ($schema->getPossibleTypes($type) as $possibleType) {
87
        $typeFields = $possibleType->getFields();
88
        if (!isset($typeFields[$fieldName])) {
89
            break;
90
        }
91
92
        $suggestedObjectTypes[] = $possibleType->getName();
93
94
        /** @var InterfaceType $possibleInterface */
95
        foreach ($possibleType->getInterfaces() as $possibleInterface) {
96
            $interfaceFields = $possibleInterface->getFields();
97
            if (!isset($interfaceFields[$fieldName])) {
98
                break;
99
            }
100
101
            $interfaceName                       = $possibleInterface->getName();
102
            $interfaceUsageCount[$interfaceName] = ($interfaceUsageCount[$interfaceName] ?? 0) + 1;
103
        }
104
    }
105
106
    $suggestedInterfaceTypes = array_keys($interfaceUsageCount);
107
108
    uasort($suggestedInterfaceTypes, function ($a, $b) use ($interfaceUsageCount) {
109
        return $interfaceUsageCount[$b] - $interfaceUsageCount[$a];
110
    });
111
112
    $result = array_merge($suggestedInterfaceTypes, $suggestedObjectTypes);
113
114
    return $result;
115
}
116
117
/**
118
 * For the field name provided, determine if there are any similar field names
119
 * that may be the result of a typo.
120
 *
121
 * @param OutputTypeInterface $type
122
 * @param string              $fieldName
123
 * @return array
124
 * @throws \Exception
125
 */
126
function getSuggestedFieldNames(OutputTypeInterface $type, string $fieldName): array
127
{
128
    if (!($type instanceof ObjectType || $type instanceof InterfaceType)) {
129
        // Otherwise, must be a Union type, which does not define fields.
130
        return [];
131
    }
132
133
    /** @var FieldsTrait $type */
134
    $possibleFieldNames = array_keys($type->getFields());
135
    return suggestionList($fieldName, $possibleFieldNames);
136
}
137