Completed
Pull Request — master (#188)
by Sebastian
06:47 queued 27s
created

Lexer::getReader()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.2
c 0
b 0
f 0
cc 4
eloc 4
nc 3
nop 2
1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\LanguageException;
6
use Digia\GraphQL\Error\SyntaxErrorException;
7
use Digia\GraphQL\Language\Reader\ReaderInterface;
0 ignored issues
show
Bug introduced by
The type Digia\GraphQL\Language\Reader\ReaderInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
9
class Lexer implements LexerInterface
10
{
11
12
    /**
13
     * @var Source|null
14
     */
15
    protected $source;
16
17
    /**
18
     * @var array
19
     */
20
    protected $options = [];
21
22
    /**
23
     * @var array|ReaderInterface[]
24
     */
25
    protected $readers;
26
27
    /**
28
     * The previously focused non-ignored token.
29
     *
30
     * @var Token
31
     */
32
    protected $lastToken;
33
34
    /**
35
     * The currently focused non-ignored token.
36
     *
37
     * @var Token
38
     */
39
    protected $token;
40
41
    /**
42
     * The (1-indexed) line containing the current token.
43
     *
44
     * @var int
45
     */
46
    protected $line;
47
48
    /**
49
     * The character offset at which the current line begins.
50
     *
51
     * @var int
52
     */
53
    protected $lineStart;
54
55
    /**
56
     * The token reader.
57
     *
58
     * @var \Digia\GraphQL\Language\TokenReaderInterface
59
     */
60
    protected $reader;
61
62
    /**
63
     * Lexer constructor.
64
     *
65
     * @param \Digia\GraphQL\Language\TokenReaderInterface $reader
66
     */
67
    public function __construct(TokenReaderInterface $reader)
68
    {
69
        $startOfFileToken = new Token(TokenKindEnum::SOF);
70
71
        $this->reader    = $reader;
72
        $this->reader->setLexer($this);
73
74
        $this->lastToken = $startOfFileToken;
75
        $this->token     = $startOfFileToken;
76
        $this->line      = 1;
77
        $this->lineStart = 0;
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    public function advance(): Token
84
    {
85
        $this->lastToken = $this->token;
86
87
        return $this->token = $this->lookahead();
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public function lookahead(): Token
94
    {
95
        $token = $this->token;
96
97
        if (TokenKindEnum::EOF !== $token->getKind()) {
98
            do {
99
                $next = $this->readToken($token);
100
                $token->setNext($next);
101
                $token = $next;
102
            } while (TokenKindEnum::COMMENT === $token->getKind());
103
        }
104
105
        return $token;
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function getOption(string $name, $default = null)
112
    {
113
        return $this->options[$name] ?? $default;
114
    }
115
116
    /**
117
     * @inheritdoc
118
     */
119
    public function getBody(): string
120
    {
121
        return $this->getSource()->getBody();
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function getTokenKind(): string
128
    {
129
        return $this->token->getKind();
130
    }
131
132
    /**
133
     * @inheritdoc
134
     */
135
    public function getTokenValue(): ?string
136
    {
137
        return $this->token->getValue();
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function getToken(): Token
144
    {
145
        return $this->token;
146
    }
147
148
    /**
149
     * @inheritdoc
150
     */
151
    public function getSource(): Source
152
    {
153
        if ($this->source instanceof Source) {
154
            return $this->source;
155
        }
156
157
        throw new LanguageException('No source has been set.');
158
    }
159
160
    /**
161
     * @inheritdoc
162
     */
163
    public function getLastToken(): Token
164
    {
165
        return $this->lastToken;
166
    }
167
168
    /**
169
     * @inheritdoc
170
     */
171
    public function setSource(Source $source)
172
    {
173
        $this->source = $source;
174
        return $this;
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180
    public function setOptions(array $options)
181
    {
182
        $this->options = $options;
183
        return $this;
184
    }
185
186
    /**
187
     * @param int   $code
188
     * @param int   $pos
189
     * @param int   $line
190
     * @param int   $col
191
     * @param Token $prev
192
     * @return Token
193
     * @throws SyntaxErrorException
194
     */
195
    public function read(int $code, int $pos, int $line, int $col, Token $prev): Token
196
    {
197
        if ($token = $this->reader->read($code, $pos, $line, $col, $prev)) {
198
          return $token;
199
        }
200
201
        throw new SyntaxErrorException($this->source, $pos, $this->unexpectedCharacterMessage($code));
0 ignored issues
show
Bug introduced by
It seems like $this->source can also be of type null; however, parameter $source of Digia\GraphQL\Error\Synt...xception::__construct() does only seem to accept Digia\GraphQL\Language\Source, 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

201
        throw new SyntaxErrorException(/** @scrutinizer ignore-type */ $this->source, $pos, $this->unexpectedCharacterMessage($code));
Loading history...
202
    }
203
204
    /**
205
     * @param Token $prev
206
     * @return Token
207
     * @throws SyntaxErrorException
208
     */
209
    protected function readToken(Token $prev): Token
210
    {
211
        $body       = $this->source->getBody();
0 ignored issues
show
Bug introduced by
The method getBody() does not exist on null. ( Ignorable by Annotation )

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

211
        /** @scrutinizer ignore-call */ 
212
        $body       = $this->source->getBody();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
212
        $bodyLength = mb_strlen($body);
213
214
        $pos  = $this->positionAfterWhitespace($body, $prev->getEnd());
215
        $line = $this->line;
216
        $col  = 1 + $pos - $this->lineStart;
217
218
        if ($pos >= $bodyLength) {
219
            return new Token(TokenKindEnum::EOF, $bodyLength, $bodyLength, $line, $col, $prev);
220
        }
221
222
        $code = charCodeAt($body, $pos);
223
224
        if (isSourceCharacter($code)) {
225
            throw new SyntaxErrorException(
226
                $this->source,
0 ignored issues
show
Bug introduced by
It seems like $this->source can also be of type null; however, parameter $source of Digia\GraphQL\Error\Synt...xception::__construct() does only seem to accept Digia\GraphQL\Language\Source, 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
                /** @scrutinizer ignore-type */ $this->source,
Loading history...
227
                $pos,
228
                sprintf('Cannot contain the invalid character %s', printCharCode($code))
229
            );
230
        }
231
232
        return $this->read($code, $pos, $line, $col, $prev);
233
    }
234
235
    /**
236
     * @param int $code
237
     * @return string
238
     */
239
    protected function unexpectedCharacterMessage(int $code): string
240
    {
241
        if ($code === 39) {
242
            // '
243
            return 'Unexpected single quote character (\'), did you mean to use a double quote (")?';
244
        }
245
246
        return sprintf('Cannot parse the unexpected character %s', printCharCode($code));
247
    }
248
249
    /**
250
     * @param string $body
251
     * @param int    $startPosition
252
     * @return int
253
     */
254
    protected function positionAfterWhitespace(string $body, int $startPosition): int
255
    {
256
        $bodyLength = mb_strlen($body);
257
        $pos        = $startPosition;
258
259
        while ($pos < $bodyLength) {
260
            $code = charCodeAt($body, $pos);
261
262
            if ($code === 9 || $code === 32 || $code === 44 || $code === 0xfeff) {
263
                // tab | space | comma | BOM
264
                ++$pos;
265
            } elseif ($code === 10) {
266
                // new line
267
                ++$pos;
268
                $this->advanceLine($pos);
269
            } elseif ($code === 13) {
270
                // carriage return
271
                if (charCodeAt($body, $pos + 1) === 10) {
272
                    $pos += 2;
273
                } else {
274
                    ++$pos;
275
                }
276
                $this->advanceLine($pos);
277
            } else {
278
                break;
279
            }
280
        }
281
282
        return $pos;
283
    }
284
285
    /**
286
     * @param int $pos
287
     */
288
    protected function advanceLine(int $pos)
289
    {
290
        ++$this->line;
291
        $this->lineStart = $pos;
292
    }
293
}
294