Completed
Push — master ( f7852e...e666ea )
by Richard
06:08
created

Indexer::on_leave_misc()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
nc 1
cc 1
eloc 3
nop 2
crap 2
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 license along with the code.
9
 */
10
11
namespace Lechimp\Dicto\Indexer;
12
13
use Lechimp\Dicto\Variables\Variable;
14
use PhpParser\Node as N;
15
use Psr\Log\LoggerInterface as Log;
16
use League\Flysystem\Adapter\Local;
17
use League\Flysystem\Filesystem;
18
use Lechimp\Flightcontrol\Flightcontrol;
19
use Lechimp\Flightcontrol\File;
20
use Lechimp\Flightcontrol\FSObject;
21
22
/**
23
 * Creates an index of source files.
24
 */
25
class Indexer implements Location, ListenerRegistry, \PhpParser\NodeVisitor {
26
    /**
27
     * @var Log
28
     */
29
    protected $log;
30
31
    /**
32
     * @var Insert
33
     */
34
    protected $insert;
35
36
    /**
37
     * @var \PhpParser\Parser
38
     */
39
    protected $parser;
40
41
    /**
42
     * @var array   string => array()
43
     */
44
    protected $listeners_enter_definition;
45
46
    /**
47
     * @var array   string => array()
48
     */
49
    protected $listeners_leave_definition;
50
51
    /**
52
     * @var array   string => array()
53
     */
54
    protected $listeners_enter_misc;
55
56
    /**
57
     * @var array   string => array()
58
     */
59
    protected $listeners_leave_misc;
60
61
    // state for parsing a file
62
63
    /**
64
     * @var string|null
65
     */
66
    protected $file_path = null;
67
68
    /**
69
     * @var string|null
70
     */
71
    protected $file_content = null;
72
73
    /**
74
     * This contains the stack of ids were currently in, i.e. the nesting of
75
     * known code blocks we are in.
76
     *
77
     * @var array|null  contain ($definition_type, $definition_id)
78
     */
79
    protected $definition_stack = null;
80
81 30
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
82 30
        $this->log = $log;
83 30
        $this->parser = $parser;
84 30
        $this->insert = $insert;
85 30
        $this->listeners_enter_definition = array
86 30
            ( 0 => array()
87 30
            );
88 30
        $this->listeners_leave_definition = array
89 30
            ( 0 => array()
90 30
            );
91 30
        $this->listeners_enter_misc = array
92 30
            ( 0 => array()
93 30
            );
94 30
        $this->listeners_leave_misc = array
95 30
            ( 0 => array()
96 30
            );
97 30
    }
98
99
    /**
100
     * Index a directory.
101
     *
102
     * @param   string  $path
103
     * @param   array   $ignore_paths
104
     * @return  null
105
     */
106 3
    public function index_directory($path, array $ignore_paths) {
107 3
        $fc = $this->init_flightcontrol($path);
108 3
        $fc->directory("/")
109 3
            ->recurseOn()
110
            ->filter(function(FSObject $obj) use (&$ignore_paths) {
111 3
                foreach ($ignore_paths as $pattern) {
112 3
                    if (preg_match("%$pattern%", $obj->path()) !== 0) {
113 3
                        return false;
114
                    }
115 3
                }
116 3
                return true;
117 3
            })
118 3
            ->foldFiles(null, function($_, File $file) use ($path) {
119
                try {
120 3
                    $this->index_file($path, $file->path());
121
                }
122 3
                catch (\PhpParser\Error $e) {
123
                    $this->log->error("in ".$file->path().": ".$e->getMessage());
124
                }
125 3
            });
126
127 3
    }
128
129
    /**
130
     * Initialize the filesystem abstraction.
131
     *
132
     * @return  Flightcontrol
133
     */
134 3
    public function init_flightcontrol($path) {
135 3
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
136 3
        $flysystem = new Filesystem($adapter);
137 3
        return new Flightcontrol($flysystem);
138
    }
