From 5e0f750d8661c39c92160e5fd2acbb84a28f0e4a Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Mon, 29 Nov 2021 11:15:46 +0100 Subject: [PATCH 1/2] Add support for a custom node visitor to override the default. --- src/GenerateStubsCommand.php | 20 +++++++++++++++++++- src/NodeVisitor.php | 2 +- src/StubsGenerator.php | 10 ++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/GenerateStubsCommand.php b/src/GenerateStubsCommand.php index 9ed13c6..581e1fb 100644 --- a/src/GenerateStubsCommand.php +++ b/src/GenerateStubsCommand.php @@ -58,6 +58,7 @@ public function configure(): void ->addOption('out', null, InputOption::VALUE_REQUIRED, 'Path to a file to write pretty-printed stubs to. If unset, stubs will be written to stdout.') ->addOption('force', null, InputOption::VALUE_NONE, 'Whether to force an overwrite.') ->addOption('finder', null, InputOption::VALUE_REQUIRED, 'Path to a PHP file which returns a `Symfony\Finder` instance including the set of files that should be parsed. Can be used instead of, but not in addition to, passing sources directly.') + ->addOption('visitor', null, InputOption::VALUE_REQUIRED, 'Path to a PHP file which returns a `StubsGenerator\NodeVisitor` instance to replace the default node visitor.') ->addOption('header', null, InputOption::VALUE_REQUIRED, 'A doc comment to prepend to the top of the generated stubs file. (Will be added below the opening `addOption('nullify-globals', null, InputOption::VALUE_NONE, 'Initialize all global variables with a value of `null`, instead of their assigned value.') ->addOption('stats', null, InputOption::VALUE_NONE, 'Whether to print stats instead of outputting stubs. Stats will always be printed if --out is provided.'); @@ -94,13 +95,30 @@ protected function interact(InputInterface $input, OutputInterface $output): voi protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); + $visitor = null; + $visitorPath = $input->getOption('visitor'); + + if ($visitorPath) { + $visitorPath = $this->resolvePath($visitorPath); + if (!$this->filesystem->exists($visitorPath) || is_dir($visitorPath)) { + throw new InvalidArgumentException("Bad --visitor path: '$visitorPath' does not exist or is a directory."); + } + try { + $visitor = @include $visitorPath; + } catch (Exception $e) { + throw new RuntimeException("Could not resolve a `StubsGenerator\NodeVisitor` from '$visitorPath'.", 0, $e); + } + if (!$visitor || !($visitor instanceof NodeVisitor)) { + throw new RuntimeException("Could not resolve a `StubsGenerator\NodeVisitor` from '$visitorPath'."); + } + } $finder = $this->parseSources($input); $generator = new StubsGenerator($this->parseSymbols($input), [ 'nullify_globals' => $input->getOption('nullify-globals'), ]); - $result = $generator->generate($finder); + $result = $generator->generate($finder, $visitor); $printer = new Standard(); diff --git a/src/NodeVisitor.php b/src/NodeVisitor.php index e299e6b..608ab77 100644 --- a/src/NodeVisitor.php +++ b/src/NodeVisitor.php @@ -84,7 +84,7 @@ class NodeVisitor extends NodeVisitorAbstract /** * @param int $symbols Set of symbol types to include stubs for. */ - public function __construct(int $symbols = StubsGenerator::DEFAULT, array $config = []) + public function init(int $symbols = StubsGenerator::DEFAULT, array $config = []) { $this->needsFunctions = ($symbols & StubsGenerator::FUNCTIONS) !== 0; $this->needsClasses = ($symbols & StubsGenerator::CLASSES) !== 0; diff --git a/src/StubsGenerator.php b/src/StubsGenerator.php index 1a36d94..d0af0b2 100644 --- a/src/StubsGenerator.php +++ b/src/StubsGenerator.php @@ -99,15 +99,21 @@ public function __construct(int $symbols = self::DEFAULT, array $config = []) * pretty-printed stubs. * * @param Finder $finder The set of files to generate (merged) stubs for. + * @param NodeVisitor $visitor The optional node visitor to override the default. * * @return Result */ - public function generate(Finder $finder): Result + public function generate(Finder $finder, NodeVisitor $visitor = null): Result { $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + if (!($visitor instanceof NodeVisitor)) { + $visitor = new NodeVisitor; + } + + $visitor->init($this->symbols, $this->config); + $traverser = new NodeTraverser(); - $visitor = new NodeVisitor($this->symbols, $this->config); $traverser->addVisitor(new NameResolver()); $traverser->addVisitor($visitor); From cb070a78124e85c143afc728fb85b77e2f1927c0 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 2 Dec 2021 21:37:10 +0100 Subject: [PATCH 2/2] Correctly initialise the node visitor used during tests. --- test/NodeVisitorTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/NodeVisitorTest.php b/test/NodeVisitorTest.php index 70b3d05..35c151e 100644 --- a/test/NodeVisitorTest.php +++ b/test/NodeVisitorTest.php @@ -16,7 +16,8 @@ private function parse(string $php, int $symbols, array $config): NodeVisitor $traverser = new NodeTraverser(); $traverser->addVisitor(new NameResolver()); - $visitor = new NodeVisitor($symbols, $config); + $visitor = new NodeVisitor(); + $visitor->init($symbols, $config); $traverser->addVisitor($visitor); $stmts = $parser->parse($php);