Merge branch 'develop' into feature/77-globals

This commit is contained in:
Daniel Siepmann 2017-05-16 16:24:33 +02:00
commit 180a8a77c2
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
53 changed files with 3666 additions and 4430 deletions

1
.gitignore vendored
View file

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

View file

@ -143,6 +143,52 @@ Using ``runtime-set``:
--runtime-set removedFunctionConfigFiles "/Some/Absolute/Path/*.yaml"
.. _configuration-removedSignalConfigFiles:
removedSignalConfigFiles
^^^^^^^^^^^^^^^^^^^^^^^^^^
Configure where to look for configuration files defining the removed signals. Default
is ``Configuration/Removed/Signals/*.yaml`` inside the standard itself. We already try to deliver
as much as possible.
Globing is used, so placeholders like ``*`` are possible, see
https://secure.php.net/manual/en/function.glob.php
Using :file:`ruleset.xml`:
.. code:: xml
<config name="removedSignalConfigFiles" value="/Some/Absolute/Path/*.yaml"/>
Using ``runtime-set``:
.. code:: bash
--runtime-set removedSignalConfigFiles "/Some/Absolute/Path/*.yaml"
.. _configuration-removedHookConfigFiles:
removedHookConfigFiles
^^^^^^^^^^^^^^^^^^^^^^^^^^
Configure where to look for configuration files defining the removed hooks. Default
is ``Configuration/Removed/Hooks/*.yaml`` inside the standard itself. We already try to deliver
as much as possible.
Globing is used, so placeholders like ``*`` are possible, see
https://secure.php.net/manual/en/function.glob.php
Using :file:`ruleset.xml`:
.. code:: xml
<config name="removedHookConfigFiles" value="/Some/Absolute/Path/*.yaml"/>
Using ``runtime-set``:
.. code:: bash
--runtime-set removedHookConfigFiles "/Some/Absolute/Path/*.yaml"
.. _configuration-removedConstantConfigFiles:
removedConstantConfigFiles

View file

@ -109,6 +109,12 @@ functions. For configuration options see :ref:`configuration-removedClassConfigF
Check for usage of *removed PHP globals*. The globals are configured in same way as removed
functions. For configuration options see :ref:`configuration-removedGlobalConfigFiles`.
Check for usage of *removed signals*. The signals are configured in same way as removed
functions. For configuration options see :ref:`configuration-removedSignalConfigFiles`.
Check for usage of *removed hooks*. The hooks are configured in same way as removed
functions. For configuration options see :ref:`configuration-removedHookConfigFiles`.
Check for usage of *removed TypoScript*. The TypoScript objects are configured in same way as
removed functions. For configuration options see :ref:`configuration-removedTypoScriptConfigFiles`.
This will check whether you are using already removed TypoScript parts, supported are:
@ -127,3 +133,7 @@ Further checks
--------------
- 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

@ -16,7 +16,8 @@
},
"autoload": {
"psr-4": {
"Typo3Update\\": "src/Standards/Typo3Update/"
"Typo3Update\\": "src/Standards/Typo3Update/",
"Typo3Update\\CodeSniffer\\": "src/CodeSniffer/"
},
"files": [
"src/CodeSniffer/Tokenizers/TypoScript.php"
@ -31,6 +32,7 @@
},
"require-dev": {
"phpunit/phpunit": "5.7.*",
"mikey179/vfsStream": "1.6.*",
"symfony/finder": "3.2.*",
"phpmd/phpmd": "2.6.*",
"pdepend/pdepend": "2.5.*"

View file

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

View file

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

View file

@ -0,0 +1,124 @@
<?php
namespace Typo3Update\CodeSniffer\Tokenizers;
/*
* 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.
*/
/**
* Update tokens with fully qualified object identifier.
*/
class FQObjectIdentifier
{
/**
* Key used to save identifier to token.
* @var string
*/
const IDENTIFIER = 'fqObjectIdentifier';
/**
* The fully qualified object identifier, dot separated.
*
* @var array
*/
protected $fqPath = [];
/**
* Current "real" depth, count of opening braces.
* @var int
*/
protected $depth = 0;
/**
* Add token as path segment.
* @param array $token
*/
public function addPathSegment(array &$token)
{
$this->syncPath();
$path = [];
foreach (explode('.', $token['content']) as $pathSegment) {
$path[] = [
'content' => $pathSegment,
'depth' => $this->getDepth(),
];
}
$this->fqPath = array_merge($this->fqPath, $path);
$this->addFqObjectIdentifier($token);
}
/**
* Sync path with current depth.
*/
public function syncPath()
{
$this->fqPath = array_filter(
$this->fqPath,
function ($pathSegment) {
return $pathSegment['depth'] < $this->depth;
}
);
}
/**
* Respect opening brace internal.
*/
public function handleOpeningBrace()
{
++$this->depth;
}
/**
* Respect closing brace internal.
*/
public function handleClosingBrace()
{
--$this->depth;
}
/**
* @return int
*/
public function getDepth()
{
return $this->depth;
}
/**
* @return string
*/
public function getPath()
{
$path = '';
foreach ($this->fqPath as $pathSegment) {
$path .= '.' . $pathSegment['content'];
}
return substr($path, 1);
}
/**
* @param array $token
*/
protected function addFqObjectIdentifier(array &$token)
{
$token[static::IDENTIFIER] = $this->getPath();
}
}

