‪TYPO3CMS  ‪main
validateRstFiles.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 require __DIR__ . '/../../vendor/autoload.php';
19 
20 if (PHP_SAPI !== 'cli') {
21  die('Script must be called from command line.' . chr(10));
22 }
23 
24 use Symfony\Component\Finder\Finder;
25 
36 {
40  protected ‪$messages;
41 
42  protected array ‪$linkTargets = [];
43 
47  protected ‪$isError;
48 
52  protected ‪$baseDir = 'typo3/sysext/core/Documentation/Changelog';
53 
54  public function ‪__construct(string ‪$dir = '')
55  {
56  if (‪$dir) {
57  $this->baseDir = ‪$dir;
58  }
59  }
60 
61  public function ‪validate()
62  {
63  printf('Searching for rst snippets in ' . $this->baseDir . chr(10));
64 
65  $count = 0;
66  ‪$finder = $this->‪findFiles();
67  foreach (‪$finder as $file) {
68  $filename = (string)$file;
69  $this->‪clearMessages();
70  $fileContent = $file->getContents();
71  $this->‪validateContent($fileContent);
72  $a = explode(chr(10), trim($fileContent));
73  $lastLine = array_pop($a);
74  $this->‪validateLastLine($lastLine);
75  $this->‪validateLastLineByFilename($filename, $lastLine);
76 
77  if ($this->isError) {
78  $shortPath = substr($filename, strlen($this->baseDir));
79  $shortPath = ltrim($shortPath, '/\\');
80  $count++;
81  printf(
82  '%-10s | %-12s | %-17s | %s ' . chr(10),
83  $this->messages['include']['title'],
84  $this->messages['reference']['title'],
85  $this->messages['index']['title'],
86  $shortPath
87  );
88  foreach ($this->messages as $message) {
89  if ($message['message']) {
90  printf($message['message'] . chr(10));
91  }
92  }
93  }
94  }
95 
96  if ($count > 0) {
97  fwrite(STDERR, 'Found ' . $count . ' rst files with errors, check full log for details.' . chr(10));
98  exit(1);
99  }
100  exit(0);
101  }
102 
103  public function ‪findFiles(): Finder
104  {
105  ‪$finder = new Finder();
106  ‪$finder
107  ->files()
108  ->in($this->baseDir)
109  ->name('/\.rst$/')
110  ->notName('Index.rst')
111  ->notName('Howto.rst');
112 
113  return ‪$finder;
114  }
115 
116  protected function ‪clearMessages()
117  {
118  $this->messages = [
119  'include' => [
120  'title' => '',
121  'message' => '',
122  ],
123  'reference' => [
124  'title' => '',
125  'message' => '',
126  ],
127  'index' => [
128  'title' => '',
129  'message' => '',
130  ],
131  ];
132 
133  $this->isError = false;
134  }
135 
136  protected function ‪validateContent(string $fileContent)
137  {
138  $checkFor = [
139  [
140  'type' => 'include',
141  'regex' => '#^\\.\\. include:: /Includes.rst.txt#m',
142  'title' => 'no include',
143  'message' => 'insert \'.. include:: /Includes.rst.txt\' in first line of the file',
144  ],
145  [
146  'type' => 'title',
147  'regex' => '#\={2,}\n.*\n\={2,}#m',
148  'title' => 'no title',
149  'message' => 'Each document must have a title with multiple === above and below',
150  ],
151  [
152  'type' => 'titleinvalid',
153  'regex' => '#(\={2,}\n)(Deprecation|Feature|Breaking|Important)(\:\s+\#)([0-9]{4,8})(=?.*\n\={2,})#m',
154  'title' => 'invalid title format',
155  'message' => 'A changelog entry title must have the following format: '
156  . '\'(Breaking|Feature|Deprecation|Important) #<issue nr>: <title>\'',
157  ],
158  [
159  'type' => 'titleformat',
160  'regex' => '#^See :issue:`[0-9]{4,6}`#m',
161  'title' => 'no reference',
162  'message' => 'insert \'See :issue:`<issuenumber>`\' after headline',
163  ],
164  ];
165 
166  foreach ($checkFor as $values) {
167  if (preg_match($values['regex'], $fileContent) !== 1) {
168  $this->‪setError($values);
169  }
170  }
171  $this->‪validateLinkTarget($fileContent);
172  }
173 
174  private function ‪setError(array $config)
175  {
176  $this->messages[$config['type']]['title'] = $config['title'];
177  $this->messages[$config['type']]['message'] = $config['message'];
178  $this->isError = true;
179  }
180 
181  private function ‪validateLinkTarget(string $fileContent)
182  {
183  $linkTargetConfig = [
184  'type' => 'linktarget',
185  'regex' => '#(\.\.\s+\_)([a-zA-Z0-9-_]*)(\:\s*)(\={2,}\n.*\n\={2,})#m',
186  'title' => 'no link target',
187  'message' => 'Each document must have a unique link target right before the main headline. '
188  . ' \'.. _deprecation-issuenumber:\' or \'.. _feature-issuenumber-currenttimestamp:\' are good choices.',
189  ];
190  $result = preg_match($linkTargetConfig['regex'], $fileContent, $matches);
191  if ($result === 1 && count($matches) > 2) {
192  $linkTarget = $matches[2];
193  if (in_array($linkTarget, $this->linkTargets)) {
194  $this->‪setError([
195  'type' => 'linktarget',
196  'title' => 'linktarget',
197  'message' => 'Link target _' . $linkTarget . ': is not unique. '
198  . 'Try adding a timestamp for uniqueness. i.e. _' . $linkTarget . '-' . time() . ':',
199  ]);
200  } else {
201  $this->linkTargets[] = $linkTarget;
202  }
203  } else {
204  $this->‪setError($linkTargetConfig);
205  }
206  }
207 
208  protected function ‪validateLastLine(string $line)
209  {
210  $checkFor = [
211  [
212  'type' => 'index',
213  'regex' => '#^\.\. index:: (?:(?:TypoScript|TSConfig|TCA|FlexForm|LocalConfiguration|Fluid|FAL|Database|JavaScript|PHP-API|Frontend|Backend|CLI|RTE|YAML|ext:[a-zA-Z_0-9]+)(?:,\\s|$))+#',
214  'title' => 'no or wrong index',
215  'message' => 'insert \'.. index:: <at least one valid keyword>\' at the last line of the file. See Build/Scripts/validateRstFiles.php for allowed keywords',
216  ],
217  ];
218 
219  foreach ($checkFor as $values) {
220  if (preg_match($values['regex'], $line) !== 1) {
221  $this->messages[$values['type']]['title'] = $values['title'];
222  $this->messages[$values['type']]['message'] = $values['message'];
223  $this->isError = true;
224  }
225  }
226  }
227 
228  protected function ‪validateLastLineByFilename(string $path, string $lastLine)
229  {
230  $checkFor = [
231  [
232  'type' => 'index',
233  'regexIgnoreFilename' => '#'
234  . 'Changelog[\\\\/]' // Ignore all Changelog files
235  . '(?:' // which are either
236  . '.+[\\\\/](?:Feature|Important)' // from any version but of type "Feature" or "Important"
237  . '|' // or
238  . '[78]' // from 7.x and 8.x (as there was no extension scanner back then)
239  . ')'
240  . '#',
241  'regex' => '#^\.\. index::.*(,|\s)(?:Fully|Partially|Not)Scanned([, ]|$).*#',
242  'title' => 'missing FullyScanned / PartiallyScanned / NotScanned tag',
243  'message' => 'insert \'.. index:: <at least one valid keyword and either FullyScanned, PartiallyScanned or NotScanned>\' at the last line of the file. See Build/Scripts/validateRstFiles.php for allowed keywords',
244  ],
245  ];
246 
247  foreach ($checkFor as $values) {
248  if (preg_match($values['regexIgnoreFilename'], $path) === 1) {
249  continue;
250  }
251  if (preg_match($values['regex'], $lastLine) !== 1) {
252  $this->messages[$values['type']]['title'] = $values['title'];
253  $this->messages[$values['type']]['message'] = $values['message'];
254  $this->isError = true;
255  }
256  }
257  }
258 }
259 
260 ‪$dir = '';
261 ‪$args = getopt('d:');
262 if (isset(‪$args['d'])) {
263  ‪$dir = ‪$args['d'];
264 }
266 ‪$validator->validate();
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪validateRstFiles\validateLastLine
‪validateLastLine(string $line)
Definition: validateRstFiles.php:205
‪validateRstFiles\$baseDir
‪string $baseDir
Definition: validateRstFiles.php:49
‪$dir
‪$dir
Definition: validateRstFiles.php:257
‪validateRstFiles\clearMessages
‪clearMessages()
Definition: validateRstFiles.php:113
‪validateRstFiles\$messages
‪array $messages
Definition: validateRstFiles.php:39
‪validateRstFiles\$isError
‪bool $isError
Definition: validateRstFiles.php:45
‪validateRstFiles\validateLastLineByFilename
‪validateLastLineByFilename(string $path, string $lastLine)
Definition: validateRstFiles.php:225
‪validateRstFiles\validate
‪validate()
Definition: validateRstFiles.php:58
‪validateRstFiles\__construct
‪__construct(string $dir='')
Definition: validateRstFiles.php:51
‪validateRstFiles\$linkTargets
‪array $linkTargets
Definition: validateRstFiles.php:41
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:262
‪validateRstFiles\findFiles
‪findFiles()
Definition: validateRstFiles.php:100
‪$args
‪$args
Definition: validateRstFiles.php:258
‪validateRstFiles\setError
‪setError(array $config)
Definition: validateRstFiles.php:171
‪validateRstFiles
Definition: validateRstFiles.php:36
‪validateRstFiles\validateLinkTarget
‪validateLinkTarget(string $fileContent)
Definition: validateRstFiles.php:178
‪validateRstFiles\validateContent
‪validateContent(string $fileContent)
Definition: validateRstFiles.php:133