Completed
Push — master ( 7b3f43...b8da62 )
by Richard
05:54
created

Indexer::index_file()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3.0261

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 19
ccs 12
cts 14
cp 0.8571
rs 9.4285
cc 3
eloc 13
nc 3
nop 1
crap 3.0261
1
<?php
2
/******************************************************************************
3
 * An implementation of dicto (scg.unibe.ch/dicto) in and for PHP.
4
 *
5
 * Copyright (c) 2016, 2015 Richard Klees <[email protected]>
6
 *
7
 * This software is licensed under The MIT License. You should have received
8
 * a copy of the licence along with the code.
9
 */
10
11
namespace Lechimp\Dicto\Indexer;
12
13
use Lechimp\Dicto\Indexer as I;
14
use Lechimp\Dicto\Variables\Variable;
15
use PhpParser\Node as N;
16
17
/**
18
 * Implementation of Indexer with PhpParser.
19
 */
20
class Indexer implements Location, ListenerRegistry, \PhpParser\NodeVisitor {
21
    /**
22
     * @var string
23
     */
24
    protected $project_root_path;
25
26
    /**
27
     * @var I\Insert|null
28
     */
29
    protected $insert;
30
31
    /**
32
     * @var \PhpParser\Parser
33
     */
34
    protected $parser;
35
36
    /**
37
     * @var array   string => array()
38
     */
39
    protected $listeners_enter_entity;
40
41
    /**
42
     * @var array   string => array()
43
     */
44
    protected $listeners_leave_entity;
45
46
    /**
47
     * @var array   string => array()
48
     */
49
    protected $listeners_enter_misc;
50
51
    /**
52
     * @var array   string => array()
53
     */
54
    protected $listeners_leave_misc;
55
56
    // state for parsing a file
57
58
    /**
59
     * @var string|null
60
     */
61
    protected $file_path = null;
62
63
    /**
64
     * @var string[]|null
65
     */
66
    protected $file_content = null;
67
68
    /**
69
     * This contains the stack of ids were currently in, i.e. the nesting of
70
     * known code blocks we are in.
71
     *
72
     * @var array|null  contain ($entity_type, $entity_id)
73
     */
74
    protected $entity_stack = null;
75
76
    /**
77
     * @param   string  $project_root_path
78
     */
79 24
    public function __construct(\PhpParser\Parser $parser, $project_root_path, Insert $insert) {
80 24
        $this->parser = $parser;
81 24
        assert('is_string($project_root_path)');
82 24
        $this->project_root_path = $project_root_path;
83 24
        $this->insert = $insert;
84 24
        $this->listeners_enter_entity = array
85 24
            ( 0 => array()
86 24
            );
87 24
        $this->listeners_leave_entity = array
88 24
            ( 0 => array()
89 24
            );
90 24
        $this->listeners_enter_misc = array
91 24
            ( 0 => array()
92 24
            );
93 24
        $this->listeners_leave_misc = array
94 24
            ( 0 => array()
95 24
            );
96 24
    }
97
98
    /**
99
     * @param   string  $path
100
     */
101 23
    public function index_file($path) {
102 23
        $content = file_get_contents($this->project_root_path."/$path");
103 23
        if ($content === false) {
104
            throw \InvalidArgumentException("Can't read file $path.");
105
        }
106
107 23
        $stmts = $this->parser->parse($content);
108 23
        if ($stmts === null) {
109
            throw new \RuntimeException("Can't parse file $path.");
110
        }
111
112 23
        $traverser = new \PhpParser\NodeTraverser;
113 23
        $traverser->addVisitor($this);
114
115 23
        $this->entity_stack = array();
116 23
        $this->file_path = $path;
117 23
        $this->file_content = explode("\n", $content);
118 23
        $traverser->traverse($stmts);
119 23
    }
120
121
    // helper
122
123 23
    private function lines_from_to($start, $end) {
124 23
        assert('is_int($start)');
125 23
        assert('is_int($end)');
126 23
        return implode("\n", array_slice($this->file_content, $start-1, $end-$start+1));
127
    }
128
129
   // from ListenerRegistry 
130
131
    /**
132
     * @inheritdoc
133
     */
134 1
    public function on_enter_entity($types, \Closure $listener) {
135 1
        $this->on_enter_or_leave_something("listeners_enter_entity", $types, $listener);
136 1
        return $this;
137
    }
138
139
    /**
140
     * @inheritdoc
141
     */
142 1
    public function on_leave_entity($types, \Closure $listener) {
143 1
        $this->on_enter_or_leave_something("listeners_leave_entity", $types, $listener);
144 1
        return $this;
145
    }
146
147
    /**
148
     * @inheritdoc
149
     */
150 23
    public function on_enter_misc($classes, \Closure $listener) {
151 23
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
152 23
        return $this;
153
    }
154
155
    /**
156
     * @inheritdoc
157
     */
158 1
    public function on_leave_misc($classes, \Closure $listener) {
159 1
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
160 1
        return $this;
161
    }
162
163
    // generalizes over over on_enter/leave_xx
164
165
    /**
166
     * @param   string      $what
167
     * @param   array|null  $things
168
     */
169 23
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
170 23
        $loc = &$this->$what;
171 23
        if ($things === null) {
172 1
            $loc[0][] = $listener;
173 1
        }
174
        else {
175 23
            foreach ($things as $thing) {
176 23
                assert('is_string($thing)');
177 23
                if (!array_key_exists($thing, $loc)) {
178 23
                    $loc[$thing] = array();
179 23
                }
180 23
                $loc[$thing][] = $listener;
181 23
            }
182
        }
183 23
    }
