Completed
Push — master ( 7bef7a...470c23 )
by Richard
07:03
created

Indexer::index_directory()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 4.0027

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 17
cts 18
cp 0.9444
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 17
nc 1
nop 2
crap 4.0027
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\Regexp;
14
use Lechimp\Dicto\Variables\Variable;
15
use PhpParser\Node as N;
16
use Psr\Log\LoggerInterface as Log;
17
use League\Flysystem\Adapter\Local;
18
use League\Flysystem\Filesystem;
19
use Lechimp\Flightcontrol\Flightcontrol;
20
use Lechimp\Flightcontrol\File;
21
use Lechimp\Flightcontrol\FSObject;
22
23
/**
24
 * Creates an index of source files.
25
 */
26
class Indexer implements ListenerRegistry, \PhpParser\NodeVisitor {
27
    /**
28
     * @var Log
29
     */
30
    protected $log;
31
32
    /**
33
     * @var Insert
34
     */
35
    protected $insert;
36
37
    /**
38
     * @var \PhpParser\Parser
39
     */
40
    protected $parser;
41
42
    /**
43
     * @var array   string => array()
44
     */
45
    protected $listeners_enter_definition;
46
47
    /**
48
     * @var array   string => array()
49
     */
50
    protected $listeners_enter_misc;
51
52
    /**
53
     * @var LocationImpl|null
54
     */
55
    protected $location = null;
56
57 52
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
58 52
        $this->log = $log;
59 52
        $this->parser = $parser;
60 52
        $this->insert = $insert;
61 52
        $this->listeners_enter_definition = array
62 52
            ( 0 => array()
63 52
            );
64 52
        $this->listeners_enter_misc = array
65 52
            ( 0 => array()
66 52
            );
67 52
    }
68
69
    /**
70
     * Index a directory.
71
     *
72
     * @param   string  $path
73
     * @param   array   $ignore_paths
74
     * @return  null
75
     */
76 7
    public function index_directory($path, array $ignore_paths) {
77
        $ignore_paths_re = array_map(function($ignore) {
78 7
            return new Regexp($ignore);
79 7
        }, $ignore_paths);
80 7
        $fc = $this->init_flightcontrol($path);
81 7
        $fc->directory("/")
82 7
            ->recurseOn()
83
            ->filter(function(FSObject $obj) use (&$ignore_paths_re) {
84 7
                foreach ($ignore_paths_re as $re) {
85 7
                    if ($re->match($obj->path())) {
86 7
                        return false;
87
                    }
88 7
                }
89 7
                return true;
90 7
            })
91 7
            ->foldFiles(null, function($_, File $file) use ($path) {
92
                try {
93 7
                    $this->index_file($path, $file->path());
94
                }
95 7
                catch (\PhpParser\Error $e) {
96
                    $this->log->error("in ".$file->path().": ".$e->getMessage());
97
                }
98 7
            });
99 7
    }
100
101
    /**
102
     * Initialize the filesystem abstraction.
103
     *
104
     * @return  Flightcontrol
105
     */
106 7
    public function init_flightcontrol($path) {
107 7
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
108 7
        $flysystem = new Filesystem($adapter);
109 7
        return new Flightcontrol($flysystem);
110
    }
111
112
    /**
113
     * @param   string  $base_dir
114
     * @param   string  $path
115
     * @return  null
116
     */
117
    public function index_file($base_dir, $path) {
118
        assert('is_string($base_dir)');
119
        assert('is_string($path)');
120
        $this->log->info("indexing: ".$path);
121
        $full_path = "$base_dir/$path";
122
        $content = file_get_contents($full_path);
123
        if ($content === false) {
124
            throw \InvalidArgumentException("Can't read file $path.");
125
        }
126
        $this->index_content($path, $content);
127
    }
128
129
    /**
130
     * @param   string  $path
131
     * @param   string  $content
132
     * @return  null
133
     */
134 52
    public function index_content($path, $content) {
135 52
        assert('is_string($path)');
136 52
        assert('is_string($content)');
137
138 52
        $stmts = $this->parser->parse($content);
139 52
        if ($stmts === null) {
140
            throw new \RuntimeException("Can't parse file $path.");
141
        }
142
143 52
        $traverser = new \PhpParser\NodeTraverser;
144 52
        $traverser->addVisitor($this);
145
146 52
        $this->location = new LocationImpl($path, $content);
147 52
        $traverser->traverse($stmts);
148 52
        $this->location = null;
149 52
    }
150
151
   // from ListenerRegistry 
152
153
    /**
154
     * @inheritdoc
155
     */
156
    public function on_enter_definition($types, \Closure $listener) {
157
        $this->on_enter_something("listeners_enter_definition", $types, $listener);
158
        return $this;
159
    }
160
161
    /**
162
     * @inheritdoc
163
     */
164 25
    public function on_enter_misc($classes, \Closure $listener) {
165 25
        $this->on_enter_something("listeners_enter_misc", $classes, $listener);
166 25
        return $this;
167
    }
168
169
    // generalizes over over on_enter
170
171
    /**
172
     * TODO: Maybe remove this in favour of duplicates.
173
     *
174
     * @param   string      $what
175
     * @param   array|null  $things
176
     */
177 25
    protected function on_enter_something($what, $things, \Closure $listener) {
178 25
        $loc = &$this->$what;
179 25
        if ($things === null) {
180
            $loc[0][] = $listener;
181
        }
182
        else {
183 25
            foreach ($things as $thing) {
184 25
                assert('is_string($thing)');
185 25
                if (!array_key_exists($thing, $loc)) {
186 25
                    $loc[$thing] = array();
187 25
                }
188 25
                $loc[$thing][] = $listener;
189 25
            }
190
        }
191 25
    }
