diff --git a/src/GenerateStubsCommand.php b/src/GenerateStubsCommand.php index 2793768..03433ff 100644 --- a/src/GenerateStubsCommand.php +++ b/src/GenerateStubsCommand.php @@ -59,6 +59,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.'); @@ -95,13 +96,30 @@ protected function interact(InputInterface $input, OutputInterface $output): voi protected function execute(InputInterface $input, OutputInterface $output): int { $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 01d05bd..f6a672c 100644 --- a/src/NodeVisitor.php +++ b/src/NodeVisitor.php @@ -90,7 +90,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 f0242d8..afa6c10 100644 --- a/src/StubsGenerator.php +++ b/src/StubsGenerator.php @@ -106,15 +106,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); 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);