Completed
Push — master ( b2a65d...63a169 )
by Kirill
02:18
created

StringValueBuilder::match()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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\Frontend\Builder\Value;
11
12
use Railt\Parser\Ast\RuleInterface;
13
use Railt\SDL\Frontend\Builder\BaseBuilder;
14
use Railt\SDL\Frontend\Context\ContextInterface;
15
use Railt\SDL\IR\SymbolTable\Value;
16
use Railt\SDL\IR\SymbolTable\ValueInterface;
17
use Railt\SDL\IR\Type;
18
19
/**
20
 * Class StringValueBuilder
21
 */
22
class StringValueBuilder extends BaseBuilder
23
{
24
    /**
25
     * @var string
26
     */
27
    private const UTF_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\u([0-9a-f]{4})/ui';
28
29
    /**
30
     * @var string
31
     */
32
    private const CHAR_SEQUENCE_PATTERN = '/(?<!\\\\)\\\\(b|f|n|r|t)/u';
33
34
    /**
35
     * @param RuleInterface $rule
36
     * @return bool
37
     */
38
    public function match(RuleInterface $rule): bool
39
    {
40
        return $rule->getName() === 'StringValue';
41
    }
42
43
    /**
44
     * @param ContextInterface $ctx
45
     * @param RuleInterface $rule
46
     * @return mixed|Value
47
     */
48
    public function reduce(ContextInterface $ctx, RuleInterface $rule): ValueInterface
49
    {
50
        $value = $this->parse($this->getNativeValue($rule));
51
52
        return new Value($value, Type::string());
0 ignored issues
show
Bug introduced by
It seems like \Railt\SDL\IR\Type::string() targeting Railt\SDL\IR\Type\TypeCo...eConstructors::string() can also be of type object<Railt\SDL\IR\Type...untimeTypeConstructors>; however, Railt\SDL\IR\SymbolTable\Value::__construct() does only seem to accept object<Railt\SDL\IR\Type\TypeInterface>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
53
    }
54
55
    /**
56
     * @param string $value
57
     * @return string
58
     */
59
    public function parse(string $value): string
60
    {
61
        // Encode slashes to special "pattern" chars
62
        $value = $this->encodeSlashes($value);
63
64
        // Transform utf char \uXXXX -> X
65
        $value = $this->renderUtfSequences($value);
66
67
        // Transform special chars
68
        $value = $this->renderSpecialCharacters($value);
69
70
        // Decode special patterns to source chars (rollback)
71
        $value = $this->decodeSlashes($value);
72
73
        return $value;
74
    }
75
76
    /**
77
     * @param string $value
78
     * @return string
79
     */
80
    private function encodeSlashes(string $value): string
81
    {
82
        return \str_replace(['\\\\', '\\"'], ["\0", '"'], $value);
83
    }
84
85
    /**
86
     * Method for parsing and decode utf-8 character
87
     * sequences like "\uXXXX" type.
88
     *
89
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
90
     * @param string $body
91
     * @return string
92
     */
93
    private function renderUtfSequences(string $body): string
94
    {
95
        $callee = function (array $matches): string {
96
            [$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...
97
98
            try {
99
                return $this->forwardRenderUtfSequences($code);
100
            } 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...
101
                return $this->fallbackRenderUtfSequences($char);
102
            }
103
        };
104
105
        return @\preg_replace_callback(self::UTF_SEQUENCE_PATTERN, $callee, $body) ?? $body;
106
    }
107
108
    /**
109
     * @param string $body
110
     * @return string
111
     */
112
    private function forwardRenderUtfSequences(string $body): string
113
    {
114
        return \mb_convert_encoding(\pack('H*', $body), 'UTF-8', 'UCS-2BE');
115
    }
116
117
    /**
118
     * @param string $body
119
     * @return string
120
     */
121
    private function fallbackRenderUtfSequences(string $body): string
122
    {
123
        try {
124
            if (\function_exists('\\json_decode')) {
125
                $result = @\json_decode('{"char": "' . $body . '"}')->char;
126
127
                if (\json_last_error() === \JSON_ERROR_NONE) {
128
                    $body = $result;
129
                }
130
            }
131
        } finally {
132
            return $body;
133
        }
134
    }
135
136
    /**
137
     * Method for parsing special control characters.
138
     *
139
     * @see http://facebook.github.io/graphql/October2016/#sec-String-Value
140
     * @param string $body
141
     * @return string
142
     */
143
    private function renderSpecialCharacters(string $body): string
144
    {
145
        $callee = function (array $matches): string {
146
            [$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...
147
148
            switch ($code) {
149
                case 'b':
150
                    return "\u{0008}";
151
                case 'f':
152
                    return "\u{000C}";
153
                case 'n':
154
                    return "\u{000A}";
155
                case 'r':
156
                    return "\u{000D}";
157
                case 't':
158
                    return "\u{0009}";
159
            }
160
161
            return $char;
162
        };
163
164
        return @\preg_replace_callback(self::CHAR_SEQUENCE_PATTERN, $callee, $body) ?? $body;
165
    }
166
167
    /**
168
     * @param string $value
169
     * @return string
170
     */
171
    private function decodeSlashes(string $value): string
172
    {
173
        return \str_replace("\0", '\\', $value);
174
    }
175
176
    /**
177
     * @param RuleInterface $rule
178
     * @return string
179
     */
180
    private function getNativeValue(RuleInterface $rule): string
181
    {
182
        return $rule->getChild(0)->getValue(1);
183
    }
184
185
    /**
186
     * @return string
187
     */
188
    private function getStringValue(): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
189
    {
190
        /** @var LeafInterface $leaf */
191
        $leaf = $this->getChild(0);
0 ignored issues
show
Bug introduced by
The method getChild() does not seem to exist on object<Railt\SDL\Fronten...lue\StringValueBuilder>.

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

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

Loading history...
192
193
        return $leaf->getValue(1);
194
    }
195
}
196