Completed
Push — master ( 9b2a73...93cace )
by Richard
05:06
created

Indexer::file()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
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 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
    /**
52
     * @var LocationImpl|null
53
     */
54
    protected $location = null;
55
56 42
    public function __construct(Log $log, \PhpParser\Parser $parser, Insert $insert) {
57 42
        $this->log = $log;
58 42
        $this->parser = $parser;
59 42
        $this->insert = $insert;
60 42
        $this->listeners_enter_definition = array
61 42
            ( 0 => array()
62 42
            );
63 42
        $this->listeners_enter_misc = array
64 42
            ( 0 => array()
65 42
            );
66 42
    }
67
68
    /**
69
     * Index a directory.
70
     *
71
     * @param   string  $path
72
     * @param   array   $ignore_paths
73
     * @return  null
74
     */
75 7
    public function index_directory($path, array $ignore_paths) {
76 7
        $fc = $this->init_flightcontrol($path);
77 7
        $fc->directory("/")
78 7
            ->recurseOn()
79
            ->filter(function(FSObject $obj) use (&$ignore_paths) {
80 7
                foreach ($ignore_paths as $pattern) {
81 7
                    if (preg_match("%$pattern%", $obj->path()) !== 0) {
82 7
                        return false;
83
                    }
84 7
                }
85 7
                return true;
86 7
            })
87 7
            ->foldFiles(null, function($_, File $file) use ($path) {
88
                try {
89 7
                    $this->index_file($path, $file->path());
90
                }
91 7
                catch (\PhpParser\Error $e) {
92
                    $this->log->error("in ".$file->path().": ".$e->getMessage());
93
                }
94 7
            });
95 7
    }
96
97
    /**
98
     * Initialize the filesystem abstraction.
99
     *
100
     * @return  Flightcontrol
101
     */
102 7
    public function init_flightcontrol($path) {
103 7
        $adapter = new Local($path, LOCK_EX, Local::SKIP_LINKS);
104 7
        $flysystem = new Filesystem($adapter);
105 7
        return new Flightcontrol($flysystem);
106
    }
107
108
    /**
109
     * @param   string  $base_dir
110
     * @param   string  $path
111
     * @return  null
112
     */
113
    public function index_file($base_dir, $path) {
114
        assert('is_string($base_dir)');
115
        assert('is_string($path)');
116
        $this->log->info("indexing: ".$path);
117
        $full_path = "$base_dir/$path";
118
        $content = file_get_contents($full_path);
119
        if ($content === false) {
120
            throw \InvalidArgumentException("Can't read file $path.");
121
        }
122
        $this->index_content($path, $content);
123
    }
124
125
    /**
126
     * @param   string  $path
127
     * @param   string  $content
128
     * @return  null
129
     */
130 42
    public function index_content($path, $content) {
131 42
        assert('is_string($path)');
132 42
        assert('is_string($content)');
133
134 42
        $stmts = $this->parser->parse($content);
135 42
        if ($stmts === null) {
136
            throw new \RuntimeException("Can't parse file $path.");
137
        }
138
139 42
        $traverser = new \PhpParser\NodeTraverser;
140 42
        $traverser->addVisitor($this);
141
142 42
        $this->location = new LocationImpl($path, $content);
143 42
        $traverser->traverse($stmts);
144 42
        $this->location = null;
145 42
    }
146
147
   // from ListenerRegistry 
148
149
    /**
150
     * @inheritdoc
151
     */
152
    public function on_enter_definition($types, \Closure $listener) {
153
        $this->on_enter_something("listeners_enter_definition", $types, $listener);
154
        return $this;
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160 25
    public function on_enter_misc($classes, \Closure $listener) {
161 25
        $this->on_enter_something("listeners_enter_misc", $classes, $listener);
162 25
        return $this;
163
    }
164
165
    // generalizes over over on_enter
166
167
    /**
168
     * TODO: Maybe remove this in favour of duplicates.
169
     *
170
     * @param   string      $what
171
     * @param   array|null  $things
172
     */
173 25
    protected function on_enter_something($what, $things, \Closure $listener) {
174 25
        $loc = &$this->$what;
175 25
        if ($things === null) {
176
            $loc[0][] = $listener;
177
        }
178
        else {
179 25
            foreach ($things as $thing) {
180 25
                assert('is_string($thing)');
181 25
                if (!array_key_exists($thing, $loc)) {
182 25
                    $loc[$thing] = array();
183 25
                }
184 25
                $loc[$thing][] = $listener;
185 25
            }
186
        }
187 25
    }
188
189
    // generalizes over calls to misc listeners
190
191
    /**
192
     * @param   string          $which
193
     * @param   \PhpParser\Node $node
0 ignored issues
show
Bug introduced by
There is no parameter named $node. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
194
     */
195 31 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...
196 31
        $listeners = &$this->$which;
197 31
        $current_node = $this->location->current_node();
198 31
        foreach ($listeners[0] as $listener) {
199
            $listener($this->insert, $this->location, $current_node);
200 31
        }
201 31
        $cls = get_class($current_node);
202 31
        if (array_key_exists($cls, $listeners)) {
203 23
            foreach ($listeners[$cls] as $listener) {
204 23
                $listener($this->insert, $this->location, $current_node);
205 23
            }
206 23
        }
207 31
    }
