Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #3826 Add CombineNestedDirnameFixer (gharlan)
This PR was squashed before being merged into the 2.13-dev branch (closes #3826). Discussion ---------- Add CombineNestedDirnameFixer Input: ```php dirname(dirname(dirname($path))); ``` is fixed to: ```php dirname($path, 3); ``` (Requires PHP >= 7.0) Commits ------- 398f343 Add CombineNestedDirnameFixer
- Loading branch information
Showing
11 changed files
with
433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 226 additions & 0 deletions
226
src/Fixer/FunctionNotation/CombineNestedDirnameFixer.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of PHP CS Fixer. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* Dariusz Rumiński <dariusz.ruminski@gmail.com> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace PhpCsFixer\Fixer\FunctionNotation; | ||
|
||
use PhpCsFixer\AbstractFixer; | ||
use PhpCsFixer\FixerDefinition\FixerDefinition; | ||
use PhpCsFixer\FixerDefinition\VersionSpecification; | ||
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; | ||
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; | ||
use PhpCsFixer\Tokenizer\Token; | ||
use PhpCsFixer\Tokenizer\Tokens; | ||
|
||
/** | ||
* @author Gregor Harlan | ||
*/ | ||
final class CombineNestedDirnameFixer extends AbstractFixer | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getDefinition() | ||
{ | ||
return new FixerDefinition( | ||
'Replace multiple nested calls of `dirname` by only one call with second `$level` parameter. Requires PHP >= 7.0.', | ||
[ | ||
new VersionSpecificCodeSample( | ||
"<?php\ndirname(dirname(dirname(\$path)));\n", | ||
new VersionSpecification(70000) | ||
), | ||
], | ||
null, | ||
'Risky when the function `dirname` is overridden.' | ||
); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isCandidate(Tokens $tokens) | ||
{ | ||
return \PHP_VERSION_ID >= 70000 && $tokens->isTokenKindFound(T_STRING); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isRisky() | ||
{ | ||
return true; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getPriority() | ||
{ | ||
// should run after DirConstantFixer | ||
// should run before MethodArgumentSpaceFixer, NoSpacesInsideParenthesisFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer | ||
return 3; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function applyFix(\SplFileInfo $file, Tokens $tokens) | ||
{ | ||
for ($index = $tokens->count() - 1; 0 <= $index; --$index) { | ||
$token = $tokens[$index]; | ||
|
||
if (!$token->equals([T_STRING, 'dirname'], false)) { | ||
continue; | ||
} | ||
|
||
$dirnameInfo = $this->getDirnameInfo($tokens, $index); | ||
|
||
if (!$dirnameInfo) { | ||
continue; | ||
} | ||
|
||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); | ||
|
||
if (!$tokens[$prev]->equals('(')) { | ||
continue; | ||
} | ||
|
||
$prev = $tokens->getPrevMeaningfulToken($prev); | ||
|
||
$firstArgumentEnd = $dirnameInfo['end']; | ||
|
||
$dirnameInfoArray = [$dirnameInfo]; | ||
|
||
while ($dirnameInfo = $this->getDirnameInfo($tokens, $prev, $firstArgumentEnd)) { | ||
$dirnameInfoArray[] = $dirnameInfo; | ||
|
||
$prev = $tokens->getPrevMeaningfulToken($dirnameInfo['indexes'][0]); | ||
|
||
if (!$tokens[$prev]->equals('(')) { | ||
break; | ||
} | ||
|
||
$prev = $tokens->getPrevMeaningfulToken($prev); | ||
$firstArgumentEnd = $dirnameInfo['end']; | ||
} | ||
|
||
if (\count($dirnameInfoArray) > 1) { | ||
$this->combineDirnames($tokens, $dirnameInfoArray); | ||
} | ||
|
||
$index = $prev; | ||
} | ||
} | ||
|
||
/** | ||
* @param Tokens $tokens | ||
* @param int $index Index of `dirname` | ||
* @param null|int $firstArgumentEndIndex Index of last token of first argument of `dirname` call | ||
* | ||
* @return array|bool `false` when it is not a (supported) `dirname` call, an array with info about the dirname call otherwise | ||
*/ | ||
private function getDirnameInfo(Tokens $tokens, $index, $firstArgumentEndIndex = null) | ||
{ | ||
if (!$tokens[$index]->equals([T_STRING, 'dirname'], false)) { | ||
return false; | ||
} | ||
|
||
if (!(new FunctionsAnalyzer())->isGlobalFunctionCall($tokens, $index)) { | ||
return false; | ||
} | ||
|
||
$info['indexes'] = []; | ||
|
||
$prev = $tokens->getPrevMeaningfulToken($index); | ||
|
||
if ($tokens[$prev]->isGivenKind(T_NS_SEPARATOR)) { | ||
$info['indexes'][] = $prev; | ||
} | ||
|
||
$info['indexes'][] = $index; | ||
|
||
// opening parenthesis "(" | ||
$next = $tokens->getNextMeaningfulToken($index); | ||
$info['indexes'][] = $next; | ||
|
||
if ($firstArgumentEndIndex) { | ||
$next = $tokens->getNextMeaningfulToken($firstArgumentEndIndex); | ||
} else { | ||
$next = $tokens->getNextMeaningfulToken($next); | ||
|
||
if ($tokens[$next]->equals(')')) { | ||
return false; | ||
} | ||
|
||
while (!$tokens[$next]->equalsAny([',', ')'])) { | ||
$blockType = Tokens::detectBlockType($tokens[$next]); | ||
|
||
if ($blockType) { | ||
$next = $tokens->findBlockEnd($blockType['type'], $next); | ||
} | ||
|
||
$next = $tokens->getNextMeaningfulToken($next); | ||
} | ||
} | ||
|
||
$info['indexes'][] = $next; | ||
|
||
if ($tokens[$next]->equals(')')) { | ||
$info['levels'] = 1; | ||
$info['end'] = $next; | ||
|
||
return $info; | ||
} | ||
|
||
$next = $tokens->getNextMeaningfulToken($next); | ||
|
||
if (!$tokens[$next]->isGivenKind(T_LNUMBER)) { | ||
return false; | ||
} | ||
|
||
$info['indexes'][] = $next; | ||
$info['secondArgument'] = $next; | ||
$info['levels'] = (int) $tokens[$next]->getContent(); | ||
|
||
$next = $tokens->getNextMeaningfulToken($next); | ||
|
||
if (!$tokens[$next]->equals(')')) { | ||
return false; | ||
} | ||
|
||
$info['indexes'][] = $next; | ||
$info['end'] = $next; | ||
|
||
return $info; | ||
} | ||
|
||
private function combineDirnames(Tokens $tokens, array $dirnameInfoArray) | ||
{ | ||
$outerDirnameInfo = array_pop($dirnameInfoArray); | ||
$levels = $outerDirnameInfo['levels']; | ||
|
||
foreach ($dirnameInfoArray as $dirnameInfo) { | ||
$levels += $dirnameInfo['levels']; | ||
|
||
foreach ($dirnameInfo['indexes'] as $index) { | ||
$tokens->clearTokenAndMergeSurroundingWhitespace($index); | ||
} | ||
} | ||
|
||
$levelsToken = new Token([T_LNUMBER, (string) $levels]); | ||
|
||
if (isset($outerDirnameInfo['secondArgument'])) { | ||
$tokens[$outerDirnameInfo['secondArgument']] = $levelsToken; | ||
} else { | ||
$tokens->insertAt($outerDirnameInfo['end'], [new Token(','), $levelsToken]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
tests/Fixer/FunctionNotation/CombineNestedDirnameFixerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of PHP CS Fixer. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* Dariusz Rumiński <dariusz.ruminski@gmail.com> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
namespace PhpCsFixer\Tests\Fixer\FunctionNotation; | ||
|
||
use PhpCsFixer\Tests\Test\AbstractFixerTestCase; | ||
|
||
/** | ||
* @author Gregor Harlan | ||
* | ||
* @internal | ||
* | ||
* @covers \PhpCsFixer\Fixer\FunctionNotation\CombineNestedDirnameFixer | ||
*/ | ||
final class CombineNestedDirnameFixerTest extends AbstractFixerTestCase | ||
{ | ||
/** | ||
* @param string $expected | ||
* @param null|string $input | ||
* | ||
* @dataProvider provideFixCases | ||
* @requires PHP 7.0 | ||
*/ | ||
public function testFix($expected, $input = null) | ||
{ | ||
$this->doTest($expected, $input); | ||
} | ||
|
||
public function provideFixCases() | ||
{ | ||
return [ | ||
[ | ||
'<?php dirname();', | ||
], | ||
[ | ||
'<?php dirname($path);', | ||
], | ||
[ | ||
'<?php dirname($path, 3);', | ||
], | ||
[ | ||
'<?php dirname($path,2);', | ||
'<?php dirname(dirname($path));', | ||
], | ||
[ | ||
'<?php dirname /* a */ ( /* b */ /* c */ $path /* d */,2);', | ||
'<?php dirname /* a */ ( /* b */ dirname( /* c */ $path) /* d */);', | ||
], | ||
[ | ||
'<?php dirname($path,3);', | ||
'<?php dirname(\dirname(dirname($path)));', | ||
], | ||
[ | ||
'<?php dirname($path ,4);', | ||
'<?php dirname(dirname($path, 3));', | ||
], | ||
[ | ||
'<?php dirname($path, 4);', | ||
'<?php dirname(dirname($path), 3);', | ||
], | ||
[ | ||
'<?php dirname($path , 5);', | ||
'<?php dirname(dirname($path, 2), 3);', | ||
], | ||
[ | ||
'<?php dirname($path ,5);', | ||
'<?php dirname(dirname(dirname($path), 3));', | ||
], | ||
[ | ||
'<?php dirname(dirname($path, $level));', | ||
], | ||
[ | ||
'<?php dirname("foo/".dirname($path));', | ||
], | ||
[ | ||
'<?php dirname(dirname($path).$foo);', | ||
], | ||
[ | ||
'<?php foo\dirname(dirname($path));', | ||
], | ||
[ | ||
'<?php dirname(foo(dirname($path,2)),2);', | ||
'<?php dirname(dirname(foo(dirname(dirname($path)))));', | ||
], | ||
[ | ||
'<?php new dirname(dirname($path,2));', | ||
'<?php new dirname(dirname(dirname($path)));', | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @requires PHP <7.0 | ||
*/ | ||
public function testDoNotFix() | ||
{ | ||
$this->doTest('<?php dirname(dirname($path));'); | ||
} | ||
} |
Oops, something went wrong.