Completed
Push — v5-dev ( a42eb3 )
by Oscar
01:56
created

PhpFunctionsScanner::unicodeChar()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 1
dl 0
loc 26
rs 9.1928
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace Gettext\Scanner;
5
6
class PhpFunctionsScanner implements FunctionsScannerInterface
7
{
8
    protected $comments = true;
9
    protected $constants = [];
10
11
    /**
12
     * Include related comments to the functions
13
     * 
14
     * @param bool|array $comments Boolean to enable/disable. Array to filter comments by prefixes
15
     */
16
    public function includeComments($comments = true): void
17
    {
18
        $this->comments = $comments;
0 ignored issues
show
Documentation Bug introduced by
It seems like $comments can also be of type array. However, the property $comments is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
19
    }
20
21
    /**
22
     * Replace constants found in the code by values
23
     */
24
    public function setDefinedConstants(array $constants): void
25
    {
26
        $this->constants = $constants;
27
    }
28
29
    public function scan(string $code, string $filename = null): array
30
    {
31
        $tokens = static::tokenize($code);
32
        $total = count($tokens);
0 ignored issues
show
Unused Code introduced by
$total 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...
33
34
        $bufferFunctions = [];
35
        $lastComment = null;
36
        $functions = [];
37
38
        $token = current($tokens);
39
        $lastLine = 1;
40
41
        while ($token !== false) {
42
            $nextToken = next($tokens);
43
44
            if (is_string($token)) {
45
                if (isset($bufferFunctions[0])) {
46
                    switch ($token) {
47
                        case ',':
48
                            if (!$bufferFunctions[0]->countArguments()) {
49
                                $bufferFunctions[0]->addArgument();
50
                            }
51
                            $bufferFunctions[0]->addArgument();
52
                            break;
53
54
                        case ')':
55
                            $functions[] = array_shift($bufferFunctions)->setLastLine($lastLine);
56
                            break;
57
58
                        case '.':
59
                            break;
60
61
                        default:
62
                            if (!$bufferFunctions[0]->countArguments()) {
63
                                $bufferFunctions[0]->addArgument();
64
                            }
65
                            $bufferFunctions[0]->closeArgument();
66
                            break;
67
                    }
68
                }
69
70
                $token = $nextToken;
71
                continue;
72
            }
73
74
            $lastLine = $token[2];
75
76
            switch ($token[0]) {
77
                case T_CONSTANT_ENCAPSED_STRING:
78
                    if (isset($bufferFunctions[0])) {
79
                        $bufferFunctions[0]->addArgumentChunk(static::decode($token[1]));
80
                    }
81
                    break;
82
83
                case T_LNUMBER:
84
                case T_DNUMBER:
85
                    if (isset($bufferFunctions[0])) {
86
                        $bufferFunctions[0]->addArgumentChunk((string) $token[1]);
87
                    }
88
                    break;
89
90
                case T_STRING:
91
                    //New function found
92
                    if ($nextToken === '(') {
93
                        $function = new ParsedFunction($token[1], $filename, $token[2]);
94
95
                        if ($lastComment) {
96
                            if ($lastComment->isRelatedWith($function)) {
97
                                $function->addComment($lastComment);
98
                            }
99
100
                            $lastComment = null;
101
                        }
102
103
                        array_unshift($bufferFunctions, $function);
104
                        $nextToken = next($tokens);
105
                        break;
106
                    }
107
108
                    if (isset($this->constants[$token[1]])) {
109
                        $bufferFunctions[0]->addArgumentChunk($this->constants[$token[1]]);
110
                        break;
111
                    }
112
113
                    if (isset($bufferFunctions) && !$bufferFunctions[0]->countArguments()) {
114
                        $bufferFunctions[0]->addArgument();
115
                    }
116
                    break;
117
118
                case T_COMMENT:
119
                    if ($this->comments === false) {
120
                        break;
121
                    }
122
123
                    $comment = new ParsedComment(trim($token[1]), $filename, $token[2]);
124
125
                    if ($this->comments !== true && !$comment->isPrefixed($this->comments)) {
0 ignored issues
show
Documentation introduced by
$this->comments is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
126
                        break;
127
                    }
128
129
                    // The comment is inside the function call.
130
                    if (isset($bufferFunctions[0])) {
131
                        $bufferFunctions[0]->addComment($comment);
132
                    } else {
133
                        $lastComment = $comment;
134
                    }
135
136
                    break;
137
138
                default:
139
                    if (isset($bufferFunctions[0])) {
140
                        if (!$bufferFunctions[0]->countArguments()) {
141
                            $bufferFunctions[0]->addArgument();
142
                        }
143
144
                        $bufferFunctions[0]->closeArgument();
145
                    }
146
                    break;
147
            }
148
149
            $token = $nextToken;
150
        }
151
152
        return $functions;
153
    }
154
155
    protected static function tokenize(string $code): array
156
    {
157
        return array_values(
158
            array_filter(
159
                token_get_all($code),
160
                function ($token) {
161
                    return !is_array($token) || $token[0] !== T_WHITESPACE;
162
                }
163
            )
164
        );
165
    }
166
167
    /**
168
     * Decodes a T_CONSTANT_ENCAPSED_STRING string.
169
     */
170
    public static function decode(string $value): string
171
    {
172
        if (strpos($value, '\\') === false) {
173
            return substr($value, 1, -1);
174
        }
175
176
        if ($value[0] === "'") {
177
            return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']);
178
        }
179
180
        $value = substr($value, 1, -1);
181
182
        return preg_replace_callback(
183
            '/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/',
184
            function ($match) {
185
                switch ($match[1][0]) {
186
                    case 'n':
187
                        return "\n";
188
                    case 'r':
189
                        return "\r";
190
                    case 't':
191
                        return "\t";
192
                    case 'v':
193
                        return "\v";
194
                    case 'e':
195
                        return "\e";
196
                    case 'f':
197
                        return "\f";
198
                    case '$':
199
                        return '$';
200
                    case '"':
201
                        return '"';
202
                    case '\\':
203
                        return '\\';
204
                    case 'x':
205
                        return chr(hexdec(substr($match[1], 1)));
206
                    case 'u':
207
                        return self::unicodeChar(hexdec(substr($match[1], 1)));
208
                    default:
209
                        return chr(octdec($match[1]));
210
                }
211
            },
212
            $value
213
        );
214
    }
215
216
    /**
217
     * @param number $dec
218
     * @see http://php.net/manual/en/function.chr.php#118804
219
     */
220
    protected static function unicodeChar($dec): ?string
221
    {
222
        if ($dec < 0x80) {
223
            return chr($dec);
224
        }
225
226
        if ($dec < 0x0800) {
227
            return chr(0xC0 + ($dec >> 6))
228
                . chr(0x80 + ($dec & 0x3f));
229
        }
230
231
        if ($dec < 0x010000) {
232
            return chr(0xE0 + ($dec >> 12))
233
                . chr(0x80 + (($dec >> 6) & 0x3f))
234
                . chr(0x80 + ($dec & 0x3f));
235
        }
236
237
        if ($dec < 0x200000) {
238
            return chr(0xF0 + ($dec >> 18))
239
                . chr(0x80 + (($dec >> 12) & 0x3f))
240
                . chr(0x80 + (($dec >> 6) & 0x3f))
241
                . chr(0x80 + ($dec & 0x3f));
242
        }
243
244
        return null;
245
    }
246
}
247