Completed
Push — master ( 1e9ef5...3740bc )
by Richard
05:53 queued 29s
created

Indexer::on_enter_something()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0582

Importance

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