@@ -26,21 +26,26 @@ final class Extractor
2626
2727 private $ code ;
2828 private $ statements ;
29+ private $ printer ;
2930
3031
3132 public function __construct (string $ code )
3233 {
3334 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 . " );
3536 }
37+ $ this ->printer = new PhpParser \PrettyPrinter \Standard ;
3638 $ this ->parseCode ($ code );
3739 }
3840
3941
4042 private function parseCode (string $ code ): void
4143 {
44+ if (substr ($ code , 0 , 5 ) !== '<?php ' ) {
45+ throw new Nette \InvalidStateException ('The input string is not a PHP code. ' );
46+ }
4247 $ 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 ' ]]);
4449 $ parser = (new ParserFactory )->create (ParserFactory::ONLY_PHP7 , $ lexer );
4550 $ stmts = $ parser ->parse ($ this ->code );
4651
@@ -167,6 +172,248 @@ private function performReplacements(string $s, array $replacements): string
167172 }
168173
169174
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+
170417 private function getNodeContents (Node ...$ nodes ): string
171418 {
172419 $ start = $ nodes [0 ]->getStartFilePos ();
0 commit comments