View file

@ -19,7 +19,9 @@
* 02110-1301, USA.
*/
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use Helmich\TypoScriptParser\Tokenizer\Tokenizer;
use Typo3Update\CodeSniffer\Tokenizers\FQObjectIdentifier;
/**
* Tokenizes a string of TypoScript.
@ -68,8 +70,33 @@ class PHP_CodeSniffer_Tokenizers_TYPOSCRIPT
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) We need to match the signature.
*/
public function processAdditional(&$tokens, $eolChar)
public function processAdditional(array &$tokens, $eolChar)
{
return;
$this->addFQObjectIdentifiers($tokens);
}
/**
* Add fully qualified object identifier to all object identifiers.
*
* @param array $tokens
*/
protected function addFQObjectIdentifiers(array &$tokens)
{
$fqObjectIdentifier = new FQObjectIdentifier();
foreach ($tokens as &$token) {
if ($token['type'] === TokenInterface::TYPE_OBJECT_IDENTIFIER) {
$fqObjectIdentifier->addPathSegment($token);
continue;
}
if ($token['type'] === TokenInterface::TYPE_BRACE_OPEN) {
$fqObjectIdentifier->handleOpeningBrace();
continue;
}
if ($token['type'] === TokenInterface::TYPE_BRACE_CLOSE) {
$fqObjectIdentifier->handleClosingBrace();
continue;
}
}
}
}

View file

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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
# Breaking changes in 7.3: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Index.html#breaking-changes
'7.3':
typo3/index.php->loginScriptHook:
replacement: 'Use the new backend login form API'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Breaking-66669-BackendLoginControllerRefactored.html
typo3/index.php->loginFormHook:
replacement: 'Use the new backend login form API'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Breaking-66669-BackendLoginControllerRefactored.html
t3lib/class.t3lib_tceforms.php->getSingleFieldClass:
replacement: null
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Breaking-63846-FormEngineRefactoring.html

View file

@ -0,0 +1,5 @@
# Breaking changes in 7.5: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.5/Index.html#breaking-changes
'7.5':
t3lib/class.t3lib_tceforms.php->getMainFieldsClass:
replacement: 'Use FormDataProvider to change data given to the render engine of FormEngine from now on'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.5/Breaking-69568-FormEngine.html

View file

@ -0,0 +1,17 @@
# Breaking changes in 7.2: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Index.html#breaking-changes
'7.2':
\TYPO3\CMS\Backend\Controller\LoginController::makeLoginBoxImage:
replacement: 'Use the introduced Fluid view to adapt the login screen to your demands'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Breaking-65939-BackendLoginRefactoring.html
\TYPO3\CMS\Backend\Controller\LoginController::wrapLoginForm:
replacement: 'Use the introduced Fluid view to adapt the login screen to your demands'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Breaking-65939-BackendLoginRefactoring.html
\TYPO3\CMS\Backend\Controller\LoginController::makeLoginNews:
replacement: 'Use the introduced Fluid view to adapt the login screen to your demands'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Breaking-65939-BackendLoginRefactoring.html
\TYPO3\CMS\Backend\Controller\LoginController::makeLoginForm:
replacement: 'Use the introduced Fluid view to adapt the login screen to your demands'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Breaking-65939-BackendLoginRefactoring.html
\TYPO3\CMS\Backend\Controller\LoginController::makeLogoutForm:
replacement: 'Use the introduced Fluid view to adapt the login screen to your demands'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.2/Breaking-65939-BackendLoginRefactoring.html

