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