@@ -26,21 +26,26 @@ final class Extractor
26
26
27
27
private $ code ;
28
28
private $ statements ;
29
+ private $ printer ;
29
30
30
31
31
32
public function __construct (string $ code )
32
33
{
33
34
if (!class_exists (ParserFactory::class)) {
34
- throw new Nette \NotSupportedException ("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'. " );
35
+ throw new Nette \NotSupportedException ("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer . " );
35
36
}
37
+ $ this ->printer = new PhpParser \PrettyPrinter \Standard ;
36
38
$ this ->parseCode ($ code );
37
39
}
38
40
39
41
40
42
private function parseCode (string $ code ): void
41
43
{
44
+ if (substr ($ code , 0 , 5 ) !== '<?php ' ) {
45
+ throw new Nette \InvalidStateException ('The input string is not a PHP code. ' );
46
+ }
42
47
$ this ->code = str_replace ("\r\n" , "\n" , $ code );
43
- $ lexer = new PhpParser \Lexer (['usedAttributes ' => ['startFilePos ' , 'endFilePos ' ]]);
48
+ $ lexer = new PhpParser \Lexer \ Emulative (['usedAttributes ' => ['startFilePos ' , 'endFilePos ' , ' comments ' ]]);
44
49
$ parser = (new ParserFactory )->create (ParserFactory::ONLY_PHP7 , $ lexer );
45
50
$ stmts = $ parser ->parse ($ this ->code );
46
51
@@ -167,6 +172,248 @@ private function performReplacements(string $s, array $replacements): string
167
172
}
168
173
169
174
175
+ public function extractAll (): PhpFile
176
+ {
177
+ $ phpFile = new PhpFile ;
178
+ $ namespace = '' ;
179
+ $ visitor = new class extends PhpParser \NodeVisitorAbstract {
180
+ public function enterNode (Node $ node )
181
+ {
182
+ return ($ this ->callback )($ node );
183
+ }
184
+ };
185
+
186
+ $ visitor ->callback = function (Node $ node ) use (&$ class , &$ namespace , $ phpFile ) {
187
+ if ($ node instanceof Node \Stmt \DeclareDeclare && $ node ->key ->name === 'strict_types ' ) {
188
+ $ phpFile ->setStrictTypes ((bool ) $ node ->value ->value );
189
+ } elseif ($ node instanceof Node \Stmt \Namespace_) {
190
+ $ namespace = $ node ->name ? $ node ->name ->toString () : '' ;
191
+ } elseif ($ node instanceof Node \Stmt \Use_) {
192
+ $ this ->addUseToNamespace ($ node , $ phpFile ->addNamespace ($ namespace ));
193
+ } elseif ($ node instanceof Node \Stmt \Class_) {
194
+ if (!$ node ->name ) {
195
+ return PhpParser \NodeTraverser::DONT_TRAVERSE_CHILDREN ;
196
+ }
197
+ $ class = $ this ->addClassToFile ($ phpFile , $ node );
198
+ } elseif ($ node instanceof Node \Stmt \Interface_) {
199
+ $ class = $ this ->addInterfaceToFile ($ phpFile , $ node );
200
+ } elseif ($ node instanceof Node \Stmt \Trait_) {
201
+ $ class = $ this ->addTraitToFile ($ phpFile , $ node );
202
+ } elseif ($ node instanceof Node \Stmt \Enum_) {
203
+ $ class = $ this ->addEnumToFile ($ phpFile , $ node );
204
+ } elseif ($ node instanceof Node \Stmt \Function_) {
205
+ $ this ->addFunctionToFile ($ phpFile , $ node );
206
+ } elseif ($ node instanceof Node \Stmt \TraitUse) {
207
+ $ this ->addTraitToClass ($ class , $ node );
208
+ } elseif ($ node instanceof Node \Stmt \Property) {
209
+ $ this ->addPropertyToClass ($ class , $ node );
210
+ } elseif ($ node instanceof Node \Stmt \ClassMethod) {
211
+ $ this ->addMethodToClass ($ class , $ node );
212
+ } elseif ($ node instanceof Node \Stmt \ClassConst) {
213
+ $ this ->addConstantToClass ($ class , $ node );
214
+ } elseif ($ node instanceof Node \Stmt \EnumCase) {
215
+ $ this ->addEnumCaseToClass ($ class , $ node );
216
+ }
217
+ if ($ node instanceof Node \FunctionLike) {
218
+ return PhpParser \NodeTraverser::DONT_TRAVERSE_CHILDREN ;
219
+ }
220
+ };
221
+
222
+ $ traverser = new PhpParser \NodeTraverser ;
223
+ $ traverser ->addVisitor ($ visitor );
224
+ $ traverser ->traverse ($ this ->statements );
225
+ return $ phpFile ;
226
+ }
227
+
228
+
229
+ private function addUseToNamespace (Node \Stmt \Use_ $ node , PhpNamespace $ namespace ): void
230
+ {
231
+ if ($ node ->type === $ node ::TYPE_NORMAL ) {
232
+ foreach ($ node ->uses as $ use ) {
233
+ $ namespace ->addUse ($ use ->name ->toString (), $ use ->alias ? $ use ->alias ->toString () : null );
234
+ }
235
+ }
236
+ }
237
+
238
+
239
+ private function addClassToFile (PhpFile $ phpFile , Node \Stmt \Class_ $ node ): ClassType
240
+ {
241
+ $ class = $ phpFile ->addClass ($ node ->namespacedName ->toString ());
242
+ if ($ node ->extends ) {
243
+ $ class ->setExtends ($ node ->extends ->toString ());
244
+ }
245
+ foreach ($ node ->implements as $ item ) {
246
+ $ class ->addImplement ($ item ->toString ());
247
+ }
248
+ $ class ->setFinal ($ node ->isFinal ());
249
+ $ class ->setAbstract ($ node ->isAbstract ());
250
+ $ this ->addCommentAndAttributes ($ class , $ node );
251
+ return $ class ;
252
+ }
253
+
254
+
255
+ private function addInterfaceToFile (PhpFile $ phpFile , Node \Stmt \Interface_ $ node ): ClassType
256
+ {
257
+ $ class = $ phpFile ->addInterface ($ node ->namespacedName ->toString ());
258
+ foreach ($ node ->extends as $ item ) {
259
+ $ class ->addExtend ($ item ->toString ());
260
+ }
261
+ $ this ->addCommentAndAttributes ($ class , $ node );
262
+ return $ class ;
263
+ }
264
+
265
+
266
+ private function addTraitToFile (PhpFile $ phpFile , Node \Stmt \Trait_ $ node ): ClassType
267
+ {
268
+ $ class = $ phpFile ->addTrait ($ node ->namespacedName ->toString ());
269
+ $ this ->addCommentAndAttributes ($ class , $ node );
270
+ return $ class ;
271
+ }
272
+
273
+
274
+ private function addEnumToFile (PhpFile $ phpFile , Node \Stmt \Enum_ $ node ): ClassType
275
+ {
276
+ $ class = $ phpFile ->addEnum ($ node ->namespacedName ->toString ());
277
+ foreach ($ node ->implements as $ item ) {
278
+ $ class ->addImplement ($ item ->toString ());
279
+ }
280
+ $ this ->addCommentAndAttributes ($ class , $ node );
281
+ return $ class ;
282
+ }
283
+
284
+
285
+ private function addFunctionToFile (PhpFile $ phpFile , Node \Stmt \Function_ $ node ): void
286
+ {
287
+ $ function = $ phpFile ->addFunction ($ node ->namespacedName ->toString ());
288
+ $ this ->setupFunction ($ function , $ node );
289
+ }
290
+
291
+
292
+ private function addTraitToClass (ClassType $ class , Node \Stmt \TraitUse $ node ): void
293
+ {
294
+ $ res = [];
295
+ foreach ($ node ->adaptations as $ item ) {
296
+ $ res [] = trim ($ this ->toPhp ($ item ), '; ' );
297
+ }
298
+ foreach ($ node ->traits as $ trait ) {
299
+ $ class ->addTrait ($ trait ->toString (), $ res );
300
+ $ res = [];
301
+ }
302
+ }
303
+
304
+
305
+ private function addPropertyToClass (ClassType $ class , Node \Stmt \Property $ node ): void
306
+ {
307
+ foreach ($ node ->props as $ item ) {
308
+ $ prop = $ class ->addProperty ($ item ->name ->toString ());
309
+ $ prop ->setStatic ($ node ->isStatic ());
310
+ if ($ node ->isPrivate ()) {
311
+ $ prop ->setPrivate ();
312
+ } elseif ($ node ->isProtected ()) {
313
+ $ prop ->setProtected ();
314
+ }
315
+ $ prop ->setType ($ node ->type ? $ this ->toPhp ($ node ->type ) : null );
316
+ if ($ item ->default ) {
317
+ $ prop ->setValue (new Literal ($ this ->toPhp ($ item ->default )));
318
+ }
319
+ $ prop ->setReadOnly (method_exists ($ node , 'isReadonly ' ) && $ node ->isReadonly ());
320
+ $ this ->addCommentAndAttributes ($ prop , $ node );
321
+ }
322
+ }
323
+
324
+
325
+ private function addMethodToClass (ClassType $ class , Node \Stmt \ClassMethod $ node ): void
326
+ {
327
+ $ method = $ class ->addMethod ($ node ->name ->toString ());
328
+ $ method ->setAbstract ($ node ->isAbstract ());
329
+ $ method ->setFinal ($ node ->isFinal ());
330
+ $ method ->setStatic ($ node ->isStatic ());
331
+ if ($ node ->isPrivate ()) {
332
+ $ method ->setPrivate ();
333
+ } elseif ($ node ->isProtected ()) {
334
+ $ method ->setProtected ();
335
+ }
336
+ $ this ->setupFunction ($ method , $ node );
337
+ }
338
+
339
+
340
+ private function addConstantToClass (ClassType $ class , Node \Stmt \ClassConst $ node ): void
341
+ {
342
+ foreach ($ node ->consts as $ item ) {
343
+ $ const = $ class ->addConstant ($ item ->name ->toString (), new Literal ($ this ->toPhp ($ item ->value )));
344
+ if ($ node ->isPrivate ()) {
345
+ $ const ->setPrivate ();
346
+ } elseif ($ node ->isProtected ()) {
347
+ $ const ->setProtected ();
348
+ }
349
+ $ const ->setFinal (method_exists ($ node , 'isFinal ' ) && $ node ->isFinal ());
350
+ $ this ->addCommentAndAttributes ($ const , $ node );
351
+ }
352
+ }
353
+
354
+
355
+ private function addEnumCaseToClass (ClassType $ class , Node \Stmt \EnumCase $ node )
356
+ {
357
+ $ case = $ class ->addCase ($ node ->name ->toString (), $ node ->expr ? $ node ->expr ->value : null );
358
+ $ this ->addCommentAndAttributes ($ case , $ node );
359
+ }
360
+
361
+
362
+ private function addCommentAndAttributes ($ element , Node $ node ): void
363
+ {
364
+ if ($ node ->getDocComment ()) {
365
+ $ comment = $ node ->getDocComment ()->getReformattedText ();
366
+ $ comment = Helpers::unformatDocComment ($ comment );
367
+ $ element ->setComment ($ comment );
368
+ }
369
+
370
+ foreach ($ node ->attrGroups ?? [] as $ group ) {
371
+ foreach ($ group ->attrs as $ attribute ) {
372
+ $ args = [];
373
+ foreach ($ attribute ->args as $ arg ) {
374
+ $ value = new Literal ($ this ->toPhp ($ arg ));
375
+ if ($ arg ->name ) {
376
+ $ args [$ arg ->name ->toString ()] = $ value ;
377
+ } else {
378
+ $ args [] = $ value ;
379
+ }
380
+ }
381
+ $ element ->addAttribute ($ attribute ->name ->toString (), $ args );
382
+ }
383
+ }
384
+ }
385
+
386
+
387
+ /**
388
+ * @param GlobalFunction|Method $function
389
+ */
390
+ private function setupFunction ($ function , Node \FunctionLike $ node ): void
391
+ {
392
+ $ function ->setReturnReference ($ node ->returnsByRef ());
393
+ $ function ->setReturnType ($ node ->getReturnType () ? $ this ->toPhp ($ node ->getReturnType ()) : null );
394
+ foreach ($ node ->params as $ item ) {
395
+ $ param = $ function ->addParameter ($ item ->var ->name );
396
+ $ param ->setType ($ item ->type ? $ this ->toPhp ($ item ->type ) : null );
397
+ $ param ->setReference ($ item ->byRef );
398
+ $ function ->setVariadic ($ item ->variadic );
399
+ if ($ item ->default ) {
400
+ $ param ->setDefaultValue (new Literal ($ this ->toPhp ($ item ->default )));
401
+ }
402
+ $ this ->addCommentAndAttributes ($ param , $ item );
403
+ }
404
+ $ this ->addCommentAndAttributes ($ function , $ node );
405
+ if ($ node ->stmts ) {
406
+ $ function ->setBody ($ this ->getReformattedBody ($ node ->stmts , 2 ));
407
+ }
408
+ }
409
+
410
+
411
+ private function toPhp ($ value ): string
412
+ {
413
+ return $ this ->printer ->prettyPrint ([$ value ]);
414
+ }
415
+
416
+
170
417
private function getNodeContents (Node ...$ nodes ): string
171
418
{
172
419
$ start = $ nodes [0 ]->getStartFilePos ();
0 commit comments