View file

@ -0,0 +1,5 @@
# Breaking changes in 7.3: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Index.html#breaking-changes
'7.3':
\TYPO3\CMS\Lang\Service\UpdateTranslationService::postProcessMirrorUrl:
replacement: 'Change the slot to use the \TYPO3\CMS\Lang\Service\TranslationService::postProcessMirrorUrl signal'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.3/Breaking-62983-PostProcessMirrorUrlSignalHasMoved.html

View file

@ -0,0 +1,5 @@
# Breaking changes in 7.4: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.4/Index.html#breaking-changes
'7.4':
\TYPO3\CMS\Core\Resource\ResourceStorage::preFileAdd:
replacement: 'The signal will now receive an empty string in $sourceFilePath'
docsUrl: https://docs.typo3.org/typo3cms/extensions/core/7.6/Changelog/7.4/Breaking-67545-PreFileAddSignalBehaviourChanged.html

View file

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

View file

@ -45,6 +45,7 @@ final class LegacyClassnameMapping
return static::$instance;
}
// @codeCoverageIgnoreStart
private function __clone()
{
}
@ -54,17 +55,54 @@ final class LegacyClassnameMapping
private function __wakeup()
{
}
// @codeCoverageIgnoreEnd
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
/**
* Contains mappings as defined by composer for alias mapping.
* @var array
*/
protected $typo3Mappings = [];
/**
* @var array
*/
protected $typo3MappingsKeys = [];
/**
* @var array
*/
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,
@ -75,7 +113,22 @@ final class LegacyClassnameMapping
*/
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)
{
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)
{
$key = strtolower($legacyClassname);
$this->mappings['aliasToClassNameMapping'][$key] = $newClassname;
$this->mappings['classNameToAliasMapping'][$newClassname] = [$key => $key];
$this->mappings[$legacyClassname] = $newClassname;
$this->mappingsKeys[strtolower($legacyClassname)] = $legacyClassname;
$this->dirty = true;
}
/**
* Used to persist new mappings.
*/
public function __destruct()
public function persistMappings()
{
// 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) {
if ($this->dirty === false) {
return;
}
file_put_contents(
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

@ -69,6 +69,32 @@ class Options
);
}
/**
* Returns an array of absolute file names containing removed function configurations.
*
* @return array<string>
*/
public static function getRemovedSignalConfigFiles()
{
return static::getOptionFileNames(
'removedSignalConfigFiles',
__DIR__ . '/Configuration/Removed/Signals/*.yaml'
);
}
/**
* Returns an array of absolute file names containing removed function configurations.
*
* @return array<string>
*/
public static function getRemovedHookConfigFiles()
{
return static::getOptionFileNames(
'removedHookConfigFiles',
__DIR__ . '/Configuration/Removed/Hooks/*.yaml'
);
}
/**
* Returns an array of absolute file names containing removed constant configurations.
*

View file

@ -38,7 +38,7 @@ class Typo3Update_Sniffs_Classname_PhpDocCommentSniff implements PhpCsSniff
* The configured tags will be processed.
* @var array<string>
*/
public $allowedTags = ['@param', '@return', '@var'];
public $allowedTags = ['@param', '@return', '@var', '@validate'];
/**
* Returns the token types that this sniff is interested in.
@ -69,7 +69,7 @@ class Typo3Update_Sniffs_Classname_PhpDocCommentSniff implements PhpCsSniff
if ($classnamePosition === false) {
return;
}
$classnames = explode('|', explode(' ', $tokens[$classnamePosition]['content'])[0]);
$classnames = array_filter(preg_split('/\||\s|\<|\>|\(/', $tokens[$classnamePosition]['content']));
foreach ($classnames as $classname) {
$this->processFeatures($phpcsFile, $classnamePosition, $classname);

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

@ -28,6 +28,28 @@ use PHP_CodeSniffer_Tokens as Tokens;
*/
trait ExtendedPhpCsSupportTrait
{
/**
* Check whether variable at $stackPtr is global.
*
* @param PhpCsFile $phpcsFile
* @param int $stackPtr
*
* @return bool
*/
protected function isGlobalVariable(PhpCsFile $phpcsFile, $stackPtr)
{
$position = $phpcsFile->findPrevious(T_GLOBAL, $stackPtr, null, false, null, true);
if ($position !== false) {
return true;
}
if ($phpcsFile->getTokens()[$stackPtr]['content'] === '$GLOBALS') {
return true;
}
return false;
}
/**
* Check whether current stackPtr is a function call.
*
@ -68,24 +90,29 @@ trait ExtendedPhpCsSupportTrait
}
/**
* Check whether variable at $stackPtr is global.
*
* @param PhpCsFile $phpcsFile
* @param int $stackPtr
*
* @return bool
* @return array<string>
*/
protected function isGlobalVariable(PhpCsFile $phpcsFile, $stackPtr)
protected function getFunctionCallParameters(PhpCsFile $phpcsFile, $stackPtr)
{
$position = $phpcsFile->findPrevious(T_GLOBAL, $stackPtr, null, false, null, true);
if ($position !== false) {
return true;
}
$start = $phpcsFile->findNext(T_OPEN_PARENTHESIS, $stackPtr) + 1;
$parameters = explode(',', $phpcsFile->getTokensAsString(
$start,
$phpcsFile->findNext(T_CLOSE_PARENTHESIS, $stackPtr) - $start
));
if ($phpcsFile->getTokens()[$stackPtr]['content'] === '$GLOBALS') {
return true;
}
return array_map([$this, 'getStringContent'], $parameters);
}
return false;
/**
* Remove special chars like quotes from string.
*
* @param string $string
* @return string
*/
public function getStringContent($string)
{
return trim($string, " \t\n\r\0\x0B'\"");
}
}

