Passed
Push — parser-refactoring ( e758c6 )
by Luis
04:05
created

TokenParser::saveModifier()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 7
ccs 4
cts 5
cp 0.8
crap 2.032
rs 9.4285
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 30
    public function __construct(
29
        Definitions $definitions = null,
30
        RelationsResolver $resolver = null,
31
        StructureBuilder $builder = null
32
    ) {
33 30
        $this->builder = $builder ?? new StructureBuilder();
34 30
        $this->definitions = $definitions ?? new Definitions();
35 30
        $this->resolver = $resolver ?? new RelationsResolver();
36 30
    }
37
38 30
    public function createStructure(array $files): Structure
39
    {
40 30
        $this->initGlobalAttributes();
41 30
        foreach ($files as $file) {
42 30
            $this->initParserAttributes();
43 30
            $tokens = token_get_all(file_get_contents($file));
44 30
            $this->process($tokens);
45 30
            $this->storeClassOrInterface();
46
        }
47 30
        $this->resolver->resolve($this->definitions);
48 30
        return $this->builder->buildFromDefinitions($this->definitions);
49
    }
50
51 30
    private function process(array $tokens): void
52
    {
53 30
        foreach ($tokens as $token) {
54 30
            if (is_array($token)) {
55 30
                $this->processComplex(...$token);
0 ignored issues
show
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

55
                $this->/** @scrutinizer ignore-call */ 
56
                       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...
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

55
                $this->processComplex(/** @scrutinizer ignore-type */ ...$token);
Loading history...
56
            } else {
57 30
                $this->processSimple($token);
58
            }
59
        }
60 30
    }
61
62 30
    private function processSimple(string $token): void
63
    {
64 10
        switch ($token) {
65 20
            case '(':
66 12
                break;
67 20
            case ',':
68 12
                $this->resetTypeHint();
69 12
                break;
70 20
            case '=':
71 3
                $this->resetToken();
72 3
                break;
73 20
            case ')':
74 12
                $this->saveMethodDefinition();
75 12
                break;
76
            default:
77
                // Ignore everything else
78 30
                $this->lastToken = null;
79
        }
80 30
    }
81
82 30
    private function processComplex(int $type, string $value): void
83
    {
84
        switch ($type) {
85 30
            case T_WHITESPACE:
86 30
                break;
87 30
            case T_VAR:
88 30
            case T_ARRAY:
89 30
            case T_CONSTANT_ENCAPSED_STRING:
90 30
            case T_LNUMBER:
91 30
            case T_DNUMBER:
92 30
            case T_PAAMAYIM_NEKUDOTAYIM:
93 6
                $this->resetToken();
94 6
                break;
95 30
            case T_FUNCTION:
96 12
                $this->startMethodDefinition($type);
97 12
                break;
98 30
            case T_INTERFACE:
99 30
            case T_CLASS:
100 30
                $this->startClassOrInterfaceDefinition($type);
101 30
                break;
102 30
            case T_IMPLEMENTS:
103 30
            case T_EXTENDS:
104 12
                $this->startExtendsOrImplementsDeclaration($type);
105 12
                break;
106 30
            case T_VARIABLE:
107 18
                $this->saveAttributeOrParameter($value);
108 18
                break;
109 30
            case T_STRING:
110 30
                $this->saveIdentifier($value);
111 30
                break;
112 30
            case T_PUBLIC:
113 30
            case T_PROTECTED:
114 30
            case T_PRIVATE:
115 18
                $this->saveModifier($type, $value);
116 18
                break;
117 30
            case T_DOC_COMMENT:
118 6
                $this->saveDocBlock($value);
119 6
                break;
120
            default:
121
                // Ignore everything else
122 30
                $this->lastToken = null;
123
                // And reset the docblock
124 30
                $this->structure['docblock'] = null;
125
        }
126 30
    }
127
128 30
    private function initGlobalAttributes(): void
129
    {
130 30
        $this->classes = [];
0 ignored issues
show
Bug Best Practice introduced by
The property classes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
131 30
        $this->interfaces = [];
0 ignored issues
show
Bug Best Practice introduced by
The property interfaces does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
132 30
    }
133
134 30
    private function initParserAttributes(): void
135
    {
136 30
        $this->structure = [
137
            'class' => null,
138
            'interface' => null,
139
            'function' => null,
140
            'attributes' => [],
141
            'functions' => [],
142
            'typehint' => null,
143
            'params' => [],
144
            'implements' => [],
145
            'extends' => null,
146
            'modifier' => 'public',
147
            'docblock' => null,
148
        ];
149
150 30
        $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...
151 30
    }
152
153 12
    private function resetTypeHint(): void
154
    {
155 12
        $this->structure['typehint'] = null;
156 12
    }
157
158 6
    private function resetToken(): void
159
    {
160 6
        if ($this->lastToken !== T_FUNCTION) {
161 6
            $this->lastToken = null;
162
        }
163 6
    }
164
165 12
    private function startMethodDefinition(int $type): void
