Merge branch 'feature/15-check-all-possible-occurences-of-class' into 'develop'

FEATURE: Detect classes in strings

Closes #15 and #43

See merge request !84
This commit is contained in:
Daniel Hürtgen 2017-05-16 15:32:55 +02:00
commit 0c91d7b22c
18 changed files with 1585 additions and 4323 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/LegacyClassnames.php /LegacyClassnames.php
/tests/Fixtures/LegacyClassnames.php
/composer.lock /composer.lock
/vendor/ /vendor/

View file

@ -130,3 +130,7 @@ Further checks
-------------- --------------
- Legacy ajax registrations for TYPO3 Backend. - Legacy ajax registrations for TYPO3 Backend.
- We check usage of legacy classes in strings, covering properties and configuration directives.
They are only added as warning, not fixable error, as they might be table names and expected to
stay.

View file

@ -32,6 +32,7 @@
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "5.7.*", "phpunit/phpunit": "5.7.*",
"mikey179/vfsStream": "1.6.*",
"symfony/finder": "3.2.*", "symfony/finder": "3.2.*",
"phpmd/phpmd": "2.6.*", "phpmd/phpmd": "2.6.*",
"pdepend/pdepend": "2.5.*" "pdepend/pdepend": "2.5.*"

View file

@ -5,6 +5,7 @@
<file>./src</file> <file>./src</file>
<file>./tests</file> <file>./tests</file>
<exclude-pattern>*/Fixtures/*</exclude-pattern> <exclude-pattern>*/Fixtures/*</exclude-pattern>
<exclude-pattern>*/LegacyClassnames.php</exclude-pattern>
<!-- Excludes for PHPCS Code Style --> <!-- Excludes for PHPCS Code Style -->
<rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace"> <rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">

View file

@ -21,7 +21,11 @@
<filter> <filter>
<whitelist> <whitelist>
<directory>./src/</directory> <directory>./src</directory>
<exclude>
<directory>./src/Standards/Typo3Update/Sniffs/</directory>
<file>./src/Standards/Typo3Update/Configuration/LegacyClassnames.php</file>
</exclude>
</whitelist> </whitelist>
</filter> </filter>
</phpunit> </phpunit>

View file

