Completed
Push — master ( 144fee...5601ad )
by Colin
01:16
created

Json5Decoder   D

Complexity

Total Complexity 118

Size/Duplication

Total Lines 635
Duplicated Lines 5.83 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 86.18%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 37
loc 635
ccs 293
cts 340
cp 0.8618
rs 4.7299
wmc 118
lcom 1
cbo 1

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A decode() 0 15 3
A charAt() 0 8 3
B next() 0 26 6
A peek() 0 4 1
A getLineRemainder() 0 11 2
A match() 0 22 2
A identifier() 0 16 2
F number() 0 79 21
C string() 0 44 12
B inlineComment() 0 15 5
B blockComment() 20 20 5
A comment() 17 17 4
A white() 0 12 4
B word() 0 41 6
C arr() 0 39 7
C obj() 0 49 10
B value() 0 19 9
A throwSyntaxError() 0 4 1
A renderChar() 0 4 2
A fromCharCode() 0 4 1
B getEscapee() 0 18 11

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Json5Decoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Json5Decoder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the colinodell/json5 package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * Based on the official JSON5 implementation for JavaScript (https://github.com/json5/json5)
9
 *  - (c) 2012-2016 Aseem Kishore and others (https://github.com/json5/json5/contributors)
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace ColinODell\Json5;
16
17
final class Json5Decoder
18
{
19
    const REGEX_WHITESPACE = '/[ \t\r\n\v\f\xA0\x{FEFF}]/u';
20
21
    private $json;
22
23
    private $at = 0;
24
25
    private $lineNumber = 1;
26
27
    private $columnNumber = 1;
28
29
    private $ch;
30
31
    private $associative = false;
32
33
    private $maxDepth = 512;
34
35
    private $castBigIntToString = false;
36
37
    private $depth = 1;
38
39
    private $length;
40
41
    private $lineCache;
42
43
    /**
44
     * Private constructor.
45
     *
46
     * @param string $json
47
     * @param bool   $associative
48
     * @param int    $depth
49
     * @param bool   $castBigIntToString
50
     */
51 360
    private function __construct($json, $associative = false, $depth = 512, $castBigIntToString = false)
52
    {
53 360
        $this->json = $json;
54 360
        $this->associative = $associative;
55 360
        $this->maxDepth = $depth;
56 360
        $this->castBigIntToString = $castBigIntToString;
57
58 360
        $this->length = mb_strlen($json, 'utf-8');
59
60 360
        $this->ch = $this->charAt(0);
61 360
    }
62
63
    /**
64
     * Takes a JSON encoded string and converts it into a PHP variable.
65
     *
66
     * The parameters exactly match PHP's json_decode() function - see
67
     * http://php.net/manual/en/function.json-decode.php for more information.
68
     *
69
     * @param string $source      The JSON string being decoded.
70
     * @param bool   $associative When TRUE, returned objects will be converted into associative arrays.
71
     * @param int    $depth       User specified recursion depth.
72
     * @param int    $options     Bitmask of JSON decode options.
73
     *
74
     * @return mixed
75
     */
76 360
    public static function decode($source, $associative = false, $depth = 512, $options = 0)
77
    {
78 360
        $associative = $associative || ($options & JSON_OBJECT_AS_ARRAY);
79 360
        $castBigIntToString = $options & JSON_BIGINT_AS_STRING;
80
81 360
        $decoder = new self((string)$source, $associative, $depth, $castBigIntToString);
82
83 360
        $result = $decoder->value();
84 285
        $decoder->white();
85 282
        if ($decoder->ch) {
86 18
            $decoder->throwSyntaxError('Syntax error');
87
        }
88
89 264
        return $result;
90
    }
91
92
    /**
93
     * @param int $at
94
     *
95
     * @return string|null
96
     */
97 360
    private function charAt($at)
98
    {
99 360
        if ($at < 0 || $at >= $this->length) {
100 276
            return null;
101
        }
102
103 357
        return mb_substr($this->json, $at, 1, 'utf-8');
104
    }
105
106
    /**
107
     * Parse the next character.
108
     *
109
     * If $c is given, the next char will only be parsed if the current
110
     * one matches $c.
111
     *
112
     * @param string|null $c
113
     *
114
     * @return null|string
115
     */
116 330
    private function next($c = null)
117
    {
118
        // If a c parameter is provided, verify that it matches the current character.
119 330
        if ($c !== null && $c !== $this->ch) {
120 9
            $this->throwSyntaxError(sprintf(
121 9
                'Expected %s instead of %s',
122 9
                self::renderChar($c),
123 9
                self::renderChar($this->ch)
124 6
            ));
125
        }
126
127
        // Get the next character. When there are no more characters,
128
        // return the empty string.
129 330
        if ($this->ch === "\n" || ($this->ch === "\r" && $this->peek() !== "\n")) {
130 264
            $this->at++;
131 264
            $this->lineNumber++;
132 264
            $this->columnNumber = 1;
133 176
        } else {
134 291
            $this->at++;
135 291
            $this->columnNumber++;
136
        }
137
138 330
        $this->ch = $this->charAt($this->at);
139
140 330
        return $this->ch;
141
    }
142
143
    /**
144
     * Get the next character without consuming it or
145
     * assigning it to the ch variable.
146
     *
147
     * @return mixed
148
     */
149 12
    private function peek()
150
    {
151 12
        return $this->charAt($this->at + 1);
152
    }
153
154
    /**
155
     * @return string
156
     */
157 210
    private function getLineRemainder()
158
    {
159
        // Line are separated by "\n" or "\r" without an "\n" next
160 210
        if ($this->lineCache === null) {
161 210
            $this->lineCache = preg_split('/\n|\r\n?/u', $this->json);
162 140
        }
163
164 210
        $line = $this->lineCache[$this->lineNumber - 1];
165
166 210
        return mb_substr($line, $this->columnNumber - 1, null, 'utf-8');
167
    }
168
169
    /**
170
     * Attempt to match a regular expression at the current position on the current line.
171
     *
172
     * This function will not match across multiple lines.
173
     *
174
     * @param string $regex
175
     *
176
     * @return string|null
177
     */
178 210
    private function match($regex)
179
    {
180 210
        $subject = $this->getLineRemainder();
181
182 210
        $matches = array();
183 210
        if (!preg_match($regex, $subject, $matches, PREG_OFFSET_CAPTURE)) {
184 111
            return null;
185
        }
186
187
        // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying
188 198
        $offset = mb_strlen(mb_strcut($subject, 0, $matches[0][1], 'utf-8'), 'utf-8');
189
190
        // [0][0] contains the matched text
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
191
        // [0][1] contains the index of that match
192 198
        $advanceBy = $offset + mb_strlen($matches[0][0], 'utf-8');
193
194 198
        $this->at += $advanceBy;
195 198
        $this->columnNumber += $advanceBy;
196 198
        $this->ch = $this->charAt($this->at);
197
198 198
        return $matches[0][0];
199
    }
200
201
    /**
202
     * Parse an identifier.
203
     *
204
     * Normally, reserved words are disallowed here, but we
205
     * only use this for unquoted object keys, where reserved words are allowed,
206
     * so we don't check for those here. References:
207
     * - http://es5.github.com/#x7.6
208
     * - https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Core_Language_Features#Variables
209
     * - http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm
210
     */
211 39
    private function identifier()
212
    {
213
        // Be careful when editing this regex, there are a couple Unicode characters in between here -------------vv
214 39
        $match = $this->match('/^(?:[\$_\p{L}\p{Nl}]|\\\\u[0-9A-Fa-f]{4})(?:[\$_\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}‌‍]|\\\\u[0-9A-Fa-f]{4})*/u');
215
216 39
        if ($match === null) {
217 9
            $this->throwSyntaxError('Bad identifier as unquoted key');
218
        }
219
220
        // Un-escape escaped Unicode chars
221 30
        $unescaped = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/', function ($m) {
222 3
            return Json5Decoder::fromCharCode($m[1]);
223 30
        }, $match);
224
225 30
        return $unescaped;
226
    }
227
228 210
    private function number()
229
    {
230 210
        $number = null;
231 210
        $sign = '';
232 210
        $string = '';
233 210
        $base = 10;
234
235 210
        if ($this->ch === '-' || $this->ch === '+') {
236 93
            $sign = $this->ch;
237 93
            $this->next($this->ch);
238 62
        }
239
240
        // support for Infinity
241 210
        if ($this->ch === 'I') {
242 6
            $number = $this->word();
243 6
            if ($number === null) {
244
                $this->throwSyntaxError('Unexpected word for number');
245
            }
246
247 6
            return ($sign === '-') ? -INF : INF;
248
        }
249
250
        // support for NaN
251 204
        if ($this->ch === 'N') {
252
            $number = $this->word();
253
            if ($number !== NAN) {
254
                $this->throwSyntaxError('expected word to be NaN');
255
            }
256
257
            // ignore sign as -NaN also is NaN
258
            return $number;
259
        }
260
261 204
        if ($this->ch === '0') {
262 105
            $string .= $this->ch;
263 105
            $this->next();
264 105
            if ($this->ch === 'x' || $this->ch === 'X') {
265 33
                $string .= $this->ch;
266 33
                $this->next();
267 33
                $base = 16;
268 94
            } elseif (is_numeric($this->ch)) {
269 30
                $this->throwSyntaxError('Octal literal');
270
            }
271 50
        }
272
273
        switch ($base) {
274 174
            case 10:
275 144
                if (($match = $this->match('/^\d*\.?\d*/')) !== null) {
276 144
                    $string .= $match;
277 96
                }
278 144
                if (($match = $this->match('/^[Ee][-+]?\d*/')) !== null) {
279 45
                    $string .= $match;
280 30
                }
281 144
                $number = $string;
282 144
                break;
283 33
            case 16:
284 33
                if (($match = $this->match('/^[A-Fa-f0-9]+/')) !== null) {
285 30
                    $string .= $match;
286 30
                    $number = hexdec($string);
287 30
                    break;
288
                }
289 3
                $this->throwSyntaxError('Bad hex number');
290
        }
291
292 171
        if ($sign === '-') {
293 33
            $number = -$number;
294 22
        }
295
296 171
        if (!is_numeric($number) || !is_finite($number)) {
297 3
            $this->throwSyntaxError('Bad number');
298
        }
299
300 168
        if ($this->castBigIntToString) {
301 3
            return $number;
302
        }
303
304
        // Adding 0 will automatically cast this to an int or float
305 165
        return $number + 0;
306
    }
307
308 66
    private function string()
309
    {
310 66
        if (!($this->ch === '"' || $this->ch === "'")) {
311
            $this->throwSyntaxError('Bad string');
312
        }
313
314 66
        $string = '';
315
316 66
        $delim = $this->ch;
317 66
        while ($this->next() !== null) {
318 66
            if ($this->ch === $delim) {
319 63
                $this->next();
320
321 63
                return $string;
322 66
            } elseif ($this->ch === '\\') {
323 18
                $this->next();
324 18
                if ($this->ch === 'u') {
325
                    $this->next();
326
                    $hex = $this->match('/^[A-Fa-f0-9]{4}/');
327
                    if ($hex === null) {
328
                        break;
329
                    }
330
                    $string .= self::fromCharCode($hex);
331 18
                } elseif ($this->ch === "\r") {
332 6
                    if ($this->peek() === "\n") {
333 4
                        $this->next();
334 2
                    }
335 16
                } elseif (($escapee = self::getEscapee($this->ch)) !== null) {
336 12
                    $string .= $escapee;
337 8
                } else {
338 6
                    break;
339
                }
340 66
            } elseif ($this->ch === "\n") {
341
                // unescaped newlines are invalid; see:
342
                // https://github.com/json5/json5/issues/24
343
                // @todo this feels special-cased; are there other invalid unescaped chars?
344 3
                break;
345
            } else {
346 66
                $string .= $this->ch;
347
            }
348 44
        }
349
350 3
        $this->throwSyntaxError('Bad string');
351
    }
352
353
    /**
354
     * Skip an inline comment, assuming this is one.
355
     *
356
     * The current character should be the second / character in the // pair that begins this inline comment.
357
     * To finish the inline comment, we look for a newline or the end of the text.
358
     */
359 36
    private function inlineComment()
360
    {
361 36
        if ($this->ch !== '/') {
362
            $this->throwSyntaxError('Not an inline comment');
363
        }
364
365
        do {
366 36
            $this->next();
367 36
            if ($this->ch === "\n" || $this->ch === "\r") {
368 33
                $this->next();
369
370 33
                return;
371
            }
372 36
        } while ($this->ch !== null);
373 3
    }
374
375
    /**
376
     * Skip a block comment, assuming this is one.
377
     *
378
     * The current character should be the * character in the /* pair that begins this block comment.
379
     * To finish the block comment, we look for an ending *​/ pair of characters,
380
     * but we also watch for the end of text before the comment is terminated.
381
     */
382 21 View Code Duplication
    private function blockComment()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
    {
384 21
        if ($this->ch !== '*') {
385
            $this->throwSyntaxError('Not a block comment');
386
        }
387
388
        do {
389 21
            $this->next();
390 21
            while ($this->ch === '*') {
391 18
                $this->next('*');
392 18
                if ($this->ch === '/') {
393 18
                    $this->next('/');
394
395 18
                    return;
396
                }
397 2
            }
398 21
        } while ($this->ch);
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->ch of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
399
400 3
        $this->throwSyntaxError('Unterminated block comment');
401
    }
402
403
    /**
404
     * Skip a comment, whether inline or block-level, assuming this is one.
405
     */
406 54 View Code Duplication
    private function comment()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
407
    {
408
        // Comments always begin with a / character.
409 54
        if ($this->ch !== '/') {
410
            $this->throwSyntaxError('Not a comment');
411
        }
412
413 54
        $this->next('/');
414
415 54
        if ($this->ch === '/') {
416 36
            $this->inlineComment();
417 43
        } elseif ($this->ch === '*') {
418 21
            $this->blockComment();
419 12
        } else {
420
            $this->throwSyntaxError('Unrecognized comment');
421
        }
422 51
    }
423
424
    /**
425
     * Skip whitespace and comments.
426
     *
427
     * Note that we're detecting comments by only a single / character.
428
     * This works since regular expressions are not valid JSON(5), but this will
429
     * break if there are other valid values that begin with a / character!
430
     */
431 360
    private function white()
432
    {
433 360
        while ($this->ch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->ch of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
434 342
            if ($this->ch === '/') {
435 54
                $this->comment();
436 340
            } elseif (preg_match(self::REGEX_WHITESPACE, $this->ch) === 1) {
437 267
                $this->next();
438 178
            } else {
439 306
                return;
440
            }
441 182
        }
442 288
    }
443
444
    /**
445
     * Matches true, false, null, etc
446
     */
447 78
    private function word()
448
    {
449 78
        switch ($this->ch) {
450 78
            case 't':
451 36
                $this->next('t');
452 36
                $this->next('r');
453 36
                $this->next('u');
454 36
                $this->next('e');
455 36
                return true;
456 57
            case 'f':
457 18
                $this->next('f');
458 18
                $this->next('a');
459 18
                $this->next('l');
460 18
                $this->next('s');
461 18
                $this->next('e');
462 18
                return false;
463 42
            case 'n':
464 18
                $this->next('n');
465 18
                $this->next('u');
466 18
                $this->next('l');
467 18
                $this->next('l');
468 18
                return null;
469 24
            case 'I':
470 12
                $this->next('I');
471 12
                $this->next('n');
472 12
                $this->next('f');
473 12
                $this->next('i');
474 12
                $this->next('n');
475 12
                $this->next('i');
476 12
                $this->next('t');
477 12
                $this->next('y');
478 12
                return INF;
479 12
            case 'N':
480 3
                $this->next('N');
481 3
                $this->next('a');
482 3
                $this->next('N');
483 3
                return NAN;
484 6
        }
485
486 9
        $this->throwSyntaxError('Unexpected ' . self::renderChar($this->ch));
487
    }
488
489 42
    private function arr()
490
    {
491 42
        $arr = array();
492
493 42
        if ($this->ch === '[') {
494 42
            if (++$this->depth > $this->maxDepth) {
495 3
                $this->throwSyntaxError('Maximum stack depth exceeded');
496
            }
497
498 42
            $this->next('[');
499 42
            $this->white();
500 42
            while ($this->ch !== null) {
501 42
                if ($this->ch === ']') {
502 12
                    $this->next(']');
503 12
                    $this->depth--;
504 12
                    return $arr; // Potentially empty array
505
                }
506
                // ES5 allows omitting elements in arrays, e.g. [,] and
507
                // [,null]. We don't allow this in JSON5.
508 39
                if ($this->ch === ',') {
509 6
                    $this->throwSyntaxError('Missing array element');
510
                } else {
511 33
                    $arr[] = $this->value();
512
                }
513 30
                $this->white();
514
                // If there's no comma after this value, this needs to
515
                // be the end of the array.
516 30
                if ($this->ch !== ',') {
517 21
                    $this->next(']');
518 18
                    $this->depth--;
519 18
                    return $arr;
520
                }
521 15
                $this->next(',');
522 15
                $this->white();
523 10
            }
524
        }
525
526
        $this->throwSyntaxError('Bad array');
527
    }
528
529
    /**
530
     * Parse an object value
531
     */
532 75
    private function obj()
533
    {
534 75
        $key = null;
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
535 75
        $object = $this->associative ? array() : new \stdClass;
536
537 75
        if ($this->ch === '{') {
538 75
            if (++$this->depth > $this->maxDepth) {
539
                $this->throwSyntaxError('Maximum stack depth exceeded');
540
            }
541
542 75
            $this->next('{');
543 75
            $this->white();
544 75
            while ($this->ch) {
545 75
                if ($this->ch === '}') {
546 21
                    $this->next('}');
547 21
                    $this->depth--;
548 21
                    return $object; // Potentially empty object
549
                }
550
551
                // Keys can be unquoted. If they are, they need to be
552
                // valid JS identifiers.
553 63
                if ($this->ch === '"' || $this->ch === "'") {
554 27
                    $key = $this->string();
555 18
                } else {
556 39
                    $key = $this->identifier();
557
                }
558
559 54
                $this->white();
560 54
                $this->next(':');
561 51
                if ($this->associative) {
562 45
                    $object[$key] = $this->value();
563 30
                } else {
564 48
                    $object->{$key} = $this->value();
565
                }
566 51
                $this->white();
567
                // If there's no comma after this pair, this needs to be
568
                // the end of the object.
569 51
                if ($this->ch !== ',') {
570 42
                    $this->next('}');
571 39
                    $this->depth--;
572 39
                    return $object;
573
                }
574 18
                $this->next(',');
575 18
                $this->white();
576 12
            }
577
        }
578
579
        $this->throwSyntaxError('Bad object');
580
    }
581
582
    /**
583
     * Parse a JSON value.
584
     *
585
     * It could be an object, an array, a string, a number,
586
     * or a word.
587
     */
588 360
    private function value()
589
    {
590 360
        $this->white();
591 360
        switch ($this->ch) {
592 360
            case '{':
593 75
                return $this->obj();
594 336
            case '[':
595 42
                return $this->arr();
596 324
            case '"':
597 315
            case "'":
598 54
                return $this->string();
599 279
            case '-':
600 264
            case '+':
601 249
            case '.':
602 102
                return $this->number();
603 120
            default:
604 180
                return is_numeric($this->ch) ? $this->number() : $this->word();
605 120
        }
606
    }
607
608 96
    private function throwSyntaxError($message)
609
    {
610 96
        throw new SyntaxError($message, $this->at, $this->lineNumber, $this->columnNumber);
611
    }
612
613 18
    private static function renderChar($chr)
614
    {
615 18
        return $chr === null ? 'EOF' : "'" . $chr . "'";
616
    }
617
618
    /**
619
     * @param string $hex Hex code
620
     *
621
     * @return string Unicode character
622
     */
623 3
    private static function fromCharCode($hex)
624
    {
625 3
        return mb_convert_encoding('&#' . hexdec($hex) . ';', 'UTF-8', 'HTML-ENTITIES');
626
    }
627
628
    /**
629
     * @param string $ch
630
     *
631
     * @return string|null
632
     */
633 12
    private static function getEscapee($ch)
634
    {
635
        switch ($ch) {
636
            // @codingStandardsIgnoreStart
637 12
            case "'":  return "'";
638 9
            case '"':  return '"';
639 9
            case '\\': return '\\';
640 9
            case '/':  return '/';
641 9
            case "\n": return '';
642
            case 'b':  return '\b';
643
            case 'f':  return '\f';
644
            case 'n':  return '\n';
645
            case 'r':  return '\r';
646
            case 't':  return '\t';
647
            default:   return null;
648
            // @codingStandardsIgnoreEnd
649
        }
650
    }
651
}
652