166
    {
167 12
        switch ($this->lastToken) {
168 8
            case null:
169 12
            case T_PUBLIC:
170 3
            case T_PROTECTED:
171 3
            case T_PRIVATE:
172 12
                $this->lastToken = $type;
173 12
                break;
174
            default:
175
                $this->lastToken = null;
176
        }
177 12
    }
178
179 30
    private function startClassOrInterfaceDefinition(int $type): void
180
    {
181 30
        if ($this->lastToken === null) {
182
            // New initial interface or class token
183
            // Store the class or interface definition if there is any in the
184
            // parser arrays ( There might be more than one class/interface per
185
            // file )
186 30
            $this->storeClassOrInterface();
187
188
            // Remember the last token
189 30
            $this->lastToken = $type;
190
        } else {
191
            $this->lastToken = null;
192
        }
193 30
    }
194
195 12
    private function startExtendsOrImplementsDeclaration(int $type): void
196
    {
197 12
        if ($this->lastToken === null) {
198 12
            $this->lastToken = $type;
199
        } else {
200
            $this->lastToken = null;
201
        }
202 12
    }
203
204 12
    private function saveMethodDefinition(): void
205
    {
206 12
        if ($this->lastToken === T_FUNCTION) {
207
            // The function declaration has been closed
208
209
            // Add the current function
210 12
            $this->structure['functions'][] = [
211 12
                $this->structure['function'],
212 12
                $this->structure['modifier'],
213 12
                $this->structure['params'],
214 12
                $this->structure['docblock']
215
            ];
216
            // Reset the last token
217 12
            $this->lastToken = null;
218
            //Reset the modifier state
219 12
            $this->structure['modifier'] = 'public';
220
            // Reset the params array
221 12
            $this->structure['params'] = [];
222 12
            $this->structure['typehint'] = null;
223
            // Reset the function name
224 12
            $this->structure['function'] = null;
225
            // Reset the docblock
226 12
            $this->structure['docblock'] = null;
227
        } else {
228 3
            $this->lastToken = null;
229
        }
230 12
    }
231
232 18
    private function saveAttributeOrParameter(string $identifier): void
233
    {
234 18
        switch ($this->lastToken) {
235 18
            case T_PUBLIC:
236 18
            case T_PROTECTED:
237 18
            case T_PRIVATE:
238
                // A new class attribute
239 9
                $this->structure['attributes'][] = [
240 9
                    $identifier,
241 9
                    $this->structure['modifier'],
242 9
                    $this->structure['docblock'],
243
                ];
244 9
                $this->lastToken = null;
245 9
                $this->structure['modifier'] = 'public';
246 9
                $this->structure['docblock'] = null;
247 9
                break;
248 12
            case T_FUNCTION:
249
                // A new function parameter
250 12
                $this->structure['params'][] = [
251 12
                    $this->structure['typehint'],
252 12
                    $identifier,
253
                ];
254 12
                break;
255
        }
256 18
    }
257
258 30
    private function saveIdentifier(string $identifier): void
259
    {
260 30
        switch ($this->lastToken) {
261 30
            case T_IMPLEMENTS:
262
                // Add interface to implements array
263 6
                $this->structure['implements'][] = $identifier;
264
                // We do not reset the last token here, because
265
                // there might be multiple interfaces
266 6
                break;
267 30
            case T_EXTENDS:
268
                // Set the superclass
269 9
                $this->structure['extends'] = $identifier;
270
                // Reset the last token
271 9
                $this->lastToken = null;
272 9
                break;
273 30
            case T_FUNCTION:
274
                // Add the current function only if there is no function name already
275
                // Because if we know the function name already this is a type hint
276 12
                if ($this->structure['function'] === null) {
277
                    // Function name
278 12
                    $this->structure['function'] = $identifier;
279
                } else {
280
                    // Type hint
281 12
                    $this->structure['typehint'] = $identifier;
282
                }
283 12
                break;
284 30
            case T_CLASS:
285
                // Set the class name
286 24
                $this->structure['class'] = $identifier;
287
                // Reset the last token
288 24
                $this->lastToken = null;
289 24
                break;
290 18
            case T_INTERFACE:
291
                // Set the interface name
292 12
                $this->structure['interface'] = $identifier;
293
                // Reset the last Token
294 12
                $this->lastToken = null;
295 12
                break;
296
            default:
297 12
                $this->lastToken = null;
298
        }
299 30
    }
300
301 18
    private function saveModifier(int $type, string $modifier): void
302
    {
303 18
        if ($this->lastToken === null) {
304 18
            $this->lastToken = $type;
305 18
            $this->structure['modifier'] = $modifier;
306
        } else {
307
            $this->lastToken = null;
308
        }
309 18
    }
310
311 6
    private function saveDocBlock(string $comment): void
312
    {
313 6
        if ($this->lastToken === null) {
314 6
            $this->structure['docblock'] = $comment;
315
        } else {
316
            $this->lastToken = null;
317
            $this->structure['docblock'] = null;
318
        }
319 6
    }
320
321 30
    private function storeClassOrInterface(): void
322
    {
323 30
        $this->definitions->add($this->structure);
324 30
        $this->initParserAttributes();
325 30
    }
326
}
327