Skip to content

Commit

Permalink
Merge branch '2.15' into 2.16
Browse files Browse the repository at this point in the history
* 2.15:
  DX: simplify Utils::camelCaseToUnderscore
  FunctionToConstantFixer - get_class($this) support
  NoUnneededFinalMethodFixer - update description
  Do not apply any text/.git filters to fixtures
  Simplify installing PCOV
  • Loading branch information
SpacePossum committed Feb 3, 2020
2 parents 5f240a3 + c96a06c commit 45ec06c
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
*.php text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,tabwidth=4 diff=php
*.rst text whitespace=blank-at-eol,blank-at-eof
*.yml text whitespace=blank-at-eol,blank-at-eof,space-before-tab,tab-in-indent,tabwidth=4
/tests/Fixtures/**/* -text
/tests/Fixtures/**/* -text -filter
9 changes: 1 addition & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,7 @@ jobs:
- composer require --dev --no-update phpunit/phpunit:^8

# Install PCOV
- |
git clone --single-branch --branch=v1.0.6 --depth=1 https://github.com/krakjoe/pcov
cd pcov
phpize
./configure
make clean install
echo "extension=pcov.so" > $HOME/.phpenv/versions/$TRAVIS_PHP_VERSION/etc/conf.d/pcov.ini
cd $TRAVIS_BUILD_DIR
- pecl install pcov
before_script:
# Make code compatible with PHPUnit 8
- PHP_CS_FIXER_FUTURE_MODE=1 php php-cs-fixer fix --rules=void_return -q tests || return 0
Expand Down
8 changes: 5 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,9 @@ Choose from the list of available rules:
Configuration options:

- ``functions`` (a subset of ``['get_called_class', 'get_class',
'php_sapi_name', 'phpversion', 'pi']``): list of function names to fix;
defaults to ``['get_class', 'php_sapi_name', 'phpversion', 'pi']``
'php_sapi_name', 'phpversion', 'pi', 'get_class_this']``): list of
function names to fix; defaults to ``['get_class', 'php_sapi_name',
'phpversion', 'pi']``

* **function_typehint_space** [@Symfony, @PhpCsFixer]

Expand Down Expand Up @@ -1195,7 +1196,8 @@ Choose from the list of available rules:

* **no_unneeded_final_method** [@Symfony, @PhpCsFixer]

A final class must not have final methods.
A ``final`` class must not have ``final`` methods and ``private`` method must
not be ``final``.

* **no_unreachable_default_argument_value** [@PhpCsFixer:risky]

Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/ClassNotation/NoUnneededFinalMethodFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class NoUnneededFinalMethodFixer extends AbstractFixer
public function getDefinition()
{
return new FixerDefinition(
'A final class must not have final methods.',
'A `final` class must not have `final` methods and `private` method must not be `final`.',
[
new CodeSample(
'<?php
Expand Down
127 changes: 106 additions & 21 deletions src/Fixer/LanguageConstruct/FunctionToConstantFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
Expand Down Expand Up @@ -51,6 +52,11 @@ public function __construct()
'php_sapi_name' => [new Token([T_STRING, 'PHP_SAPI'])],
'phpversion' => [new Token([T_STRING, 'PHP_VERSION'])],
'pi' => [new Token([T_STRING, 'M_PI'])],
'get_class_this' => [
new Token([T_STATIC, 'static']),
new Token([T_DOUBLE_COLON, '::']),
new Token([CT::T_CLASS_CONSTANT, 'class']),
],
];
}

Expand Down Expand Up @@ -78,8 +84,13 @@ public function getDefinition()
return new FixerDefinition(
'Replace core functions calls returning constants with the constants.',
[
new CodeSample("<?php\necho phpversion();\necho pi();\necho php_sapi_name();\n"),
new CodeSample("<?php\necho phpversion();\necho pi();\n", ['functions' => ['phpversion']]),
new CodeSample(
"<?php\necho phpversion();\necho pi();\necho php_sapi_name();\nclass Foo\n{\n public function Bar()\n {\n echo get_class();\n echo get_called_class();\n }\n}\n"
),
new CodeSample(
"<?php\necho phpversion();\necho pi();\nclass Foo\n{\n public function Bar()\n {\n echo get_class();\n get_class(\$this);\n echo get_called_class();\n }\n}\n",
['functions' => ['phpversion', 'get_called_class', 'get_class_this']]
),
],
null,
'Risky when any of the configured functions to replace are overridden.'
Expand Down Expand Up @@ -118,8 +129,10 @@ public function isRisky()
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
$functionAnalyzer = new FunctionsAnalyzer();

for ($index = $tokens->count() - 4; $index > 0; --$index) {
$candidate = $this->getReplaceCandidate($tokens, $index);
$candidate = $this->getReplaceCandidate($tokens, $functionAnalyzer, $index);
if (null === $candidate) {
continue;
}
Expand Down Expand Up @@ -164,8 +177,13 @@ protected function createConfigurationDefinition()
*/
private function fixFunctionCallToConstant(Tokens $tokens, $index, $braceOpenIndex, $braceCloseIndex, array $replacements)
{
$tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex);
$tokens->clearTokenAndMergeSurroundingWhitespace($braceOpenIndex);
for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) {
if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT]])) {
continue;
}

