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

Lexer::createEndOfFileToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 3
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
     * The source file for this lexer.
33
     *
34
     * @var Source|null
35
     */
36
    protected $source;
37
38
    /**
39
     * The contents of the source file.
40
     *
41
     * @var string|null
42
     */
43
    protected $body;
44
45
    /**
46
     * The total number of characters in the source file.
47
     *
48
     * @var int
49
     */
50
    protected $bodyLength;
51
52
    /**
53
     * The options for this lexer.
54
     *
55
     * @var array
56
     */
57
    protected $options = [];
58
59
    /**
60
     * The previously focused non-ignored token.
61
     *
62
     * @var Token
63
     */
64
    protected $lastToken;
65
66
    /**
67
     * The currently focused non-ignored token.
68
     *
69
     * @var Token
70
     */
71
    protected $token;
72
73
    /**
74
     * The current position.
75
     *
76
     * @var int
77
     */
78
    protected $pos;
79
80
    /**
81
     * The (1-indexed) line containing the current token.
82
     *
83
     * @var int
84
     */
85
    protected $line;
86
87
    /**
88
     * The character offset at which the current line begins.
89
     *
90
     * @var int
91
     */
92
    protected $lineStart;
93
94
    /**
95
     * Lexer constructor.
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
     * Reads the token after the given token.
220
     *
221
     * @param Token $prev
222
     * @return Token
223
     * @throws SyntaxErrorException
224
     */
225
    protected function readToken(Token $prev): Token
226
    {
227
        $this->pos = $prev->getEnd();
228
229
        $this->skipWhitespace();
230
231
        $line = $this->line;
232
        $col  = 1 + $this->pos - $this->lineStart;
233
234
        if ($this->pos >= $this->bodyLength) {
235
            return $this->createEndOfFileToken($line, $col, $prev);
236
        }
237
238
        $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

238
        $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos);
Loading history...
239
240
        // Punctuation: [!$&:=@|()\[\]{}]{1}
241
        if (33 === $code || 36 === $code || 38 === $code || 58 === $code || 61 === $code || 64 === $code || 124 === $code ||
242
            40 === $code || 41 === $code || 91 === $code || 93 === $code || 123 === $code || 125 === $code) {
243
            return $this->lexPunctuation($code, $line, $col, $prev);
244
        }
245
246
        // Comment: #[\u0009\u0020-\uFFFF]*
247
        if (35 === $code) {
248
            return $this->lexComment($line, $col, $prev);
249
        }
250
251
        // Int:   -?(0|[1-9][0-9]*)
252
        // Float: -?(0|[1-9][0-9]*)(\.[0-9]+)?((E|e)(+|-)?[0-9]+)?
253
        if (45 === $code || isNumber($code)) {
254
            return $this->lexNumber($code, $line, $col, $prev);
255
        }
256
257
        // Name: [_A-Za-z][_0-9A-Za-z]*
258
        if (isAlphaNumeric($code)) {
259
            return $this->lexName($line, $col, $prev);
260
        }
261
262
        // Spread: ...
263
        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

263
        if ($this->bodyLength >= 3 && isSpread(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
264
            return $this->lexSpread($line, $col, $prev);
265
        }
266
267
        // String: "([^"\\\u000A\u000D]|(\\(u[0-9a-fA-F]{4}|["\\/bfnrt])))*"
268
        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

268
        if (isString(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
269
            return $this->lexString($line, $col, $prev);
270
        }
271
272
        // Block String: """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
273
        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

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

326
            ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null &&
Loading history...
327
            isAlphaNumeric($code)) {
328
            ++$this->pos;
329
        }
330
331
        $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

331
        $value = sliceString(/** @scrutinizer ignore-type */ $this->body, $start, $this->pos);
Loading history...
332
333
        return new Token(TokenKindEnum::NAME, $start, $this->pos, $line, $col, $prev, $value);
334
    }
335
336
    /**
337
     * Reads a number (int or float) token from the source file.
338
     *
339
     * @param int   $code
340
     * @param int   $line
341
     * @param int   $col
342
     * @param Token $prev
343
     * @return Token
344
     * @throws SyntaxErrorException
345
     */
346
    protected function lexNumber(int $code, int $line, int $col, Token $prev): Token
347
    {
348
        $start   = $this->pos;
349
        $isFloat = false;
350
351
        if (45 === $code) {
352
            // -
353
            $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

353
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
354
        }
355
356
        if (48 === $code) {
357
            // 0
358
            $code = charCodeAt($this->body, ++$this->pos);
359
360
            if (isNumber($code)) {
361
                throw $this->createSyntaxErrorException(
362
                    \sprintf('Invalid number, unexpected digit after 0: %s.', printCharCode($code))
363
                );
364
            }
365
        } else {
366
            $this->skipDigits($code);
367
            $code = charCodeAt($this->body, $this->pos);
368
        }
369
370
        if (46 === $code) {
371
            // .
372
            $isFloat = true;
373
            $code    = charCodeAt($this->body, ++$this->pos);
374
375
            $this->skipDigits($code);
376
377
            $code = charCodeAt($this->body, $this->pos);
378
        }
379
380
        if (69 === $code || 101 === $code) {
381
            // e or E
382
            $isFloat = true;
383
            $code    = charCodeAt($this->body, ++$this->pos);
384
385
            if (43 === $code || 45 === $code) {
386
                // + or -
387
                $code = charCodeAt($this->body, ++$this->pos);
388
            }
389
390
            $this->skipDigits($code);
391
        }
392
393
        return new Token(
394
            $isFloat ? TokenKindEnum::FLOAT : TokenKindEnum::INT,
395
            $start,
396
            $this->pos,
397
            $line,
398
            $col,
399
            $prev,
400
            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

400
            sliceString(/** @scrutinizer ignore-type */ $this->body, $start, $this->pos)
Loading history...
401
        );
402
    }
