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

KnownDirectivesRule   A

Complexity

Total Complexity 5

Size/Duplication

Total Lines 34
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 10
c 0
b 0
f 0
wmc 5

1 Method

Rating   Name   Duplication   Size   Complexity  
B enterNode() 0 29 5
1
<?php
2
3
namespace Digia\GraphQL\Validation\Rule;
4
5
use Digia\GraphQL\Error\ValidationException;
6
use Digia\GraphQL\Language\AST\DirectiveLocationEnum;
7
use Digia\GraphQL\Language\AST\Node\DirectiveNode;
8
use Digia\GraphQL\Language\AST\Node\EnumTypeDefinitionNode;
9
use Digia\GraphQL\Language\AST\Node\EnumTypeExtensionNode;
10
use Digia\GraphQL\Language\AST\Node\EnumValueDefinitionNode;
11
use Digia\GraphQL\Language\AST\Node\FieldDefinitionNode;
12
use Digia\GraphQL\Language\AST\Node\FieldNode;
13
use Digia\GraphQL\Language\AST\Node\FragmentDefinitionNode;
14
use Digia\GraphQL\Language\AST\Node\FragmentSpreadNode;
15
use Digia\GraphQL\Language\AST\Node\InlineFragmentNode;
16
use Digia\GraphQL\Language\AST\Node\InputObjectTypeDefinitionNode;
17
use Digia\GraphQL\Language\AST\Node\InputObjectTypeExtensionNode;
18
use Digia\GraphQL\Language\AST\Node\InputValueDefinitionNode;
19
use Digia\GraphQL\Language\AST\Node\InterfaceTypeDefinitionNode;
20
use Digia\GraphQL\Language\AST\Node\InterfaceTypeExtensionNode;
21
use Digia\GraphQL\Language\AST\Node\NodeInterface;
22
use Digia\GraphQL\Language\AST\Node\ObjectTypeDefinitionNode;
23
use Digia\GraphQL\Language\AST\Node\ObjectTypeExtensionNode;
24
use Digia\GraphQL\Language\AST\Node\OperationDefinitionNode;
25
use Digia\GraphQL\Language\AST\Node\ScalarTypeDefinitionNode;
26
use Digia\GraphQL\Language\AST\Node\ScalarTypeExtensionNode;
27
use Digia\GraphQL\Language\AST\Node\SchemaDefinitionNode;
28
use Digia\GraphQL\Language\AST\Node\UnionTypeDefinitionNode;
29
use Digia\GraphQL\Language\AST\Node\UnionTypeExtensionNode;
30
use Digia\GraphQL\Language\AST\Visitor\AcceptVisitorTrait;
31
use Digia\GraphQL\Type\Definition\Directive;
32
use function Digia\GraphQL\Util\find;
33
34
class KnownDirectivesRule extends AbstractRule
35
{
36
    /**
37
     * @inheritdoc
38
     */
39
    public function enterNode(NodeInterface $node): ?NodeInterface
40
    {
41
        if ($node instanceof DirectiveNode) {
42
            /** @var Directive $directiveDefinition */
43
            $directiveDefinition = find(
44
                $this->context->getSchema()->getDirectives(),
45
                function (Directive $definition) use ($node) {
46
                    return $definition->getName() === $node->getNameValue();
47
                }
48
            );
49
50
            if (null == $directiveDefinition) {
51
                $this->context->reportError(
52
                    new ValidationException(unknownDirectiveMessage((string)$node), [$node])
53
                );
54
55
                return $node;
56
            }
57
58
            $location = getDirectiveLocationFromASTPath($node);
59
60
            if (null !== $location && !in_array($location, $directiveDefinition->getLocations())) {
61
                $this->context->reportError(
62
                    new ValidationException(misplacedDirectiveMessage((string)$node, $location), [$node])
63
                );
64
            }
65
        }
66
67
        return $node;
68
    }
69
}
70
71
/**
72
 * @param NodeInterface|AcceptVisitorTrait $node
73
 * @return string
74
 */
