Completed
Push — master ( 31785d...5192f1 )
by Richard
06:45
created

Indexer::index_content()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.0017

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 12
cts 13
cp 0.9231
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 12
nc 2
nop 2
crap 2.0017
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 3
    }
127
128
    /**
129
     * Initialize the filesystem abstraction.
130
     *
131
     * @return  Flightcontrol
132
     */
133 3
    public function init_flightcontrol($path) {
134 3
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
135 3
        $flysystem = new Filesystem($adapter);
136 3
        return new Flightcontrol($flysystem);
137
    }
138
139
    /**
140
     * @param   string  $base_dir
141
     * @param   string  $path
142
     * @return  null
143
     */
144
    public function index_file($base_dir, $path) {
145
        assert('is_string($base_dir)');
146
        assert('is_string($path)');
147
        $this->log->info("indexing: ".$path);
148
        $full_path = "$base_dir/$path";
149
        $content = file_get_contents($full_path);
150
        if ($content === false) {
151
            throw \InvalidArgumentException("Can't read file $path.");
152
        }
153
        $this->index_content($path, $content);
154
    }
155
156
    /**
157
     * @param   string  $path
158
     * @param   string  $content
159
     * @return  null
160
     */
161 37
    public function index_content($path, $content) {
162 37
        assert('is_string($path)');
163 37
        assert('is_string($content)');
164
165 37
        $stmts = $this->parser->parse($content);
166 37
        if ($stmts === null) {
167
            throw new \RuntimeException("Can't parse file $path.");
168
        }
169
170 37
        $traverser = new \PhpParser\NodeTraverser;
171 37
        $traverser->addVisitor($this);
172
173 37
        $this->definition_stack = array();
174 37
        $this->file_path = $path;
175 37
        $this->file_content = $content;
176 37
        $traverser->traverse($stmts);
177 37
    }
178
179
   // from ListenerRegistry 
180
181
    /**
182
     * @inheritdoc
183
     */
184
    public function on_enter_definition($types, \Closure $listener) {
185
        $this->on_enter_or_leave_something("listeners_enter_definition", $types, $listener);
186
        return $this;
187
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192
    public function on_leave_definition($types, \Closure $listener) {
193
        $this->on_enter_or_leave_something("listeners_leave_definition", $types, $listener);
194
        return $this;
195
    }
196
197
    /**
198
     * @inheritdoc
199
     */
200 24
    public function on_enter_misc($classes, \Closure $listener) {
201 24
        $this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener);
202 24
        return $this;
203
    }
204
205
    /**
206
     * @inheritdoc
207
     */
208
    public function on_leave_misc($classes, \Closure $listener) {
209
        $this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener);
210
        return $this;
211
    }
212
213
    // generalizes over over on_enter/leave_xx
214
215
    /**
216
     * @param   string      $what
217
     * @param   array|null  $things
218
     */
219 24
    protected function on_enter_or_leave_something($what, $things, \Closure $listener) {
220 24
        $loc = &$this->$what;
221 24
        if ($things === null) {
222
            $loc[0][] = $listener;
223
        }
224
        else {
225 24
            foreach ($things as $thing) {
226 24
                assert('is_string($thing)');
227 24
                if (!array_key_exists($thing, $loc)) {
228 24
                    $loc[$thing] = array();
229 24
                }
230 24
                $loc[$thing][] = $listener;
231 24
            }
232
        }
233 24
    }
234
235
    // generalizes over calls to misc listeners
236
237
    /**
238
     * @param   string          $which
239
     * @param   \PhpParser\Node $node
240
     */
241 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...
242 27
        $listeners = &$this->$which;
243 27
        foreach ($listeners[0] as $listener) {
244
            $listener($this->insert, $this, $node);
245 27
        }
246 27
        $cls = get_class($node);
247 27
        if (array_key_exists($cls, $listeners)) {
248 22
            foreach ($listeners[$cls] as $listener) {
249 22
                $listener($this->insert, $this, $node);
250 22
            }
251 22
        }
252 27
    }
253
254
    /**
255
     * @param   string                  $which
256
     * @param   string                  $type
257
     * @param   int                     $type
258
     * @param   \PhpParser\Node|null    $node
259
     */
260 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...
261 36
        $listeners = &$this->$which;
