Completed
Push — master ( d07dcb...608de6 )
by Richard
05:27
created

Indexer::file_content()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 10
ccs 0
cts 6
cp 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
crap 6
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 = $content;
118 23
        $traverser->traverse($stmts);
119 23
    }
120
121
   // from ListenerRegistry 
122
123
    /**
124
     * @inheritdoc
125
     */
126 1
    public function on_enter_entity($types, \Closure $listener) {
127 1
        $this->on_enter_or_leave_something("listeners_enter_entity", $types, $listener);
128 1
        return $this;
129
    }
130
131
    /**
132
     * @inheritdoc
133
     */
134 1
    public function on_leave_entity($types, \Closure $listener) {
135 1
        $this->on_enter_or_leave_something("listeners_leave_entity", $types, $listener);
136 1
        return $this;
137
    }
138
139
    /**
140
     * @inheritdoc
141
     */
142 23
    public function on_enter_misc($classes, \Closure $listener) {
143 23
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
144 23
        return $this;
145
    }
146
147
    /**
148
     * @inheritdoc
149
     */
150 1
    public function on_leave_misc($classes, \Closure $listener) {
151 1
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
152 1
        return $this;
153
    }
154
155
    // generalizes over over on_enter/leave_xx
156
157
    /**
158
     * @param   string      $what
159
     * @param   array|null  $things
160
     */
161 23
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
162 23
        $loc = &$this->$what;
163 23
        if ($things === null) {
164 1
            $loc[0][] = $listener;
165 1
        }
166
        else {
167 23
            foreach ($things as $thing) {
168 23
                assert('is_string($thing)');
169 23
                if (!array_key_exists($thing, $loc)) {
170 23
                    $loc[$thing] = array();
171 23
                }
172 23
                $loc[$thing][] = $listener;
173 23
            }
174
        }
175 23
    }
176
177
    // generalizes over calls to misc listeners
178
179
    /**
180
     * @param   string          $which
181
     * @param   \PhpParser\Node $node
182
     */
183 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...
184 23
        $listeners = &$this->$which;
185 23
        foreach ($listeners[0] as $listener) {
186
            $listener($this->insert, $this, $node);
187 23
        }
188 23
        $cls = get_class($node);
189 23
        if (array_key_exists($cls, $listeners)) {
190 21
            foreach ($listeners[$cls] as $listener) {
191 21
                $listener($this->insert, $this, $node);
192 21
            }
193 21
        }
194 23
    }
195
196
    /**
197
     * @param   string                  $which
198
     * @param   string                  $type
199
     * @param   int                     $type
200
     * @param   \PhpParser\Node|null    $node
201
     */
202 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...
203 23
        $listeners = &$this->$which;
204 23
        foreach ($listeners[0] as $listener) {
205 1
            $listener($this->insert, $this, $type, $id, $node);
206 23
        }
207 23
        if (array_key_exists($type, $listeners)) {
208
            foreach ($listeners[$type] as $listener) {
209
                $listener($this->insert, $this, $type, $id, $node);
210
            }
211
        }
212 23
    }
213
214
   // from Location
215
216
    /**
217
     * @inheritdoc
218
     */
219 17
    public function file_path() {
220 17
        return $this->file_path;
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226 17
    public function in_entities() {
227 17
        return $this->entity_stack;
228
    }
229
230
    // from \PhpParser\NodeVisitor
231
232
    /**
233
     * @inheritdoc
234
     */
235 23
    public function beforeTraverse(array $nodes) {
236 23
        $this->insert->source_file($this->file_path, $this->file_content);
237
238
        // for sure found a file
239 23
        $id = $this->insert->entity
240 23
            ( Variable::FILE_TYPE
241 23
            , $this->file_path
242 23
            , $this->file_path
243 23
            , 1
244 23
            , substr_count($this->file_content, "\n") + 1
245 23
            );
246
247 23
        $this->entity_stack[] = array(Variable::FILE_TYPE, $id);
248
249 23
        $this->call_entity_listener("listeners_enter_entity", Variable::FILE_TYPE, $id, null);
250
251 23
        return null;
252
    }
253
254
    /**
255
     * @inheritdoc
256
     */
257 23
    public function afterTraverse(array $nodes) {
258 23
        list($type, $id) = array_pop($this->entity_stack);
259
260 23
        $this->call_entity_listener("listeners_leave_entity", $type, $id, null);
261
262 23
        return null;
263
    }
264
265
    /**
266
     * @inheritdoc
267
     */
268 23
    public function enterNode(\PhpParser\Node $node) {
269 23
        $start_line = $node->getAttribute("startLine");
270 23
        $end_line = $node->getAttribute("endLine");
271
272 23
        $type = null;
0 ignored issues
show
Unused Code introduced by
$type 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...
273
274
        // Class
275 23
        if ($this->is_entity($node)) {
276 22
            $type = $this->get_type_of($node);
277 22
            $id = $this->insert->entity
278 22
                ( $type
279 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...
280 22
                , $this->file_path
281 22
                , $start_line
282 22
                , $end_line
283 22
                );
284 22
            $this->call_entity_listener("listeners_enter_entity",  $type, $id, $node);
285 22
            $this->entity_stack[] = array($type, $id);
286 22
        }
287
        else {
288 23
            $this->call_misc_listener("listeners_enter_misc", $node);
289
        }
290 23
    }
291
292 22
    protected function get_type_of(\PhpParser\Node $node) {
293
        // Class
294 22
        if ($node instanceof N\Stmt\Class_) {
295 22
            return Variable::CLASS_TYPE;
296
        }
297
        // Method or Function
298 22
        elseif ($node instanceof N\Stmt\ClassMethod) {
299 22
            return Variable::METHOD_TYPE;
300
        }
301
        elseif ($node instanceof N\Stmt\Function_) {
302
            return Variable::FUNCTION_TYPE;
303
        }
304
        throw \InvalidArgumentException("'".get_class($node)."' has no type.");
305
    }
306
307 23
    protected function is_entity(\PhpParser\Node $node) {
308
        return     $node instanceof N\Stmt\Class_
309 23
                || $node instanceof N\Stmt\ClassMethod
310 23
                || $node instanceof N\Stmt\Function_;
311
    }
312
313
    /**
314
     * @inheritdoc
315
     */
316 23
    public function leaveNode(\PhpParser\Node $node) {
317
        // Class
318 23
        if($this->is_entity($node)) {
319 22
            list($type, $id) = array_pop($this->entity_stack);
320 22
            $this->call_entity_listener("listeners_leave_entity", $type, $id, $node);
321 22
        }
322
        else {
323 23
            $this->call_misc_listener("listeners_leave_misc", $node);
324
        }
325 23
    }
326
}
327