[BUGFIX] Apply enableFields in JOIN's ON condition
[Packages/TYPO3.CMS.git] / Build / Scripts / docBlockChecker.php
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 {
29 /**
30 * @var DocBlockFactory
31 */
32 private $docBlockFactory;
33
34 /**
35 * @var string|null
36 */
37 public $namespace;
38
39 /**
40 * @var string|null
41 */
42 public $className;
43
44 /**
45 * @var string|null
46 */
47 public $classCommentError;
48
49 /**
50 * @var array
51 */
52 public $properties = [];
53
54 /**
55 * @var array
56 */
57 public $methods = [];
58
59 /**
60 * @var bool
61 */
62 public $hasErrors = false;
63
64 /**
65 * Called once before traversal.
66 *
67 * Return value semantics:
68 * * null: $nodes stays as-is
69 * * otherwise: $nodes is set to the return value
70 *
71 * @param Node[] $nodes Array of nodes
72 *
73 * @return Node[]|null Array of nodes
74 */
75 public function beforeTraverse(array $nodes)
76 {
77 $this->docBlockFactory = DocBlockFactory::createInstance();
78 return null;
79 }
80
81 /**
82 * Called when entering a node.
83 *
84 * Return value semantics:
85 * * null
86 * => $node stays as-is
87 * * NodeTraverser::DONT_TRAVERSE_CHILDREN
88 * => Children of $node are not traversed. $node stays as-is
89 * * NodeTraverser::STOP_TRAVERSAL
90 * => Traversal is aborted. $node stays as-is
91 * * otherwise
92 * => $node is set to the return value
93 *
94 * @param Node $node Node
95 *
96 * @return int|Node|null Replacement node (or special return value)
97 */
98 public function enterNode(Node $node)
99 {
100 switch (get_class($node)) {
101 case Node\Stmt\Namespace_::class:
102 /** @var Node\Stmt\Namespace_ $node */
103 $this->namespace = (string)$node->name;
104 break;
105 case Node\Stmt\Class_::class:
106 /** @var Node\Stmt\Class_ $node */
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:
120 /** @var Node\Stmt\Property $node */
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:
139 /** @var Node\Stmt\ClassMethod $node */
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
164 /**
165 * Called when leaving a node.
166 *
167 * Return value semantics:
168 * * null
169 * => $node stays as-is
170 * * NodeTraverser::REMOVE_NODE
171 * => $node is removed from the parent array
172 * * NodeTraverser::STOP_TRAVERSAL
173 * => Traversal is aborted. $node stays as-is
174 * * array (of Nodes)
175 * => The return value is merged into the parent array (at the position of the $node)
176 * * otherwise
177 * => $node is set to the return value
178 *
179 * @param Node $node Node
180 *
181 * @return int|Node|Node[]|null Replacement node (or special return value)
182 */
183 public function leaveNode(Node $node)
184 {
185 return null;
186 }
187
188 /**
189 * Called once after traversal.
190 *
191 * Return value semantics:
192 * * null: $nodes stays as-is
193 * * otherwise: $nodes is set to the return value
194 *
195 * @param Node[] $nodes Array of nodes
196 *
197 * @return Node[]|null Array of nodes
198 */
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);