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