$tokens->clearTokenAndMergeSurroundingWhitespace($i);
}

if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) {
$prevIndex = $tokens->getPrevMeaningfulToken($index);
Expand All @@ -184,43 +202,110 @@ private function fixFunctionCallToConstant(Tokens $tokens, $index, $braceOpenInd
*
* @return null|array
*/
private function getReplaceCandidate(Tokens $tokens, $index)
{
// test if we are at a function all
private function getReplaceCandidate(
Tokens $tokens,
FunctionsAnalyzer $functionAnalyzer,
$index
) {
if (!$tokens[$index]->isGivenKind(T_STRING)) {
return null;
}

$lowerContent = strtolower($tokens[$index]->getContent());

if ('get_class' === $lowerContent) {
return $this->fixGetClassCall($tokens, $functionAnalyzer, $index);
}

if (!isset($this->functionsFixMap[$lowerContent])) {
return null;
}

if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
return null;
}

// test if function call without parameters
$braceOpenIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$braceOpenIndex]->equals('(')) {
return null;
}

// test if function call without parameters
$braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
if (!$tokens[$braceCloseIndex]->equals(')')) {
return null;
}

$functionNamePrefix = $tokens->getPrevMeaningfulToken($index);
if ($tokens[$functionNamePrefix]->isGivenKind([T_DOUBLE_COLON, T_NEW, T_OBJECT_OPERATOR, T_FUNCTION, CT::T_RETURN_REF])) {
return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex);
}

/**
* @param int $index
*
* @return null|array
*/
private function fixGetClassCall(
Tokens $tokens,
FunctionsAnalyzer $functionAnalyzer,
$index
) {
if (!isset($this->functionsFixMap['get_class']) && !isset($this->functionsFixMap['get_class_this'])) {
return null;
}

if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
return null;
}

if ($tokens[$functionNamePrefix]->isGivenKind(T_NS_SEPARATOR)) {
// skip if the call is to a constructor or to a function in a namespace other than the default
$prevIndex = $tokens->getPrevMeaningfulToken($functionNamePrefix);
if ($tokens[$prevIndex]->isGivenKind([T_STRING, T_NEW])) {
return null;
$braceOpenIndex = $tokens->getNextMeaningfulToken($index);
$braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);

if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { // no arguments passed
if (isset($this->functionsFixMap['get_class'])) {
return $this->getReplacementTokenClones('get_class', $braceOpenIndex, $braceCloseIndex);
}
}
} else {
if (isset($this->functionsFixMap['get_class_this'])) {
$isThis = false;

// test if the function call is to a native PHP function
$lowerContent = strtolower($tokens[$index]->getContent());
if (!\array_key_exists($lowerContent, $this->functionsFixMap)) {
return null;
for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) {
if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], ')'])) {
continue;
}