262 36
        foreach ($listeners[0] as $listener) {
263
            $listener($this->insert, $this, $type, $id, $node);
264 36
        }
265 36
        if (array_key_exists($type, $listeners)) {
266
            foreach ($listeners[$type] as $listener) {
267
                $listener($this->insert, $this, $type, $id, $node);
268
            }
269
        }
270 36
    }
271
272
   // from Location
273
274
    /**
275
     * @inheritdoc
276
     */
277 22
    public function file() {
278 22
        return $this->definition_stack[0][1];
279
    }
280
281
    /**
282
     * @inheritdoc
283
     */
284 22
    public function in_entities() {
285 22
        return $this->definition_stack;
286
    }
287
288
    // from \PhpParser\NodeVisitor
289
290
    /**
291
     * @inheritdoc
292
     */
293 37
    public function beforeTraverse(array $nodes) {
294 37
        $handle = $this->insert->_file($this->file_path, $this->file_content);
295
296 37
        $this->definition_stack[] = [Variable::FILE_TYPE, $handle];
297 37
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302 37
    public function afterTraverse(array $nodes) {
303 37
        assert('count($this->definition_stack) == 1');
304 37
        array_pop($this->definition_stack);
305 37
    }
306
307
    /**
308
     * @inheritdoc
309
     */
310 37
    public function enterNode(\PhpParser\Node $node) {
311 37
        $start_line = $node->getAttribute("startLine");
312 37
        $end_line = $node->getAttribute("endLine");
313
314 37
        $handle = null;
315 37
        $type = null;
316 37
        if ($node instanceof N\Stmt\Class_) {
317 33
            assert('count($this->definition_stack) == 1');
318 33
            $handle = $this->insert->_class
319 33
                ( $node->name
320 33
                , $this->definition_stack[0][1]
321 33
                , $start_line
322 33
                , $end_line
323 33
                );
324 33
            $type = Variable::CLASS_TYPE;
325 33
        }
326 33
        else if ($node instanceof N\Stmt\Interface_) {
327 2
            assert('count($this->definition_stack) == 1');
328 2
            $handle = $this->insert->_interface
329 2
                ( $node->name
330 2
                , $this->definition_stack[0][1]
331 2
                , $start_line
332 2
                , $end_line
333 2
                );
334 2
            $type = Variable::INTERFACE_TYPE;
335 2
        }
336 32
        else if ($node instanceof N\Stmt\ClassMethod) {
337 30
            assert('count($this->definition_stack) == 2');
338 30
            $handle = $this->insert->_method
339 30
                ( $node->name
340 30
                , $this->definition_stack[1][1]
341 30
                , $this->definition_stack[0][1]
342 30
                , $start_line
343 30
                , $end_line
344 30
                );
345 30
            $type = Variable::METHOD_TYPE;
346 30
        }
347 29
        else if ($node instanceof N\Stmt\Function_) {
348 2
            assert('count($this->definition_stack) >= 1');
349 2
            $handle = $this->insert->_function
350 2
                ( $node->name
351 2
                , $this->definition_stack[0][1]
352 2
                , (int)$start_line
353 2
                , (int)$end_line
354 2
                );
355 2
            $type = Variable::FUNCTION_TYPE;
356 2
        }
357
358 37
        if ($handle !== null) {
359 36
            assert('$type !== null');
360 36
            $this->call_definition_listener("listeners_enter_definition",  $type, $handle, $node);
361 36
            $this->definition_stack[] = [$type, $handle];
362
363 36
        }
364
        else {
365 27
            assert('$type === null');
366 27
            $this->call_misc_listener("listeners_enter_misc", $node);
367
        }
368 37
    }
369
370
    /**
371
     * @inheritdoc
372
     */
373 37
    public function leaveNode(\PhpParser\Node $node) {
374
        // Class
375
        if (  $node instanceof N\Stmt\Class_
376 37
           || $node instanceof N\Stmt\Interface_
377 33
           || $node instanceof N\Stmt\ClassMethod
378 37
           || $node instanceof N\Stmt\Function_) {
379
380 36
            list($type, $handle) = array_pop($this->definition_stack);
381 36
            $this->call_definition_listener("listeners_leave_definition", $type, $handle, $node);
382 36
        }
383
        else {
384 27
            $this->call_misc_listener("listeners_leave_misc", $node);
385
        }
386 37
    }
387
}
388