‪TYPO3CMS  10.4
docBlockChecker.php
Go to the documentation of this file.
1 #!/usr/bin/env php
2 <?php
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
18 use phpDocumentor\Reflection\DocBlockFactory;
19 use PhpParser\Error;
20 use PhpParser\Node;
21 use PhpParser\NodeTraverser;
22 use PhpParser\ParserFactory;
23 use Symfony\Component\Console\Output\ConsoleOutput;
24 
25 require_once __DIR__ . '/../../vendor/autoload.php';
26 
27 class NodeVisitor implements \PhpParser\NodeVisitor
28 {
32  private ‪$docBlockFactory;
33 
37  public ‪$namespace;
38 
42  public ‪$className;
43 
48 
52  public ‪$properties = [];
53 
57  public ‪$methods = [];
58 
62  public ‪$hasErrors = false;
63 
75  public function ‪beforeTraverse(array $nodes)
76  {
77  $this->docBlockFactory = DocBlockFactory::createInstance();
78  return null;
79  }
80 
98  public function ‪enterNode(Node $node)
99  {
100  switch (get_class($node)) {
101  case Node\Stmt\Namespace_::class:
103  $this->namespace = (string)$node->name;
104  break;
105  case Node\Stmt\Class_::class:
107  $this->className = (string)$node->name;
108 
109  try {
110  $docComment = $node->getDocComment();
111  if ($docComment instanceof \PhpParser\Comment) {
112  $this->docBlockFactory->create($docComment->getText());
113  }
114  } catch (\Throwable $e) {
115  $this->hasErrors = true;
116  $this->classCommentError = $e->getMessage();
117  }
118  break;
119  case Node\Stmt\Property::class:
121  $property = [
122  'name' => (string)$node->props[0]->name,
123  'error' => null
124  ];
125 
126  try {
127  $docComment = $node->getDocComment();
128  if ($docComment instanceof \PhpParser\Comment) {
129  $this->docBlockFactory->create($docComment->getText());
130  }
131  } catch (\Throwable $e) {
132  $this->hasErrors = true;
133  $property['error'] = $e->getMessage();
134  }
135 
136  $this->properties[] = $property;
137  break;
138  case Node\Stmt\ClassMethod::class:
140  $method = [
141  'name' => (string)$node->name,
142  'error' => null
143  ];
144 
145  try {
146  $docComment = $node->getDocComment();
147  if ($docComment instanceof \PhpParser\Comment) {
148  $this->docBlockFactory->create($docComment->getText());
149  }
150  } catch (\Throwable $e) {
151  $this->hasErrors = true;
152  $method['error'] = $e->getMessage();
153  }
154 
155  $this->methods[] = $method;
156  break;
157  default:
158  break;
159  }
160 
161  return null;
162  }
163 
183  public function leaveNode(Node $node)
184  {
185  return null;
186  }
187 
199  public function afterTraverse(array $nodes)
200  {
201  return null;
202  }
203 }
204 
205 ‪$parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
206 
207 ‪$finder = new Symfony\Component\Finder\Finder();
208 ‪$finder->files()
209  ->in(__DIR__ . '/../../typo3/sysext/*/Classes/')
210  ->in(__DIR__ . '/../../typo3/sysext/*/Tests/')
211  ->notPath('_generated')
212  ->name('/\.php$/')
213 // ->notName('ServiceProviderRegistry.php')
214 ;
215 
216 ‪$output = new ConsoleOutput();
217 
218 ‪$errors = [];
219 foreach (‪$finder as $file) {
220  try {
221  $ast = ‪$parser->parse($file->getContents());
222  } catch (Error $error) {
223  ‪$output->writeln('<error>Parse error: ' . $error->getMessage() . '</error>');
224  exit(1);
225  }
226 
227  $visitor = new NodeVisitor();
228 
229  $traverser = new NodeTraverser();
230  $traverser->addVisitor($visitor);
231 
232  try {
233  $ast = $traverser->traverse($ast);
234  } catch (\Throwable $e) {
235  ‪$errors[$file->getRealPath()]['error'] = $e->getMessage();
236  ‪$output->write('<error>F</error>');
237  continue;
238  }
239 
240  if ($visitor->className === null || $visitor->namespace === null) {
241  // only process files that contain classes for now
242  continue;
243  }
244 
245  if ($visitor->hasErrors) {
246  ‪$errors[$file->getRealPath()]['fqcn'] = $visitor->namespace . '\\' . $visitor->className;
247 
248  if ($visitor->classCommentError !== null) {
249  ‪$errors[$file->getRealPath()]['class'] = $visitor->classCommentError;
250  }
251 
252  foreach ($visitor->properties as $property) {
253  if (empty($property['error'])) {
254  continue;
255  }
256 
257  ‪$errors[$file->getRealPath()]['properties'][$property['name']] = $property['error'];
258  }
259 
260  foreach ($visitor->methods as $method) {
261  if (empty($method['error'])) {
262  continue;
263  }
264 
265  ‪$errors[$file->getRealPath()]['methods'][$method['name']] = $method['error'];
266  }
267 
268  ‪$output->write('<error>F</error>');
269  } else {
270  ‪$output->write('<fg=green>.</>');
271  }
272 }
273 
274 ‪$output->writeln('');
275 
276 if (!empty(‪$errors)) {
277  foreach (‪$errors as $file => $errorsInFile) {
278  ‪$output->writeln('');
279  ‪$output->writeln('');
280  ‪$output->writeln('<error>' . $file . '</error>');
281  ‪$output->writeln('</>');
282 
283  if (isset($errorsInFile['class'])) {
284  $table = new \Symfony\Component\Console\Helper\Table(‪$output);
285  $table->setHeaders(['Class', 'Errors']);
286  $table->addRow([$errorsInFile['fqcn'], $errorsInFile['class']]);
287  $table->setStyle('borderless');
288  $table->render();
289  }
290 
291  $properties = $errorsInFile['properties'] ?? [];
292  if (count($properties)) {
293  $table = new \Symfony\Component\Console\Helper\Table(‪$output);
294  $table->setHeaders(['Properties', 'Errors']);
295  foreach ($properties as $propertyName => $error) {
296  $table->addRow([$errorsInFile['fqcn'] . '::' . $propertyName, $error]);
297  }
298  $table->setStyle('borderless');
299  $table->render();
300  }
301 
302  $methods = $errorsInFile['methods'] ?? [];
303  if (count($methods)) {
304  $table = new \Symfony\Component\Console\Helper\Table(‪$output);
305  $table->setHeaders(['Methods', 'Errors']);
306  foreach ($methods as $methodName => $error) {
307  $table->addRow([$errorsInFile['fqcn'] . '::' . $methodName . '()', $error]);
308  }
309  $table->setStyle('borderless');
310  $table->render();
311  }
312  }
313  exit(1);
314 }
315 
316 exit(0);
‪NodeVisitor\$className
‪string null $className
Definition: docBlockChecker.php:39
‪NodeVisitor\leaveNode
‪int Node Node[] null leaveNode(Node $node)
Definition: docBlockChecker.php:176
‪NodeVisitor\enterNode
‪enterNode(Node $node)
Definition: annotationChecker.php:37
‪NodeVisitor\$classCommentError
‪string null $classCommentError
Definition: docBlockChecker.php:43
‪$errors
‪$errors
Definition: docBlockChecker.php:211
‪$finder
‪$finder
Definition: docBlockChecker.php:200
‪NodeVisitor\$methods
‪array $methods
Definition: docBlockChecker.php:51
‪NodeVisitor\beforeTraverse
‪Node[] null beforeTraverse(array $nodes)
Definition: docBlockChecker.php:68
‪NodeVisitor\$hasErrors
‪bool $hasErrors
Definition: docBlockChecker.php:55
‪NodeVisitor\$namespace
‪string null $namespace
Definition: docBlockChecker.php:35
‪NodeVisitor\afterTraverse
‪Node[] null afterTraverse(array $nodes)
Definition: docBlockChecker.php:192
‪NodeVisitor\$properties
‪array $properties
Definition: docBlockChecker.php:47
‪NodeVisitor\enterNode
‪int Node null enterNode(Node $node)
Definition: docBlockChecker.php:91
‪NodeVisitor\$docBlockFactory
‪DocBlockFactory $docBlockFactory
Definition: docBlockChecker.php:31
‪$output
‪$output
Definition: docBlockChecker.php:209
‪$parser
‪$parser
Definition: docBlockChecker.php:198