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 licence along with the code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace Lechimp\Dicto\Indexer; |
12
|
|
|
|
13
|
|
|
use Lechimp\Dicto\Indexer as I; |
14
|
|
|
use Lechimp\Dicto\Variables\Variable; |
15
|
|
|
use PhpParser\Node as N; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Implementation of Indexer with PhpParser. |
19
|
|
|
*/ |
20
|
|
|
class Indexer implements Location, ListenerRegistry, \PhpParser\NodeVisitor { |
21
|
|
|
/** |
22
|
|
|
* @var string |
23
|
|
|
*/ |
24
|
|
|
protected $project_root_path; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var I\Insert|null |
28
|
|
|
*/ |
29
|
|
|
protected $insert; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var \PhpParser\Parser |
33
|
|
|
*/ |
34
|
|
|
protected $parser; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var array string => array() |
38
|
|
|
*/ |
39
|
|
|
protected $listeners; |
40
|
|
|
|
41
|
|
|
// state for parsing a file |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var string|null |
45
|
|
|
*/ |
46
|
|
|
protected $file_path = null; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var string[]|null |
50
|
|
|
*/ |
51
|
|
|
protected $file_content = null; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* This contains the stack of ids were currently in, i.e. the nesting of |
55
|
|
|
* known code blocks we are in. |
56
|
|
|
* |
57
|
|
|
* @var array|null contain ($entity_type, $entity_id) |
58
|
|
|
*/ |
59
|
|
|
protected $entity_stack = null; |
60
|
|
|
|
61
|
22 |
|
public function __construct(\PhpParser\Parser $parser, $project_root_path, Insert $insert) { |
62
|
22 |
|
$this->parser = $parser; |
63
|
22 |
|
assert('is_string($project_root_path)'); |
64
|
22 |
|
$this->project_root_path = $project_root_path; |
65
|
22 |
|
$this->insert = $insert; |
66
|
|
|
// TODO: This could contain class names from PhpParser as optimisation. |
67
|
22 |
|
$this->listeners = array("misc" => array()); |
68
|
|
|
// TODO: This should be more dynamic. |
69
|
22 |
|
$this->register_listeners(); |
70
|
22 |
|
} |
71
|
|
|
|
72
|
22 |
|
protected function register_listeners() { |
73
|
22 |
|
$d = new \Lechimp\Dicto\Rules\DependOn(); |
74
|
22 |
|
$d->register_listeners($this); |
75
|
22 |
|
$i = new \Lechimp\Dicto\Rules\Invoke(); |
76
|
22 |
|
$i->register_listeners($this); |
77
|
22 |
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @param string $path |
81
|
|
|
*/ |
82
|
21 |
|
public function index_file($path) { |
83
|
21 |
|
if ($this->insert === null) { |
84
|
|
|
throw new \RuntimeException( |
85
|
|
|
"Set an inserter to be used before starting to index files."); |
86
|
|
|
} |
87
|
|
|
|
88
|
21 |
|
$content = file_get_contents($this->project_root_path."/$path"); |
89
|
21 |
|
if ($content === false) { |
90
|
|
|
throw \InvalidArgumentException("Can't read file $path."); |
91
|
|
|
} |
92
|
|
|
|
93
|
21 |
|
$stmts = $this->parser->parse($content); |
94
|
21 |
|
if ($stmts === null) { |
95
|
|
|
throw new \RuntimeException("Can't parse file $path."); |
96
|
|
|
} |
97
|
|
|
|
98
|
21 |
|
if ($stmts === null) { |
99
|
|
|
throw new \RuntimeException("Could not parse file '$path'."); |
100
|
|
|
} |
101
|
|
|
|
102
|
21 |
|
$traverser = new \PhpParser\NodeTraverser; |
103
|
21 |
|
$traverser->addVisitor($this); |
104
|
|
|
|
105
|
21 |
|
$this->entity_stack = array(); |
106
|
21 |
|
$this->file_path = $path; |
107
|
21 |
|
$this->file_content = explode("\n", $content); |
108
|
21 |
|
$traverser->traverse($stmts); |
109
|
21 |
|
$this->entity_stack = null; |
110
|
21 |
|
$this->file_path = null; |
111
|
21 |
|
$this->file_content = null; |
112
|
21 |
|
} |
113
|
|
|
|
114
|
|
|
// helper |
115
|
|
|
|
116
|
21 |
|
private function lines_from_to($start, $end) { |
117
|
21 |
|
assert('is_int($start)'); |
118
|
21 |
|
assert('is_int($end)'); |
119
|
21 |
|
return implode("\n", array_slice($this->file_content, $start-1, $end-$start+1)); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
// from ListenerRegistry |
123
|
|
|
|
124
|
22 |
|
public function on_enter_misc(\Closure $listener) { |
125
|
22 |
|
$this->listeners["misc"][] = $listener; |
126
|
22 |
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
// from Location |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @inheritdoc |
133
|
|
|
*/ |
134
|
16 |
|
public function file_path() { |
135
|
16 |
|
return $this->file_path; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @inheritdoc |
140
|
|
|
*/ |
141
|
16 |
|
public function file_content($from_line = null, $to_line = null) { |
142
|
16 |
|
if ($from_line !== null) { |
143
|
16 |
|
assert('$to_line !== null'); |
144
|
16 |
|
return $this->lines_from_to($from_line, $to_line); |
145
|
|
|
} |
146
|
|
|
else { |
147
|
|
|
assert('$to_line === null'); |
148
|
|
|
return implode("\n", $this->file_content); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @inheritdoc |
154
|
|
|
*/ |
155
|
16 |
|
public function in_entities() { |
156
|
16 |
|
return $this->entity_stack; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
// from \PhpParser\NodeVisitor |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @inheritdoc |
163
|
|
|
*/ |
164
|
21 |
|
public function beforeTraverse(array $nodes) { |
165
|
|
|
// for sure found a file |
166
|
21 |
|
$id = $this->insert->entity |
167
|
21 |
|
( Variable::FILE_TYPE |
168
|
21 |
|
, $this->file_path |
169
|
21 |
|
, $this->file_path |
170
|
21 |
|
, 1 |
171
|
21 |
|
, count($this->file_content) |
172
|
21 |
|
, implode("\n", $this->file_content) |
173
|
21 |
|
); |
174
|
|
|
|
175
|
21 |
|
$this->entity_stack[] = array(Variable::FILE_TYPE, $id); |
176
|
|
|
|
177
|
|
|
// TODO: reimplement this in some other way. |
178
|
|
|
/* |
|
|
|
|
179
|
|
|
foreach ($this->listeners as $listener) { |
180
|
|
|
$listener->on_enter_file($id, $this->file_path, implode("\n", $this->file_content)); |
181
|
|
|
} |
182
|
|
|
*/ |
183
|
|
|
|
184
|
21 |
|
return null; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @inheritdoc |
189
|
|
|
*/ |
190
|
21 |
|
public function afterTraverse(array $nodes) { |
191
|
21 |
|
$type_and_id = array_pop($this->entity_stack); |
|
|
|
|
192
|
|
|
|
193
|
|
|
// TODO: reimplement this in some other way. |
194
|
|
|
/* |
|
|
|
|
195
|
|
|
foreach ($this->listeners as $listener) { |
196
|
|
|
$listener->on_leave_file($type_and_id[1]); |
197
|
|
|
} |
198
|
|
|
*/ |
199
|
|
|
|
200
|
21 |
|
return null; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @inheritdoc |
205
|
|
|
*/ |
206
|
21 |
|
public function enterNode(\PhpParser\Node $node) { |
207
|
21 |
|
$start_line = $node->getAttribute("startLine"); |
208
|
21 |
|
$end_line = $node->getAttribute("endLine"); |
209
|
21 |
|
$source = $this->lines_from_to($start_line, $end_line); |
210
|
|
|
|
211
|
21 |
|
$id = null; |
212
|
21 |
|
$type = null; |
213
|
|
|
|
214
|
|
|
// Class |
215
|
21 |
|
if ($node instanceof N\Stmt\Class_) { |
216
|
20 |
|
$type = Variable::CLASS_TYPE; |
217
|
20 |
|
$id = $this->insert->entity |
218
|
20 |
|
( $type |
219
|
20 |
|
, $node->name |
220
|
20 |
|
, $this->file_path |
221
|
20 |
|
, $start_line |
222
|
20 |
|
, $end_line |
223
|
20 |
|
, $source |
224
|
20 |
|
); |
225
|
|
|
|
226
|
|
|
// TODO: reimplement this in some other way. |
227
|
|
|
/* |
|
|
|
|
228
|
|
|
foreach ($this->listeners as $listener) { |
229
|
|
|
$listener->on_enter_class($id, $node); |
230
|
|
|
} |
231
|
|
|
*/ |
232
|
20 |
|
} |
233
|
|
|
// Method or Function |
234
|
21 |
View Code Duplication |
elseif ($node instanceof N\Stmt\ClassMethod) { |
|
|
|
|
235
|
20 |
|
$type = Variable::METHOD_TYPE; |
236
|
20 |
|
$id = $this->insert->entity |
237
|
20 |
|
( $type |
238
|
20 |
|
, $node->name |
239
|
20 |
|
, $this->file_path |
240
|
20 |
|
, $start_line |
241
|
20 |
|
, $end_line |
242
|
20 |
|
, $source |
243
|
20 |
|
); |
244
|
|
|
|
245
|
|
|
// TODO: reimplement this in some other way. |
246
|
|
|
/* |
|
|
|
|
247
|
|
|
foreach ($this->listeners as $listener) { |
248
|
|
|
$listener->on_enter_method($id, $node); |
249
|
|
|
} |
250
|
|
|
*/ |
251
|
20 |
|
} |
252
|
21 |
View Code Duplication |
elseif ($node instanceof N\Stmt\Function_) { |
|
|
|
|
253
|
|
|
$type = Variable::FUNCTION_TYPE; |
254
|
|
|
$id = $this->insert->entity |
255
|
|
|
( $type |
256
|
|
|
, $node->name |
257
|
|
|
, $this->file_path |
258
|
|
|
, $start_line |
259
|
|
|
, $end_line |
260
|
|
|
, $source |
261
|
|
|
); |
262
|
|
|
|
263
|
|
|
// TODO: reimplement this in some other way. |
264
|
|
|
/* |
|
|
|
|
265
|
|
|
foreach ($this->listeners as $listener) { |
266
|
|
|
$listener->on_enter_function($id, $node); |
267
|
|
|
} |
268
|
|
|
*/ |
269
|
|
|
} |
270
|
|
|
else { |
271
|
21 |
|
foreach ($this->listeners["misc"] as $listener) { |
272
|
21 |
|
$listener($this->insert, $this, $node); |
273
|
21 |
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
21 |
|
if ($id !== null) { |
277
|
20 |
|
$this->entity_stack[] = array($type, $id); |
278
|
20 |
|
} |
279
|
21 |
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* @inheritdoc |
283
|
|
|
*/ |
284
|
21 |
|
public function leaveNode(\PhpParser\Node $node) { |
285
|
|
|
// Class |
286
|
21 |
|
if ($node instanceof N\Stmt\Class_) { |
287
|
|
|
// We pushed it, we need to pop it now, as we are leaving the class. |
288
|
20 |
|
$type_and_id = array_pop($this->entity_stack); |
|
|
|
|
289
|
|
|
|
290
|
|
|
// TODO: reimplement this in some other way. |
291
|
|
|
/* |
|
|
|
|
292
|
|
|
foreach ($this->listeners as $listener) { |
293
|
|
|
$listener->on_leave_class($type_and_id[1]); |
294
|
|
|
} |
295
|
|
|
*/ |
296
|
20 |
|
} |
297
|
|
|
// Method or Function |
298
|
21 |
|
elseif ($node instanceof N\Stmt\ClassMethod) { |
299
|
|
|
// We pushed it, we need to pop it now, as we are leaving the method |
300
|
|
|
// or function. |
301
|
20 |
|
$type_and_id = array_pop($this->entity_stack); |
|
|
|
|
302
|
|
|
|
303
|
|
|
// TODO: reimplement this in some other way. |
304
|
|
|
/* |
|
|
|
|
305
|
|
|
foreach ($this->listeners as $listener) { |
306
|
|
|
$listener->on_leave_method($type_and_id[1]); |
307
|
|
|
} |
308
|
|
|
*/ |
309
|
20 |
|
} |
310
|
21 |
|
elseif ($node instanceof N\Stmt\Function_) { |
311
|
|
|
// We pushed it, we need to pop it now, as we are leaving the method |
312
|
|
|
// or function. |
313
|
|
|
$type_and_id = array_pop($this->entity_stack); |
|
|
|
|
314
|
|
|
|
315
|
|
|
// TODO: reimplement this in some other way. |
316
|
|
|
/* |
|
|
|
|
317
|
|
|
foreach ($this->listeners as $listener) { |
318
|
|
|
$listener->on_leave_function($type_and_id[1]); |
319
|
|
|
} |
320
|
|
|
*/ |
321
|
|
|
} |
322
|
|
|
else { |
|
|
|
|
323
|
|
|
// TODO: reimplement this in some other way. |
324
|
|
|
/* |
|
|
|
|
325
|
|
|
foreach ($this->listeners as $listener) { |
326
|
|
|
$listener->on_leave_misc($this->insert, $this, $node); |
327
|
|
|
} |
328
|
|
|
*/ |
329
|
|
|
} |
330
|
21 |
|
} |
331
|
|
|
} |
332
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.