75
function getDirectiveLocationFromASTPath(NodeInterface $node): string
76
{
77
    /** @var NodeInterface $appliedTo */
78
    $appliedTo = $node->getAncestor();
0 ignored issues
show
Bug introduced by
The method getAncestor() does not exist on Digia\GraphQL\Language\AST\Node\NodeInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...eExtensionNodeInterface or Digia\GraphQL\Language\AST\Node\ValueNodeInterface or Digia\GraphQL\Language\A...\SelectionNodeInterface or Digia\GraphQL\Language\AST\Node\TypeNodeInterface or Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...DefinitionNodeInterface or Digia\GraphQL\Language\A...DefinitionNodeInterface. 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

78
    /** @scrutinizer ignore-call */ 
79
    $appliedTo = $node->getAncestor();
Loading history...
79
80
    if ($appliedTo instanceof OperationDefinitionNode) {
81
        switch ($appliedTo->getOperation()) {
82
            case 'query':
83
                return DirectiveLocationEnum::QUERY;
84
            case 'mutation':
85
                return DirectiveLocationEnum::MUTATION;
86
            case 'subscription':
87
                return DirectiveLocationEnum::SUBSCRIPTION;
88
            default:
89
                // TODO: Throw appropriate exception.
90
        }
91
    }
92
    if ($appliedTo instanceof FieldNode) {
93
        return DirectiveLocationEnum::FIELD;
94
    }
95
    if ($appliedTo instanceof FragmentSpreadNode) {
96
        return DirectiveLocationEnum::FRAGMENT_SPREAD;
97
    }
98
    if ($appliedTo instanceof InlineFragmentNode) {
99
        return DirectiveLocationEnum::INLINE_FRAGMENT;
100
    }
101
    if ($appliedTo instanceof FragmentDefinitionNode) {
102
        return DirectiveLocationEnum::FRAGMENT_DEFINITION;
103
    }
104
    if ($appliedTo instanceof SchemaDefinitionNode) {
105
        return DirectiveLocationEnum::SCHEMA;
106
    }
107
    if ($appliedTo instanceof ScalarTypeDefinitionNode || $appliedTo instanceof ScalarTypeExtensionNode) {
108
        return DirectiveLocationEnum::SCALAR;
109
    }
110
    if ($appliedTo instanceof ObjectTypeDefinitionNode || $appliedTo instanceof ObjectTypeExtensionNode) {
111
        return DirectiveLocationEnum::OBJECT;
112
    }
113
    if ($appliedTo instanceof FieldDefinitionNode) {
114
        return DirectiveLocationEnum::FIELD_DEFINITION;
115
    }
116
    if ($appliedTo instanceof InterfaceTypeDefinitionNode || $appliedTo instanceof InterfaceTypeExtensionNode) {
117
        return DirectiveLocationEnum::INTERFACE;
118
    }
119
    if ($appliedTo instanceof UnionTypeDefinitionNode || $appliedTo instanceof UnionTypeExtensionNode) {
120
        return DirectiveLocationEnum::UNION;
121
    }
122
    if ($appliedTo instanceof EnumTypeDefinitionNode || $appliedTo instanceof EnumTypeExtensionNode) {
123
        return DirectiveLocationEnum::ENUM;
124
    }
125
    if ($appliedTo instanceof EnumValueDefinitionNode) {
126
        return DirectiveLocationEnum::ENUM_VALUE;
127
    }
128
    if ($appliedTo instanceof InputObjectTypeDefinitionNode || $appliedTo instanceof InputObjectTypeExtensionNode) {
129
        return DirectiveLocationEnum::INPUT_OBJECT;
130
    }
131
    if ($appliedTo instanceof InputValueDefinitionNode) {
132
        $parentNode = $node->getAncestor(2);
133
        return $parentNode instanceof InputObjectTypeDefinitionNode
134
            ? DirectiveLocationEnum::INPUT_FIELD_DEFINITION
135
            : DirectiveLocationEnum::ARGUMENT_DEFINITION;
136
    }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 131 is false. This is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
137
138
    // TODO: Throw appropriate exception.
139
}
140