-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
292 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
<?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\Preload; | ||
|
||
use PhpCsFixer\AbstractFixer; | ||
use PhpCsFixer\FixerDefinition\FixerDefinition; | ||
use PhpCsFixer\Tokenizer\CT; | ||
use PhpCsFixer\Tokenizer\Token; | ||
use PhpCsFixer\Tokenizer\Tokens; | ||
|
||
/** | ||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> | ||
*/ | ||
class ExplicitlyLoadClass extends AbstractFixer | ||
{ | ||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getDefinition() | ||
{ | ||
return new FixerDefinition( | ||
'Adds extra `class_exists` to help PHP 7.4 preloading.', | ||
[ | ||
] | ||
); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isCandidate(Tokens $tokens) | ||
{ | ||
return $tokens->isTokenKindFound(T_CLASS) || $tokens->isTokenKindFound(T_TRAIT); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function applyFix(\SplFileInfo $file, Tokens $tokens) | ||
{ | ||
$candidates = $this->parse($tokens, '__construct'); | ||
$classesNotToLoad = $this->getPreloadedClasses($file, $tokens); | ||
|
||
$classesToLoad = array_diff($candidates, $classesNotToLoad); | ||
$this->injectClasses($tokens, $classesToLoad); | ||
} | ||
|
||
/** | ||
* @param string $functionName | ||
* | ||
* @return string[] classes | ||
*/ | ||
private function parse(Tokens $tokens, $functionName) | ||
{ | ||
$classes = []; | ||
$index = $this->findFunction($tokens, $functionName); | ||
// if not public, get the types. | ||
// TODO there may be other keyword but public/private/protected, eg "static" | ||
if ('public' !== $tokens[$tokens->getPrevMeaningfulToken($index)]->getContent()) { | ||
// Get argument types | ||
$startedParsingArguments = false; | ||
for ($i = $index; $i < \count($tokens); ++$i) { | ||
$token = $tokens[$i]; | ||
// Look for when the arguments begin | ||
if ('(' === $token->getContent()) { | ||
$startedParsingArguments = true; | ||
|
||
continue; | ||
} | ||
|
||
// If we have not reached the arguments yet | ||
if (!$startedParsingArguments) { | ||
continue; | ||
} | ||
|
||
// Are all arguments parsed? | ||
if (')' === $token->getContent()) { | ||
break; | ||
} | ||
|
||
if ($token->isGivenKind(T_STRING) && !$token->isKeyword() && !\in_array($token->getContent(), ['string', 'bool', 'array', 'float', 'int'], true)) { | ||
$classes[] = $token->getContent(); | ||
} | ||
} | ||
} | ||
|
||
// TODO parse body. | ||
|
||
return $classes; | ||
} | ||
|
||
/** | ||
* Get classes that are found by the preloader. Ie classes we shouldn't include in `class_exists`. | ||
* | ||
* @return string[] | ||
*/ | ||
private function getPreloadedClasses(\SplFileInfo $file, Tokens $tokens) | ||
{ | ||
$classes = []; | ||
|
||
foreach ($tokens as $index => $token) { | ||
if (!$token->isGivenKind(T_STRING)) { | ||
continue; | ||
} | ||
|
||
// TODO rework so it is way better | ||
if ('class_exists' === $token->getContent()) { | ||
$nextToken = $tokens[$index + 2]; | ||
$classes[] = $nextToken->getContent(); | ||
} | ||
} | ||
|
||
return $classes; | ||
} | ||
|
||
/** | ||
* Find a function in the tokens. | ||
* | ||
* @param string $name | ||
* | ||
* @return null|int the index or null. The index is to the "function" token. | ||
*/ | ||
private function findFunction(Tokens $tokens, $name) | ||
{ | ||
foreach ($tokens as $index => $token) { | ||
if (!$token->isGivenKind(T_FUNCTION)) { | ||
continue; | ||
} | ||
|
||
$nextTokenIndex = $tokens->getNextMeaningfulToken($index); | ||
$nextToken = $tokens[$nextTokenIndex]; | ||
|
||
if ($nextToken->getContent() !== $name) { | ||
continue; | ||
} | ||
|
||
return $index; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private function injectClasses(Tokens $tokens, array $classes) | ||
{ | ||
$insertAfter = null; | ||
foreach ($tokens as $index => $token) { | ||
if (!$token->isGivenKind(T_CLASS)) { | ||
continue; | ||
} | ||
|
||
$insertAfter = $tokens->getPrevMeaningfulToken($index); | ||
|
||
break; | ||
} | ||
|
||
if (null === $insertAfter) { | ||
return; | ||
} | ||
|
||
$newTokens = []; | ||
foreach ($classes as $class) { | ||
//$newTokens[] = new Token([T_STRING, 'class_exists('.$class.'::class);'."\n"]); | ||
$newTokens[] = new Token([T_STRING, 'class_exists']); | ||
$newTokens[] = new Token('('); | ||
$newTokens[] = new Token([T_STRING, $class]); | ||
$newTokens[] = new Token([T_DOUBLE_COLON, '::']); | ||
$newTokens[] = new Token([CT::T_CLASS_CONSTANT, 'class']); | ||
$newTokens[] = new Token(')'); | ||
$newTokens[] = new Token(';'); | ||
$newTokens[] = new Token([T_WHITESPACE, "\n"]); | ||
} | ||
|
||
$tokens->insertAt($insertAfter + 2, $newTokens); | ||
} | ||
} |
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,59 @@ | ||
<?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\Preload; | ||
|
||
use PhpCsFixer\Tests\Test\AbstractFixerTestCase; | ||
use Symfony\Component\Finder\Finder; | ||
use Symfony\Component\Finder\SplFileInfo; | ||
|
||
/** | ||
* @author Tobias Nyholm <tobias.nyholm@gmail.com> | ||
* | ||
* @internal | ||
* | ||
* @covers \PhpCsFixer\Fixer\Preload\ExplicitlyLoadClass | ||
*/ | ||
final class ExplicitlyLoadClassTest extends AbstractFixerTestCase | ||
{ | ||
/** | ||
* @param string $expected | ||
* @param null|string $input | ||
* | ||
* @dataProvider provideFixCases | ||
*/ | ||
public function testFix($expected, $input = null) | ||
{ | ||
$this->doTest($expected, $input); | ||
} | ||
|
||
public function provideFixCases() | ||
{ | ||
$testDir = \dirname(__DIR__, 2).'/Fixtures/Preload'; | ||
$finder = new Finder(); | ||
$finder->in($testDir)->name('*_Out.php'); | ||
|
||
/** @var SplFileInfo $file */ | ||
foreach ($finder as $file) { | ||
$path = $file->getRealPath(); | ||
$output = file_get_contents($path); | ||
|
||
$input = null; | ||
$inputFile = substr($path, 0, -7).'In.php'; | ||
if (is_file($inputFile)) { | ||
$input = file_get_contents($inputFile); | ||
} | ||
|
||
yield $file->getFilename() => [$output, $input]; | ||
} | ||
} | ||
} |
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,17 @@ | ||
<?php | ||
|
||
use App\Foo; | ||
|
||
/** | ||
* Private constructor will run class_exists on arguments. | ||
*/ | ||
class Case001 | ||
{ | ||
private $foo; | ||
private $bar; | ||
private function __construct(Foo $foo, string $bar) | ||
{ | ||
$this->foo = $foo; | ||
$this->bar = $bar; | ||
} | ||
} |
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,18 @@ | ||
<?php | ||
|
||
use App\Foo; | ||
|
||
class_exists(Foo::class); | ||
/** | ||
* Private constructor will run class_exists on arguments. | ||
*/ | ||
class Case001 | ||
{ | ||
private $foo; | ||
private $bar; | ||
private function __construct(Foo $foo, string $bar) | ||
{ | ||
$this->foo = $foo; | ||
$this->bar = $bar; | ||
} | ||
} |
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,13 @@ | ||
<?php | ||
|
||
/** | ||
* Test public constructor will not generate extra class_exists | ||
*/ | ||
class Case002 | ||
{ | ||
private $foo; | ||
public function __construct(Foo $foo) | ||
{ | ||
$this->foo = $foo; | ||
} | ||
} |