diff --git a/Readme.rst b/Readme.rst index 6dce9d2..a7a1243 100644 --- a/Readme.rst +++ b/Readme.rst @@ -49,6 +49,8 @@ new ones like ``\TYPO3\Extbase\...``. This is done for: - Static calls like ``t3lib_div::`` to ``\TYPO3\Core\Utility\GeneralUtility``. +- Static call also checks for ``::class``, as technically we just look before the ``::``. + - Typehints in methods and function like injects. - ``instanceof`` checks. @@ -69,6 +71,30 @@ new ones like ``\TYPO3\Extbase\...``. This is done for: - ``catch`` of legacy class names. +- Convert old legacy class *definitions* in extensions to namespaces. + +- Convert usage of previously converted class definitions. On first run the definition will be + converted, on second run the usage. This is due to the fact, that PHPCS might find the definition + after the usage, so please run twice. + + *NOTE* The configured file will be updated after each run, for each converted class, trait and + interface definition. See options. + +- Add missing vendor to plugin and module registrations and configurations. + You might want to set this to non fixable and warning if you already provide the vendor inside a + single Variable, together with your extension key, as this is not recognized. So the following + will be recognized: + + - ``$_EXTKEY,`` + + - ``$VENDOR . $_EXTKEY,`` + + - ``'VENDOR.' . $_EXTKEY,`` + + While the following will not: + + - ``$key = 'Vendor.' . $_EXTKEY;`` + Also we check for the following deprecated calls: - Check for ``create`` on ``ObjectManager``, which is "stupid" just all ``create`` calls are marked @@ -152,3 +178,19 @@ Example: .. code:: bash --runtime-set mappingFile /projects/typo3_installation/vendor/composer/autoload_classaliasmap.php + +``vendor`` + Configure your vendor through ``ruleset.xml`` or using ``--runtime-set``. Default is + ``YourCompany``. + + Example: + +.. code:: xml + + + +Example: + +.. code:: bash + + --runtime-set vendor YourVendor diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/ClassnameCheckerTrait.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/AbstractClassnameChecker.php similarity index 79% rename from src/Standards/Typo3Update/Sniffs/LegacyClassnames/ClassnameCheckerTrait.php rename to src/Standards/Typo3Update/Sniffs/LegacyClassnames/AbstractClassnameChecker.php index a59f8de..a13fe15 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/ClassnameCheckerTrait.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/AbstractClassnameChecker.php @@ -22,17 +22,16 @@ namespace Typo3Update\Sniffs\LegacyClassnames; use PHP_CodeSniffer as PhpCs; use PHP_CodeSniffer_File as PhpCsFile; +use PHP_CodeSniffer_Sniff as PhpCsSniff; +use Typo3Update\Sniffs\LegacyClassnames\Mapping; +use Typo3Update\Sniffs\OptionsAccessTrait; /** * Provide common uses for all sniffs, regarding class name checks. */ -trait ClassnameCheckerTrait +abstract class AbstractClassnameChecker implements PhpCsSniff { - /** - * Contains mapping from old -> new class names. - * @var array - */ - private $legacyClassnames = []; + use OptionsAccessTrait; /** * A list of extension names that might contain legacy class names. @@ -45,20 +44,22 @@ trait ClassnameCheckerTrait public $legacyExtensions = ['Extbase', 'Fluid']; /** - * Initialize, used internally, to not initialize if not needed inside __construct. + * @var Mapping */ - private function initialize() - { - $mappingFile = PhpCs::getConfigData('mappingFile'); - if (!$mappingFile) { - $mappingFile = __DIR__ . '/../../../../../LegacyClassnames.php'; - } - if ($this->legacyClassnames !== []) { - return; - } + protected $legacyMapping; - $legacyClassnames = require $mappingFile; - $this->legacyClassnames = $legacyClassnames['aliasToClassNameMapping']; + /** + * Used by some sniffs to keep original token for replacement. + * + * E.g. when Token itself is a whole inline comment, and we just want to replace the classname within. + * + * @var string + */ + protected $originalTokenContent = ''; + + public function __construct() + { + $this->legacyMapping = Mapping::getInstance(); } /** @@ -110,10 +111,9 @@ trait ClassnameCheckerTrait * @param string $classname * @return bool */ - private function isLegacyClassname($classname) + protected function isLegacyClassname($classname) { - $this->initialize(); - return isset($this->legacyClassnames[strtolower($classname)]); + return $this->legacyMapping->isLegacyClassname($classname); } /** @@ -145,10 +145,23 @@ trait ClassnameCheckerTrait * @param string $classname * @return string */ - private function getNewClassname($classname) + protected function getNewClassname($classname) { - $this->initialize(); - return $this->legacyClassnames[strtolower($classname)]; + return $this->legacyMapping->getNewClassname($classname); + } + + /** + * Use to add new mappings found during parsing. + * E.g. in MissingNamespaceSniff old class definitions are fixed and a new mapping exists afterwards. + * + * @param string $legacyClassname + * @param string $newClassname + * + * @return void + */ + protected function addLegacyClassname($legacyClassname, $newClassname) + { + $this->legacyMapping->addLegacyClassname($legacyClassname, $newClassname); } /** @@ -206,11 +219,16 @@ trait ClassnameCheckerTrait * @param PhpCsFile $phpcsFile * @param int $classnamePosition * @param string $classname + * @param bool $forceEmptyPrefix Defines whether '\\' prefix should be checked or always be left out. */ - private function replaceLegacyClassname(PhpCsFile $phpcsFile, $classnamePosition, $classname) - { + protected function replaceLegacyClassname( + PhpCsFile $phpcsFile, + $classnamePosition, + $classname, + $forceEmptyPrefix = false + ) { $prefix = '\\'; - if ($phpcsFile->getTokens()[$classnamePosition -1]['code'] === T_NS_SEPARATOR) { + if ($forceEmptyPrefix || $phpcsFile->getTokens()[$classnamePosition -1]['code'] === T_NS_SEPARATOR) { $prefix = ''; } diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/DocCommentSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/DocCommentSniff.php index 7ab8eca..2a85c89 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/DocCommentSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/DocCommentSniff.php @@ -19,27 +19,22 @@ * 02110-1301, USA. */ +use PHP_CodeSniffer_File as PhpCsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Migrate PHP Doc comments. * * E.g. annotations like @param or @return, see $allowedTags. */ -class Typo3Update_Sniffs_LegacyClassnames_DocCommentSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_DocCommentSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * The configured tags will be processed. * @var array */ public $allowedTags = ['@param', '@return', '@var']; - /** - * Original token content for reuse accross methods. - * @var string - */ - protected $originalTokenContent = ''; - /** * Returns the token types that this sniff is interested in. * @@ -55,13 +50,13 @@ class Typo3Update_Sniffs_LegacyClassnames_DocCommentSniff implements PHP_CodeSni /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); if (!in_array($tokens[$stackPtr]['content'], $this->allowedTags)) { diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InheritanceSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InheritanceSniff.php index 72c5280..59c9dd3 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InheritanceSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InheritanceSniff.php @@ -19,13 +19,14 @@ * 02110-1301, USA. */ +use PHP_CodeSniffer_File as PhpCsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Detect and migrate extend and implement of old legacy classnames. */ -class Typo3Update_Sniffs_LegacyClassnames_InheritanceSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InheritanceSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * @@ -38,4 +39,56 @@ class Typo3Update_Sniffs_LegacyClassnames_InheritanceSniff implements PHP_CodeSn T_IMPLEMENTS, ]; } + + /** + * Processes the tokens that this sniff is interested in. + * + * This is the default implementation, as most of the time next T_STRING is + * the class name. This way only the register method has to be registered + * in default cases. + * + * @param PhpCsFile $phpcsFile The file where the token was found. + * @param int $stackPtr The position in the stack where + * the token was found. + * + * @return void + */ + public function process(PhpCsFile $phpcsFile, $stackPtr) + { + if ($phpcsFile->getTokens()[$stackPtr]['code'] === T_IMPLEMENTS) { + $this->processInterfaces($phpcsFile, $stackPtr); + return; + } + + parent::process($phpcsFile, $stackPtr); + } + + /** + * Process all interfaces for current class. + * + * @param PhpCsFile $phpcsFile + * @param int $stackPtr + * + * @return void + */ + protected function processInterfaces(PhpCsFile $phpcsFile, $stackPtr) + { + $interfaces = $phpcsFile->findImplementedInterfaceNames($phpcsFile->findPrevious(T_CLASS, $stackPtr)); + if ($interfaces === false) { + return; + } + + foreach ($interfaces as $interface) { + if (! $this->isLegacyClassname($interface)) { + continue; + } + + $position = $phpcsFile->findNext(T_STRING, $stackPtr, null, false, $interface); + if ($position === false) { + continue; + } + + $this->addFixableError($phpcsFile, $position, $interface); + } + } } diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InlineCommentSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InlineCommentSniff.php index c6e8303..dc9c17d 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InlineCommentSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InlineCommentSniff.php @@ -19,19 +19,14 @@ * 02110-1301, USA. */ +use PHP_CodeSniffer_File as PhpCsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Migrate PHP inline comments, e.g. for IDEs. */ -class Typo3Update_Sniffs_LegacyClassnames_InlineCommentSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InlineCommentSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - - /** - * Original token content for reuse accross methods. - * @var string - */ - protected $originalTokenContent = ''; - /** * Returns the token types that this sniff is interested in. * @@ -47,19 +42,20 @@ class Typo3Update_Sniffs_LegacyClassnames_InlineCommentSniff implements PHP_Code /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $this->originalTokenContent = $tokens[$stackPtr]['content']; $commentParts = preg_split('/\s+/', $this->originalTokenContent); - if (count($commentParts) !== 5 || $commentParts[1] !== '@var' || ($commentParts[2][0] !== '$' && $commentParts[3][0] !== '$')) { + if (count($commentParts) !== 5 || $commentParts[1] !== '@var' + || ($commentParts[2][0] !== '$' && $commentParts[3][0] !== '$')) { return; } diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstanceofSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstanceofSniff.php index 1274923..b261922 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstanceofSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstanceofSniff.php @@ -19,13 +19,13 @@ * 02110-1301, USA. */ +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Detect and migrate instanceof checks of old legacy classnames. */ -class Typo3Update_Sniffs_LegacyClassnames_InstanceofSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InstanceofSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithMakeInstanceSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithMakeInstanceSniff.php index 8b46d7e..7e06b6b 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithMakeInstanceSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithMakeInstanceSniff.php @@ -19,22 +19,17 @@ * 02110-1301, USA. */ +use PHP_CodeSniffer_File as PhpCsFile; use PHP_CodeSniffer_Tokens as Tokens; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Detect and migrate instantiations of old legacy classnames using "makeInstance". */ -class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithMakeInstanceSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithMakeInstanceSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; use \Typo3Update\Sniffs\ExtendedPhpCsSupportTrait; - /** - * Original token content for reuse accross methods. - * @var string - */ - protected $originalTokenContent = ''; - /** * Returns the token types that this sniff is interested in. * @@ -48,13 +43,13 @@ class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithMakeInstanceSniff imp /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { return; diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithNewSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithNewSniff.php index 318cbcd..9a1758e 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithNewSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithNewSniff.php @@ -19,13 +19,13 @@ * 02110-1301, USA. */ +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Detect and migrate old legacy classnames instantiations using phps "new". */ -class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithNewSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithNewSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithObjectManagerSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithObjectManagerSniff.php index 632edce..a3d173f 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithObjectManagerSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/InstantiationWithObjectManagerSniff.php @@ -19,14 +19,15 @@ * 02110-1301, USA. */ +use PHP_CodeSniffer_File as PhpCsFile; use PHP_CodeSniffer_Tokens as Tokens; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Detect and migrate old legacy classname instantiations using objectmanager create and get. */ -class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithObjectManagerSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithObjectManagerSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; use \Typo3Update\Sniffs\ExtendedPhpCsSupportTrait; /** @@ -42,13 +43,13 @@ class Typo3Update_Sniffs_LegacyClassnames_InstantiationWithObjectManagerSniff im /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { return; diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/IsACallSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/IsACallSniff.php index 3d41927..b4a2f30 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/IsACallSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/IsACallSniff.php @@ -19,22 +19,16 @@ * 02110-1301, USA. */ -use PHP_CodeSniffer_Tokens as Tokens; +use PHP_CodeSniffer_File as PhpcsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Detect and migrate instantiations of old legacy classnames using "makeInstance". */ -class Typo3Update_Sniffs_LegacyClassnames_IsACallSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_IsACallSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; use \Typo3Update\Sniffs\ExtendedPhpCsSupportTrait; - /** - * Original token content for reuse accross methods. - * @var string - */ - protected $originalTokenContent = ''; - /** * Returns the token types that this sniff is interested in. * @@ -48,13 +42,13 @@ class Typo3Update_Sniffs_LegacyClassnames_IsACallSniff implements PHP_CodeSniffe /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { return; diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/Mapping.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/Mapping.php new file mode 100644 index 0000000..16a8acf --- /dev/null +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/Mapping.php @@ -0,0 +1,123 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Typo3Update\Sniffs\OptionsAccessTrait; + +/** + * Singleton wrapper for mappings. + * + * Will check the configured file for whether a class is legacy and provides further methods. + * Also can update to add new migrated class names. + */ +final class Mapping +{ + use OptionsAccessTrait; + + // Singleton implementation - Start + static protected $instance = null; + /** + * Get the singleton instance. + * + * @return Mapping + */ + public static function getInstance() + { + if (static::$instance === null) { + static::$instance = new Mapping(); + } + + return static::$instance; + } + private function __clone() + { + } + private function __wakeup() + { + } + private function __construct() + { + $this->mappings = require $this->getMappingFile(); + } + // Singleton implementation - End + + /** + * Contains mappings as defined by composer for alias mapping. + * @var array + */ + protected $mappings = []; + + /** + * Checks whether a mapping exists for the given $classname, + * indicating it's legacy. + * + * @param string $classname + * @return bool + */ + public function isLegacyClassname($classname) + { + return isset($this->mappings['aliasToClassNameMapping'][strtolower($classname)]); + } + + /** + * @param string $classname + * @return string + */ + public function getNewClassname($classname) + { + return $this->mappings['aliasToClassNameMapping'][strtolower($classname)]; + } + + /** + * Use to add new mappings found during parsing. + * E.g. in MissingNamespaceSniff old class definitions are fixed and a new mapping exists afterwards. + * + * @param string $legacyClassname + * @param string $newClassname + * + * @return void + */ + public function addLegacyClassname($legacyClassname, $newClassname) + { + $key = strtolower($legacyClassname); + + $this->mappings['aliasToClassNameMapping'][$key] = $newClassname; + $this->mappings['classNameToAliasMapping'][$newClassname] = [$key => $key]; + } + + /** + * Used to persist new mappings. + */ + public function __destruct() + { + // For some reasons desctruct is called multiple times, while construct + // is called once. Until we know the issue and fix it, this is our + // workaround to not break the file and do stuff in an unkown instance. + if ($this !== static::$instance) { + return; + } + + file_put_contents( + $this->getMappingFile(), + 'mappings, true) . ';' + ); + } +} diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingNamespaceSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingNamespaceSniff.php new file mode 100644 index 0000000..3f8a197 --- /dev/null +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingNamespaceSniff.php @@ -0,0 +1,164 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use PHP_CodeSniffer_File as PhpCsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + +/** + * Detect missing namespaces for class definitions. + */ +class Typo3Update_Sniffs_LegacyClassnames_MissingNamespaceSniff extends AbstractClassnameChecker +{ + /** + * Returns the token types that this sniff is interested in. + * + * @return array + */ + public function register() + { + return [ + T_CLASS, + T_INTERFACE, + T_TRAIT, + ]; + } + + /** + * Processes the tokens that this sniff is interested in. + * + * @param PhpCsFile $phpcsFile The file where the token was found. + * @param int $stackPtr The position in the stack where + * the token was found. + * + * @return void + */ + public function process(PhpCsFile $phpcsFile, $stackPtr) + { + $tokens = $phpcsFile->getTokens(); + + $namespacePosition = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr); + if ($namespacePosition !== false) { + return; + } + $classnamePosition = $phpcsFile->findNext(T_STRING, $stackPtr); + + if ($classnamePosition === false) { + return; + } + + $classname = $tokens[$classnamePosition]['content']; + $this->addFixableError($phpcsFile, $classnamePosition, $classname); + } + + /** + * Overwrite as we don't look up the classname, but check whether the style is legacy. + * + * @param string $classname + * @return bool + */ + protected function isLegacyClassname($classname) + { + return strpos($classname, 'Tx_') === 0; + } + + /** + * @param string $classname + * @return string + */ + protected function getNewClassname($classname) + { + return substr($classname, strrpos($classname, '_') + 1); + } + + /** + * + * @param PhpCsFile $phpcsFile + * @param int $classnamePosition + * @param string $classname + * @param bool $forceEmptyPrefix Defines whether '\\' prefix should be checked or always be left out. + */ + protected function replaceLegacyClassname( + PhpCsFile $phpcsFile, + $classnamePosition, + $classname, + $forceEmptyPrefix = true + ) { + parent::replaceLegacyClassname($phpcsFile, $classnamePosition, $classname, $forceEmptyPrefix); + + $tokens = $phpcsFile->getTokens(); + $lineEndings = PhpCsFile::detectLineEndings($phpcsFile->getFilename()); + $suffix = $lineEndings; + + if ($tokens[1]['code'] !== T_WHITESPACE) { + $suffix .= $lineEndings; + } + + $phpcsFile->fixer->replaceToken( + $this->getNamespacePosition($phpcsFile), + 'getNamespaceDefinition($classname) . $suffix + ); + $this->addLegacyClassname( + $classname, + $this->getNamespace($classname) . '\\' . $this->getNewClassname($classname) + ); + } + + /** + * @param PhpCsFile $phpcsFile + * @return int|false + */ + protected function getNamespacePosition(PhpCsFile $phpcsFile) + { + return $phpcsFile->findNext(T_OPEN_TAG, 0); + } + + /** + * Returns whole statement to define namespace. + * + * E.g. namespace VENDOR\ExtName\FolderName; + * + * @param string $classname + * @return string + */ + protected function getNamespaceDefinition($classname) + { + return 'namespace ' . $this->getNamespace($classname) . ';'; + } + + /** + * Returns namespace, based on legacy class name. + * + * E.g. Tx_ExtName_FolderName_FileName -> VENDOR\ExtName\FolderName + * + * @param string $classname + * @return string + */ + protected function getNamespace($classname) + { + $vendor = trim($this->getVendor(), '\\/'); + $classnameParts = explode('_', $classname); + + unset($classnameParts[0]); // Remove Tx_ + unset($classnameParts[count($classnameParts)]); // Remove class name itself. + + return $vendor . '\\' . implode('\\', $classnameParts); + } +} diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingVendorForPluginsAndModulesSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingVendorForPluginsAndModulesSniff.php new file mode 100644 index 0000000..3f2825f --- /dev/null +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/MissingVendorForPluginsAndModulesSniff.php @@ -0,0 +1,114 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use PHP_CodeSniffer_File as PhpCsFile; +use PHP_CodeSniffer_Sniff as PhpCsSniff; +use PHP_CodeSniffer_Tokens as Tokens; + +/** + * Detect whether vendor is missing for plugins and modules registrations and configurations. + */ +class Typo3Update_Sniffs_LegacyClassnames_MissingVendorForPluginsAndModulesSniff implements PhpCsSniff +{ + use \Typo3Update\Sniffs\ExtendedPhpCsSupportTrait; + use \Typo3Update\Sniffs\OptionsAccessTrait; + + /** + * Returns the token types that this sniff is interested in. + * + * @return array + */ + public function register() + { + return Tokens::$functionNameTokens; + } + + /** + * Processes the tokens that this sniff is interested in. + * + * @param PhpCsFile $phpcsFile The file where the token was found. + * @param int $stackPtr The position in the stack where + * the token was found. + * + * @return void + */ + public function process(PhpCsFile $phpcsFile, $stackPtr) + { + $functionsToHandle = ['registerPlugin', 'configurePlugin', 'registerModule']; + if (!$this->isFunctionCall($phpcsFile, $stackPtr)) { + return; + } + $tokens = $phpcsFile->getTokens(); + + if (!in_array($tokens[$stackPtr]['content'], $functionsToHandle)) { + return; + } + + $firstArgument = $phpcsFile->findNext([T_WHITESPACE, T_OPEN_PARENTHESIS], $stackPtr + 1, null, true); + if (! $this->isVendorMissing($phpcsFile, $firstArgument)) { + return; + } + + $fix = $phpcsFile->addFixableError( + 'No vendor is given, that will break TYPO3 handling for namespaced classes.' + . ' Add vendor before Extensionkey like: "%s." . $_EXTKEY', + $firstArgument, + 'missingVendor', + [$this->getVendor()] + ); + + if ($fix === true) { + $phpcsFile->fixer->replaceToken( + $firstArgument, + "'{$this->getVendor()}.' . {$tokens[$firstArgument]['content']}" + ); + } + } + + /** + * Checks whether vendor is missing in given argument. + * + * @param PhpCsFile $phpcsFile + * @param int|bool $argumentStart + * + * @return bool + */ + protected function isVendorMissing(PhpCsFile $phpcsFile, $argumentStart) + { + if ($argumentStart === false) { + return false; + } + + $argumentEnd = $phpcsFile->findNext(T_COMMA, $argumentStart); + if ($argumentEnd === false) { + return false; + } + + $stringConcats = array_filter( + array_slice($phpcsFile->getTokens(), $argumentStart, $argumentEnd - $argumentStart), + function (array $token) { + return $token['code'] === 'PHPCS_T_STRING_CONCAT'; + } + ); + + return count($stringConcats) === 0; + } +} diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/StaticCallSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/StaticCallSniff.php index d10ce73..1be8982 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/StaticCallSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/StaticCallSniff.php @@ -19,13 +19,13 @@ * 02110-1301, USA. */ +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; + /** * Detect and migrate static calls to old legacy classnames. */ -class Typo3Update_Sniffs_LegacyClassnames_StaticCallSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_StaticCallSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Define whether the T_STRING default behaviour should be checked before * or after the $stackPtr. diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintCatchExceptionSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintCatchExceptionSniff.php index 6bd9b10..876aec8 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintCatchExceptionSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintCatchExceptionSniff.php @@ -19,15 +19,13 @@ * 02110-1301, USA. */ -use PHP_CodeSniffer_Tokens as Tokens; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Migrate Typehints in catch statements. */ -class Typo3Update_Sniffs_LegacyClassnames_TypehintCatchExceptionSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_TypehintCatchExceptionSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintSniff.php index 7e1bb56..3901309 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/TypeHintSniff.php @@ -19,15 +19,14 @@ * 02110-1301, USA. */ -use PHP_CodeSniffer_Tokens as Tokens; +use PHP_CodeSniffer_File as PhpCsFile; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Migrate Typehints in function / method definitions. */ -class Typo3Update_Sniffs_LegacyClassnames_TypehintSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_TypehintSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * @@ -41,13 +40,13 @@ class Typo3Update_Sniffs_LegacyClassnames_TypehintSniff implements PHP_CodeSniff /** * Processes the tokens that this sniff is interested in. * - * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. + * @param PhpCsFile $phpcsFile The file where the token was found. * @param int $stackPtr The position in the stack where * the token was found. * * @return void */ - public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) + public function process(PhpCsFile $phpcsFile, $stackPtr) { $params = $phpcsFile->getMethodParameters($stackPtr); foreach ($params as $parameter) { diff --git a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/UseSniff.php b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/UseSniff.php index 0b7bf6d..9d882c5 100644 --- a/src/Standards/Typo3Update/Sniffs/LegacyClassnames/UseSniff.php +++ b/src/Standards/Typo3Update/Sniffs/LegacyClassnames/UseSniff.php @@ -19,15 +19,13 @@ * 02110-1301, USA. */ -use PHP_CodeSniffer_Tokens as Tokens; +use Typo3Update\Sniffs\LegacyClassnames\AbstractClassnameChecker; /** * Migrate old legacy class names in use statements. */ -class Typo3Update_Sniffs_LegacyClassnames_UseSniff implements PHP_CodeSniffer_Sniff +class Typo3Update_Sniffs_LegacyClassnames_UseSniff extends AbstractClassnameChecker { - use \Typo3Update\Sniffs\LegacyClassnames\ClassnameCheckerTrait; - /** * Returns the token types that this sniff is interested in. * diff --git a/src/Standards/Typo3Update/Sniffs/OptionsAccessTrait.php b/src/Standards/Typo3Update/Sniffs/OptionsAccessTrait.php new file mode 100644 index 0000000..7193e07 --- /dev/null +++ b/src/Standards/Typo3Update/Sniffs/OptionsAccessTrait.php @@ -0,0 +1,57 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use PHP_CodeSniffer as PhpCs; + +/** + * Wrapper to retrieve options from PhpCs with defaults. + */ +trait OptionsAccessTrait +{ + /** + * Returns the configured vendor, e.g. to generate new namespaces. + * + * @return string + */ + public function getVendor() + { + $vendor = PhpCs::getConfigData('vendor'); + if (!$vendor) { + $vendor = 'YourCompany'; + } + return trim($vendor, '\\/'); + } + + /** + * Returns the configured file path containing the mappings for classes, interfaced and traits. + * + * @return string + */ + public function getMappingFile() + { + $mappingFile = PhpCs::getConfigData('mappingFile'); + if (!$mappingFile) { + $mappingFile = __DIR__ . '/../../../../LegacyClassnames.php'; + } + return $mappingFile; + } +} diff --git a/src/Standards/Typo3Update/ruleset.xml b/src/Standards/Typo3Update/ruleset.xml index c8e6a69..260cadb 100644 --- a/src/Standards/Typo3Update/ruleset.xml +++ b/src/Standards/Typo3Update/ruleset.xml @@ -12,4 +12,8 @@ + + + Legacy class definitions are not allowed; found "%s". Wrap your class inside a namespace. +