‪TYPO3CMS  10.4
ConstructorArgumentMatcher.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 
20 use PhpParser\Node;
21 use PhpParser\Node\Expr\ConstFetch;
22 use PhpParser\Node\Expr\New_;
23 
37 {
38  protected const ‪TOPIC_TYPE_REQUIRED = 'required';
39  protected const ‪TOPIC_TYPE_DROPPED = 'dropped';
40  protected const ‪TOPIC_TYPE_CALLED = 'called';
41  protected const ‪TOPIC_TYPE_UNUSED = 'unused';
42 
48  public function ‪__construct(array ‪$matcherDefinitions)
49  {
50  $this->matcherDefinitions = ‪$matcherDefinitions;
52  self::TOPIC_TYPE_REQUIRED => ['numberOfMandatoryArguments'],
53  self::TOPIC_TYPE_DROPPED => ['maximumNumberOfArguments'],
54  self::TOPIC_TYPE_CALLED => ['numberOfMandatoryArguments', 'maximumNumberOfArguments'],
55  self::TOPIC_TYPE_UNUSED => ['unusedArgumentNumbers'],
56  ]);
57  }
58 
65  public function ‪enterNode(Node $node)
66  {
67  if ($this->‪isFileIgnored($node) || $this->‪isLineIgnored($node)) {
68  return;
69  }
70  $resolvedNode = $node->getAttribute(self::NODE_RESOLVED_AS, null) ?? $node;
71  if (!$resolvedNode instanceof New_
72  || !isset($resolvedNode->class)
73  || is_object($node->class) && !method_exists($node->class, '__toString')
74  || !array_key_exists((string)$resolvedNode->class, $this->matcherDefinitions)
75  ) {
76  return;
77  }
78 
79  // A method call is considered a match if it is not called with argument unpacking
80  // and number of used arguments is lower than numberOfMandatoryArguments
81  if ($this->‪isArgumentUnpackingUsed($resolvedNode->args)) {
82  return;
83  }
84 
85  // $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
86  // $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
87  $this->‪handleRequiredArguments($node, $resolvedNode);
88  $this->‪handleDroppedArguments($node, $resolvedNode);
89  $this->‪handleCalledArguments($node, $resolvedNode);
90  $this->‪handleUnusedArguments($node, $resolvedNode);
91  }
92 
98  protected function ‪handleRequiredArguments(Node $node, Node $resolvedNode): bool
99  {
100  $className = (string)$resolvedNode->class;
101  $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_REQUIRED] ?? null;
102  $mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
103  $numberOfArguments = count($resolvedNode->args);
104 
105  if ($candidate === null || $numberOfArguments >= $mandatoryArguments) {
106  return false;
107  }
108 
109  $this->matches[] = [
110  'restFiles' => $candidate['restFiles'],
111  'line' => $node->getAttribute('startLine'),
112  'message' => sprintf(
113  '%s::__construct requires at least %d arguments (%d given).',
114  $className,
115  $mandatoryArguments,
116  $numberOfArguments
117  ),
118  'indicator' => 'strong',
119  ];
120  return true;
121  }
122 
128  protected function ‪handleDroppedArguments(Node $node, Node $resolvedNode): bool
129  {
130  $className = (string)$resolvedNode->class;
131  $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_DROPPED] ?? null;
132  $maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
133  $numberOfArguments = count($resolvedNode->args);
134 
135  if ($candidate === null || $numberOfArguments <= $maximumArguments) {
136  return false;
137  }
138 
139  $this->matches[] = [
140  'restFiles' => $candidate['restFiles'],
141  'line' => $node->getAttribute('startLine'),
142  'message' => sprintf(
143  '%s::__construct supports only %d arguments (%d given).',
144  $className,
145  $maximumArguments,
146  $numberOfArguments
147  ),
148  'indicator' => 'strong',
149  ];
150  return true;
151  }
152 
158  protected function ‪handleCalledArguments(Node $node, Node $resolvedNode): bool
159  {
160  $className = (string)$resolvedNode->class;
161  $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_CALLED] ?? null;
162  $isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($resolvedNode->args);
163  $mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
164  $maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
165  $numberOfArguments = count($resolvedNode->args);
166 
167  if ($candidate === null
168  || !$isArgumentUnpackingUsed
169  && ($numberOfArguments < $mandatoryArguments || $numberOfArguments > $maximumArguments)) {
170  return false;
171  }
172 
173  $this->matches[] = [
174  'restFiles' => $candidate['restFiles'],
175  'line' => $node->getAttribute('startLine'),
176  'message' => sprintf(
177  '%s::__construct being called (%d arguments given).',
178  $className,
179  $numberOfArguments
180  ),
181  'indicator' => 'weak',
182  ];
183  return true;
184  }
185 
191  protected function ‪handleUnusedArguments(Node $node, Node $resolvedNode): bool
192  {
193  $className = (string)$resolvedNode->class;
194  $candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_UNUSED] ?? null;
195  // values in array (if any) are actual position counts
196  // e.g. `[2, 4]` refers to internal argument indexes `[1, 3]`
197  $unusedArgumentPositions = $candidate['unusedArgumentNumbers'] ?? null;
198 
199  ‪if ($candidate === null || empty($unusedArgumentPositions)) {
200  return false;
201  }
202 
203  $arguments = $resolvedNode->args;
204  // keeping positions having argument values that are not null
205  $unusedArgumentPositions = array_filter(
206  $unusedArgumentPositions,
207  function (int $position) use ($arguments) {
208  $index = $position - 1;
209  return isset($arguments[$index]->value)
210  && !$arguments[$index]->value instanceof ConstFetch
211  && (
212  !isset($arguments[$index]->value->name->name->parts[0])
213  || $arguments[$index]->value->name->name->parts[0] !== null
214  );
215  }
216  );
217  if (empty($unusedArgumentPositions)) {
218  return false;
219  }
220 
221  $this->matches[] = [
222  'restFiles' => $candidate['restFiles'],
223  'line' => $node->getAttribute('startLine'),
224  'message' => sprintf(
225  '%s::__construct was called with argument positions %s not being null.',
226  $className,
227  implode(', ', $unusedArgumentPositions)
228  ),
229  'indicator' => 'strong',
230  ];
231  return true;
232  }
233 
234  protected function ‪validateMatcherDefinitionsTopicRequirements(array $topicRequirements): void
235  {
236  foreach ($this->matcherDefinitions as $key => $matcherDefinition) {
237  foreach ($topicRequirements as $topic => $requiredArrayKeys) {
238  if (empty($matcherDefinition[$topic])) {
239  continue;
240  }
241  $this->‪validateMatcherDefinitionKeys($key, $matcherDefinition[$topic], $requiredArrayKeys);
242  }
243  }
244  }
245 }
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\handleDroppedArguments
‪bool handleDroppedArguments(Node $node, Node $resolvedNode)
Definition: ConstructorArgumentMatcher.php:128
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher\isArgumentUnpackingUsed
‪bool isArgumentUnpackingUsed(array $arguments=[])
Definition: AbstractCoreMatcher.php:164
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher\validateMatcherDefinitionKeys
‪validateMatcherDefinitionKeys(string $key, array $matcherDefinition, array $requiredArrayKeys=[])
Definition: AbstractCoreMatcher.php:95
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\TOPIC_TYPE_DROPPED
‪const TOPIC_TYPE_DROPPED
Definition: ConstructorArgumentMatcher.php:39
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher
Definition: AbstractCoreMatcher.php:18
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\handleRequiredArguments
‪bool handleRequiredArguments(Node $node, Node $resolvedNode)
Definition: ConstructorArgumentMatcher.php:98
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\TOPIC_TYPE_UNUSED
‪const TOPIC_TYPE_UNUSED
Definition: ConstructorArgumentMatcher.php:41
‪if
‪if(PHP_SAPI !=='cli')
Definition: splitAcceptanceTests.php:33
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher\isLineIgnored
‪bool isLineIgnored(Node $node)
Definition: AbstractCoreMatcher.php:181
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher
Definition: AbstractCoreMatcher.php:34
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\__construct
‪__construct(array $matcherDefinitions)
Definition: ConstructorArgumentMatcher.php:48
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\TOPIC_TYPE_CALLED
‪const TOPIC_TYPE_CALLED
Definition: ConstructorArgumentMatcher.php:40
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\enterNode
‪enterNode(Node $node)
Definition: ConstructorArgumentMatcher.php:65
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher\isFileIgnored
‪bool isFileIgnored(Node $node)
Definition: AbstractCoreMatcher.php:214
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\AbstractCoreMatcher\$matcherDefinitions
‪array $matcherDefinitions
Definition: AbstractCoreMatcher.php:41
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher
Definition: ConstructorArgumentMatcher.php:37
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\handleUnusedArguments
‪bool handleUnusedArguments(Node $node, Node $resolvedNode)
Definition: ConstructorArgumentMatcher.php:191
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\TOPIC_TYPE_REQUIRED
‪const TOPIC_TYPE_REQUIRED
Definition: ConstructorArgumentMatcher.php:38
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\validateMatcherDefinitionsTopicRequirements
‪validateMatcherDefinitionsTopicRequirements(array $topicRequirements)
Definition: ConstructorArgumentMatcher.php:234
‪TYPO3\CMS\Install\ExtensionScanner\Php\Matcher\ConstructorArgumentMatcher\handleCalledArguments
‪bool handleCalledArguments(Node $node, Node $resolvedNode)
Definition: ConstructorArgumentMatcher.php:158