Completed
Push — symfony-console-application ( 3187e2...c3ee2a )
by Luis
10:39
created

TokenParser::saveIdentifier()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 24
nc 7
nop 1
dl 0
loc 40
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * PHP version 7.1
4
 *
5
 * This source file is subject to the license that is bundled with this package in the file LICENSE.
6
 */
7
namespace PhUml\Parser;
8
9
use PhUml\Code\Structure;
10
11
class TokenParser
12
{
13
    /** @var array */
14
    private $structure;
15
16
    /** @var int */
17
    private $lastToken;
18
19
    /** @var StructureBuilder */
20
    private $builder;
21
22
    /** @var Definitions */
23
    private $definitions;
24
25
    /** @var RelationsResolver */
26
    private $resolver;
27
28
    public function __construct(
29
        Definitions $definitions = null,
30
        RelationsResolver $resolver = null,
31
        StructureBuilder $builder = null
32
    ) {
33
        $this->builder = $builder ?? new StructureBuilder();
34
        $this->definitions = $definitions ?? new Definitions();
35
        $this->resolver = $resolver ?? new RelationsResolver();
36
    }
37
38
    public function parse(CodeFinder $finder): Structure
39
    {
40
        foreach ($finder->files() as $code) {
41
            $this->initParserAttributes();
42
            $this->process(token_get_all($code));
43
            $this->storeClassOrInterface();
44
        }
45
        $this->resolver->resolve($this->definitions);
46
        return $this->builder->buildFromDefinitions($this->definitions);
47
    }
48
49
    private function process(array $tokens): void
50
    {
51
        foreach ($tokens as $token) {
52
            if (is_array($token)) {
53
                $this->processComplex(...$token);
0 ignored issues
show
Bug introduced by
$token is expanded, but the parameter $type of PhUml\Parser\TokenParser::processComplex() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
                $this->processComplex(/** @scrutinizer ignore-type */ ...$token);
Loading history...
Bug introduced by
The call to PhUml\Parser\TokenParser::processComplex() has too few arguments starting with value. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
                $this->/** @scrutinizer ignore-call */ 
54
                       processComplex(...$token);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
54
            } else {
55
                $this->processSimple($token);
56
            }
57
        }
58
    }
59
60
    private function processSimple(string $token): void
61
    {
62
        switch ($token) {
63
            case '(':
64
                break;
65
            case ',':
66
                $this->resetTypeHint();
67
                break;
68
            case '=':
69
                $this->resetToken();
70
                break;
71
            case ')':
72
                $this->saveMethodDefinition();
73
                break;
74
            default:
75
                // Ignore everything else
76
                $this->lastToken = null;
77
        }
78
    }
79
80
    private function processComplex(int $type, string $value): void
81
    {
82
        switch ($type) {
83
            case T_WHITESPACE:
84
                break;
85
            case T_VAR:
86
            case T_ARRAY:
87
            case T_CONSTANT_ENCAPSED_STRING:
88
            case T_LNUMBER:
89
            case T_DNUMBER:
90
            case T_PAAMAYIM_NEKUDOTAYIM:
91
                $this->resetToken();
92
                break;
93
            case T_FUNCTION:
94
                $this->startMethodDefinition($type);
95
                break;
96
            case T_INTERFACE:
97
            case T_CLASS:
98
                $this->startClassOrInterfaceDefinition($type);
99
                break;
100
            case T_IMPLEMENTS:
101
            case T_EXTENDS:
102
                $this->startExtendsOrImplementsDeclaration($type);
103
                break;
104
            case T_VARIABLE:
105
                $this->saveAttributeOrParameter($value);
106
                break;
107
            case T_STRING:
108
                $this->saveIdentifier($value);
109
                break;
110
            case T_PUBLIC:
111
            case T_PROTECTED:
112
            case T_PRIVATE:
113
                $this->saveModifier($type, $value);
114
                break;
115
            case T_DOC_COMMENT:
116
                $this->saveDocBlock($value);
117
                break;
118
            default:
119
                // Ignore everything else
120
                $this->lastToken = null;
121
                // And reset the docblock
122
                $this->structure['docblock'] = null;
123
        }
124
    }
125
126
    private function initParserAttributes(): void
127
    {
128
        $this->structure = [
129
            'class' => null,
130
            'interface' => null,
131
            'function' => null,
132
            'attributes' => [],
133
            'functions' => [],
134
            'typehint' => null,
135
            'params' => [],
136
            'implements' => [],
137
            'extends' => null,
138
            'modifier' => 'public',
139
            'docblock' => null,
140
        ];
141
142
        $this->lastToken = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type integer of property $lastToken.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
143
    }
144
145
    private function resetTypeHint(): void
146
    {
147
        $this->structure['typehint'] = null;
148
    }
149
150
    private function resetToken(): void
151
    {
152
        if ($this->lastToken !== T_FUNCTION) {
153
            $this->lastToken = null;
154
        }
155
    }
156
157
    private function startMethodDefinition(int $type): void
158
    {
159
        switch ($this->lastToken) {
160
            case null:
161
            case T_PUBLIC:
162
            case T_PROTECTED:
163
            case T_PRIVATE:
164
                $this->lastToken = $type;
165
                break;
166
            default:
167
                $this->lastToken = null;
168
        }
169
    }
170
171
    private function startClassOrInterfaceDefinition(int $type): void
172
    {
173
        if ($this->lastToken === null) {
174
            // New initial interface or class token
175
            // Store the class or interface definition if there is any in the
176
            // parser arrays ( There might be more than one class/interface per
177
            // file )
178
            $this->storeClassOrInterface();
179
180
            // Remember the last token
181
            $this->lastToken = $type;
182
        } else {
183
            $this->lastToken = null;
184
        }
185
    }
186
187
    private function startExtendsOrImplementsDeclaration(int $type): void
188
    {
189
        if ($this->lastToken === null) {
190
            $this->lastToken = $type;
191
        } else {
192
            $this->lastToken = null;
193
        }
194
    }
195
196
    private function saveMethodDefinition(): void
197
    {
198
        if ($this->lastToken === T_FUNCTION) {
199
            // The function declaration has been closed
200
201
            // Add the current function
202
            $this->structure['functions'][] = [
203
                $this->structure['function'],
204
                $this->structure['modifier'],
205
                $this->structure['params'],
206
                $this->structure['docblock']
207
            ];
208
            // Reset the last token
209
            $this->lastToken = null;
210
            //Reset the modifier state
211
            $this->structure['modifier'] = 'public';
212
            // Reset the params array
213
            $this->structure['params'] = [];
214
            $this->structure['typehint'] = null;
215
            // Reset the function name
216
            $this->structure['function'] = null;
217
            // Reset the docblock
218
            $this->structure['docblock'] = null;
219
        } else {
220
            $this->lastToken = null;
221
        }
222
    }
223
224
    private function saveAttributeOrParameter(string $identifier): void
225
    {
226
        switch ($this->lastToken) {
227
            case T_PUBLIC:
228
            case T_PROTECTED:
229
            case T_PRIVATE:
230
                // A new class attribute
231
                $this->structure['attributes'][] = [
232
                    $identifier,
233
                    $this->structure['modifier'],
234
                    $this->structure['docblock'],
235
                ];
236
                $this->lastToken = null;
237
                $this->structure['modifier'] = 'public';
238
                $this->structure['docblock'] = null;
239
                break;
240
            case T_FUNCTION:
241
                // A new function parameter
242
                $this->structure['params'][] = [
243
                    $this->structure['typehint'],
244
                    $identifier,
245
                ];
246
                break;
247
        }
248
    }
249
250
    private function saveIdentifier(string $identifier): void
251
    {
252
        switch ($this->lastToken) {
253
            case T_IMPLEMENTS:
254
                // Add interface to implements array
255
                $this->structure['implements'][] = $identifier;
256
                // We do not reset the last token here, because
257
                // there might be multiple interfaces
258
                break;
259
            case T_EXTENDS:
260
                // Set the superclass
261
                $this->structure['extends'] = $identifier;
262
                // Reset the last token
263
                $this->lastToken = null;
264
                break;
265
            case T_FUNCTION:
266
                // Add the current function only if there is no function name already
267
                // Because if we know the function name already this is a type hint
268
                if ($this->structure['function'] === null) {
269
                    // Function name
270
                    $this->structure['function'] = $identifier;
271
                } else {
272
                    // Type hint
273
                    $this->structure['typehint'] = $identifier;
274
                }
275
                break;
276
            case T_CLASS:
277
                // Set the class name
278
                $this->structure['class'] = $identifier;
279
                // Reset the last token
280
                $this->lastToken = null;
281
                break;
282
            case T_INTERFACE:
283
                // Set the interface name
284
                $this->structure['interface'] = $identifier;
285
                // Reset the last Token
286
                $this->lastToken = null;
287
                break;
288
            default:
289
                $this->lastToken = null;
290
        }
291
    }
292
293
    private function saveModifier(int $type, string $modifier): void
294
    {
295
        if ($this->lastToken === null) {
296
            $this->lastToken = $type;
297
            $this->structure['modifier'] = $modifier;
298
        } else {
299
            $this->lastToken = null;
300
        }
301
    }
302
303
    private function saveDocBlock(string $comment): void
304
    {
305
        if ($this->lastToken === null) {
306
            $this->structure['docblock'] = $comment;
307
        } else {
308
            $this->lastToken = null;
309
            $this->structure['docblock'] = null;
310
        }
311
    }
312
313
    private function storeClassOrInterface(): void
314
    {
315
        $this->definitions->add($this->structure);
316
        $this->initParserAttributes();
317
    }
318
}
319