403
404
    /**
405
     * Skips digits at the current position.
406
     *
407
     * @param int $code
408
     * @throws SyntaxErrorException
409
     */
410
    protected function skipDigits(int $code): void
411
    {
412
        if (isNumber($code)) {
413
            do {
414
                $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

414
                $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
415
            } while (isNumber($code));
416
417
            return;
418
        }
419
420
        throw $this->createSyntaxErrorException(
421
            \sprintf('Invalid number, expected digit but got: %s.', printCharCode($code))
422
        );
423
    }
424
425
    /**
426
     * Reads a comment token from the source file.
427
     *
428
     * @param int   $line
429
     * @param int   $col
430
     * @param Token $prev
431
     * @return Token
432
     */
433
    protected function lexComment(int $line, int $col, Token $prev): Token
434
    {
435
        $start = $this->pos;
436
437
        do {
438
            $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

438
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, ++$this->pos);
Loading history...
439
        } while ($code !== null && ($code > 0x001f || 0x0009 === $code)); // SourceCharacter but not LineTerminator
440
441
        return new Token(
442
            TokenKindEnum::COMMENT,
443
            $start,
444
            $this->pos,
445
            $line,
446
            $col,
447
            $prev,
448
            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

448
            sliceString(/** @scrutinizer ignore-type */ $this->body, $start + 1, $this->pos)
Loading history...
449
        );
450
    }
451
452
    /**
453
     * Reads a spread token from the source.
454
     *
455
     * @param int   $line
456
     * @param int   $col
457
     * @param Token $prev
458
     * @return Token
459
     */
460
    protected function lexSpread(int $line, int $col, Token $prev): Token
461
    {
462
        return new Token(TokenKindEnum::SPREAD, $this->pos, $this->pos + 3, $line, $col, $prev);
463
    }
464
465
    /**
466
     * Reads a string token from the source.
467
     *
468
     * @param int   $line
469
     * @param int   $col
470
     * @param Token $prev
471
     * @return Token
472
     * @throws SyntaxErrorException
473
     */
474
    protected function lexString(int $line, int $col, Token $prev): Token
475
    {
476
        $start      = $this->pos;
477
        $chunkStart = ++$this->pos;
478
        $value      = '';
479
480
        while ($this->pos < $this->bodyLength &&
481
            ($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

481
            ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null && !isLineTerminator($code)) {
Loading history...
482
            // Closing Quote (")
483
            if (34 === $code) {
484
                $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

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

571
        while ($this->pos < $this->bodyLength && ($code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos)) !== null) {
Loading history...
572
            // Closing Triple-Quote (""")
573
            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

573
            if (isTripleQuote(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
574
                $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

574
                $rawValue .= sliceString(/** @scrutinizer ignore-type */ $this->body, $chunkStart, $this->pos);
Loading history...
575
                return new Token(
576
                    TokenKindEnum::BLOCK_STRING,
577
                    $start,
578
                    $this->pos + 3,
579
                    $line,
580
                    $col,
581
                    $prev,
582
                    blockStringValue($rawValue)
583
                );
584
            }
585
586
            if (isSourceCharacter($code) && !isLineTerminator($code)) {
587
                throw $this->createSyntaxErrorException(
588
                    \sprintf('Invalid character within String: %s.', printCharCode($code))
589
                );
590
            }
591
592
            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

592
            if (isEscapedTripleQuote(/** @scrutinizer ignore-type */ $this->body, $code, $this->pos)) {
Loading history...
593
                $rawValue   .= sliceString($this->body, $chunkStart, $this->pos) . '"""';
594
                $this->pos  += 4;
595
                $chunkStart = $this->pos;
596
            } else {
597
                ++$this->pos;
598
            }
599
        }
600
601
        throw $this->createSyntaxErrorException('Unterminated string.');
602
    }
603
604
    /**
605
     * Skips whitespace at the current position.
606
     */
607
    protected function skipWhitespace(): void
608
    {
609
        while ($this->pos < $this->bodyLength) {
610
            $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

610
            $code = charCodeAt(/** @scrutinizer ignore-type */ $this->body, $this->pos);
Loading history...
611
612
            if (9 === $code || 32 === $code || 44 === $code || 0xfeff === $code) {
613
                // tab | space | comma | BOM
614
                ++$this->pos;
615
            } elseif (10 === $code) {
616
                // new line (\n)
617
                ++$this->pos;
618
                ++$this->line;
619
                $this->lineStart = $this->pos;
620
            } elseif (13 === $code) {
621
                // carriage return (\r)
622
                if (10 === charCodeAt($this->body, $this->pos + 1)) {
623
                    // carriage return and new line (\r\n)
624
                    $this->pos += 2;
625
                } else {
626
                    ++$this->pos;
627
                }
628
                ++$this->line;
629
                $this->lineStart = $this->pos;
630
            } else {
631
                break;
632
            }
633
        }
634
    }
635
636
    /**
637
     * Creates a `SyntaxErrorException` for the current position in the source.
638
     *
639
     * @param null|string $description
640
     * @return SyntaxErrorException
641
     */
642
    protected function createSyntaxErrorException(?string $description = null): SyntaxErrorException
643
    {
644
        $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

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

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