192
193
    // generalizes over calls to misc listeners
194
195
    /**
196
     * @param   string          $which
197
     */
198 37 View Code Duplication
    protected function call_misc_listener($which) {
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...
199 37
        $listeners = &$this->$which;
200 37
        $current_node = $this->location->current_node();
201 37
        foreach ($listeners[0] as $listener) {
202
            $listener($this->insert, $this->location, $current_node);
203 37
        }
204 37
        $cls = get_class($current_node);
205 37
        if (array_key_exists($cls, $listeners)) {
206 23
            foreach ($listeners[$cls] as $listener) {
207 23
                $listener($this->insert, $this->location, $current_node);
208 23
            }
209 23
        }
210 37
    }
211
212
    /**
213
     * @param   string                  $which
214
     * @param   string                  $type
215
     * @param   int                     $type
216
     */
217 50 View Code Duplication
    protected function call_definition_listener($which, $type, $id) {
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...
218 50
        $listeners = &$this->$which;
219 50
        $current_node = $this->location->current_node();
220 50
        foreach ($listeners[0] as $listener) {
221
            $listener($this->insert, $this->location, $type, $id, $current_node);
222 50
        }
223 50
        if (array_key_exists($type, $listeners)) {
224
            foreach ($listeners[$type] as $listener) {
225
                $listener($this->insert, $this->location, $type, $id, $current_node);
226
            }
227
        }
228 50
    }
229
230
    // from \PhpParser\NodeVisitor
231
232
    /**
233
     * @inheritdoc
234
     */
235 52
    public function beforeTraverse(array $nodes) {
236 52
        $handle = $this->insert->_file($this->location->_file_name(), $this->location->_file_content());
237 52
        $this->location->push_entity(Variable::FILE_TYPE, $handle);
238 52
    }
239
240
    /**
241
     * @inheritdoc
242
     */
243 52
    public function afterTraverse(array $nodes) {
244 52
    }
245
246
    /**
247
     * @inheritdoc
248
     */
249 52
    public function enterNode(\PhpParser\Node $node) {
250 52
        $start_line = $node->getAttribute("startLine");
251 52
        $end_line = $node->getAttribute("endLine");
252
253 52
        $this->location->set_current_node($node);
254
255 52
        $handle = null;
256 52
        $type = null;
257 52
        if ($node instanceof N\Stmt\Namespace_) {
258 6
            $handle = $this->insert->_namespace
259 6
                ( $node->name
0 ignored issues
show
Documentation introduced by
$node->name is of type null|object<PhpParser\Node\Name>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
260 6
                );
261 6
            $type = Variable::NAMESPACE_TYPE;
262 6
        }
263 52
        else if ($node instanceof N\Stmt\Class_) {
264 43
            $handle = $this->insert->_class
265 43
                ( $node->name
266 43
                , $this->location->_file()
267 43
                , $start_line
268 43
                , $end_line
269 43
                , $this->location->_namespace()
270 43
                );
271 43
            $type = Variable::CLASS_TYPE;
272 43
        }
273 48
        else if ($node instanceof N\Stmt\Interface_) {
274 3
            $handle = $this->insert->_interface
275 3
                ( $node->name
276 3
                , $this->location->_file()
277 3
                , $start_line
278 3
                , $end_line
279 3
                , $this->location->_namespace()
280 3
                );
281 3
            $type = Variable::INTERFACE_TYPE;
282 3
        }
283 47
        else if ($node instanceof N\Stmt\Trait_) {
284 3
            $handle = $this->insert->_trait
285 3
                ( $node->name
286 3
                , $this->location->_file()
287 3
                , $start_line
288 3
                , $end_line
289 3
                , $this->location->_namespace()
290 3
                );
291 3
            $type = Variable::INTERFACE_TYPE;
292 3
        }
293 46
        else if ($node instanceof N\Stmt\ClassMethod) {
294 38
            $handle = $this->insert->_method
295 38
                ( $node->name
296 38
                , $this->location->_class_interface_trait()
297 38
                , $this->location->_file()
298 38
                , $start_line
299 38
                , $end_line
300 38
                );
301 38
            $type = Variable::METHOD_TYPE;
302 38
        }
303 41
        else if ($node instanceof N\Stmt\Function_) {
304 4
            $handle = $this->insert->_function
305 4
                ( $node->name
306 4
                , $this->location->_file()
307 4
                , (int)$start_line
308 4
                , (int)$end_line
309 4
                );
310 4
            $type = Variable::FUNCTION_TYPE;
311 4
        }
312
313 52
        if ($handle !== null) {
314 50
            assert('$type !== null');
315 50
            if ($type !== Variable::NAMESPACE_TYPE) {
316 50
               $this->call_definition_listener("listeners_enter_definition",  $type, $handle);
317 50
            }
318 50
            $this->location->push_entity($type, $handle);
319 50
        }
320
        else {
321 37
            assert('$type === null');
322 37
            $this->call_misc_listener("listeners_enter_misc");
323
        }
324
325 52
        $this->location->flush_current_node();
326 52
    }
327
328
    /**
329
     * @inheritdoc
330
     */
331 52
    public function leaveNode(\PhpParser\Node $node) {
332
        if (  $node instanceof N\Stmt\Namespace_
333 52
           || $node instanceof N\Stmt\Class_
334 52
           || $node instanceof N\Stmt\Interface_
335 48
           || $node instanceof N\Stmt\ClassMethod
336 52
           || $node instanceof N\Stmt\Function_) {
337
338 49
            $this->location->pop_entity();
339 49
        }
340 52
    }
341
}
342