184
185
    // generalizes over calls to misc listeners
186
187
    /**
188
     * @param   string          $which
189
     * @param   \PhpParser\Node $node
190
     */
191 23 View Code Duplication
    protected function call_misc_listener($which, \PhpParser\Node $node) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192 23
        $listeners = &$this->$which;
193 23
        foreach ($listeners[0] as $listener) {
194
            $listener($this->insert, $this, $node);
195 23
        }
196 23
        $cls = get_class($node);
197 23
        if (array_key_exists($cls, $listeners)) {
198 21
            foreach ($listeners[$cls] as $listener) {
199 21
                $listener($this->insert, $this, $node);
200 21
            }
201 21
        }
202 23
    }
203
204
    /**
205
     * @param   string                  $which
206
     * @param   string                  $type
207
     * @param   int                     $type
208
     * @param   \PhpParser\Node|null    $node
209
     */
210 23 View Code Duplication
    protected function call_entity_listener($which, $type, $id, \PhpParser\Node $node = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211 23
        $listeners = &$this->$which;
212 23
        foreach ($listeners[0] as $listener) {
213 1
            $listener($this->insert, $this, $type, $id, $node);
214 23
        }
215 23
        if (array_key_exists($type, $listeners)) {
216
            foreach ($listeners[$type] as $listener) {
217
                $listener($this->insert, $this, $type, $id, $node);
218
            }
219
        }
220 23
    }
221
222
   // from Location
223
224
    /**
225
     * @inheritdoc
226
     */
227 17
    public function file_path() {
228 17
        return $this->file_path;
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234
    public function file_content($from_line = null, $to_line = null) {
235
        if ($from_line !== null) {
236
            assert('$to_line !== null');
237
            return $this->lines_from_to($from_line, $to_line);
238
        }
239
        else {
240
            assert('$to_line === null');
241
            return implode("\n", $this->file_content);
242
        }
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248 17
    public function in_entities() {
249 17
        return $this->entity_stack;
250
    }
251
252
    // from \PhpParser\NodeVisitor
253
254
    /**
255
     * @inheritdoc
256
     */
257 23
    public function beforeTraverse(array $nodes) {
258 23
        $this->insert->source_file($this->file_path, implode("\n", $this->file_content));
259
260
        // for sure found a file
261 23
        $id = $this->insert->entity
262 23
            ( Variable::FILE_TYPE
263 23
            , $this->file_path
264 23
            , $this->file_path
265 23
            , 1
266 23
            , count($this->file_content)
267 23
            );
268
269 23
        $this->entity_stack[] = array(Variable::FILE_TYPE, $id);
270
271 23
        $this->call_entity_listener("listeners_enter_entity", Variable::FILE_TYPE, $id, null);
272
273 23
        return null;
274
    }
275
276
    /**
277
     * @inheritdoc
278
     */
279 23
    public function afterTraverse(array $nodes) {
280 23
        list($type, $id) = array_pop($this->entity_stack);
281
282 23
        $this->call_entity_listener("listeners_leave_entity", $type, $id, null);
283
284 23
        return null;
285
    }
286
287
    /**
288
     * @inheritdoc
289
     */
290 23
    public function enterNode(\PhpParser\Node $node) {
291 23
        $start_line = $node->getAttribute("startLine");
292 23
        $end_line = $node->getAttribute("endLine");
293 23
        $source = $this->lines_from_to($start_line, $end_line);
0 ignored issues
show
Unused Code introduced by
$source 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...
294
295 23
        $type = null;
296
297
        // Class
298 23
        if ($node instanceof N\Stmt\Class_) {
299 22
            $type = Variable::CLASS_TYPE;
300 22
        }
301
        // Method or Function
302 23
        elseif ($node instanceof N\Stmt\ClassMethod) {
303 22
            $type = Variable::METHOD_TYPE;
304 22
        }
305 23
        elseif ($node instanceof N\Stmt\Function_) {
306
            $type = Variable::FUNCTION_TYPE;
307
        }
308
309 23
        if ($type !== null) {
310 22
            $id = $this->insert->entity
311 22
                ( $type
312 22
                , $node->name
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
313 22
                , $this->file_path
314 22
                , $start_line
315 22
                , $end_line
316 22
                );
317 22
            $this->call_entity_listener("listeners_enter_entity",  $type, $id, $node);
318 22
            $this->entity_stack[] = array($type, $id);
319 22
        }
320
        else {
321 23
            $this->call_misc_listener("listeners_enter_misc", $node);
322
        }
323 23
    }
324
325
    /**
326
     * @inheritdoc
327
     */
328 23
    public function leaveNode(\PhpParser\Node $node) {
329
        // Class
330
        if($node instanceof N\Stmt\Class_
331 23
        || $node instanceof N\Stmt\ClassMethod
332 23
        || $node instanceof N\Stmt\Function_) {
333 22
            list($type, $id) = array_pop($this->entity_stack);
334 22
            $this->call_entity_listener("listeners_leave_entity", $type, $id, $node);
335 22
        }
336
        else {
337 23
            $this->call_misc_listener("listeners_leave_misc", $node);
338
        }
339 23
    }
340
}
341