View file

@ -67,6 +67,14 @@ class Typo3Update_Sniffs_LegacyClassname_MissingNamespaceSniff implements PhpCsS
}
$classname = $tokens[$classnamePosition]['content'];
if (substr($classname, 0, 3) !== 'Tx_') {
return;
}
LegacyClassnameMapping::getInstance()->addLegacyClassname(
$classname,
$this->getNamespace($classname) . '\\' . $this->getNewClassname($classname)
);
$fix = $phpcsFile->addFixableError(
'Legacy class definitions are not allowed; found "%s".'
. ' Wrap your class inside a namespace.',
@ -106,10 +114,6 @@ class Typo3Update_Sniffs_LegacyClassname_MissingNamespaceSniff implements PhpCsS
$this->getNamespacePosition($phpcsFile),
'<?php' . $lineEndings . $this->getNamespaceDefinition($classname) . $suffix
);
LegacyClassnameMapping::getInstance()->addLegacyClassname(
$classname,
$this->getNamespace($classname) . '\\' . $this->getNewClassname($classname)
);
}
/**

View file

@ -0,0 +1,69 @@
<?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_Tokens as PhpCsTokens;
use Typo3Update\Options;
use Typo3Update\Sniffs\ExtendedPhpCsSupportTrait;
use Typo3Update\Sniffs\Removed\AbstractGenericPhpUsage;
class Typo3Update_Sniffs_Removed_GenericHookSniff extends AbstractGenericPhpUsage
{
use ExtendedPhpCsSupportTrait;
public function register()
{
return PhpCsTokens::$stringTokens;
}
protected function getRemovedConfigFiles()
{
return Options::getRemovedHookConfigFiles();
}
protected function findRemoved(PhpCsFile $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$firstPart = $this->getStringContent($tokens[$stackPtr]['content']);
$secondPart = $this->getStringContent($tokens[$phpcsFile->findNext(PhpCsTokens::$stringTokens, $stackPtr + 1)]['content']);
$lookup = $firstPart . '->' . $secondPart;
if ($this->configured->isRemoved($lookup) === false) {
return [];
}
return [$this->configured->getRemoved($lookup)];
}
protected function getIdentifier(array $config)
{
$search = ['/', '.'];
$replace = ['-'];
return str_replace($search, $replace, $config['fqcn']) . $config['name'];
}
protected function getOldUsage(array $config)
{
return '["' . $config['fqcn'] . '"]["' . $config['name'] . '"] = ...';
}
}

View file

@ -0,0 +1,86 @@
<?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 Typo3Update\Options;
use Typo3Update\Sniffs\ExtendedPhpCsSupportTrait;
use Typo3Update\Sniffs\Removed\AbstractGenericPhpUsage;
class Typo3Update_Sniffs_Removed_GenericSignalSniff extends AbstractGenericPhpUsage
{
use ExtendedPhpCsSupportTrait;
protected function getRemovedConfigFiles()
{
return Options::getRemovedSignalConfigFiles();
}
protected function findRemoved(PhpCsFile $phpcsFile, $stackPtr)
{
if (!$this->isFunctionCall($phpcsFile, $stackPtr) || $phpcsFile->getTokens()[$stackPtr]['content'] !== 'connect') {
return [];
}
$parameters = $this->getFunctionCallParameters($phpcsFile, $stackPtr);
if (count($parameters) < 4) {
return [];
}
$lookup = $this->getClass($parameters[0]) . '::' . $parameters[1];
if ($this->configured->isRemoved($lookup) === false) {
return [];
}
return [$this->configured->getRemoved($lookup)];
}
/**
* Returns same formatted class representation for incoming strings.
*
* @param string $string
* @return string
*/
protected function getClass($string)
{
$search = [
'::class',
'\\\\',
];
$replace = [
'',
'\\',
];
$string = str_replace($search, $replace, $string);
if ($string[0] !== '\\') {
$string = '\\' . $string;
}
return $string;
}
protected function getOldUsage(array $config)
{
return $config['fqcn'] . '::' . $config['name'];
}
}