208
209
    /**
210
     * @param   string                  $which
211
     * @param   string                  $type
212
     * @param   int                     $type
213
     * @param   \PhpParser\Node|null    $node
0 ignored issues
show
Bug introduced by
There is no parameter named $node. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
214
     */
215 40 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...
216 40
        $listeners = &$this->$which;
217 40
        $current_node = $this->location->current_node();
218 40
        foreach ($listeners[0] as $listener) {
219
            $listener($this->insert, $this->location, $type, $id, $current_node);
220 40
        }
221 40
        if (array_key_exists($type, $listeners)) {
222
            foreach ($listeners[$type] as $listener) {
223
                $listener($this->insert, $this->location, $type, $id, $current_node);
224
            }
225
        }
226 40
    }
227
228
    // from \PhpParser\NodeVisitor
229
230
    /**
231
     * @inheritdoc
232
     */
233 42
    public function beforeTraverse(array $nodes) {
234 42
        $handle = $this->insert->_file($this->location->file_name(), $this->location->file_content());
235 42
        $this->location->push_entity(Variable::FILE_TYPE, $handle);
236 42
    }
237
238
    /**
239
     * @inheritdoc
240
     */
241 42
    public function afterTraverse(array $nodes) {
242 42
    }
243
244
    /**
245
     * @inheritdoc
246
     */
247 42
    public function enterNode(\PhpParser\Node $node) {
248 42
        $start_line = $node->getAttribute("startLine");
249 42
        $end_line = $node->getAttribute("endLine");
250
251 42
        $this->location->set_current_node($node);
252
253 42
        $handle = null;
254 42
        $type = null;
255 42
        if ($node instanceof N\Stmt\Class_) {
256 37
            assert('$this->location->count_in_entity() == 1');
257 37
            $handle = $this->insert->_class
258 37
                ( $node->name
259 37
                , $this->location->in_entity(0)[1]
260 37
                , $start_line
261 37
                , $end_line
262 37
                );
263 37
            $type = Variable::CLASS_TYPE;
264 37
        }
265 38
        else if ($node instanceof N\Stmt\Interface_) {
266 2
            assert('$this->location->count_in_entity() == 1');
267 2
            $handle = $this->insert->_interface
268 2
                ( $node->name
269 2
                , $this->location->in_entity(0)[1]
270 2
                , $start_line
271 2
                , $end_line
272 2
                );
273 2
            $type = Variable::INTERFACE_TYPE;
274 2
        }
275 37
        else if ($node instanceof N\Stmt\ClassMethod) {
276 34
            assert('$this->location->count_in_entity() == 2');
277 34
            $handle = $this->insert->_method
278 34
                ( $node->name
279 34
                , $this->location->in_entity(1)[1]
280 34
                , $this->location->in_entity(0)[1]
281 34
                , $start_line
282 34
                , $end_line
283 34
                );
284 34
            $type = Variable::METHOD_TYPE;
285 34
        }
286 33
        else if ($node instanceof N\Stmt\Function_) {
287 2
            assert('$this->location->count_in_entity() == 1');
288 2
            $handle = $this->insert->_function
289 2
                ( $node->name
290 2
                , $this->location->in_entity(0)[1]
291 2
                , (int)$start_line
292 2
                , (int)$end_line
293 2
                );
294 2
            $type = Variable::FUNCTION_TYPE;
295 2
        }
296
297 42
        if ($handle !== null) {
298 40
            assert('$type !== null');
299 40
            $this->call_definition_listener("listeners_enter_definition",  $type, $handle);
300 40
            $this->location->push_entity($type, $handle);
301
302 40
        }
303
        else {
304 31
            assert('$type === null');
305 31
            $this->call_misc_listener("listeners_enter_misc");
306
        }
307
308 42
        $this->location->flush_current_node();
309 42
    }
310
311
    /**
312
     * @inheritdoc
313
     */
314 42
    public function leaveNode(\PhpParser\Node $node) {
315
        // Class
316
        if (  $node instanceof N\Stmt\Class_
317 42
           || $node instanceof N\Stmt\Interface_
318 38
           || $node instanceof N\Stmt\ClassMethod
319 42
           || $node instanceof N\Stmt\Function_) {
320
321 40
            $this->location->pop_entity();
322 40
        }
323 42
    }
324
}
325