TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Install\ExtensionScanner\CodeScannerInterface;
|
||||
|
||||
/**
|
||||
* Single "core matcher" classes extend from this.
|
||||
* It brings a set of protected methods to help single matcher classes doing common stuff.
|
||||
* This abstract extends the nikic/php-parser NodeVisitorAbstract which implements the main
|
||||
* parser interface, and it implements the TYPO3 specific CodeScannerInterface to retrieve matches.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
abstract class AbstractCoreMatcher extends NodeVisitorAbstract implements CodeScannerInterface
|
||||
{
|
||||
public const NODE_RESOLVED_AS = 'nodeResolvedAs';
|
||||
|
||||
/**
|
||||
* Incoming main configuration array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $matcherDefinitions = [];
|
||||
|
||||
/**
|
||||
* @var array List of accumulated matches
|
||||
*/
|
||||
protected $matches = [];
|
||||
|
||||
/**
|
||||
* Helper property containing an array derived from $this->matcherDefinitions
|
||||
* created in __construct() if needed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $flatMatcherDefinitions = [];
|
||||
|
||||
/**
|
||||
* @var int Helper variable for ignored line detection
|
||||
*/
|
||||
protected $currentCodeLine = 0;
|
||||
|
||||
/**
|
||||
* @var bool True if line with $lastIgnoredLineNumber is ignored
|
||||
*/
|
||||
protected $isCurrentLineIgnored = false;
|
||||
|
||||
/**
|
||||
* @var bool True if the entire file is ignored due to a @extensionScannerIgnoreFile class comment
|
||||
*/
|
||||
protected $isFullFileIgnored = false;
|
||||
|
||||
/**
|
||||
* Return list of matches after processing
|
||||
*/
|
||||
public function getMatches(): array
|
||||
{
|
||||
return $this->matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some matcher need specific keys in the array definition to work properly.
|
||||
* This method is called typically in __construct() of a matcher to
|
||||
* verify these are given.
|
||||
* This method is a measure against broken core configuration. It should be
|
||||
* pretty quick and is only called in __construct() once, no kitten should be harmed.
|
||||
*
|
||||
* This method works on $this->matcherDefinitions.
|
||||
*
|
||||
* @param array $requiredArrayKeys List of required keys for single matchers
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function validateMatcherDefinitions(array $requiredArrayKeys = [])
|
||||
{
|
||||
foreach ($this->matcherDefinitions as $key => $matcherDefinition) {
|
||||
$this->validateMatcherDefinitionKeys($key, $matcherDefinition, $requiredArrayKeys);
|
||||
}
|
||||
}
|
||||
|
||||
protected function validateMatcherDefinitionKeys(string $key, array $matcherDefinition, array $requiredArrayKeys = []): void
|
||||
{
|
||||
// Each config must point to at least one .rst file
|
||||
if (empty($matcherDefinition['restFiles'])) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Each configuration must have at least one referenced "restFiles" entry. Offending key: ' . $key,
|
||||
1500496068
|
||||
);
|
||||
}
|
||||
foreach ($matcherDefinition['restFiles'] as $file) {
|
||||
if (empty($file)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Empty restFiles definition',
|
||||
1500735983
|
||||
);
|
||||
}
|
||||
}
|
||||
// Config broken if not all required array keys are specified in config
|
||||
$sharedArrays = array_intersect(array_keys($matcherDefinition), $requiredArrayKeys);
|
||||
if (count($sharedArrays) !== count($requiredArrayKeys)) {
|
||||
$missingKeys = array_diff($requiredArrayKeys, array_keys($matcherDefinition));
|
||||
throw new \InvalidArgumentException(
|
||||
'Required matcher definitions missing: ' . implode(', ', $missingKeys) . ' offending key: ' . $key,
|
||||
1500492001
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize helper lookup array $this->flatMatcherDefinitions.
|
||||
* For class\name->foo matcherDefinitions, it creates a helper array
|
||||
* containing only the method name as array keys for "weak" matches.
|
||||
*
|
||||
* If methods with the same name from different classes are defined,
|
||||
* a "candidate" array is created containing details of single possible
|
||||
* matches for further analysis.
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function initializeFlatMatcherDefinitions()
|
||||
{
|
||||
$methodNameArray = [];
|
||||
foreach ($this->matcherDefinitions as $classAndMethod => $details) {
|
||||
$method = GeneralUtility::trimExplode('::', $classAndMethod);
|
||||
if (count($method) !== 2) {
|
||||
$method = GeneralUtility::trimExplode('->', $classAndMethod);
|
||||
}
|
||||
if (count($method) !== 2) {
|
||||
throw new \RuntimeException(
|
||||
'Keys in $this->matcherDefinitions must have a Class\Name->method or Class\Name::method structure',
|
||||
1500557309
|
||||
);
|
||||
}
|
||||
$method = $method[1];
|
||||
if (!array_key_exists($method, $methodNameArray)) {
|
||||
$methodNameArray[$method]['candidates'] = [];
|
||||
}
|
||||
$methodNameArray[$method]['candidates'][] = $details;
|
||||
}
|
||||
$this->flatMatcherDefinitions = $methodNameArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if one argument is given as "...$someArray".
|
||||
* If so, it kinda defeats any "argument count" approach.
|
||||
*
|
||||
* @param array $arguments List of arguments
|
||||
*/
|
||||
protected function isArgumentUnpackingUsed(array $arguments = []): bool
|
||||
{
|
||||
foreach ($arguments as $arg) {
|
||||
if ($arg->unpack === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a comment before a statement is
|
||||
* marked as "@extensionScannerIgnoreLine"
|
||||
*/
|
||||
protected function isLineIgnored(Node $node): bool
|
||||
{
|
||||
// Early return if this line is marked as ignored
|
||||
$startLineOfNode = $node->getAttribute('startLine');
|
||||
if ($startLineOfNode === $this->currentCodeLine) {
|
||||
return $this->isCurrentLineIgnored;
|
||||
}
|
||||
if ($this->isCurrentLineIgnored) {
|
||||
// "ignoreMode" is still active, but we're past the line
|
||||
// where it was enabled. Reset this beauty.
|
||||
$this->isCurrentLineIgnored = false;
|
||||
}
|
||||
|
||||
$currentLineIsIgnored = false;
|
||||
if ($startLineOfNode !== $this->currentCodeLine) {
|
||||
$this->currentCodeLine = $startLineOfNode;
|
||||
// First node of a new line may contain the annotation
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (str_contains($comment->getText(), '@extensionScannerIgnoreLine')) {
|
||||
$this->isCurrentLineIgnored = true;
|
||||
$currentLineIsIgnored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $currentLineIsIgnored;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the node is ignored since the entire file is ignored.
|
||||
* Sets ignore status if a class node is given having the annotation.
|
||||
*/
|
||||
protected function isFileIgnored(Node $node): bool
|
||||
{
|
||||
if ($this->isFullFileIgnored) {
|
||||
return true;
|
||||
}
|
||||
$currentFileIsIgnored = false;
|
||||
if ($node instanceof Class_) {
|
||||
$comments = $node->getAttribute('comments');
|
||||
if (!empty($comments)) {
|
||||
foreach ($comments as $comment) {
|
||||
if (str_contains($comment->getText(), '@extensionScannerIgnoreFile')) {
|
||||
$this->isFullFileIgnored = true;
|
||||
$currentFileIsIgnored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $currentFileIsIgnored;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Find usages of defined methods within a class that are deprecated/removed.
|
||||
* Requires to extend a TYPO3 API class/abstract.
|
||||
* This is a strong match.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class AbstractMethodImplementationMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
protected const DEFINITION_STATIC = 'static';
|
||||
protected const DEFINITION_LOCAL = 'local';
|
||||
protected array $matcherDefinitionLookup = [];
|
||||
|
||||
/**
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
|
||||
// initializeFlatMatcherDefinitions() unfortunately does not deliver the actual
|
||||
// method, so we need to do something 99% similar here for a custom
|
||||
// property, to not require larger changes to the underlying abstract method.
|
||||
foreach ($this->matcherDefinitions as $classAndMethod => $details) {
|
||||
$parts = GeneralUtility::trimExplode('::', $classAndMethod);
|
||||
$definition = self::DEFINITION_STATIC;
|
||||
if (count($parts) !== 2) {
|
||||
$parts = GeneralUtility::trimExplode('->', $classAndMethod);
|
||||
$definition = self::DEFINITION_LOCAL;
|
||||
}
|
||||
// Exception-Handling removed, covered by initializeFlatMatcherDefinitions();
|
||||
|
||||
$method = $parts[1];
|
||||
$class = $parts[0];
|
||||
if (!array_key_exists($class, $this->matcherDefinitionLookup)) {
|
||||
$this->matcherDefinitionLookup[$class][$definition][$method]['candidates'] = [];
|
||||
}
|
||||
$this->matcherDefinitionLookup[$class][$definition][$method]['candidates'][] = $details;
|
||||
|
||||
// Builds something like:
|
||||
// [
|
||||
// 'TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper' => [
|
||||
// 'static' => [
|
||||
// 'renderStatic' => [
|
||||
// 'candidates' => [
|
||||
// [
|
||||
// 'restFiles' => [
|
||||
// 'Deprecation-104789-RenderStaticForFluidViewHelpers.rst',
|
||||
// ],
|
||||
// ],
|
||||
// ],
|
||||
// ]
|
||||
// ],
|
||||
// ],
|
||||
// 'TYPO3\CMS\AbstractSomething' => [
|
||||
// 'local' => [
|
||||
// 'someMethodName' => [
|
||||
// 'candidates' => [
|
||||
// [
|
||||
// 'restFiles' => [
|
||||
// 'Breaking-12345-something.rst',
|
||||
// ],
|
||||
// ],
|
||||
// [
|
||||
// 'restFiles' => [
|
||||
// 'Breaking-67890-something.rst',
|
||||
// ],
|
||||
// ],
|
||||
// ],
|
||||
// ]
|
||||
// ],
|
||||
// ],
|
||||
// ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for a defined method that shall longer be utilized (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof Node\Stmt\Class_
|
||||
&& $node->extends) {
|
||||
|
||||
// We found a class definition.
|
||||
// Check what classes this definition is extending (Abstract).
|
||||
// Without a class extending something, this is not API usage and thus not scanned.
|
||||
// Now check if the extended class is part of our matcherDefinition to inspect
|
||||
if (array_key_exists($node->extends->name, $this->matcherDefinitionLookup)) {
|
||||
|
||||
// Iterate all declared methods (of the inspected custom class, NOT the abstract!)
|
||||
$lookupMethods = $this->matcherDefinitionLookup[$node->extends->name];
|
||||
foreach ($node->getMethods() as $method) {
|
||||
|
||||
// The matcherDefinition can utilize 'Abstract::staticMethod' or 'Abstract->localMethod',
|
||||
// which is handled distinctly, so that the matches are stronger.
|
||||
$lookupKey = $method->isStatic() ? self::DEFINITION_STATIC : self::DEFINITION_LOCAL;
|
||||
|
||||
if (isset($lookupMethods[$lookupKey][$method->name->toString()]['candidates'])) {
|
||||
// The checked method of an object extending a deprecated/BC class was a match.
|
||||
// Gather final match info (multiple ReST files can apply to a single class+method)
|
||||
foreach ($lookupMethods[$lookupKey][$method->name->toString()]['candidates'] as $candidate) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $method->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'Definition of %s method "%s" extends from "%s"',
|
||||
$lookupKey,
|
||||
$method->name->toString(),
|
||||
$node->extends->name
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Find usages of dropped configuration values and hook registrations.
|
||||
* Matches on "last" key only.
|
||||
* Definition of $GLOBALS['foo']['bar'] and usage as $foo['bar'] matches.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ArrayDimensionMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Initialize "flat" matcher array from matcher definitions.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeLastArrayKeyNameArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ArrayDimFetch
|
||||
&& isset($node->dim->value)
|
||||
&& array_key_exists($node->dim->value, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Access to array key "' . $node->dim->value . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->dim->value]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare 'lastKey' => [$details] array in flatMatcherDefinitions
|
||||
*/
|
||||
protected function initializeLastArrayKeyNameArray()
|
||||
{
|
||||
$methodNameArray = [];
|
||||
foreach ($this->matcherDefinitions as $fullArrayString => $details) {
|
||||
// Goal: find last part "foobar" of an array path "$foo['bar']['foobar']"
|
||||
// Reverse string $foo['bar']['foobar']
|
||||
$lastKey = strrev($fullArrayString);
|
||||
// Cut off "['"
|
||||
$lastKey = substr($lastKey, 2);
|
||||
$lastKey = GeneralUtility::trimExplode('\'[', $lastKey);
|
||||
// Last key name
|
||||
$lastKey = $lastKey[0];
|
||||
// And reverse key name again
|
||||
$lastKey = strrev($lastKey);
|
||||
|
||||
if (!array_key_exists($lastKey, $methodNameArray)) {
|
||||
$methodNameArray[$lastKey]['candidates'] = [];
|
||||
}
|
||||
$methodNameArray[$lastKey]['candidates'][] = $details;
|
||||
}
|
||||
$this->flatMatcherDefinitions = $methodNameArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrayDimFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
|
||||
/**
|
||||
* Match access to a one dimensional $GLOBAL array
|
||||
* Example "$GLOBALS['TYPO3_DB']"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ArrayGlobalMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Initialize "flat" matcher array from matcher definitions.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ArrayDimFetch
|
||||
&& $node->var instanceof Variable
|
||||
&& $node->var->name === 'GLOBALS'
|
||||
&& $node->dim instanceof String_
|
||||
&& array_key_exists('$GLOBALS[\'' . $node->dim->value . '\']', $this->matcherDefinitions)
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions['$GLOBALS[\'' . $node->dim->value . '\']']['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Access to array global array "' . $node->dim->value . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of class constants.
|
||||
*
|
||||
* Test for "Class\Name::THE_CONSTANT", matches are considered "strong"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ClassConstantMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ClassConstFetch
|
||||
&& $node->class instanceof FullyQualified
|
||||
&& array_key_exists($node->class->toString() . '::' . $node->name, $this->matcherDefinitions)
|
||||
) {
|
||||
// No weak test implemented - combination class::const name tested
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$node->class->toString() . '::' . $node->name]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to class constant "' . $node->class->toString() . '::' . $node->name . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of class / interface names which are entirely deprecated or removed
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ClassNameMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Default constructor validates matcher definition.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node Given node to test
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof FullyQualified
|
||||
) {
|
||||
$fullyQualifiedClassName = $node->toString();
|
||||
if (array_key_exists($fullyQualifiedClassName, $this->matcherDefinitions)) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fullyQualifiedClassName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Usage of class "' . $fullyQualifiedClassName . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
|
||||
/**
|
||||
* Find usages of class constants.
|
||||
*
|
||||
* Test for "THE_CONSTANT", matches are considered "strong"
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ConstantMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof ConstFetch
|
||||
&& array_key_exists($node->name->toString(), $this->matcherDefinitions)
|
||||
) {
|
||||
// Access to constants is detected as strong match
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$node->name->toString()]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to global constant "' . $node->name->toString() . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
|
||||
/**
|
||||
* Finds invocations to class constructors and the amount of passed arguments.
|
||||
* This matcher supports direct `new MyClass(123)` invocations as well as delegated
|
||||
* calls to `GeneralUtility::makeInstance(MyClass::class, 123)` using `GeneratorClassResolver`.
|
||||
*
|
||||
* These configuration property names are handled independently:
|
||||
* + numberOfMandatoryArguments
|
||||
* + maximumNumberOfArguments
|
||||
* + unusedArgumentNumbers
|
||||
*
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ConstructorArgumentMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
protected const TOPIC_TYPE_REQUIRED = 'required';
|
||||
protected const TOPIC_TYPE_DROPPED = 'dropped';
|
||||
protected const TOPIC_TYPE_CALLED = 'called';
|
||||
protected const TOPIC_TYPE_UNUSED = 'unused';
|
||||
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitionsTopicRequirements([
|
||||
self::TOPIC_TYPE_REQUIRED => ['numberOfMandatoryArguments'],
|
||||
self::TOPIC_TYPE_DROPPED => ['maximumNumberOfArguments'],
|
||||
self::TOPIC_TYPE_CALLED => ['numberOfMandatoryArguments', 'maximumNumberOfArguments'],
|
||||
self::TOPIC_TYPE_UNUSED => ['unusedArgumentNumbers'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if ($this->isFileIgnored($node) || $this->isLineIgnored($node)) {
|
||||
return null;
|
||||
}
|
||||
$resolvedNode = $node->getAttribute(self::NODE_RESOLVED_AS, null) ?? $node;
|
||||
if (!$resolvedNode instanceof New_
|
||||
|| !isset($resolvedNode->class)
|
||||
|| (isset($node->class) && is_object($node->class) && !method_exists($node->class, '__toString'))
|
||||
|| !array_key_exists((string)$resolvedNode->class, $this->matcherDefinitions)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lower than numberOfMandatoryArguments
|
||||
if ($this->isArgumentUnpackingUsed($resolvedNode->args)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
// $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
$this->handleRequiredArguments($node, $resolvedNode);
|
||||
$this->handleDroppedArguments($node, $resolvedNode);
|
||||
$this->handleCalledArguments($node, $resolvedNode);
|
||||
$this->handleUnusedArguments($node, $resolvedNode);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
*/
|
||||
protected function handleRequiredArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)($resolvedNode->class ?? '');
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_REQUIRED] ?? null;
|
||||
$mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args ?? []);
|
||||
|
||||
if ($candidate === null || $numberOfArguments >= $mandatoryArguments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct requires at least %d arguments (%d given).',
|
||||
$className,
|
||||
$mandatoryArguments,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
*/
|
||||
protected function handleDroppedArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)($resolvedNode->class ?? '');
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_DROPPED] ?? null;
|
||||
$maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args ?? []);
|
||||
|
||||
if ($candidate === null || $numberOfArguments <= $maximumArguments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct supports only %d arguments (%d given).',
|
||||
$className,
|
||||
$maximumArguments,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
*/
|
||||
protected function handleCalledArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)($resolvedNode->class ?? '');
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_CALLED] ?? null;
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($resolvedNode->args ?? []);
|
||||
$mandatoryArguments = $candidate['numberOfMandatoryArguments'] ?? null;
|
||||
$maximumArguments = $candidate['maximumNumberOfArguments'] ?? null;
|
||||
$numberOfArguments = count($resolvedNode->args ?? []);
|
||||
|
||||
if ($candidate === null
|
||||
|| !$isArgumentUnpackingUsed
|
||||
&& ($numberOfArguments < $mandatoryArguments || $numberOfArguments > $maximumArguments)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct being called (%d arguments given).',
|
||||
$className,
|
||||
$numberOfArguments
|
||||
),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node $node reflects invocation, e.g. `GeneralUtility::makeInstance(MyClass::class, 123)`
|
||||
* @param Node $resolvedNode reflects resolved and actual usage, e.g. `new MyClass(123)`
|
||||
*/
|
||||
protected function handleUnusedArguments(Node $node, Node $resolvedNode): bool
|
||||
{
|
||||
$className = (string)($resolvedNode->class ?? '');
|
||||
$candidate = $this->matcherDefinitions[$className][self::TOPIC_TYPE_UNUSED] ?? null;
|
||||
// values in array (if any) are actual position counts
|
||||
// e.g. `[2, 4]` refers to internal argument indexes `[1, 3]`
|
||||
$unusedArgumentPositions = $candidate['unusedArgumentNumbers'] ?? null;
|
||||
|
||||
if ($candidate === null || empty($unusedArgumentPositions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$arguments = $resolvedNode->args ?? [];
|
||||
// keeping positions having argument values that are not null
|
||||
$unusedArgumentPositions = array_filter(
|
||||
$unusedArgumentPositions,
|
||||
static function (int $position) use ($arguments) {
|
||||
$index = $position - 1;
|
||||
return isset($arguments[$index]->value)
|
||||
&& !$arguments[$index]->value instanceof ConstFetch
|
||||
&& (
|
||||
!isset($arguments[$index]->value->name->name->parts[0])
|
||||
|| $arguments[$index]->value->name->name->parts[0] !== null
|
||||
);
|
||||
}
|
||||
);
|
||||
if (empty($unusedArgumentPositions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->matches[] = [
|
||||
'restFiles' => $candidate['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => sprintf(
|
||||
'%s::__construct was called with argument positions %s not being null.',
|
||||
$className,
|
||||
implode(', ', $unusedArgumentPositions)
|
||||
),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validateMatcherDefinitionsTopicRequirements(array $topicRequirements): void
|
||||
{
|
||||
foreach ($this->matcherDefinitions as $key => $matcherDefinition) {
|
||||
foreach ($topicRequirements as $topic => $requiredArrayKeys) {
|
||||
if (empty($matcherDefinition[$topic])) {
|
||||
continue;
|
||||
}
|
||||
$this->validateMatcherDefinitionKeys($key, $matcherDefinition[$topic], $requiredArrayKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of global function calls which were removed / deprecated.
|
||||
* This is a strong match.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class FunctionCallMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "removedFunction()" (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof FuncCall
|
||||
&& $node->name instanceof FullyQualified
|
||||
&& array_key_exists($node->name->toString(), $this->matcherDefinitions)
|
||||
) {
|
||||
$functionName = $node->name->toString();
|
||||
$matchDefinition = $this->matcherDefinitions[$functionName];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $matchDefinition['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $matchDefinition['maximumNumberOfArguments'])
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $matchDefinition['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to function "' . $functionName . '"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
/**
|
||||
* Matches interface method arguments which have been dropped.
|
||||
*
|
||||
* This does *not* test if a class implements an interface.
|
||||
* The scanner only looks for:
|
||||
* - Class method names not having specified number of arguments
|
||||
* - Method calls with given method name not having this number of arguments
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class InterfaceMethodChangedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Default constructor validates config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
// newNumberOfArguments must exist in all matcherDefinitions
|
||||
$this->validateMatcherDefinitions(['newNumberOfArguments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "public function like($arg1, $arg2, $arg3) {}" (weak match)
|
||||
* Test for "->like($arg1, $arg2, $arg3); (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if ($this->isFileIgnored($node) || $this->isLineIgnored($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Match method name of a class, must be public, wouldn't make sense as interface if protected/private
|
||||
if ($node instanceof ClassMethod
|
||||
&& array_key_exists($node->name->name, $this->matcherDefinitions)
|
||||
&& $node->flags & Modifiers::PUBLIC // public
|
||||
&& ($node->flags & Modifiers::STATIC) !== Modifiers::STATIC // not static
|
||||
) {
|
||||
$methodName = $node->name->name;
|
||||
$numberOfUsedArguments = 0;
|
||||
if (is_array($node->params ?? null)) {
|
||||
$numberOfUsedArguments = count($node->params);
|
||||
}
|
||||
$numberOfAllowedArguments = $this->matcherDefinitions[$methodName]['newNumberOfArguments'];
|
||||
if ($numberOfUsedArguments > $numberOfAllowedArguments) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$methodName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Implementation of dropped interface argument for method "' . $methodName . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Match method call (not static) with number of arguments
|
||||
if ($node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->matcherDefinitions)
|
||||
) {
|
||||
$methodName = $node->name->name;
|
||||
$numberOfUsedArguments = 0;
|
||||
if (is_array($node->args ?? null)) {
|
||||
$numberOfUsedArguments = count($node->args);
|
||||
}
|
||||
// @todo: Test for argument unpacking
|
||||
$numberOfAllowedArguments = $this->matcherDefinitions[$methodName]['newNumberOfArguments'];
|
||||
if ($numberOfUsedArguments > $numberOfAllowedArguments) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$methodName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to interface method "' . $methodName . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
/**
|
||||
* Find usages of method annotations
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodAnnotationMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for method annotations (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if ($node instanceof ClassMethod
|
||||
&& ($docComment = $node->getDocComment()) instanceof Doc
|
||||
&& !$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
) {
|
||||
$isPossibleMatch = false;
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
|
||||
$matches = [];
|
||||
preg_match_all(
|
||||
'/\s*\s@(?<annotations>[^\s.]*).*\n/',
|
||||
$docComment->getText(),
|
||||
$matches
|
||||
);
|
||||
|
||||
foreach ($matches['annotations'] as $annotation) {
|
||||
$annotation = '@' . $annotation;
|
||||
|
||||
if (!isset($this->matcherDefinitions[$annotation])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name . '" uses an ' . $annotation . ' annotation.';
|
||||
$match['restFiles'] = array_unique(array_merge(
|
||||
$match['restFiles'],
|
||||
$this->matcherDefinitions[$annotation]['restFiles']
|
||||
));
|
||||
}
|
||||
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which changed signature and dropped arguments,
|
||||
* but are called with more arguments.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentDroppedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is higher than maximumNumberOfArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments > $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" supports only ' . $candidate['maximumNumberOfArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which were removed / deprecated.
|
||||
* This is a "strong" match if class name is given and "weak" if not.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentDroppedStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match static method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($node->class instanceof FullyQualified && $node->name instanceof Identifier) {
|
||||
// 'Foo\Bar::aMethod()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)
|
||||
&& count($node->args) > $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Method "' . $node->name->name . '()" supports only '
|
||||
. $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
. ' arguments.',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is higher than maximumNumberOfArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments > $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" supports only '
|
||||
. $candidate['maximumNumberOfArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which changed signature and added required arguments.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentRequiredMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lower than numberOfMandatoryArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments < $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method ' . $node->name->name . '() needs at least ' . $candidate['numberOfMandatoryArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which gained new mandatory arguments.
|
||||
* This is a "strong" match if class name is given and "weak" if not.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentRequiredStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "::function($1, $2, $3)" (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match static method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
if ($node->class instanceof FullyQualified && $node->name instanceof Identifier) {
|
||||
// 'Foo\Bar::aMethod()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
$numberOfArguments = count($node->args);
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)
|
||||
&& $numberOfArguments < $this->matcherDefinitions[$fqdnClassWithMethod]['numberOfMandatoryArguments']
|
||||
// maximum number of arguments is just a measure against false positives
|
||||
&& $numberOfArguments <= $this->matcherDefinitions[$fqdnClassWithMethod]['maximumNumberOfArguments']
|
||||
) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Method "' . $node->name->name . '()" needs at least '
|
||||
. $this->matcherDefinitions[$fqdnClassWithMethod]['numberOfMandatoryArguments']
|
||||
. ' arguments.',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is not called with argument unpacking
|
||||
// and number of used arguments is lesser than numberOfMandatoryArguments
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments < $candidate['numberOfMandatoryArguments']
|
||||
// maximum number of arguments is just a measure against false positives
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments']
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Method "' . $node->name->name . '()" needs at least '
|
||||
. $candidate['numberOfMandatoryArguments'] . ' arguments.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Match method usages where arguments "in between" are unused but not given as "null":
|
||||
*
|
||||
* public function foo($arg1, $unused1 = null, $unused2 = null, $arg4)
|
||||
* but called with:
|
||||
* ->foo('arg1', 'notNull', null, 'arg4');
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodArgumentUnusedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['unusedArgumentNumbers']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
foreach ($candidate['unusedArgumentNumbers'] as $droppedArgumentNumber) {
|
||||
// A method call is considered a match if name matches, unpacking is not used
|
||||
// and the registered argument is not given as null.
|
||||
if (!$isArgumentUnpackingUsed
|
||||
&& $numberOfArguments >= $droppedArgumentNumber
|
||||
&& !($node->args[$droppedArgumentNumber - 1]->value instanceof ConstFetch)
|
||||
&& (!isset($node->args[$droppedArgumentNumber - 1]->value->name->name->parts[0])
|
||||
|| $node->args[$droppedArgumentNumber - 1]->value->name->name->parts[0] !== null)
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Call to method "' . $node->name->name . '()" with'
|
||||
. ' argument ' . $droppedArgumentNumber . ' not given as null.';
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
/**
|
||||
* Find usages of arguments in method calls which were removed / deprecated.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodCallArgumentValueMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['argumentMatches']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->method($someArgument)" (weak match)
|
||||
* and for "fqcn::method($someArgument)" (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if ($this->isFileIgnored($node)
|
||||
|| $this->isLineIgnored($node)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Expr\StaticCall
|
||||
&& $node->class instanceof FullyQualified
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->class->toString() . '::' . $node->name->name, $this->matcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to specific argument (#%s) of static method "' . $node->class->toString() . '::' . $node->name->name . '()"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
|
||||
$matchCandidate = [$this->matcherDefinitions[$node->class->toString() . '::' . $node->name->name]];
|
||||
} elseif ($node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to specific argument (#%s) of method "' . $node->name->name . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$matchCandidate = $this->flatMatcherDefinitions[$node->name->name]['candidates'];
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$isPossibleMatch = false;
|
||||
|
||||
// So far, the candidates just have their argument numbering and method name matching applied
|
||||
// Now let's inspect whether the argument actually holds the value our droids are looking for
|
||||
foreach ($matchCandidate as $candidate) {
|
||||
$argumentNumbers = $this->isArgumentMatched($node, $candidate);
|
||||
if ($argumentNumbers !== []) {
|
||||
$isPossibleMatch = true;
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
$match['message'] = sprintf($match['message'], implode(', ', $argumentNumbers));
|
||||
// One match will shortcut checking for others.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of matched arguments on a match candidate.
|
||||
* Returns empty array if either none found or not ALL matches are matched (AND combined)
|
||||
*/
|
||||
private function isArgumentMatched(Node $node, array $candidate): array
|
||||
{
|
||||
$matchedArgumentNumbers = [];
|
||||
|
||||
foreach (($candidate['argumentMatches'] ?? []) as $argumentMatchArray) {
|
||||
if (isset($node->args[$argumentMatchArray['argumentIndex']]->value->value)
|
||||
&& $node->args[$argumentMatchArray['argumentIndex']]->value instanceof Scalar
|
||||
&& $node->args[$argumentMatchArray['argumentIndex']]->value->value === $argumentMatchArray['argumentValue']) {
|
||||
|
||||
$matchedArgumentNumbers[] = $argumentMatchArray['argumentIndex'];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($matchedArgumentNumbers) !== count($candidate['argumentMatches'] ?? [])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $matchedArgumentNumbers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of method calls which were removed / deprecated.
|
||||
* This is a "weak" match since we're just testing for method name
|
||||
* but not connected class.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodCallMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "->deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match method call (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof MethodCall
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Call to method "' . $node->name->name . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is called with argument unpacking, or
|
||||
// if the number of given arguments is within range of mandatory / max number of arguments
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments'])
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
|
||||
/**
|
||||
* Find usages of static method calls which were removed / deprecated.
|
||||
*
|
||||
* This match is performed either is case of a direct "foo\bar::aMethod()" call
|
||||
* as "strong" match, or as only "::aMethod()" as "weak" match.
|
||||
*
|
||||
* As additional indicator, the number of required, mandatory arguments is
|
||||
* recognized: If calling a static method as "$foo::aMethod($arg1), but the
|
||||
* method needs two arguments, this is *not* considered a match. This would
|
||||
* have raised a fatal PHP error anyway and this is nothing we test here.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class MethodCallStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare weak matcher array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions(['numberOfMandatoryArguments', 'maximumNumberOfArguments']);
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for "foo\bar::deprecated()" (strong match)
|
||||
* Test for "::deprecated()" (weak match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Static call, not method call
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof StaticCall
|
||||
) {
|
||||
if ($node->class instanceof FullyQualified && $node->name instanceof Identifier) {
|
||||
// 'Foo\Bar::deprecated()' -> strong match
|
||||
$fqdnClassWithMethod = $node->class->toString() . '::' . $node->name->name;
|
||||
if (array_key_exists($fqdnClassWithMethod, $this->matcherDefinitions)) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$fqdnClassWithMethod]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of static class method call "' . $fqdnClassWithMethod . '()"',
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
}
|
||||
} elseif ($node->class instanceof Variable
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of static class method call "' . $node->name->name . '()"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
$numberOfArguments = count($node->args);
|
||||
$isArgumentUnpackingUsed = $this->isArgumentUnpackingUsed($node->args);
|
||||
|
||||
$isPossibleMatch = false;
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
// A method call is considered a match if it is called with argument unpacking, or
|
||||
// if the number of given arguments is within range of mandatory / max number of arguments
|
||||
if ($isArgumentUnpackingUsed
|
||||
|| ($numberOfArguments >= $candidate['numberOfMandatoryArguments']
|
||||
&& $numberOfArguments <= $candidate['maximumNumberOfArguments'])
|
||||
) {
|
||||
$isPossibleMatch = true;
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
}
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Comment\Doc;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\PropertyItem;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
|
||||
/**
|
||||
* Find usages of property annotations
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyAnnotationMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
* Test for property annotations (strong match)
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if ($node instanceof Property
|
||||
&& ($property = reset($node->props)) instanceof PropertyItem
|
||||
&& ($docComment = $node->getDocComment()) instanceof Doc
|
||||
&& !$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
) {
|
||||
/** @var PropertyItem $property */
|
||||
$isPossibleMatch = false;
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $property->getAttribute('startLine'),
|
||||
'indicator' => 'strong',
|
||||
];
|
||||
|
||||
$matches = [];
|
||||
preg_match_all(
|
||||
'/\s*\s@(?<annotations>[^\s.]*).*\n/',
|
||||
$docComment->getText(),
|
||||
$matches
|
||||
);
|
||||
|
||||
foreach ($matches['annotations'] as $annotation) {
|
||||
$annotation = '@' . $annotation;
|
||||
|
||||
if (!isset($this->matcherDefinitions[$annotation])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isPossibleMatch = true;
|
||||
$match['message'] = 'Property "' . $property->name . '" uses an ' . $annotation . ' annotation.';
|
||||
$match['restFiles'] = array_unique(array_merge(
|
||||
$match['restFiles'],
|
||||
$this->matcherDefinitions[$annotation]['restFiles']
|
||||
));
|
||||
}
|
||||
|
||||
if ($isPossibleMatch) {
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
|
||||
/**
|
||||
* Find usages of properties which have been deprecated or removed.
|
||||
* Useful if abstract classes remove properties.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyExistsStaticMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare flat mach array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof Property
|
||||
&& $node->isStatic()
|
||||
&& !$node->isPrivate()
|
||||
&& array_key_exists($node->props[0]->name->name, $this->matcherDefinitions)
|
||||
) {
|
||||
$propertyName = $node->props[0]->name->name;
|
||||
$match = [
|
||||
'restFiles' => $this->matcherDefinitions[$propertyName]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Use of property "' . $node->props[0]->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of properties which have been made protected and are
|
||||
* not called in $this context.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyProtectedMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Validate config and prepare flat mach array
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof PropertyFetch
|
||||
&& $node->name instanceof Identifier
|
||||
&& ($node->var->name ?? '') !== 'this'
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Fetch of property "' . $node->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
/**
|
||||
* Find usages of properties which were removed / deprecated.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class PropertyPublicMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Prepare $this->flatMatcherDefinitions once and validate config
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
$this->initializeFlatMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Match property access (not static)
|
||||
if (!$this->isFileIgnored($node)
|
||||
&& !$this->isLineIgnored($node)
|
||||
&& $node instanceof PropertyFetch
|
||||
&& $node->name instanceof Identifier
|
||||
&& array_key_exists($node->name->name, $this->flatMatcherDefinitions)
|
||||
) {
|
||||
$match = [
|
||||
'restFiles' => [],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Fetch of property "' . $node->name->name . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
|
||||
foreach ($this->flatMatcherDefinitions[$node->name->name]['candidates'] as $candidate) {
|
||||
$match['restFiles'] = array_unique(array_merge($match['restFiles'], $candidate['restFiles']));
|
||||
}
|
||||
$this->matches[] = $match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Install\ExtensionScanner\Php\Matcher;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
/**
|
||||
* Find usage of special "magic" strings like TYPO3_MODE, so that
|
||||
* usage scenarios like `defined('TYPO3_MODE') || die()` will be scanned,
|
||||
* where the actual constant is NOT used.
|
||||
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class ScalarStringMatcher extends AbstractCoreMatcher
|
||||
{
|
||||
/**
|
||||
* Default constructor validates matcher definition.
|
||||
*
|
||||
* @param array $matcherDefinitions Incoming main configuration
|
||||
*/
|
||||
public function __construct(array $matcherDefinitions)
|
||||
{
|
||||
$this->matcherDefinitions = $matcherDefinitions;
|
||||
$this->validateMatcherDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by PhpParser.
|
||||
*
|
||||
* @param Node $node Given node to test
|
||||
*/
|
||||
public function enterNode(Node $node): null
|
||||
{
|
||||
// Early return
|
||||
if ($this->isFileIgnored($node)
|
||||
|| $this->isLineIgnored($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if the node contains the specific string
|
||||
if (!$node instanceof Node\Scalar\String_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Note: This is intentionally meant to be an exact match for now, no trimming or substring.
|
||||
// Could be enhanced in the future with options to the configuration how to match.
|
||||
// Using weak match to indicate that the magic string usage may not necessarily
|
||||
// refer to the functionality we're matching. Other than TYPO3_MODE, future definitions
|
||||
// will probably be weaker than this strong constant comparison.
|
||||
$stringToMatch = (string)($node->name ?? $node->value);
|
||||
if (array_key_exists($stringToMatch, $this->matcherDefinitions)) {
|
||||
$this->matches[] = [
|
||||
'restFiles' => $this->matcherDefinitions[$stringToMatch]['restFiles'],
|
||||
'line' => $node->getAttribute('startLine'),
|
||||
'message' => 'Usage of string "' . $stringToMatch . '"',
|
||||
'indicator' => 'weak',
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user