139
140
    /**
141
     * @param   string  $base_dir
142
     * @param   string  $path
143
     * @return  null
144
     */
145
    public function index_file($base_dir, $path) {
146
        assert('is_string($base_dir)');
147
        assert('is_string($path)');
148
        $this->log->info("indexing: ".$path);
149
        $full_path = "$base_dir/$path";
150
        $content = file_get_contents($full_path);
151
        if ($content === false) {
152
            throw \InvalidArgumentException("Can't read file $path.");
153
        }
154
        $this->index_content($path, $content);
155
    }
156
157
    /**
158
     * @param   string  $path
159
     * @param   string  $content
160
     * @return  null
161
     */
162 30
    public function index_content($path, $content) {
163 30
        assert('is_string($path)');
164 30
        assert('is_string($content)');
165
166 30
        $stmts = $this->parser->parse($content);
167 30
        if ($stmts === null) {
168
            throw new \RuntimeException("Can't parse file $path.");
169
        }
170
171 30
        $traverser = new \PhpParser\NodeTraverser;
172 30
        $traverser->addVisitor($this);
173
174 30
        $this->definition_stack = array();
175 30
        $this->file_path = $path;
176 30
        $this->file_content = $content;
177 30
        $traverser->traverse($stmts);
178 30
    }
179
180
   // from ListenerRegistry 
181
182
    /**
183
     * @inheritdoc
184
     */
185
    public function on_enter_definition($types, \Closure $listener) {
186
        $this->on_enter_or_leave_something("listeners_enter_definition", $types, $listener);
187
        return $this;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193
    public function on_leave_definition($types, \Closure $listener) {
194
        $this->on_enter_or_leave_something("listeners_leave_definition", $types, $listener);
195
        return $this;
196
    }
197
198
    /**
199
     * @inheritdoc
200
     */
201 19
    public function on_enter_misc($classes, \Closure $listener) {
202 19
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
203 19
        return $this;
204
    }
205
206
    /**
207
     * @inheritdoc
208
     */
209
    public function on_leave_misc($classes, \Closure $listener) {
210
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
211
        return $this;
212
    }
213
214
    // generalizes over over on_enter/leave_xx
215
216
    /**
217
     * @param   string      $what
218
     * @param   array|null  $things
219
     */
220 19
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
221 19
        $loc = &$this->$what;
222 19
        if ($things === null) {
223
            $loc[0][] = $listener;
224
        }
225
        else {
226 19
            foreach ($things as $thing) {
227 19
                assert('is_string($thing)');
228 19
                if (!array_key_exists($thing, $loc)) {
229 19
                    $loc[$thing] = array();
230 19
                }
231 19
                $loc[$thing][] = $listener;
232 19
            }
233
        }
234 19
    }
235
236
    // generalizes over calls to misc listeners
237
238
    /**
239
     * @param   string          $which
240
     * @param   \PhpParser\Node $node
241
     */
242 22 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...
243 22
        $listeners = &$this->$which;
244 22
        foreach ($listeners[0] as $listener) {
245
            $listener($this->insert, $this, $node);
246 22
        }
247 22
        $cls = get_class($node);
248 22
        if (array_key_exists($cls, $listeners)) {
249 17
            foreach ($listeners[$cls] as $listener) {
250 17
                $listener($this->insert, $this, $node);
251 17
            }
252 17
        }
253 22
    }
254
255
    /**
256
     * @param   string                  $which
257
     * @param   string                  $type
258
     * @param   int                     $type
259
     * @param   \PhpParser\Node|null    $node
260
     */
261 29 View Code Duplication
    protected function call_definition_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...
262 29
        $listeners = &$this->$which;
263 29
        foreach ($listeners[0] as $listener) {
264
            $listener($this->insert, $this, $type, $id, $node);
265 29
        }