View file

@ -21,6 +21,7 @@
use Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use PHP_CodeSniffer_File as PhpCsFile;
use Typo3Update\CodeSniffer\Tokenizers\FQObjectIdentifier;
use Typo3Update\Options;
use Typo3Update\Sniffs\Removed\AbstractGenericUsage;
@ -68,20 +69,27 @@ class Typo3Update_Sniffs_Removed_TypoScriptSniff extends AbstractGenericUsage
protected function findRemoved(PhpCsFile $phpcsFile, $stackPtr)
{
$removed = [];
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
$objectIdentifier = $token['content'];
$identifiersToCheck = [$token['content']];
if (!$this->configured->isRemoved($objectIdentifier)) {
return [];
if (isset($token[FQObjectIdentifier::IDENTIFIER]) && $token[FQObjectIdentifier::IDENTIFIER] !== $token['content']) {
$identifiersToCheck[] = $token[FQObjectIdentifier::IDENTIFIER];
}
$removed = $this->configured->getRemoved($objectIdentifier);
if ($token['type'] === $removed['type']) {
return [$removed];
foreach ($identifiersToCheck as $objectIdentifier) {
if ($this->configured->isRemoved($objectIdentifier) === false) {
continue;
}
$configured = $this->configured->getRemoved($objectIdentifier);
if ($token['type'] === $configured['type']) {
$removed[] = $configured;
}
}
return [];
return $removed;
}
protected function getRemovedConfigFiles()

View file

@ -9,7 +9,7 @@
</rule>
<rule ref="Typo3Update.Classname.PhpDocComment">
<properties>
<property name="allowedTags" type="array" value="@param,@return,@var,@see,@throws"/>
<property name="allowedTags" type="array" value="@param,@return,@var,@validate,@see,@throws"/>
</properties>
</rule>
</ruleset>

View file

@ -0,0 +1,158 @@
<?php
namespace Typo3Update\Tests\CodeSniffer\Tokenizers;
/*
* 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 Helmich\TypoScriptParser\Tokenizer\TokenInterface;
use PHPUnit\Framework\TestCase;
use Typo3Update\CodeSniffer\Tokenizers\FQObjectIdentifier;
class FqObjectIdentifierTest extends TestCase
{
/**
* @test
*/
public function addingPathSegmentAddsFqToNewToken()
{
$initialToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'plugin.tx_example',
];
$lastToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'settings',
];
$expectedResult = $lastToken;
$expectedResult['fqObjectIdentifier'] = 'plugin.tx_example.settings';
$identifier = new FqObjectIdentifier();
$identifier->addPathSegment($initialToken);
$identifier->handleOpeningBrace();
$identifier->addPathSegment($lastToken);
$this->assertEquals(
$expectedResult,
$lastToken,
'Adding path segment does not add FQObjectIdentifier to token.'
);
}
/**
* @test
*/
public function addingPathSegment2ndTimeAddsFqToNewToken()
{
$initialToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'plugin.tx_example',
];
$firstToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'settings',
];
$lastToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'someSetting',
];
$expectedResult = $lastToken;
$expectedResult['fqObjectIdentifier'] = 'plugin.tx_example.settings.someSetting';
$identifier = new FqObjectIdentifier();
$identifier->addPathSegment($initialToken);
$identifier->handleOpeningBrace();
$identifier->addPathSegment($firstToken);
$identifier->handleOpeningBrace();
$identifier->addPathSegment($lastToken);
$this->assertEquals(
$expectedResult,
$lastToken,
'Adding path segment does not add FQObjectIdentifier to token on 2nd call.'
);
}
/**
* @test
*/
public function openingAndClosingBracesWillAdjustPath()
{
$initialToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'plugin.tx_example',
];
$firstToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'settings',
];
$secondToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'someSetting',
];
$lastToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'view',
];
$expectedResult = $lastToken;
$expectedResult['fqObjectIdentifier'] = 'plugin.tx_example.view';
$identifier = new FqObjectIdentifier();
$identifier->addPathSegment($initialToken);
$identifier->handleOpeningBrace();
$identifier->addPathSegment($firstToken);
$identifier->handleOpeningBrace();
$identifier->addPathSegment($secondToken);
$identifier->handleClosingBrace();
$identifier->addPathSegment($lastToken);
$this->assertEquals(
$expectedResult,
$lastToken,
'Curly braces do not modify path as expected.'
);
}
/**
* @test
*/
public function addingPathSegmentAfterAnotherResetsPath()
{
$initialToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'plugin.tx_example.settings.someThing',
];
$lastToken = [
'type' => TokenInterface::TYPE_OBJECT_IDENTIFIER,
'content' => 'plugin.tx_example.settings.anotherOne',
];
$expectedResult = $lastToken;
$expectedResult['fqObjectIdentifier'] = $expectedResult['content'];
$identifier = new FqObjectIdentifier();
$identifier->addPathSegment($initialToken);
$identifier->addPathSegment($lastToken);
$this->assertEquals(
$expectedResult,
$lastToken,
'Adding path segment without braces in between resets.'
);
}
}

View file

@ -22,6 +22,8 @@ namespace Typo3Update\Tests\CodeSniffer\Tokenizers;
*/
use PHPUnit\Framework\TestCase;
use PHP_CodeSniffer_File as PhpCsFile;
use PHP_CodeSniffer as PhpCs;
/**
* Test TypoScript tokenizer.
@ -55,9 +57,12 @@ class TypoScriptTest extends TestCase
'example.ts',
]);
// Initialize constants, etc.
new PhpCs();
$this->assertEquals(
require $resultFile,
$subject->tokenizeString(file_get_contents($testFile), "\n"),
PhpCsFile::tokenizeString(file_get_contents($testFile), $subject, "\n"),
'Did not get expected tokens.'
);
}

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);
}
}

View file

@ -3,4 +3,17 @@ plugin {
tx_example {
settings = TEST
}
tx_example_2 {
settings = TEST
}
}
plugin.tx_example.view.something = 1
plugin.tx_example {
view {
something = 1
}
settings2 = TEST
}
config {
enable_realurl = 1
}