Passed
Pull Request — master (#196)
by Christoffer
02:22
created

Lexer::skipDigits()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 2
nop 1
1
<?php
2
3
namespace Digia\GraphQL\Language;
4
5
use Digia\GraphQL\Error\LanguageException;
6
use Digia\GraphQL\Error\SyntaxErrorException;
7
8
class Lexer implements LexerInterface
9
{
10
    /**
11
     * A map between character code and the corresponding token kind.
12
     *
13
     * @var array
14
     */
15
    protected static $codeTokenKindMap = [
16
        33  => TokenKindEnum::BANG,
17
        36  => TokenKindEnum::DOLLAR,
18
        38  => TokenKindEnum::AMP,
19
        58  => TokenKindEnum::COLON,
20
        61  => TokenKindEnum::EQUALS,
21
        64  => TokenKindEnum::AT,
22
        124 => TokenKindEnum::PIPE,
23
        40  => TokenKindEnum::PAREN_L,
24
        41  => TokenKindEnum::PAREN_R,
25
        91  => TokenKindEnum::BRACKET_L,
26
        93  => TokenKindEnum::BRACKET_R,
27
        123 => TokenKindEnum::BRACE_L,
28
        125 => TokenKindEnum::BRACE_R,
29
    ];
30
31
    /**
32
     * TODO: Document this
33
     *
34
     * @var Source|null
35
     */
36
    protected $source;
37
38
    /**
39
     * TODO: Document this
40
     *
41
     * @var string|null
42
     */
43
    protected $body;
44
45
    /**
46
     * TODO: Document this
47
     *
48
     * @var int
49
     */
50
    protected $bodyLength;
51
52
    /**
53
     * @var array
54
     */
55
    protected $options = [];
56
57
    /**
58
     * The previously focused non-ignored token.
59
     *
60
     * @var Token
61
     */
62
    protected $lastToken;
63
64
    /**
65
     * The currently focused non-ignored token.
66
     *
67
     * @var Token
68
     */
69
    protected $token;
70
71
    /**
72
     * TODO: Document this
73
     *
74
     * @var int
75
     */
76
    protected $pos;
77
78
    /**
79
     * The (1-indexed) line containing the current token.
80
     *
81
     * @var int
82
     */
83
    protected $line;
84
85
    /**
86
     * The character offset at which the current line begins.
87
     *
88
     * @var int
89
     */
90
    protected $lineStart;
91
92
    /**
93
     * Lexer constructor.
94
     *
95
     * @param TokenReaderInterface $reader
96
     */
97
    public function __construct()
98
    {
99
        $startOfFileToken = new Token(TokenKindEnum::SOF);
100
101
        $this->lastToken = $startOfFileToken;
102
        $this->token     = $startOfFileToken;
103
        $this->line      = 1;
104
        $this->lineStart = 0;
105
    }
106
107
    /**
108
     * @inheritdoc
109
     * @throws SyntaxErrorException
110
     */
111
    public function advance(): Token
112
    {
113
        $this->lastToken = $this->token;
114
        return $this->token = $this->lookahead();
115
    }
116
117
    /**
118
     * @inheritdoc
119
     * @throws SyntaxErrorException
120
     */
121
    public function lookahead(): Token
122
    {
123
        $token = $this->token;
124
125
        if (TokenKindEnum::EOF !== $token->getKind()) {
126
            do {
127
                $next = $this->readToken($token);
128
                $token->setNext($next);
129
                $token = $next;
130
            } while (TokenKindEnum::COMMENT === $token->getKind());
131
        }
132
133
        return $token;
134
    }
135
136
    /**
137
     * @inheritdoc
138
     */
139
    public function getOption(string $name, $default = null)
140
    {
141
        return $this->options[$name] ?? $default;
142
    }
143
144
    /**
145
     * @inheritdoc
146
     * @throws LanguageException
147
     */
148
    public function getBody(): string
149
    {
150
        return $this->getSource()->getBody();
151
    }
152
153
    /**
154
     * @inheritdoc
155
     */
156
    public function getTokenKind(): string
157
    {
158
        return $this->token->getKind();
159
    }
160
161
    /**
162
     * @inheritdoc
163
     */
164
    public function getTokenValue(): ?string
165
    {
166
        return $this->token->getValue();
167
    }
168
169
    /**
170
     * @inheritdoc
171
     */
172
    public function getToken(): Token
173
    {
174
        return $this->token;
175
    }
176
177
    /**
178
     * @inheritdoc
179
     * @throws LanguageException
180
     */
181
    public function getSource(): Source
182
    {
183
        if ($this->source instanceof Source) {
184
            return $this->source;
185
        }
186
187
        throw new LanguageException('No source has been set.');
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193
    public function getLastToken(): Token
194
    {
195
        return $this->lastToken;
196
    }
197
198
    /**
199
     * @inheritdoc
200
     */
201
    public function setSource(Source $source)
202
    {
203
        $this->body       = $source->getBody();
204
        $this->bodyLength = \strlen($this->body);
205
        $this->source     = $source;
206
        return $this;
207
    }
208
209
    /**
210
     * @inheritdoc
211
     */
212
    public function setOptions(array $options)
213
    {
214
        $this->options = $options;
215
        return $this;
216
    }
217
218
    /**
219
     * @param Token $prev
220
     * @return Token
221
     * @throws SyntaxErrorException
222
     */
223
    protected function readToken(Token $prev): Token
224
    {
225
        $this->pos = $prev->getEnd();
226
227
        $this->skipWhitespace();
228
229
        $line = $this->line;
230
        $col  = 1 + $this->pos - $this->lineStart;
231
232
        if ($this->pos >= $this->bodyLength) {
233
            return $this->createEndOfFileToken($line, $col, $prev);
234
        }
235
236
        $code = charCodeAt($this->body, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

236
        $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos);
Loading history...
237
238
        // Punctuation: [!$&:=@|()\[\]{}]{1}
239
        if (33 === $code || 36 === $code || 38 === $code || 58 === $code || 61 === $code || 64 === $code || 124 === $code ||
240
            40 === $code || 41 === $code || 91 === $code || 93 === $code || 123 === $code || 125 === $code) {
241
            return $this->lexPunctuation($code, $line, $col, $prev);
242
        }
243
244
        // Comment: #[\u0009\u0020-\uFFFF]*
245
        if (35 === $code) {
246
            return $this->lexComment($line, $col, $prev);
247
        }
248
249
        // Int:   -?(0|[1-9][0-9]*)
250
        // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
251
        if (45 === $code || isNumber($code)) {
252
            return $this->lexNumber($code, $line, $col, $prev);
253
        }
254
255
        // Name: [_A-Za-z][_0-9A-Za-z]*
256
        if (isAlphaNumeric($code)) {
257
            return $this->lexName($line, $col, $prev);
258
        }
259
260
        // Spread: ...
261
        if ($this->bodyLength >= 3 && isSpread($this->body, $code, $this->pos)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $body of Digia\GraphQL\Language\isSpread() does only seem to accept string, 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

261
        if ($this->bodyLength >= 3 && isSpread(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
262
            return $this->lexSpread($line, $col, $prev);
263
        }
264
265
        // String: "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
266
        if (isString($this->body, $code, $this->pos)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $body of Digia\GraphQL\Language\isString() does only seem to accept string, 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

266
        if (isString(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
267
            return $this->lexString($line, $col, $prev);
268
        }
269
270
        // Block String: """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
271
        if ($this->bodyLength >= 3 && isTripleQuote($this->body, $code, $this->pos)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $body of Digia\GraphQL\Language\isTripleQuote() does only seem to accept string, 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

271
        if ($this->bodyLength >= 3 && isTripleQuote(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
272
            return $this->lexBlockString($line, $col, $prev);
273
        }
274
275
        throw $this->createSyntaxErrorException();
276
    }
277
278
    /**
279
     * Creates an End Of File (EOF) token.
280
     *
281
     * @param int   $line
282
     * @param int   $col
283
     * @param Token $prev
284
     * @return Token
285
     */
286
    protected function createEndOfFileToken(int $line, int $col, Token $prev): Token
287
    {
288
        return new Token(TokenKindEnum::EOF, $this->bodyLength, $this->bodyLength, $line, $col, $prev);
289
    }
290
291
    /**
292
     * Reads a punctuation token from the source file.
293
     *
294
     * @param int   $code
295
     * @param int   $line
296
     * @param int   $col
297
     * @param Token $prev
298
     * @return Token
299
     * @throws SyntaxErrorException
300
     */
301
    protected function lexPunctuation(int $code, int $line, int $col, Token $prev): ?Token
302
    {
303
        if (!isset(self::$codeTokenKindMap[$code])) {
304
            throw $this->createSyntaxErrorException();
305
        }
306
307
        return new Token(self::$codeTokenKindMap[$code], $this->pos, $this->pos + 1, $line, $col, $prev);
308
    }
309
310
    /**
311
     * Reads a name token from the source file.
312
     *
313
     * @param int   $line
314
     * @param int   $col
315
     * @param Token $prev
316
     * @return Token
317
     */
318
    protected function lexName(int $line, int $col, Token $prev): Token
319
    {
320
        $start     = $this->pos;
321
        $this->pos = $start + 1;
322
323
        while ($this->pos !== $this->bodyLength &&
324
            ($code = charCodeAt($this->body, $this->pos)) !== null &&
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

324
            ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null &&
Loading history...
325
            isAlphaNumeric($code)) {
326
            ++$this->pos;
327
        }
328
329
        $value = sliceString($this->body, $start, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\sliceString() does only seem to accept string, 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

329
        $value = sliceString(/** @scrutinizer ignore-type */ $this->body, $start, $this->pos);
Loading history...
330
331
        return new Token(TokenKindEnum::NAME, $start, $this->pos, $line, $col, $prev, $value);
332
    }
333
334
    /**
335
     * Reads a number (int/float) token from the source file.
336
     *
337
     * @param int   $code
338
     * @param int   $line
339
     * @param int   $col
340
     * @param Token $prev
341
     * @return Token
342
     * @throws SyntaxErrorException
343
     */
344
    protected function lexNumber(int $code, int $line, int $col, Token $prev): Token
345
    {
346
        $start   = $this->pos;
347
        $isFloat = false;
348
349
        if (45 === $code) {
350
            // -
351
            $code = charCodeAt($this->body, ++$this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

351
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
352
        }
353
354
        if (48 === $code) {
355
            // 0
356
            $code = charCodeAt($this->body, ++$this->pos);
357
358
            if (isNumber($code)) {
359
                throw $this->createSyntaxErrorException(
360
                    \sprintf('Invalid number, unexpected digit after 0: %s.', printCharCode($code))
361
                );
362
            }
363
        } else {
364
            $this->skipDigits($code);
365
            $code = charCodeAt($this->body, $this->pos);
366
        }
367
368
        if (46 === $code) {
369
            // .
370
            $isFloat = true;
371
            $code    = charCodeAt($this->body, ++$this->pos);
372
373
            $this->skipDigits($code);
374
375
            $code = charCodeAt($this->body, $this->pos);
376
        }
377
378
        if (69 === $code || 101 === $code) {
379
            // e or E
380
            $isFloat = true;
381
            $code    = charCodeAt($this->body, ++$this->pos);
382
383
            if (43 === $code || 45 === $code) {
384
                // + or -
385
                $code = charCodeAt($this->body, ++$this->pos);
386
            }
387
388
            $this->skipDigits($code);
389
        }
390
391
        return new Token(
392
            $isFloat ? TokenKindEnum::FLOAT : TokenKindEnum::INT,
393
            $start,
394
            $this->pos,
395
            $line,
396
            $col,
397
            $prev,
398
            sliceString($this->body, $start, $this->pos)
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\sliceString() does only seem to accept string, 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

398
            sliceString(/** @scrutinizer ignore-type */ $this->body, $start, $this->pos)
Loading history...
399
        );
400
    }
401
402
    /**
403
     * Reads digits from the source file.
404
     *
405
     * @param int $code
406
     * @throws SyntaxErrorException
407
     */
408
    protected function skipDigits(int $code): void
409
    {
410
        if (isNumber($code)) {
411
            do {
412
                $code = charCodeAt($this->body, ++$this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

412
                $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
413
            } while (isNumber($code));
414
415
            return;
416
        }
417
418
        throw $this->createSyntaxErrorException(
419
            \sprintf('Invalid number, expected digit but got: %s.', printCharCode($code))
420
        );
421
    }
422
423
    /**
424
     * Reads a comment token from the source file.
425
     *
426
     * @param int   $line
427
     * @param int   $col
428
     * @param Token $prev
429
     * @return Token
430
     */
431
    protected function lexComment(int $line, int $col, Token $prev): Token
432
    {
433
        $start = $this->pos;
434
435
        do {
436
            $code = charCodeAt($this->body, ++$this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

436
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
437
        } while ($code !== null && ($code > 0x001f || 0x0009 === $code)); // SourceCharacter but not LineTerminator
438
439
        return new Token(
440
            TokenKindEnum::COMMENT,
441
            $start,
442
            $this->pos,
443
            $line,
444
            $col,
445
            $prev,
446
            sliceString($this->body, $start + 1, $this->pos)
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\sliceString() does only seem to accept string, 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

446
            sliceString(/** @scrutinizer ignore-type */ $this->body, $start + 1, $this->pos)
Loading history...
447
        );
448
    }
449
450
    /**
451
     * Reads a spread token from the source.
452
     *
453
     * @param int   $line
454
     * @param int   $col
455
     * @param Token $prev
456
     * @return Token
457
     */
458
    protected function lexSpread(int $line, int $col, Token $prev): Token
459
    {
460
        return new Token(TokenKindEnum::SPREAD, $this->pos, $this->pos + 3, $line, $col, $prev);
461
    }
462
463
    /**
464
     * @param int   $line
465
     * @param int   $col
466
     * @param Token $prev
467
     * @return Token
468
     * @throws SyntaxErrorException
469
     */
470
    protected function lexString(int $line, int $col, Token $prev): Token
471
    {
472
        $start      = $this->pos;
473
        $chunkStart = ++$this->pos;
474
        $value      = '';
475
476
        while ($this->pos < $this->bodyLength &&
477
            ($code = charCodeAt($this->body, $this->pos)) !== null && !isLineTerminator($code)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

477
            ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null && !isLineTerminator($code)) {
Loading history...
478
            // Closing Quote (")
479
            if (34 === $code) {
480
                $value .= sliceString($this->body, $chunkStart, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\sliceString() does only seem to accept string, 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

480
                $value .= sliceString(/** @scrutinizer ignore-type */ $this->body, $chunkStart, $this->pos);
Loading history...
481
                return new Token(TokenKindEnum::STRING, $start, $this->pos + 1, $line, $col, $prev, $value);
482
            }
483
484
            if (isSourceCharacter($code)) {
485
                throw $this->createSyntaxErrorException(
486
                    \sprintf('Invalid character within String: %s.', printCharCode($code))
487
                );
488
            }
489
490
            ++$this->pos;
491
492
            if (92 === $code) {
493
                // \
494
                $value .= sliceString($this->body, $chunkStart, $this->pos - 1);
495
496
                $code = charCodeAt($this->body, $this->pos);
497
498
                switch ($code) {
499
                    case 34: // "
500
                        $value .= '"';
501
                        break;
502
                    case 47: // /
503
                        $value .= '/';
504
                        break;
505
                    case 92: // \
506
                        $value .= '\\';
507
                        break;
508
                    case 98: // b
509
                        $value .= '\b';
510
                        break;
511
                    case 102: // f
512
                        $value .= '\f';
513
                        break;
514
                    case 110: // n
515
                        $value .= '\n';
516
                        break;
517
                    case 114: // r
518
                        $value .= '\r';
519
                        break;
520
                    case 116: // t
521
                        $value .= '\t';
522
                        break;
523
                    case 117: // u
524
                        $unicodeString = sliceString($this->body, $this->pos + 1, $this->pos + 5);
525
526
                        if (!\preg_match('/[0-9A-Fa-f]{4}/', $unicodeString)) {
527
                            throw $this->createSyntaxErrorException(
528
                                \sprintf('Invalid character escape sequence: \\u%s.', $unicodeString)
529
                            );
530
                        }
531
532
                        $value     .= '\\u' . $unicodeString;
533
                        $this->pos += 4;
534
535
                        break;
536
                    default:
537
                        throw $this->createSyntaxErrorException(
538
                            \sprintf('Invalid character escape sequence: \\%s.', \chr($code))
539
                        );
540
                }
541
542
                ++$this->pos;
543
544
                $chunkStart = $this->pos;
545
            }
546
        }
547
548
        throw $this->createSyntaxErrorException('Unterminated string.');
549
    }
550
551
    /**
552
     * @param int   $line
553
     * @param int   $col
554
     * @param Token $prev
555
     * @return Token
556
     * @throws SyntaxErrorException
557
     */
558
    protected function lexBlockString(int $line, int $col, Token $prev): Token
559
    {
560
        $start      = $this->pos;
561
        $this->pos  = $start + 3;
562
        $chunkStart = $this->pos;
563
        $rawValue   = '';
564
565
        while ($this->pos < $this->bodyLength && ($code = charCodeAt($this->body, $this->pos)) !== null) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

565
        while ($this->pos < $this->bodyLength && ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null) {
Loading history...
566
            // Closing Triple-Quote (""")
567
            if (isTripleQuote($this->body, $code, $this->pos)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $body of Digia\GraphQL\Language\isTripleQuote() does only seem to accept string, 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

567
            if (isTripleQuote(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
568
                $rawValue .= sliceString($this->body, $chunkStart, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\sliceString() does only seem to accept string, 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

568
                $rawValue .= sliceString(/** @scrutinizer ignore-type */ $this->body, $chunkStart, $this->pos);
Loading history...
569
                return new Token(
570
                    TokenKindEnum::BLOCK_STRING,
571
                    $start,
572
                    $this->pos + 3,
573
                    $line,
574
                    $col,
575
                    $prev,
576
                    blockStringValue($rawValue)
577
                );
578
            }
579
580
            if (isSourceCharacter($code) && !isLineTerminator($code)) {
581
                throw $this->createSyntaxErrorException(
582
                    \sprintf('Invalid character within String: %s.', printCharCode($code))
583
                );
584
            }
585
586
            if (isEscapedTripleQuote($this->body, $code, $this->pos)) {
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $body of Digia\GraphQL\Language\isEscapedTripleQuote() does only seem to accept string, 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

586
            if (isEscapedTripleQuote(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
587
                $rawValue   .= sliceString($this->body, $chunkStart, $this->pos) . '"""';
588
                $this->pos  += 4;
589
                $chunkStart = $this->pos;
590
            } else {
591
                ++$this->pos;
592
            }
593
        }
594
595
        throw $this->createSyntaxErrorException('Unterminated string.');
596
    }
597
598
    /**
599
     * Skips all whitespace after current position.
600
     */
601
    protected function skipWhitespace(): void
602
    {
603
        while ($this->pos < $this->bodyLength) {
604
            $code = charCodeAt($this->body, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

604
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos);
Loading history...
605
606
            if (9 === $code || 32 === $code || 44 === $code || 0xfeff === $code) {
607
                // tab | space | comma | BOM
608
                ++$this->pos;
609
            } elseif (10 === $code) {
610
                // new line (\n)
611
                ++$this->pos;
612
                ++$this->line;
613
                $this->lineStart = $this->pos;
614
            } elseif (13 === $code) {
615
                // carriage return (\r)
616
                if (10 === charCodeAt($this->body, $this->pos + 1)) {
617
                    // carriage return and new line (\r\n)
618
                    $this->pos += 2;
619
                } else {
620
                    ++$this->pos;
621
                }
622
                ++$this->line;
623
                $this->lineStart = $this->pos;
624
            } else {
625
                break;
626
            }
627
        }
628
    }
629
630
    /**
631
     * Creates a `SyntaxErrorException` for the current position in the source.
632
     *
633
     * @param null|string $description
634
     * @return SyntaxErrorException
635
     */
636
    protected function createSyntaxErrorException(?string $description = null): SyntaxErrorException
637
    {
638
        $code = charCodeAt($this->body, $this->pos);
0 ignored issues
show
Bug introduced by
It seems like $this->body can also be of type null; however, parameter $string of Digia\GraphQL\Language\charCodeAt() does only seem to accept string, 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

638
        $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos);
Loading history...
639
        return new SyntaxErrorException(
640
            $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

640
            /** @scrutinizer ignore-type */ $this->source,
Loading history...
641
            $this->pos,
642
            $description ?? $this->unexpectedCharacterMessage($code)
643
        );
644
    }
645
646
    /**
647
     * Report a message that an unexpected character was encountered.
648
     *
649
     * @param int $code
650
     * @return string
651
     */
652
    protected function unexpectedCharacterMessage(int $code): string
653
    {
654
        if (isSourceCharacter($code) && !isLineTerminator($code)) {
655
            return \sprintf('Cannot contain the invalid character %s.', printCharCode($code));
656
        }
657
658
        if ($code === 39) {
659
            // '
660
            return 'Unexpected single quote character (\'), did you mean to use a double quote (")?';
661
        }
662
663
        return \sprintf('Cannot parse the unexpected character %s.', printCharCode($code));
664
    }
665
}
666