Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DeclaredClassCasingFilter #3969

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.rst
Expand Up @@ -431,6 +431,10 @@ Choose from the list of available rules:

*Risky rule: forcing strict types will stop non strict code from working.*

* **declared_class_casing**

Classes should be referred to using the correct casing.

* **dir_constant** [@Symfony:risky]

Replaces ``dirname(__FILE__)`` expression with equivalent ``__DIR__``
Expand Down
116 changes: 116 additions & 0 deletions src/Fixer/Casing/DeclaredClassCasingFixer.php
@@ -0,0 +1,116 @@
<?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\Casing;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

/**
* @author siad007
*/
final class DeclaredClassCasingFixer extends AbstractFixer
{
private static $declaredClassNames;
SpacePossum marked this conversation as resolved.
Show resolved Hide resolved

public function __construct()
{
parent::__construct();

if (null === self::$declaredClassNames) {
foreach (get_declared_classes() as $class) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sceptical about this line - get_declared_classes will not return classes that are declared, but those declared in the current script - depending how autoloader was called - which mean one day it will return some classes and another day more classes - adding some fixer to config might accidentally "discover" more classes.

When I was working on similar fixer I have ended with only internal classes to handle.

self::$declaredClassNames[strtolower($class)] = $class;
}
}
}

/**
* {@inheritdoc}
*/
public function getDefinition()
{
return new FixerDefinition(
'Classes should be referred to using the correct casing.',
[new CodeSample("<?php\nnew STDCLASS();\n")]
);
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens)
{
return $tokens->isTokenKindFound(T_STRING);
}

/**
* @param \SplFileInfo $file
* @param Tokens $tokens
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
foreach ($tokens as $index => $token) {
if ($name = $this->declaredClass($index, $token, $tokens)) {
$tokens[$index] = new Token([T_STRING, self::$declaredClassNames[$name]]);
}
}
}

/**
* Get the lower case name of the declared class or null.
*
* @param int $index
* @param Token $token
* @param Tokens $tokens
*
* @return null|string
*/
private function declaredClass($index, Token $token, Tokens $tokens)
{
$beforeClassName = $tokens->getPrevMeaningfulToken($index);
$lower = strtolower($token->getContent());

$isDeclaredClass = null;
SpacePossum marked this conversation as resolved.
Show resolved Hide resolved

if (
array_key_exists($lower, self::$declaredClassNames)
&&
!$tokens[$beforeClassName]->isGivenKind(
[
T_CLASS,
T_AS,
T_DOUBLE_COLON,
T_OBJECT_OPERATOR,
T_FUNCTION,
T_CONST,
T_TRAIT,
T_USE,
CT::T_USE_TRAIT,
]
)
&&
!(
$tokens[$beforeClassName]->isGivenKind(T_NS_SEPARATOR)
&&
$tokens[$tokens->getPrevMeaningfulToken($beforeClassName)]->isGivenKind([T_STRING])
)
) {
$isDeclaredClass = $lower;
}

return $isDeclaredClass;
}
}
123 changes: 123 additions & 0 deletions tests/Fixer/Casing/DeclaredClassCasingFixerTest.php
@@ -0,0 +1,123 @@
<?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\Casing;

use PhpCsFixer\Tests\Test\AbstractFixerTestCase;

/**
* @author siad007
*
* @internal
*
* @covers \PhpCsFixer\Fixer\Casing\DeclaredClassCasingFixer
*/
final class DeclaredClassCasingFixerTest 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
$stdclass = new \stdClass();
$stdclass = new stdClass();
',
'<?php
$stdclass = new \STDCLASS();
$stdclass = new STDCLASS();
',
],
[
'<?php

namespace Foo;

class exception extends \Exception
{
}
',
'<?php

namespace Foo;

class exception extends \EXCEPTION
{
}
',
],
[
'<?php

echo \Exception::class;
echo Exception::class;
',
'<?php

echo \ExCePTion::class;
echo ExCePTion::class;
',
],
[
'<?php

$a::exception();
',
],
[
'<?php

$a->exception();
',
],
[
'<?php

function exception() {};
',
],
[
'<?php

echo "This is an " . "exception";
',
],
[
'<?php

namespace Foo;

trait stdclass
{
}

use Foo\stdclass as exception;

class test
{
use exception;
}
',
],
];
}
}