Completed
Push — master ( 332a4c...198c54 )
by Kirill
02:13
created

StringValueNode   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 160
rs 10
c 0
b 0
f 0
wmc 18
lcom 1
cbo 1

9 Methods

Rating   Name   Duplication   Size   Complexity  
A toPrimitive() 0 8 2
A getValue() 0 4 1
A parse() 0 19 1
A encodeSlashes() 0 4 1
A renderUtfSequences() 0 14 2
A forwardRenderUtfSequences() 0 4 1
A fallbackRenderUtfSequences() 0 14 3
B renderSpecialCharacters() 0 23 6
A decodeSlashes() 0 4 1
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL\Compiler\Ast\Value;
11
12
use Railt\Parser\Ast\Rule;
13
14
/**
15
 * Class StringValueNode
16
 */
17
class StringValueNode extends Rule implements ValueInterface
18
{
19
    /**
20
     * @var string
21
     */
22
    private const UTF_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\u([0-9a-f]{4})/ui';
23
24
    /**
25
     * @var string
26
     */
27
    private const CHAR_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\(b|f|n|r|t)/u';
28
29
    /**
30
     * @var string|null
31
     */
32
    private $value;
33
34
    /**
35
     * @return string
36
     */
37
    public function toPrimitive(): string
38
    {
39
        if ($this->value === null) {
40
            $this->value = $this->parse();
41
        }
42
43
        return $this->value;
44
    }
45
46
    /**
47
     * @return string
48
     */
49
    private function getValue(): string
50
    {
51
        return $this->getChild(0)->getValue(1);
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on Railt\Parser\Ast\NodeInterface. Did you maybe mean getValues()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
52
    }
53
54
    /**
55
     * @return string
56
     */
57
    private function parse(): string
58
    {
59
        // "..."
60
        $result = $this->getValue();
61
62
        // Encode slashes to special "pattern" chars
63
        $result = $this->encodeSlashes($result);
64
65
        // Transform utf char \uXXXX -> X
66
        $result = $this->renderUtfSequences($result);
67
68
        // Transform special chars
69
        $result = $this->renderSpecialCharacters($result);
70
71
        // Decode special patterns to source chars (rollback)
72
        $result = $this->decodeSlashes($result);
73
74
        return $result;
75
    }
76
77
    /**
78
     * @param string $value
79
     * @return string
80
     */
81
    private function encodeSlashes(string $value): string
82
    {
83
        return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value);
84
    }
85
86
    /**
87
     * Method for parsing and decode utf-8 character
88
     * sequences like "\uXXXX" type.
89
     *
90
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
91
     * @param string $body
92
     * @return string
93
     */
94
    private function renderUtfSequences(string $body): string
95
    {
96
        $callee = function (array $matches): string {
97
            [$char, $code] = [$matches[0], $matches[1]];
0 ignored issues
show
Bug introduced by
The variable $char does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $code does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
98
99
            try {
100
                return $this->forwardRenderUtfSequences($code);
101
            } catch (\Error | \ErrorException $error) {
0 ignored issues
show
Bug introduced by
The class ErrorException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
102
                return $this->fallbackRenderUtfSequences($char);
103
            }
104
        };
105
106
        return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body;
107
    }
108
109
    /**
110
     * @param string $body
111
     * @return string
112
     */
113
    private function forwardRenderUtfSequences(string $body): string
114
    {
115
        return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE');
116
    }
117
118
    /**
119
     * @param string $body
120
     * @return string
121
     */
122
    private function fallbackRenderUtfSequences(string $body): string
123
    {
124
        try {
125
            if (\function_exists('\\json_decode')) {
126
                $result = @\json_decode('{"char": "' . $body . '"}')->char;
127
128
                if (\json_last_error() === \JSON_ERROR_NONE) {
129
                    $body = $result;
130
                }
131
            }
132
        } finally {
133
            return $body;
134
        }
135
    }
136
137
    /**
138
     * Method for parsing special control characters.
139
     *
140
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
141
     * @param string $body
142
     * @return string
143
     */
144
    private function renderSpecialCharacters(string $body): string
145
    {
146
        $callee = function (array $matches): string {
147
            [$char, $code] = [$matches[0], $matches[1]];
0 ignored issues
show
Bug introduced by
The variable $char does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $code does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
148
149
            switch ($code) {
150
                case 'b':
151
                    return "\u{0008}";
152
                case 'f':
153
                    return "\u{000C}";
154
                case 'n':
155
                    return "\u{000A}";
156
                case 'r':
157
                    return "\u{000D}";
158
                case 't':
159
                    return "\u{0009}";
160
            }
161
162
            return $char;
163
        };
164
165
        return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body;
166
    }
167
168
    /**
169
     * @param string $value
170
     * @return string
171
     */
172
    private function decodeSlashes(string $value): string
173
    {
174
        return \str_replace("\0", '\\', $value);
175
    }
176
}
177