266 29
        if (array_key_exists($type, $listeners)) {
267
            foreach ($listeners[$type] as $listener) {
268
                $listener($this->insert, $this, $type, $id, $node);
269
            }
270
        }
271 29
    }
272
273
   // from Location
274
275
    /**
276
     * @inheritdoc
277
     */
278 17
    public function file() {
279 17
        return $this->definition_stack[0][1];
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285 17
    public function in_entities() {
286 17
        return $this->definition_stack;
287
    }
288
289
    // from \PhpParser\NodeVisitor
290
291
    /**
292
     * @inheritdoc
293
     */
294 30
    public function beforeTraverse(array $nodes) {
295 30
        $handle = $this->insert->_file($this->file_path, $this->file_content);
296
297 30
        $this->definition_stack[] = [Variable::FILE_TYPE, $handle];
298 30
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303 30
    public function afterTraverse(array $nodes) {
304 30
        assert('count($this->definition_stack) == 1');
305 30
        array_pop($this->definition_stack);
306 30
    }
307
308
    /**
309
     * @inheritdoc
310
     */
311 30
    public function enterNode(\PhpParser\Node $node) {
312 30
        $start_line = $node->getAttribute("startLine");
313 30
        $end_line = $node->getAttribute("endLine");
314
315 30
        $handle = null;
316 30
        $type = null;
317 30
        if ($node instanceof N\Stmt\Class_) {
318 28
            assert('count($this->definition_stack) == 1');
319 28
            $handle = $this->insert->_class
320 28
                ( $node->name
321 28
                , $this->definition_stack[0][1]
322 28
                , $start_line
323 28
                , $end_line
324 28
                );
325 28
            $type = Variable::CLASS_TYPE;
326 28
        }
327 26
        else if ($node instanceof N\Stmt\ClassMethod) {
328 24
            assert('count($this->definition_stack) == 2');
329 24
            $handle = $this->insert->_method
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $handle is correct as $this->insert->_method($...$start_line, $end_line) (which targets Lechimp\Dicto\Indexer\Insert::_method()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
330 24
                ( $node->name
331 24
                , $this->definition_stack[1][1]
332 24
                , $this->definition_stack[0][1]
333 24
                , $start_line
334 24
                , $end_line
335 24
                );
336 24
            $type = Variable::METHOD_TYPE;
337 24
        }
338 24
        else if ($node instanceof N\Stmt\Function_) {
339 2
            assert('count($this->definition_stack) == 1');
340 2
            $handle = $this->insert->_function
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $handle is correct as $this->insert->_function..._line, (int) $end_line) (which targets Lechimp\Dicto\Indexer\Insert::_function()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
341 2
                ( $node->name
342 2
                , $this->definition_stack[0][1]
343 2
                , (int)$start_line
344 2
                , (int)$end_line
345 2
                );
346 2
            $type = Variable::FUNCTION_TYPE;
347 2
        }
348
349 30
        if ($handle !== null) {
350 29
            assert('$type !== null');
351 29
            $this->call_definition_listener("listeners_enter_definition",  $type, $handle, $node);
352 29
            $this->definition_stack[] = [$type, $handle];
353
354 29
        }
355
        else {
356 22
            assert('$type === null');
357 22
            $this->call_misc_listener("listeners_enter_misc", $node);
358
        }
359 30
    }
360
361
    /**
362
     * @inheritdoc
363
     */
364 30
    public function leaveNode(\PhpParser\Node $node) {
365
        // Class
366
        if (  $node instanceof N\Stmt\Class_
367 30
           || $node instanceof N\Stmt\ClassMethod
368 30
           || $node instanceof N\Stmt\Function_) {
369
370 29
            list($type, $handle) = array_pop($this->definition_stack);
371 29
            $this->call_definition_listener("listeners_leave_definition", $type, $handle, $node);
372 29
        }
373
        else {
374 22
            $this->call_misc_listener("listeners_leave_misc", $node);
375
        }
376 30
    }
377
}
378