Skip to content

Commit

Permalink
feature #5953 GetClassToClassKeywordFixer - introduction (paulbalandan)
Browse files Browse the repository at this point in the history
This PR was merged into the master branch.

Discussion
----------

GetClassToClassKeywordFixer - introduction

```
$ php php-cs-fixer describe get_class_to_class_keyword`

Description of get_class_to_class_keyword rule.
Replace `get_class` calls on object variables with class keyword syntax.

Fixer applying this rule is risky.
Risky if the `get_class` function is overridden.

Fixing examples:
 * Example #1.
   ---------- begin diff ----------
   --- Original
   +++ New
   @@ -1,2 +1,2 @@
    <?php
   -get_class($a);
   +$a::class;

   ----------- end diff -----------

 * Example #2.
   ---------- begin diff ----------
   --- Original
   +++ New
   @@ -1,4 +1,4 @@
    <?php

    $date = new \DateTimeImmutable();
   -$class = get_class($date);
   +$class = $date::class;

   ----------- end diff -----------
```

Commits
-------

ee541a6 GetClassToClassKeywordFixer - introduction
  • Loading branch information
SpacePossum committed Dec 14, 2021
2 parents 3363706 + ee541a6 commit 00096f0
Show file tree
Hide file tree
Showing 14 changed files with 447 additions and 3 deletions.
9 changes: 9 additions & 0 deletions doc/list.rst
Expand Up @@ -799,6 +799,15 @@ List of Available Rules
Part of rule sets `@PhpCsFixer <./ruleSets/PhpCsFixer.rst>`_ `@Symfony <./ruleSets/Symfony.rst>`_

`Source PhpCsFixer\\Fixer\\Phpdoc\\GeneralPhpdocTagRenameFixer <./../src/Fixer/Phpdoc/GeneralPhpdocTagRenameFixer.php>`_
- `get_class_to_class_keyword <./rules/language_construct/get_class_to_class_keyword.rst>`_

Replace ``get_class`` calls on object variables with class keyword syntax.

*warning risky* Risky if the ``get_class`` function is overridden.

Part of rule set `@PHP80Migration:risky <./ruleSets/PHP80MigrationRisky.rst>`_

`Source PhpCsFixer\\Fixer\\LanguageConstruct\\GetClassToClassKeywordFixer <./../src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php>`_
- `global_namespace_import <./rules/import/global_namespace_import.rst>`_

Imports or fully qualifies global classes/functions/constants.
Expand Down
1 change: 1 addition & 0 deletions doc/ruleSets/PHP80MigrationRisky.rst
Expand Up @@ -8,6 +8,7 @@ Rules
-----

- `@PHP74Migration:risky <./PHP74MigrationRisky.rst>`_
- `get_class_to_class_keyword <./../rules/language_construct/get_class_to_class_keyword.rst>`_
- `modernize_strpos <./../rules/alias/modernize_strpos.rst>`_
- `no_alias_functions <./../rules/alias/no_alias_functions.rst>`_
config:
Expand Down
3 changes: 3 additions & 0 deletions doc/rules/index.rst
Expand Up @@ -429,6 +429,9 @@ Language Construct
- `function_to_constant <./language_construct/function_to_constant.rst>`_ *(risky)*

Replace core functions calls returning constants with the constants.
- `get_class_to_class_keyword <./language_construct/get_class_to_class_keyword.rst>`_ *(risky)*

Replace ``get_class`` calls on object variables with class keyword syntax.
- `is_null <./language_construct/is_null.rst>`_ *(risky)*

Replaces ``is_null($var)`` expression with ``null === $var``.
Expand Down
44 changes: 44 additions & 0 deletions doc/rules/language_construct/get_class_to_class_keyword.rst
@@ -0,0 +1,44 @@
===================================
Rule ``get_class_to_class_keyword``
===================================

Replace ``get_class`` calls on object variables with class keyword syntax.

.. warning:: Using this rule is risky.

Risky if the ``get_class`` function is overridden.

Examples
--------

Example #1
~~~~~~~~~~

.. code-block:: diff
--- Original
+++ New
<?php
-get_class($a);
+$a::class;
Example #2
~~~~~~~~~~

.. code-block:: diff
--- Original
+++ New
<?php
$date = new \DateTimeImmutable();
-$class = get_class($date);
+$class = $date::class;
Rule sets
---------

The rule is part of the following rule set:

@PHP80Migration:risky
Using the `@PHP80Migration:risky <./../../ruleSets/PHP80MigrationRisky.rst>`_ rule set will enable the ``get_class_to_class_keyword`` rule.
Expand Up @@ -43,7 +43,7 @@ public function getDefinition(): FixerDefinitionInterface
/**
* {@inheritdoc}
*
* Must run before FunctionToConstantFixer.
* Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer.
* Must run after PowToExponentiationFixer.
*/
public function getPriority(): int
Expand Down
167 changes: 167 additions & 0 deletions src/Fixer/LanguageConstruct/GetClassToClassKeywordFixer.php
@@ -0,0 +1,167 @@
<?php

declare(strict_types=1);

/*
* 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\LanguageConstruct;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
* @author John Paul E. Balandan, CPA <paulbalandan@gmail.com>
*/
final class GetClassToClassKeywordFixer extends AbstractFixer
{
/**
* {@inheritdoc}
*/
public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Replace `get_class` calls on object variables with class keyword syntax.',
[
new VersionSpecificCodeSample(
"<?php\nget_class(\$a);\n",
new VersionSpecification(80000)
),
new VersionSpecificCodeSample(
"<?php\n\n\$date = new \\DateTimeImmutable();\n\$class = get_class(\$date);\n",
new VersionSpecification(80000)
),
],
null,
'Risky if the `get_class` function is overridden.'
);
}

/**
* {@inheritdoc}
*
* Must run before MultilineWhitespaceBeforeSemicolonsFixer.
* Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer.
*/
public function getPriority(): int
{
return 1;
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return \PHP_VERSION_ID >= 80000 && $tokens->isAllTokenKindsFound([T_STRING, T_VARIABLE]);
}

/**
* {@inheritdoc}
*/
public function isRisky(): bool
{
return true;
}

/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
{
$functionsAnalyzer = new FunctionsAnalyzer();
$indicesToClear = [];
$tokenSlices = [];

for ($index = $tokens->count() - 1; $index > 0; --$index) {
if (!$tokens[$index]->equals([T_STRING, 'get_class'], false)) {
continue;
}

if (!$functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
continue;
}

$braceOpenIndex = $tokens->getNextMeaningfulToken($index);
$braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);

if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) {
continue; // get_class with no arguments
}

$meaningfulTokensCount = 0;
$variableTokensIndices = [];

for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) {
if (!$tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], '(', ')'])) {
++$meaningfulTokensCount;
}

if (!$tokens[$i]->isGivenKind(T_VARIABLE)) {
continue;
}

if ('$this' === strtolower($tokens[$i]->getContent())) {
continue 2; // get_class($this)
}

$variableTokensIndices[] = $i;
}

if ($meaningfulTokensCount > 1 || 1 !== \count($variableTokensIndices)) {
continue; // argument contains more logic, or more arguments, or no variable argument
}

$indicesToClear[$index] = [$braceOpenIndex, current($variableTokensIndices), $braceCloseIndex];
}

foreach ($indicesToClear as $index => $items) {
$tokenSlices[$index] = $this->getReplacementTokenSlices($tokens, $items[1]);
$this->clearGetClassCall($tokens, $index, $items[0], $items[2]);
}

$tokens->insertSlices($tokenSlices);
}

private function getReplacementTokenSlices(Tokens $tokens, int $variableIndex): array
{
return [
new Token([T_VARIABLE, $tokens[$variableIndex]->getContent()]),
new Token([T_DOUBLE_COLON, '::']),
new Token([CT::T_CLASS_CONSTANT, 'class']),
];
}

private function clearGetClassCall(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex): void
{
for ($i = $braceOpenIndex; $i <= $braceCloseIndex; ++$i) {
if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
continue;
}

$tokens->clearTokenAndMergeSurroundingWhitespace($i);
}

$prevIndex = $tokens->getPrevMeaningfulToken($index);

if ($tokens[$prevIndex]->isGivenKind(T_NS_SEPARATOR)) {
$tokens->clearAt($prevIndex);
}

$tokens->clearAt($index);
}
}
Expand Up @@ -76,7 +76,7 @@ function foo () {
* {@inheritdoc}
*
* Must run before SpaceAfterSemicolonFixer.
* Must run after CombineConsecutiveIssetsFixer, NoEmptyStatementFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer.
* Must run after CombineConsecutiveIssetsFixer, GetClassToClassKeywordFixer, NoEmptyStatementFixer, SimplifiedIfReturnFixer, SingleImportPerStatementFixer.
*/
public function getPriority(): int
{
Expand Down
2 changes: 1 addition & 1 deletion src/Fixer/Whitespace/NoSpacesInsideParenthesisFixer.php
Expand Up @@ -50,7 +50,7 @@ function foo( \$bar, \$baz )
/**
* {@inheritdoc}
*
* Must run before FunctionToConstantFixer, StringLengthToEmptyFixer.
* Must run before FunctionToConstantFixer, GetClassToClassKeywordFixer, StringLengthToEmptyFixer.
* Must run after CombineConsecutiveIssetsFixer, CombineNestedDirnameFixer, LambdaNotUsedImportFixer, ModernizeStrposFixer, NoUselessSprintfFixer, PowToExponentiationFixer.
*/
public function getPriority(): int
Expand Down
1 change: 1 addition & 0 deletions src/RuleSet/Sets/PHP80MigrationRiskySet.php
Expand Up @@ -25,6 +25,7 @@ public function getRules(): array
{
return [
'@PHP74Migration:risky' => true,
'get_class_to_class_keyword' => true,
'modernize_strpos' => true,
'no_alias_functions' => [
'sets' => [
Expand Down
3 changes: 3 additions & 0 deletions tests/AutoReview/FixerFactoryTest.php
Expand Up @@ -128,6 +128,7 @@ public function provideFixersPriorityCases(): array
[$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_separation']],
[$fixers['general_phpdoc_annotation_remove'], $fixers['phpdoc_trim']],
[$fixers['general_phpdoc_tag_rename'], $fixers['phpdoc_add_missing_param_annotation']],
[$fixers['get_class_to_class_keyword'], $fixers['multiline_whitespace_before_semicolons']],
[$fixers['global_namespace_import'], $fixers['no_unused_imports']],
[$fixers['global_namespace_import'], $fixers['ordered_imports']],
[$fixers['header_comment'], $fixers['single_line_comment_style']],
Expand Down Expand Up @@ -188,7 +189,9 @@ public function provideFixersPriorityCases(): array
[$fixers['no_php4_constructor'], $fixers['ordered_class_elements']],
[$fixers['no_short_bool_cast'], $fixers['cast_spaces']],
[$fixers['no_spaces_after_function_name'], $fixers['function_to_constant']],
[$fixers['no_spaces_after_function_name'], $fixers['get_class_to_class_keyword']],
[$fixers['no_spaces_inside_parenthesis'], $fixers['function_to_constant']],
[$fixers['no_spaces_inside_parenthesis'], $fixers['get_class_to_class_keyword']],
[$fixers['no_spaces_inside_parenthesis'], $fixers['string_length_to_empty']],
[$fixers['no_superfluous_elseif'], $fixers['simplified_if_return']],
[$fixers['no_superfluous_phpdoc_tags'], $fixers['no_empty_phpdoc']],
Expand Down

0 comments on commit 00096f0

Please sign in to comment.