Completed
Push — master ( 69f641...8fff44 )
by Richard
05:35
created

Indexer::leaveNode()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 9
nc 2
nop 1
crap 5
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 37
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
82 37
        $this->log = $log;
83 37
        $this->parser = $parser;
84 37
        $this->insert = $insert;
85 37
        $this->listeners_enter_definition = array
86 37
            ( 0 => array()
87 37
            );
88 37
        $this->listeners_leave_definition = array
89 37
            ( 0 => array()
90 37
            );
91 37
        $this->listeners_enter_misc = array
92 37
            ( 0 => array()
93 37
            );
94 37
        $this->listeners_leave_misc = array
95 37
            ( 0 => array()
96 37
            );
97 37
    }
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 37
    public function index_content($path, $content) {
163 37
        assert('is_string($path)');
164 37
        assert('is_string($content)');
165
166 37
        $stmts = $this->parser->parse($content);
167 37
        if ($stmts === null) {
168
            throw new \RuntimeException("Can't parse file $path.");
169
        }
170
171 37
        $traverser = new \PhpParser\NodeTraverser;
172 37
        $traverser->addVisitor($this);
173
174 37
        $this->definition_stack = array();
175 37
        $this->file_path = $path;
176 37
        $this->file_content = $content;
177 37
        $traverser->traverse($stmts);
178 37
    }
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 24
    public function on_enter_misc($classes, \Closure $listener) {
202 24
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
203 24
        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 24
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
221 24
        $loc = &$this->$what;
222 24
        if ($things === null) {
223
            $loc[0][] = $listener;
224
        }
225
        else {
226 24
            foreach ($things as $thing) {
227 24
                assert('is_string($thing)');
228 24
                if (!array_key_exists($thing, $loc)) {
229 24
                    $loc[$thing] = array();
230 24
                }
231 24
                $loc[$thing][] = $listener;
232 24
            }
233
        }
234 24
    }
235
236
    // generalizes over calls to misc listeners
237
238
    /**
239
     * @param   string          $which
240
     * @param   \PhpParser\Node $node
241
     */
242 27 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 27
        $listeners = &$this->$which;
244 27
        foreach ($listeners[0] as $listener) {
245
            $listener($this->insert, $this, $node);
246 27
        }
247 27
        $cls = get_class($node);
248 27
        if (array_key_exists($cls, $listeners)) {
249 22
            foreach ($listeners[$cls] as $listener) {
250 22
                $listener($this->insert, $this, $node);
251 22
            }
252 22
        }
253 27
    }
254
255
    /**
256
     * @param   string                  $which
257
     * @param   string                  $type
258
     * @param   int                     $type
259
     * @param   \PhpParser\Node|null    $node
260
     */
261 36 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 36
        $listeners = &$this->$which;
263 36
        foreach ($listeners[0] as $listener) {
264
            $listener($this->insert, $this, $type, $id, $node);
265 36
        }
266 36
        if (array_key_exists($type, $listeners)) {
267
            foreach ($listeners[$type] as $listener) {
268
                $listener($this->insert, $this, $type, $id, $node);
269
            }
270
        }
271 36
    }
272
273
   // from Location
274
275
    /**
276
     * @inheritdoc
277
     */
278 22
    public function file() {
279 22
        return $this->definition_stack[0][1];
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285 22
    public function in_entities() {
286 22
        return $this->definition_stack;
287
    }
288
289
    // from \PhpParser\NodeVisitor
290
291
    /**
292
     * @inheritdoc
293
     */
294 37
    public function beforeTraverse(array $nodes) {
295 37
        $handle = $this->insert->_file($this->file_path, $this->file_content);
296
297 37
        $this->definition_stack[] = [Variable::FILE_TYPE, $handle];
298 37
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303 37
    public function afterTraverse(array $nodes) {
304 37
        assert('count($this->definition_stack) == 1');
305 37
        array_pop($this->definition_stack);
306 37
    }
307
308
    /**
309
     * @inheritdoc
310
     */
311 37
    public function enterNode(\PhpParser\Node $node) {
312 37
        $start_line = $node->getAttribute("startLine");
313 37
        $end_line = $node->getAttribute("endLine");
314
315 37
        $handle = null;
316 37
        $type = null;
317 37
        if ($node instanceof N\Stmt\Class_) {
318 33
            assert('count($this->definition_stack) == 1');
319 33
            $handle = $this->insert->_class
320 33
                ( $node->name
321 33
                , $this->definition_stack[0][1]
322 33
                , $start_line
323 33
                , $end_line
324 33
                );
325 33
            $type = Variable::CLASS_TYPE;
326 33
        }
327 33
        else if ($node instanceof N\Stmt\Interface_) {
328 2
            assert('count($this->definition_stack) == 1');
329 2
            $handle = $this->insert->_interface
330 2
                ( $node->name
331 2
                , $this->definition_stack[0][1]
332 2
                , $start_line
333 2
                , $end_line
334 2
                );
335 2
            $type = Variable::INTERFACE_TYPE;
336 2
        }
337 32
        else if ($node instanceof N\Stmt\ClassMethod) {
338 30
            assert('count($this->definition_stack) == 2');
339 30
            $handle = $this->insert->_method
340 30
                ( $node->name
341 30
                , $this->definition_stack[1][1]
342 30
                , $this->definition_stack[0][1]
343 30
                , $start_line
344 30
                , $end_line
345 30
                );
346 30
            $type = Variable::METHOD_TYPE;
347 30
        }
348 29
        else if ($node instanceof N\Stmt\Function_) {
349 2
            assert('count($this->definition_stack) == 1');
350 2
            $handle = $this->insert->_function
351 2
                ( $node->name
352 2
                , $this->definition_stack[0][1]
353 2
                , (int)$start_line
354 2
                , (int)$end_line
355 2
                );
356 2
            $type = Variable::FUNCTION_TYPE;
357 2
        }
358
359 37
        if ($handle !== null) {
360 36
            assert('$type !== null');
361 36
            $this->call_definition_listener("listeners_enter_definition",  $type, $handle, $node);
362 36
            $this->definition_stack[] = [$type, $handle];
363
364 36
        }
365
        else {
366 27
            assert('$type === null');
367 27
            $this->call_misc_listener("listeners_enter_misc", $node);
368
        }
369 37
    }
370
371
    /**
372
     * @inheritdoc
373
     */
374 37
    public function leaveNode(\PhpParser\Node $node) {
375
        // Class
376
        if (  $node instanceof N\Stmt\Class_
377 37
           || $node instanceof N\Stmt\Interface_
378 33
           || $node instanceof N\Stmt\ClassMethod
379 37
           || $node instanceof N\Stmt\Function_) {
380
381 36
            list($type, $handle) = array_pop($this->definition_stack);
382 36
            $this->call_definition_listener("listeners_leave_definition", $type, $handle, $node);
383 36
        }
384
        else {
385 27
            $this->call_misc_listener("listeners_leave_misc", $node);
386
        }
387 37
    }
388
}
389