‪TYPO3CMS  ‪main
IncludeTreeSyntaxScannerVisitor.php
Go to the documentation of this file.
1 <?php
2 
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 
19 
32 
39 final class IncludeTreeSyntaxScannerVisitor implements IncludeTreeVisitorInterface
40 {
44  private array ‪$errors = [];
45 
49  public function getErrors(): array
50  {
51  return ‪$this->errors;
52  }
53 
54  public function visitBeforeChildren(IncludeInterface $include, int $currentDepth): void {}
55 
56  public function visit(IncludeInterface $include, int $currentDepth): void
57  {
58  $this->brokenLinesAndBraces($include);
59  $this->emptyImports($include);
60 
61  // Add the line number of the first token of the line object to the error array.
62  // Not strictly needed, but more convenient in Fluid template to render.
63  foreach ($this->errors as &$error) {
65  $line = $error['line'];
66  $error['lineNumber'] = $line->getTokenStream()->reset()->peekNext()->getLine();
67  }
68 
69  // Sort array by line number to list them top->bottom in view.
70  usort($this->errors, fn($a, $b) => $a['lineNumber'] <=> $b['lineNumber']);
71  }
72 
77  private function brokenLinesAndBraces(IncludeInterface $include): void
78  {
79  if ($include->isSplit()) {
80  // If this node is split, don't check for syntax errors, this is
81  // done for child nodes.
82  return;
83  }
84  $lineStream = $include->getLineStream();
85  if (!$lineStream) {
86  return;
87  }
88  $braceCount = 0;
89  $lastLine = null;
90  foreach ($lineStream->getNextLine() as $line) {
91  $lastLine = $line;
92  if ($line instanceof InvalidLine) {
93  $this->errors[] = [
94  'type' => 'line.invalid',
95  'include' => $include,
96  'line' => $line,
97  ];
98  }
99  if ($line instanceof IdentifierBlockOpenLine) {
100  $braceCount++;
101  }
102  if ($line instanceof BlockCloseLine) {
103  $braceCount--;
104  if ($braceCount < 0) {
105  $braceCount = 0;
106  $this->errors[] = [
107  'type' => 'brace.excess',
108  'include' => $include,
109  'line' => $line,
110  ];
111  }
112  }
113  }
114  if ($braceCount !== 0) {
115  $this->errors[] = [
116  'type' => 'brace.missing',
117  'include' => $include,
118  'line' => $lastLine,
119  ];
120  }
121  }
122 
129  private function emptyImports(IncludeInterface $include): void
130  {
131  if (!$include->isSplit()) {
132  // Nodes containing @import are always split
133  return;
134  }
135  $lineStream = $include->getLineStream();
136  if (!$lineStream) {
137  // A node that is split should never have an empty line stream,
138  // this may be obsolete, but does not hurt much.
139  return;
140  }
141  // Find @import lines in this include, index by
142  // combination of line number and column position.
143  $allImportLines = [];
144  foreach ($lineStream->getNextLine() as $line) {
145  if ($line instanceof ImportLine || $line instanceof ImportOldLine) {
146  $valueToken = $line->getValueToken();
147  $allImportLines[$valueToken->getLine() . '-' . $valueToken->getColumn()] = $line;
148  }
149  }
150  // Now iterate children to exclude valid allImportLines, those that included something.
151  foreach ($include->getNextChild() as $child) {
152  if ($child instanceof AtImportInclude || $child instanceof IncludeTyposcriptInclude) {
154  $originalLine = $child->getOriginalLine();
155  $valueToken = $originalLine->getValueToken();
156  unset($allImportLines[$valueToken->getLine() . '-' . $valueToken->getColumn()]);
157  }
158  // Condition includes don't have the "body" lines itself (or a "body" sub node). This may change,
159  // but until then we'll have to scan the parent node and loop condition includes here to find out
160  // which of them resolved to child nodes.
161  if ($child instanceof ConditionInclude
162  || $child instanceof ConditionElseInclude
163  || $child instanceof ConditionIncludeTyposcriptInclude
164  ) {
165  foreach ($child->getNextChild() as $conditionChild) {
166  if ($conditionChild instanceof AtImportInclude || $conditionChild instanceof IncludeTyposcriptInclude) {
168  $originalLine = $conditionChild->getOriginalLine();
169  $valueToken = $originalLine->getValueToken();
170  unset($allImportLines[$valueToken->getLine() . '-' . $valueToken->getColumn()]);
171  }
172  }
173  }
174  }
175  // Everything left are invalid includes
176  foreach ($allImportLines as $importLine) {
177  $this->errors[] = [
178  'type' => 'import.empty',
179  'include' => $include,
180  'line' => $importLine,
181  ];
182  }
183  }
184 }
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\LineInterface
Definition: LineInterface.php:32
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\BlockCloseLine
Definition: BlockCloseLine.php:25
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\ImportLine
Definition: ImportLine.php:33
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\ImportOldLine
Definition: ImportOldLine.php:32
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeInterface
Definition: IncludeInterface.php:39
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\IdentifierBlockOpenLine
Definition: IdentifierBlockOpenLine.php:31
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\ConditionInclude
Definition: ConditionInclude.php:29
‪$errors
‪$errors
Definition: annotationChecker.php:116
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\IncludeTyposcriptInclude
Definition: IncludeTyposcriptInclude.php:25
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\ConditionIncludeTyposcriptInclude
Definition: ConditionIncludeTyposcriptInclude.php:28
‪TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor
Definition: IncludeTreeAstBuilderVisitor.php:18
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\ConditionElseInclude
Definition: ConditionElseInclude.php:34
‪TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\AtImportInclude
Definition: AtImportInclude.php:27
‪TYPO3\CMS\Core\TypoScript\Tokenizer\Line\InvalidLine
Definition: InvalidLine.php:33