if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
$isThis = true;

continue;
}

if (false === $isThis && $tokens[$i]->equals('(')) {
continue;
}

$isThis = false;

break;
}

if ($isThis) {
return $this->getReplacementTokenClones('get_class_this', $braceOpenIndex, $braceCloseIndex);
}
}
}

return null;
}

/**
* @param string $lowerContent
* @param int $braceOpenIndex
* @param int $braceCloseIndex
*
* @return array
*/
private function getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex)
{
$clones = [];
foreach ($this->functionsFixMap[$lowerContent] as $token) {
$clones[] = clone $token;
Expand Down
11 changes: 10 additions & 1 deletion src/RuleSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,16 @@ final class RuleSet implements RuleSetInterface
'error_suppression' => true,
'fopen_flag_order' => true,
'fopen_flags' => ['b_mode' => false],
'function_to_constant' => true,
'function_to_constant' => [
'functions' => [
'get_called_class',
'get_class',
'get_class_this',
'php_sapi_name',
'phpversion',
'pi',
],
],
'implode_call' => true,
'is_null' => true,
'modernize_types_casting' => true,
Expand Down
10 changes: 2 additions & 8 deletions src/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,15 @@ public static function calculateBitmask(array $options)
}

/**
* Converts a camel cased string to an snake cased string.
* Converts a camel cased string to a snake cased string.
*
* @param string $string
*
* @return string
*/
public static function camelCaseToUnderscore($string)
{
return Preg::replaceCallback(
'/(^|[a-z0-9])([A-Z])/',
static function (array $matches) {
return strtolower('' !== $matches[1] ? $matches[1].'_'.$matches[2] : $matches[2]);
},
$string
);
return strtolower(Preg::replace('/(?<!^)((?=[A-Z][^A-Z])|(?<![A-Z])(?=[A-Z]))/', '_', $string));
}

/**
Expand Down
28 changes: 28 additions & 0 deletions tests/Fixer/LanguageConstruct/FunctionToConstantFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,34 @@ class B
null,
['functions' => ['get_called_class']],
],
[
'<?php class Foo{ public function Bar(){ echo static::class ; }}',
'<?php class Foo{ public function Bar(){ echo get_class( $This ); }}',
['functions' => ['get_class_this']],
],
[
'<?php class Foo{ public function Bar(){ echo static::class; get_class(1, 2); get_class($a); get_class($a, $b);}}',
'<?php class Foo{ public function Bar(){ echo get_class($this); get_class(1, 2); get_class($a); get_class($a, $b);}}',
['functions' => ['get_class_this']],
],
[
'<?php class Foo{ public function Bar(){ echo static::class /* 0 */ /* 1 */ ;}}',
'<?php class Foo{ public function Bar(){ echo \get_class( /* 0 */ $this /* 1 */ );}}',
['functions' => ['get_class_this']],
],
[
'<?php class Foo{ public function Bar(){ echo static::class; echo __CLASS__; }}',
'<?php class Foo{ public function Bar(){ echo \get_class((($this))); echo get_class(); }}',
['functions' => ['get_class_this', 'get_class']],
],
[
'<?php
class Foo{ public function Bar(){ echo $reflection = new \ReflectionClass(get_class($this->extension)); }}
class Foo{ public function Bar(){ echo $reflection = new \ReflectionClass(get_class($this() )); }}
',
null,
['functions' => ['get_class_this']],
],
];
}

Expand Down
28 changes: 28 additions & 0 deletions tests/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,34 @@ public function provideCamelCaseToUnderscoreCases()
[
'utf8_encoder_fixer',
],
[
'a',
'A',
],
[
'aa',
'AA',
],
[
'foo',
'FOO',
],
[
'foo_bar_baz',
'FooBarBAZ',
],
[
'foo_bar_baz',
'FooBARBaz',
],
[
'foo_bar_baz',
'FOOBarBaz',
],
[
'mr_t',
'MrT',
],
];
}

Expand Down

0 comments on commit 45ec06c

Please sign in to comment.