|
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_enter_entity; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* @var array string => array() |
|
43
|
|
|
*/ |
|
44
|
|
|
protected $listeners_leave_entity; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @var array string => array() |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $listeners_enter_misc; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* @var array string => array() |
|
53
|
|
|
*/ |
|
54
|
|
|
protected $listeners_leave_misc; |
|
55
|
|
|
|
|
56
|
|
|
// state for parsing a file |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* @var string|null |
|
60
|
|
|
*/ |
|
61
|
|
|
protected $file_path = null; |
|
62
|
|
|
|
|
63
|
|
|
/** |
|
64
|
|
|
* @var string[]|null |
|
65
|
|
|
*/ |
|
66
|
|
|
protected $file_content = null; |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* This contains the stack of ids were currently in, i.e. the nesting of |
|
70
|
|
|
* known code blocks we are in. |
|
71
|
|
|
* |
|
72
|
|
|
* @var array|null contain ($entity_type, $entity_id) |
|
73
|
|
|
*/ |
|
74
|
|
|
protected $entity_stack = null; |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* @param string $project_root_path |
|
78
|
|
|
*/ |
|
79
|
24 |
|
public function __construct(\PhpParser\Parser $parser, $project_root_path, Insert $insert) { |
|
80
|
24 |
|
$this->parser = $parser; |
|
81
|
24 |
|
assert('is_string($project_root_path)'); |
|
82
|
24 |
|
$this->project_root_path = $project_root_path; |
|
83
|
24 |
|
$this->insert = $insert; |
|
84
|
24 |
|
$this->listeners_enter_entity = array |
|
85
|
24 |
|
( 0 => array() |
|
86
|
24 |
|
); |
|
87
|
24 |
|
$this->listeners_leave_entity = array |
|
88
|
24 |
|
( 0 => array() |
|
89
|
24 |
|
); |
|
90
|
24 |
|
$this->listeners_enter_misc = array |
|
91
|
24 |
|
( 0 => array() |
|
92
|
24 |
|
); |
|
93
|
24 |
|
$this->listeners_leave_misc = array |
|
94
|
24 |
|
( 0 => array() |
|
95
|
24 |
|
); |
|
96
|
24 |
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* @param string $path |
|
100
|
|
|
*/ |
|
101
|
23 |
|
public function index_file($path) { |
|
102
|
23 |
|
$content = file_get_contents($this->project_root_path."/$path"); |
|
103
|
23 |
|
if ($content === false) { |
|
104
|
|
|
throw \InvalidArgumentException("Can't read file $path."); |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
23 |
|
$stmts = $this->parser->parse($content); |
|
108
|
23 |
|
if ($stmts === null) { |
|
109
|
|
|
throw new \RuntimeException("Can't parse file $path."); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
23 |
|
$traverser = new \PhpParser\NodeTraverser; |
|
113
|
23 |
|
$traverser->addVisitor($this); |
|
114
|
|
|
|
|
115
|
23 |
|
$this->entity_stack = array(); |
|
116
|
23 |
|
$this->file_path = $path; |
|
117
|
23 |
|
$this->file_content = explode("\n", $content); |
|
118
|
23 |
|
$traverser->traverse($stmts); |
|
119
|
23 |
|
} |
|
120
|
|
|
|
|
121
|
|
|
// helper |
|
122
|
|
|
|
|
123
|
23 |
|
private function lines_from_to($start, $end) { |
|
124
|
23 |
|
assert('is_int($start)'); |
|
125
|
23 |
|
assert('is_int($end)'); |
|
126
|
23 |
|
return implode("\n", array_slice($this->file_content, $start-1, $end-$start+1)); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
// from ListenerRegistry |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* @inheritdoc |
|
133
|
|
|
*/ |
|
134
|
1 |
|
public function on_enter_entity($types, \Closure $listener) { |
|
135
|
1 |
|
$this->on_enter_or_leave_something("listeners_enter_entity", $types, $listener); |
|
136
|
1 |
|
return $this; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* @inheritdoc |
|
141
|
|
|
*/ |
|
142
|
1 |
|
public function on_leave_entity($types, \Closure $listener) { |
|
143
|
1 |
|
$this->on_enter_or_leave_something("listeners_leave_entity", $types, $listener); |
|
144
|
1 |
|
return $this; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* @inheritdoc |
|
149
|
|
|
*/ |
|
150
|
23 |
|
public function on_enter_misc($classes, \Closure $listener) { |
|
151
|
23 |
|
$this->on_enter_or_leave_something("listeners_enter_misc", $classes, $listener); |
|
152
|
23 |
|
return $this; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* @inheritdoc |
|
157
|
|
|
*/ |
|
158
|
1 |
|
public function on_leave_misc($classes, \Closure $listener) { |
|
159
|
1 |
|
$this->on_enter_or_leave_something("listeners_leave_misc", $classes, $listener); |
|
160
|
1 |
|
return $this; |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
// generalizes over over on_enter/leave_xx |
|
164
|
|
|
|
|
165
|
|
|
/** |
|
166
|
|
|
* @param string $what |
|
167
|
|
|
* @param array|null $things |
|
168
|
|
|
*/ |
|
169
|
23 |
|
protected function on_enter_or_leave_something($what, $things, \Closure $listener) { |
|
170
|
23 |
|
$loc = &$this->$what; |
|
171
|
23 |
|
if ($things === null) { |
|
172
|
1 |
|
$loc[0][] = $listener; |
|
173
|
1 |
|
} |
|
174
|
|
|
else { |
|
175
|
23 |
|
foreach ($things as $thing) { |
|
176
|
23 |
|
assert('is_string($thing)'); |
|
177
|
23 |
|
if (!array_key_exists($thing, $loc)) { |
|
178
|
23 |
|
$loc[$thing] = array(); |
|
179
|
23 |
|
} |
|
180
|
23 |
|
$loc[$thing][] = $listener; |
|
181
|
23 |
|
} |
|
182
|
|
|
} |
|
183
|
23 |
|
} |
|
184
|
|
|
|
|
185
|
|
|
// generalizes over calls to misc listeners |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* @param string $which |
|
189
|
|
|
* @param \PhpParser\Node $node |
|
190
|
|
|
*/ |
|
191
|
23 |
View Code Duplication |
protected function call_misc_listener($which, \PhpParser\Node $node) { |
|
|
|
|
|
|
192
|
23 |
|
$listeners = &$this->$which; |
|
193
|
23 |
|
foreach ($listeners[0] as $listener) { |
|
194
|
|
|
$listener($this->insert, $this, $node); |
|
195
|
23 |
|
} |
|
196
|
23 |
|
$cls = get_class($node); |
|
197
|
23 |
|
if (array_key_exists($cls, $listeners)) { |
|
198
|
21 |
|
foreach ($listeners[$cls] as $listener) { |
|
199
|
21 |
|
$listener($this->insert, $this, $node); |
|
200
|
21 |
|
} |
|
201
|
21 |
|
} |
|
202
|
23 |
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @param string $which |
|
206
|
|
|
* @param string $type |
|
207
|
|
|
* @param int $type |
|
208
|
|
|
* @param \PhpParser\Node|null $node |
|
209
|
|
|
*/ |
|
210
|
23 |
View Code Duplication |
protected function call_entity_listener($which, $type, $id, \PhpParser\Node $node = null) { |
|
|
|
|
|
|
211
|
23 |
|
$listeners = &$this->$which; |
|
212
|
23 |
|
foreach ($listeners[0] as $listener) { |
|
213
|
1 |
|
$listener($this->insert, $this, $type, $id, $node); |
|
214
|
23 |
|
} |
|
215
|
23 |
|
if (array_key_exists($type, $listeners)) { |
|
216
|
|
|
foreach ($listeners[$type] as $listener) { |
|
217
|
|
|
$listener($this->insert, $this, $type, $id, $node); |
|
218
|
|
|
} |
|
219
|
|
|
} |
|
220
|
23 |
|
} |
|
221
|
|
|
|
|
222
|
|
|
// from Location |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* @inheritdoc |
|
226
|
|
|
*/ |
|
227
|
17 |
|
public function file_path() { |
|
228
|
17 |
|
return $this->file_path; |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
/** |
|
232
|
|
|
* @inheritdoc |
|
233
|
|
|
*/ |
|
234
|
|
|
public function file_content($from_line = null, $to_line = null) { |
|
235
|
|
|
if ($from_line !== null) { |
|
236
|
|
|
assert('$to_line !== null'); |
|
237
|
|
|
return $this->lines_from_to($from_line, $to_line); |
|
238
|
|
|
} |
|
239
|
|
|
else { |
|
240
|
|
|
assert('$to_line === null'); |
|
241
|
|
|
return implode("\n", $this->file_content); |
|
242
|
|
|
} |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* @inheritdoc |
|
247
|
|
|
*/ |
|
248
|
17 |
|
public function in_entities() { |
|
249
|
17 |
|
return $this->entity_stack; |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
// from \PhpParser\NodeVisitor |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* @inheritdoc |
|
256
|
|
|
*/ |
|
257
|
23 |
|
public function beforeTraverse(array $nodes) { |
|
258
|
23 |
|
$this->insert->source_file($this->file_path, implode("\n", $this->file_content)); |
|
259
|
|
|
|
|
260
|
|
|
// for sure found a file |
|
261
|
23 |
|
$id = $this->insert->entity |
|
262
|
23 |
|
( Variable::FILE_TYPE |
|
263
|
23 |
|
, $this->file_path |
|
264
|
23 |
|
, $this->file_path |
|
265
|
23 |
|
, 1 |
|
266
|
23 |
|
, count($this->file_content) |
|
267
|
23 |
|
); |
|
268
|
|
|
|
|
269
|
23 |
|
$this->entity_stack[] = array(Variable::FILE_TYPE, $id); |
|
270
|
|
|
|
|
271
|
23 |
|
$this->call_entity_listener("listeners_enter_entity", Variable::FILE_TYPE, $id, null); |
|
272
|
|
|
|
|
273
|
23 |
|
return null; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** |
|
277
|
|
|
* @inheritdoc |
|
278
|
|
|
*/ |
|
279
|
23 |
|
public function afterTraverse(array $nodes) { |
|
280
|
23 |
|
list($type, $id) = array_pop($this->entity_stack); |
|
281
|
|
|
|
|
282
|
23 |
|
$this->call_entity_listener("listeners_leave_entity", $type, $id, null); |
|
283
|
|
|
|
|
284
|
23 |
|
return null; |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
/** |
|
288
|
|
|
* @inheritdoc |
|
289
|
|
|
*/ |
|
290
|
23 |
|
public function enterNode(\PhpParser\Node $node) { |
|
291
|
23 |
|
$start_line = $node->getAttribute("startLine"); |
|
292
|
23 |
|
$end_line = $node->getAttribute("endLine"); |
|
293
|
23 |
|
$source = $this->lines_from_to($start_line, $end_line); |
|
|
|
|
|
|
294
|
|
|
|
|
295
|
23 |
|
$type = null; |
|
296
|
|
|
|
|
297
|
|
|
// Class |
|
298
|
23 |
|
if ($node instanceof N\Stmt\Class_) { |
|
299
|
22 |
|
$type = Variable::CLASS_TYPE; |
|
300
|
22 |
|
} |
|
301
|
|
|
// Method or Function |
|
302
|
23 |
|
elseif ($node instanceof N\Stmt\ClassMethod) { |
|
303
|
22 |
|
$type = Variable::METHOD_TYPE; |
|
304
|
22 |
|
} |
|
305
|
23 |
|
elseif ($node instanceof N\Stmt\Function_) { |
|
306
|
|
|
$type = Variable::FUNCTION_TYPE; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
23 |
|
if ($type !== null) { |
|
310
|
22 |
|
$id = $this->insert->entity |
|
311
|
22 |
|
( $type |
|
312
|
22 |
|
, $node->name |
|
|
|
|
|
|
313
|
22 |
|
, $this->file_path |
|
314
|
22 |
|
, $start_line |
|
315
|
22 |
|
, $end_line |
|
316
|
22 |
|
); |
|
317
|
22 |
|
$this->call_entity_listener("listeners_enter_entity", $type, $id, $node); |
|
318
|
22 |
|
$this->entity_stack[] = array($type, $id); |
|
319
|
22 |
|
} |
|
320
|
|
|
else { |
|
321
|
23 |
|
$this->call_misc_listener("listeners_enter_misc", $node); |
|
322
|
|
|
} |
|
323
|
23 |
|
} |
|
324
|
|
|
|
|
325
|
|
|
/** |
|
326
|
|
|
* @inheritdoc |
|
327
|
|
|
*/ |
|
328
|
23 |
|
public function leaveNode(\PhpParser\Node $node) { |
|
329
|
|
|
// Class |
|
330
|
|
|
if($node instanceof N\Stmt\Class_ |
|
331
|
23 |
|
|| $node instanceof N\Stmt\ClassMethod |
|
332
|
23 |
|
|| $node instanceof N\Stmt\Function_) { |
|
333
|
22 |
|
list($type, $id) = array_pop($this->entity_stack); |
|
334
|
22 |
|
$this->call_entity_listener("listeners_leave_entity", $type, $id, $node); |
|
335
|
22 |
|
} |
|
336
|
|
|
else { |
|
337
|
23 |
|
$this->call_misc_listener("listeners_leave_misc", $node); |
|
338
|
|
|
} |
|
339
|
23 |
|
} |
|
340
|
|
|
} |
|
341
|
|
|
|
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.