@ -9,6 +9,7 @@ Typo3Update\Feature\LegacyClassnameFeature:
- Typo3Update_Sniffs_Classname_MissingVendorForPluginsAndModulesSniff - Typo3Update_Sniffs_Classname_MissingVendorForPluginsAndModulesSniff
- Typo3Update_Sniffs_Classname_PhpDocCommentSniff - Typo3Update_Sniffs_Classname_PhpDocCommentSniff
- Typo3Update_Sniffs_Classname_StaticCallSniff - Typo3Update_Sniffs_Classname_StaticCallSniff
- Typo3Update_Sniffs_Classname_StringSniff
- Typo3Update_Sniffs_Classname_TypeHintCatchExceptionSniff - Typo3Update_Sniffs_Classname_TypeHintCatchExceptionSniff
- Typo3Update_Sniffs_Classname_TypeHintSniff - Typo3Update_Sniffs_Classname_TypeHintSniff
- Typo3Update_Sniffs_Classname_UseSniff - Typo3Update_Sniffs_Classname_UseSniff

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,7 @@ namespace Typo3Update\Feature;
use PHP_CodeSniffer as PhpCs; use PHP_CodeSniffer as PhpCs;
use PHP_CodeSniffer_File as PhpCsFile; use PHP_CodeSniffer_File as PhpCsFile;
use PHP_CodeSniffer_Sniff as PhpCsSniff; use PHP_CodeSniffer_Sniff as PhpCsSniff;
use Typo3Update_Sniffs_Classname_StringSniff as StringSniff;
/** /**
* This feature will add fixable errors for old legacy classnames. * This feature will add fixable errors for old legacy classnames.
@ -96,7 +97,11 @@ class LegacyClassnameFeature implements FeatureInterface
*/ */
protected function isLegacyClassname($classname) protected function isLegacyClassname($classname)
{ {
return $this->legacyMapping->isLegacyClassname($classname); if (get_class($this->sniff) === StringSniff::class) {
return $this->legacyMapping->isLegacyClassname($classname);
}
return $this->legacyMapping->isCaseInsensitiveLegacyClassname($classname);
} }
/** /**
@ -108,6 +113,12 @@ class LegacyClassnameFeature implements FeatureInterface
*/ */
protected function isMaybeLegacyClassname($classname) protected function isMaybeLegacyClassname($classname)
{ {
if (get_class($this->sniff) === StringSniff::class
&& $this->legacyMapping->isCaseInsensitiveLegacyClassname($classname)
) {
return true;
}
if (strpos($classname, 'Tx_') === false) { if (strpos($classname, 'Tx_') === false) {
return false; return false;
} }

View file

@ -45,6 +45,7 @@ final class LegacyClassnameMapping
return static::$instance; return static::$instance;
} }
// @codeCoverageIgnoreStart
private function __clone() private function __clone()
{ {
} }
@ -54,17 +55,54 @@ final class LegacyClassnameMapping
private function __wakeup() private function __wakeup()
{ {
} }
// @codeCoverageIgnoreEnd
private function __construct() private function __construct()
{ {
$this->mappings = require Options::getMappingFile(); if (is_file(Options::getMappingFile())) {
$this->mappings = require Options::getMappingFile();
$this->buildKeyArray($this->mappings, $this->mappingsKeys);
}
$this->typo3Mappings = require implode(DIRECTORY_SEPARATOR, [
__DIR__, '..', 'Configuration', '',
]) . 'LegacyClassnames.php';
$this->buildKeyArray($this->typo3Mappings, $this->typo3MappingsKeys);
} }
// Singleton implementation - End // Singleton implementation - End
/** /**
* Contains mappings as defined by composer for alias mapping. * @var array
*/
protected $typo3Mappings = [];
/**
* @var array
*/
protected $typo3MappingsKeys = [];
/**
* @var array * @var array
*/ */
protected $mappings = []; protected $mappings = [];
/**
* @var array
*/
protected $mappingsKeys = [];
/**
* @var bool
*/
protected $dirty = false;
/**
* @param array $originalArray
* @param array $targetVariable
*/
protected function buildKeyArray($originalArray, &$targetVariable)
{
foreach (array_keys($originalArray) as $key) {
$targetVariable[strtolower($key)] = $key;
}
}
/** /**
* Checks whether a mapping exists for the given $classname, * Checks whether a mapping exists for the given $classname,
@ -75,7 +113,22 @@ final class LegacyClassnameMapping
*/ */
public function isLegacyClassname($classname) public function isLegacyClassname($classname)
{ {
return isset($this->mappings['aliasToClassNameMapping'][strtolower($classname)]); return $this->isLegacyTypo3Classname($classname) || $this->isLegacyMappingClassname($classname);
}
/**
* Checks whether a mapping exists for the given $classname,
* indicating it's legacy.
*
* @param string $classname
* @return bool
*/
public function isCaseInsensitiveLegacyClassname($classname)
{
$lowerVersion = strtolower($classname);
return $this->isLegacyTypo3Classname($classname) || $this->isLegacyMappingClassname($classname)
|| isset($this->typo3MappingsKeys[$lowerVersion]) || isset($this->mappingsKeys[$lowerVersion]);
} }
/** /**
@ -84,7 +137,47 @@ final class LegacyClassnameMapping
*/ */
public function getNewClassname($classname) public function getNewClassname($classname)
{ {
return $this->mappings['aliasToClassNameMapping'][strtolower($classname)]; if ($this->isLegacyTypo3Classname($classname) || isset($this->typo3MappingsKeys[strtolower($classname)])) {
return $this->typo3Mappings[$this->getTypo3MappingKey($classname)];
}
return $this->mappings[$this->getLegacyMappingKey($classname)];
}
/**
* @param string $classname
* @return string
*/
protected function getTypo3MappingKey($classname)
{
return $this->typo3MappingsKeys[strtolower($classname)];
}
/**
* @param string $classname
* @return string
*/
protected function getLegacyMappingKey($classname)
{
return $this->mappingsKeys[strtolower($classname)];
}
/**
* @param string $classname
* @return bool
*/
protected function isLegacyTypo3Classname($classname)
{
return isset($this->typo3Mappings[$classname]);
}
/**
* @param string $classname
* @return bool
*/
protected function isLegacyMappingClassname($classname)
{
return isset($this->mappings[$classname]);
} }
/** /**
@ -98,27 +191,30 @@ final class LegacyClassnameMapping
*/ */
public function addLegacyClassname($legacyClassname, $newClassname) public function addLegacyClassname($legacyClassname, $newClassname)
{ {
$key = strtolower($legacyClassname); $this->mappings[$legacyClassname] = $newClassname;
$this->mappingsKeys[strtolower($legacyClassname)] = $legacyClassname;
$this->mappings['aliasToClassNameMapping'][$key] = $newClassname; $this->dirty = true;
$this->mappings['classNameToAliasMapping'][$newClassname] = [$key => $key];
} }
/** public function persistMappings()
* Used to persist new mappings.
*/
public function __destruct()
{ {
// For some reasons desctruct is called multiple times, while construct if ($this->dirty === false) {
// 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; return;
} }
file_put_contents( file_put_contents(
Options::getMappingFile(), Options::getMappingFile(),
'<?php' . PHP_EOL . 'return ' . var_export($this->mappings, true) . ';' '<?php' . PHP_EOL . 'return ' . var_export($this->mappings, true) . ';' . PHP_EOL
); );
$this->dirty = false;
}
/**
* Used to persist new mappings.
* @codeCoverageIgnore
*/
public function __destruct()
{
$this->persistMappings();
} }
} }

