#!/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);