#!/usr/bin/env php docBlockFactory = DocBlockFactory::createInstance(); return null; } /** * Called when entering a node. * * Return value semantics: * * null * => $node stays as-is * * NodeTraverser::DONT_TRAVERSE_CHILDREN * => Children of $node are not traversed. $node stays as-is * * NodeTraverser::STOP_TRAVERSAL * => Traversal is aborted. $node stays as-is * * otherwise * => $node is set to the return value * * @param Node $node Node * * @return int|Node|null Replacement node (or special return value) */ public function enterNode(Node $node) { switch (get_class($node)) { case Node\Stmt\Namespace_::class: /** @var Node\Stmt\Namespace_ $node */ $this->namespace = (string)$node->name; break; case Node\Stmt\Class_::class: /** @var Node\Stmt\Class_ $node */ $this->className = (string)$node->name; try { $docComment = $node->getDocComment(); if ($docComment instanceof \PhpParser\Comment) { $this->docBlockFactory->create($docComment->getText()); } } catch (\Throwable $e) { $this->hasErrors = true; $this->classCommentError = $e->getMessage(); } break; case Node\Stmt\Property::class: /** @var Node\Stmt\Property $node */ $property = [ 'name' => (string)$node->props[0]->name, 'error' => null ]; try { $docComment = $node->getDocComment(); if ($docComment instanceof \PhpParser\Comment) { $this->docBlockFactory->create($docComment->getText()); } } catch (\Throwable $e) { $this->hasErrors = true; $property['error'] = $e->getMessage(); } $this->properties[] = $property; break; case Node\Stmt\ClassMethod::class: /** @var Node\Stmt\ClassMethod $node */ $method = [ 'name' => (string)$node->name, 'error' => null ]; try { $docComment = $node->getDocComment(); if ($docComment instanceof \PhpParser\Comment) { $this->docBlockFactory->create($docComment->getText()); } } catch (\Throwable $e) { $this->hasErrors = true; $method['error'] = $e->getMessage(); } $this->methods[] = $method; break; default: break; } return null; } /** * Called when leaving a node. * * Return value semantics: * * null * => $node stays as-is * * NodeTraverser::REMOVE_NODE * => $node is removed from the parent array * * NodeTraverser::STOP_TRAVERSAL * => Traversal is aborted. $node stays as-is * * array (of Nodes) * => The return value is merged into the parent array (at the position of the $node) * * otherwise * => $node is set to the return value * * @param Node $node Node * * @return int|Node|Node[]|null Replacement node (or special return value) */ public function leaveNode(Node $node) { return null; } /** * Called once after traversal. * * Return value semantics: * * null: $nodes stays as-is * * otherwise: $nodes is set to the return value * * @param Node[] $nodes Array of nodes * * @return Node[]|null Array of nodes */ public function afterTraverse(array $nodes) { return null; } } $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); $finder = new Symfony\Component\Finder\Finder(); $finder->files() ->in(__DIR__ . '/../../typo3/sysext/*/Classes/') ->in(__DIR__ . '/../../typo3/sysext/*/Tests/') ->notPath('_generated') ->name('/\.php$/') // ->notName('ServiceProviderRegistry.php') ; $output = new ConsoleOutput(); $errors = []; foreach ($finder as $file) { try { $ast = $parser->parse($file->getContents()); } catch (Error $error) { $output->writeln('Parse error: ' . $error->getMessage() . ''); exit(1); } $visitor = new NodeVisitor(); $traverser = new NodeTraverser(); $traverser->addVisitor($visitor); try { $ast = $traverser->traverse($ast); } catch (\Throwable $e) { $errors[$file->getRealPath()]['error'] = $e->getMessage(); $output->write('F'); continue; } if ($visitor->className === null || $visitor->namespace === null) { // only process files that contain classes for now continue; } if ($visitor->hasErrors) { $errors[$file->getRealPath()]['fqcn'] = $visitor->namespace . '\\' . $visitor->className; if ($visitor->classCommentError !== null) { $errors[$file->getRealPath()]['class'] = $visitor->classCommentError; } foreach ($visitor->properties as $property) { if (empty($property['error'])) { continue; } $errors[$file->getRealPath()]['properties'][$property['name']] = $property['error']; } foreach ($visitor->methods as $method) { if (empty($method['error'])) { continue; } $errors[$file->getRealPath()]['methods'][$method['name']] = $method['error']; } $output->write('F'); } else { $output->write('.'); } } $output->writeln(''); if (!empty($errors)) { foreach ($errors as $file => $errorsInFile) { $output->writeln(''); $output->writeln(''); $output->writeln('' . $file . ''); $output->writeln(''); if (isset($errorsInFile['class'])) { $table = new \Symfony\Component\Console\Helper\Table($output); $table->setHeaders(['Class', 'Errors']); $table->addRow([$errorsInFile['fqcn'], $errorsInFile['class']]); $table->setStyle('borderless'); $table->render(); } $properties = $errorsInFile['properties'] ?? []; if (count($properties)) { $table = new \Symfony\Component\Console\Helper\Table($output); $table->setHeaders(['Properties', 'Errors']); foreach ($properties as $propertyName => $error) { $table->addRow([$errorsInFile['fqcn'] . '::' . $propertyName, $error]); } $table->setStyle('borderless'); $table->render(); } $methods = $errorsInFile['methods'] ?? []; if (count($methods)) { $table = new \Symfony\Component\Console\Helper\Table($output); $table->setHeaders(['Methods', 'Errors']); foreach ($methods as $methodName => $error) { $table->addRow([$errorsInFile['fqcn'] . '::' . $methodName . '()', $error]); } $table->setStyle('borderless'); $table->render(); } } exit(1); } exit(0);