View file

@ -0,0 +1,53 @@
<?php
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 PhpCsTokens;
use Typo3Update\Feature\FeaturesSupport;
class Typo3Update_Sniffs_Classname_StringSniff implements PhpCsSniff
{
use FeaturesSupport;
/**
* @return array<int>
*/
public function register()
{
return PhpCsTokens::$stringTokens;
}
/**
* @param PhpCsFile $phpcsFile
* @param int $stackPtr
*/
public function process(PhpCsFile $phpcsFile, $stackPtr)
{
$token = $phpcsFile->getTokens()[$stackPtr]['content'];
// Special chars like ":" and "&" are used in configuration directives.
$classnames = array_filter(preg_split('/\s+|:|->|&/', substr($token, 1, -1)));
foreach ($classnames as $classname) {
$this->processFeatures($phpcsFile, $stackPtr, $classname);
}
}
}

View file

@ -0,0 +1,160 @@
<?php
namespace Typo3Update\Tests\Feature;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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 PHPUnit\Framework\TestCase;
use Typo3Update\Feature\LegacyClassnameMapping;
use org\bovigo\vfs\vfsStream;
class LegacyClassnameMappingTest extends TestCase
{
/**
* @var \org\bovigo\vfs\vfsStreamDirectory
*/
protected $fileSystem;
/**
* @var LegacyClassnameMapping
*/
protected $subject;
/**
* @param string $fileName
* @return string
*/
protected function getFixturePath($fileName)
{
return implode(DIRECTORY_SEPARATOR, [
__DIR__, '..', 'Fixtures', 'Standards','Typo3Update',
'Feature', 'LegacyClassnameMapping', $fileName,
]);
}
public function setUp()
{
$this->fileSystem = vfsStream::setup('root', null, [
'LegacyClassnames.php' => file_get_contents($this->getFixturePath('MappingContent.php')),
]);
$GLOBALS['PHP_CODESNIFFER_CONFIG_DATA'] = [
'mappingFile' => vfsStream::url('root/LegacyClassnames.php'),
];
$this->subject = LegacyClassnameMapping::getInstance();
}
/**
* @test
*/
public function inCaseSensitivityLookupWorks()
{
$this->assertFalse(
$this->subject->isLegacyClassname('tx_extbase_domain_model_backenduser'),
'Classname was returned to be legacy but should not due to lowercase version and case sensitivity.'
);
$this->assertFalse(
$this->subject->isLegacyClassname('Tx_Extbase_Domain_Model_Backenduser'),
'Classname was returned to be legacy but should not due to lowercase version and case sensitivity.'
);
$this->assertTrue(
$this->subject->isCaseInsensitiveLegacyClassname('Tx_Extbase_Domain_Model_Backenduser'),
'Classname was not returned to be legacy but should due to case insensitivity.'
);
}
/**
* @test
*/
public function weCanRetrieveNewClassname()
{
$this->assertSame(
'TYPO3\CMS\Extbase\Command\HelpCommandController',
$this->subject->getNewClassname('tx_extbase_command_helpcommandcontroller'),
'New class name could not be fetched for lower cased version.'
);
$this->assertSame(
'TYPO3\CMS\Extbase\Command\HelpCommandController',
$this->subject->getNewClassname('Tx_Extbase_Command_HelpCommandController'),
'New class name could not be fetched.'
);
}
/**
* @test
*/
public function nothingWillBePersistedUntilWeAddSomething()
{
$this->subject->persistMappings();
$this->assertSame(
file_get_contents($this->getFixturePath('MappingContent.php')),
file_get_contents(vfsStream::url('root/LegacyClassnames.php')),
'File content should not be changed.'
);
$this->subject->addLegacyClassname('Tx_ExtName_Controller_ExampleController', '\\Vendor\\ExtName\\Controller\\ExampleController');
$this->subject->persistMappings();
$this->assertSame(
file_get_contents($this->getFixturePath('ExpectedMappingContent.php')),
file_get_contents(vfsStream::url('root/LegacyClassnames.php')),
'File content is not changed as expected.'
);
}
/**
* @test
* @runInSeparateProcess Because of file operations.
*/
public function addingLegacyClassnamesWillAdjustLookupAndBePersisted()
{
$this->assertFalse(
$this->subject->isLegacyClassname('Tx_ExtName_Controller_ExampleController'),
'Classname is not configured but returned to be legacy.'
);
$this->subject->addLegacyClassname('Tx_ExtName_Controller_ExampleController', '\\Vendor\\ExtName\\Controller\\ExampleController');
$this->assertTrue(
$this->subject->isLegacyClassname('Tx_ExtName_Controller_ExampleController'),
'Classname is configured but not returned to be legacy.'
);
}
/**
* @test
* @runInSeparateProcess Because of file operations.
*/
public function weCanRetrieveNewClassnameAddedBefore()
{
$this->subject->addLegacyClassname('Tx_ExtName_Controller_ExampleController', '\\Vendor\\ExtName\\Controller\\ExampleController');
$this->assertSame(
'\\Vendor\\ExtName\\Controller\\ExampleController',
$this->subject->getNewClassname('Tx_ExtName_Controller_ExampleController'),
'New class name could not be fetched.'
);
}
public function tearDown()
{
unset($this->subject);
unset($this->fileSystem);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
<?php
return array (
'Tx_ExtName_Controller_ExampleController' => '\\Vendor\\ExtName\\Controller\\ExampleController',
);

View file

@ -0,0 +1,3 @@
<?php
return array (
);

View file

@ -0,0 +1,17 @@
--- tests/Fixtures/Standards/Typo3Update/Sniffs/Classname/StringSniff/InputFileForIssues.php
+++ PHP_CodeSniffer
@@ -19,10 +19,10 @@
* 02110-1301, USA.
*/
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = 'Tx_Extbase_Command_HelpCommandController';
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "Tx_Extbase_Command_HelpCommandController";
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = '\TYPO3\CMS\Extbase\Command\HelpCommandController';
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "\\TYPO3\\CMS\\Extbase\\Command\\HelpCommandController";
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'][] = 'EXT:ext_key/Classes/Hooks/Cache.php:&Tx_Extbase_Command_HelpCommandController->getHash';
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'][] = 'EXT:ext_key/Classes/Hooks/Cache.php:&\TYPO3\CMS\Extbase\Command\HelpCommandController->getHash';
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "Tx_About_Domain_Model_Extension";
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "\\TYPO3\\CMS\\About\\Domain\\Model\\Extension";
$GLOBALS['TCA']['tx_about_domain_model_extension'] = [];

View file

@ -0,0 +1,60 @@
{
"files": {
"InputFileForIssues.php": {
"errors": 4,
"messages": [
{
"column": 80,
"fixable": true,
"line": 22,
"message": "Legacy classes are not allowed; found \"Tx_Extbase_Command_HelpCommandController\", use \"TYPO3\\CMS\\Extbase\\Command\\HelpCommandController\" instead",
"severity": 5,
"source": "Typo3Update.Classname.String.legacyClassname",
"type": "ERROR"
},
{
"column": 80,
"fixable": true,
"line": 23,
"message": "Legacy classes are not allowed; found \"Tx_Extbase_Command_HelpCommandController\", use \"TYPO3\\CMS\\Extbase\\Command\\HelpCommandController\" instead",
"severity": 5,
"source": "Typo3Update.Classname.String.legacyClassname",
"type": "ERROR"
},
{
"column": 93,
"fixable": true,
"line": 25,
"message": "Legacy classes are not allowed; found \"Tx_Extbase_Command_HelpCommandController\", use \"TYPO3\\CMS\\Extbase\\Command\\HelpCommandController\" instead",
"severity": 5,
"source": "Typo3Update.Classname.String.legacyClassname",
"type": "ERROR"
},
{
"column": 80,
"fixable": true,
"line": 27,
"message": "Legacy classes are not allowed; found \"Tx_About_Domain_Model_Extension\", use \"TYPO3\\CMS\\About\\Domain\\Model\\Extension\" instead",
"severity": 5,
"source": "Typo3Update.Classname.String.legacyClassname",
"type": "ERROR"
},
{
"column": 17,
"fixable": false,
"line": 28,
"message": "Legacy classes are not allowed; found tx_about_domain_model_extension that might be a legacy class that does not exist anymore",
"severity": 5,
"source": "Typo3Update.Classname.String.mightBeLegacyClassname",
"type": "WARNING"
}
],
"warnings": 1
}
},
"totals": {
"errors": 4,
"fixable": 4,
"warnings": 1
}
}

View file

@ -0,0 +1,28 @@
<?php
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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.
*/
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = 'Tx_Extbase_Command_HelpCommandController';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "Tx_Extbase_Command_HelpCommandController";
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['createHashBase'][] = 'EXT:ext_key/Classes/Hooks/Cache.php:&Tx_Extbase_Command_HelpCommandController->getHash';
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] = "Tx_About_Domain_Model_Extension";
$GLOBALS['TCA']['tx_about_domain_model_extension'] = [];

View file

@ -0,0 +1,28 @@
<?php
namespace Typo3Update\Tests\Sniffs\Classname;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* 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\Tests\SniffsTest;
class StringSniffTest extends SniffsTest
{
}