Resolves #4. Add fixer to replace ::className() method usages with ::class keyword.

This commit is contained in:
erickskrauch 2018-08-06 11:59:44 +03:00
parent 76bd14c167
commit 59e9994662
No known key found for this signature in database
GPG Key ID: 669339FCBB30EE0E
4 changed files with 207 additions and 0 deletions

View File

@ -261,3 +261,19 @@ an anonymous class declaration in a case when said class constructor doesn't con
* `remove_for_anonymous_classes` - if its set to `true`, then braces for anonymous classes without constructor * `remove_for_anonymous_classes` - if its set to `true`, then braces for anonymous classes without constructor
arguments will be removed. **Default**: `false`. arguments will be removed. **Default**: `false`.
### `Ely/remove_class_name_method_usages` (Yii2)
Replaces Yii2 [`BaseObject::className()`](https://github.com/yiisoft/yii2/blob/e53fc0ded1/framework/base/BaseObject.php#L84)
usages with native `::class` keyword, introduced in PHP 5.5.
```diff
--- Original
+++ New
@@ @@
<?php
use common\models\User;
- $className = User::className();
+ $className = User::class;
```

View File

@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Fixer\LanguageConstruct;
use Ely\CS\Fixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
/**
* Replaces Yii2 BaseObject::className() usages with native ::class keyword, introduced in PHP 5.5.
*
* @author ErickSkrauch <erickskrauch@ely.by>
*/
final class RemoveClassNameMethodUsagesFixer extends AbstractFixer {
/**
* @inheritdoc
*/
public function getDefinition() {
return new FixerDefinition(
'Converts Yii2 `BaseObject::className()` method usage into `::class` keyword.',
[
new CodeSample(
'<?php
use Foo\Bar\Baz;
$className = Baz::className();
'
),
]
);
}
/**
* @inheritdoc
*/
public function isCandidate(Tokens $tokens) {
return $tokens->isTokenKindFound(T_STRING);
}
/**
* {@inheritdoc}
*/
public function isRisky() {
return true;
}
/**
* {@inheritdoc}
*/
protected function applyFix(SplFileInfo $file, Tokens $tokens) {
for ($index = $tokens->count() - 4; $index > 0; --$index) {
$candidate = $this->getReplaceCandidate($tokens, $index);
if ($candidate === null) {
continue;
}
$this->fixClassNameMethodUsage(
$tokens,
$index,
$candidate[0], // brace open
$candidate[1] // brace close
);
}
}
/**
* @param Tokens $tokens
* @param int $index
*
* @return null|array
*/
private function getReplaceCandidate(Tokens $tokens, $index) {
if (!$tokens[$index]->isGivenKind(T_STRING)) {
return null;
}
$braceOpenIndex = $tokens->getNextMeaningfulToken($index);
if (!$tokens[$braceOpenIndex]->equals('(')) {
return null;
}
$braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
if (!$tokens[$braceCloseIndex]->equals(')')) {
return null;
}
$doubleColon = $tokens->getPrevMeaningfulToken($index);
if (!$tokens[$doubleColon]->isGivenKind([T_DOUBLE_COLON])) {
return null;
}
$methodName = $tokens[$index]->getContent();
if ($methodName !== 'className') {
return null;
}
return [
$braceOpenIndex,
$braceCloseIndex,
];
}
/**
* @param Tokens $tokens
* @param int $index
* @param int $braceOpenIndex
* @param int $braceCloseIndex
*/
private function fixClassNameMethodUsage(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex) {
$tokens->clearTokenAndMergeSurroundingWhitespace($braceCloseIndex);
$tokens->clearTokenAndMergeSurroundingWhitespace($braceOpenIndex);
$tokens->clearAt($index);
$tokens->insertAt($index, new Token([CT::T_CLASS_CONSTANT, 'class']));
}
}

View File

@ -65,6 +65,7 @@ class Rules {
'Ely/new_with_braces' => [ 'Ely/new_with_braces' => [
'remove_for_anonymous_classes' => true, 'remove_for_anonymous_classes' => true,
], ],
'Ely/remove_class_name_method_usages' => true,
]; ];
public static function create(array $overwrittenRules = []): array { public static function create(array $overwrittenRules = []): array {

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Ely\CS\Test\Fixer\Operator;
use Ely\CS\Fixer\LanguageConstruct\RemoveClassNameMethodUsagesFixer;
use PhpCsFixer\Tests\Test\AbstractFixerTestCase;
/**
* @covers \Ely\CS\Fixer\LanguageConstruct\RemoveClassNameMethodUsagesFixer
*/
class RemoveClassNameMethodUsagesFixerTest extends AbstractFixerTestCase {
/**
* @param string $expected
* @param null|string $input
*
* @dataProvider provideFixCases
*/
public function testFix($expected, $input = null) {
$this->doTest($expected, $input);
}
public function provideFixCases() {
return [
[
'<?php echo className();',
],
[
'<?php
use Foo\Bar\Baz;
$exceptionString = Baz::classname();
',
],
[
'<?php
use Foo\Bar\Baz;
$className = Baz::class;
',
'<?php
use Foo\Bar\Baz;
$className = Baz::className();
',
],
[
'<?php
use Foo\Bar\Baz;
$exceptionString = "The class should be instance of " . Baz::class . " and nothing else";
',
'<?php
use Foo\Bar\Baz;
$exceptionString = "The class should be instance of " . Baz::className() . " and nothing else";
',
],
];
}
protected function createFixer() {
return new RemoveClassNameMethodUsagesFixer();
}
}