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