Skip to content

Commit

Permalink
POC
Browse files Browse the repository at this point in the history
  • Loading branch information
keradus committed Apr 19, 2017
1 parent 502f06c commit 33be691
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/Fixer/LanguageConstruct/ClassKeywordFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?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\LanguageConstruct;

use PhpCsFixer\AbstractFixer;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\Tokenizer\TokensAnalyzer;
use PhpCsFixer\Indicator\ClassyExistanceIndicator;

/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class ClassKeywordFixer extends AbstractFixer
{
/**
* @var string[]
*/
private $imports = array();

/**
* {@inheritdoc}
*/
public function getDefinition()
{
return new FixerDefinition(
'Converts FQCN strings to `*::class` keywords. Requires PHP >= 5.5.',
array(
new VersionSpecificCodeSample(
'<?php
use Foo\Bar\Baz;
$className = Baz::class;
',
new VersionSpecification(50500)
),
)
);
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens)
{
return PHP_VERSION_ID >= 50500;
}

/**
* {@inheritdoc}
*/
protected function applyFix(\SplFileInfo $file, Tokens $tokens)
{
$indicator = new ClassyExistanceIndicator();

for ($index = $tokens->count() - 1; $index >= 0; --$index) {
$token = $tokens[$index];

if ($token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) {
$name = substr($token->getContent(), 1, -1);
$name = ltrim($name, '\\');
$name = str_replace('\\\\', '\\', $name);

if ($indicator->exists($name)) {
try {
$substitution = Tokens::fromCode("<?php echo \\$name::class;");
$substitution->clearRange(0, 2);
$substitution[$substitution->getSize() - 1]->clear();
$substitution->clearEmptyTokens();

$token->clear();
$tokens->insertAt($index, $substitution);
} catch (\Error $e) {
var_dump("error with parsing class", $name);
var_dump($e->getMessage());
}
}
}
}
}
}
35 changes: 35 additions & 0 deletions src/Indicator/ClassyExistanceIndicator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?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\Indicator;

/**
* @internal
*/
final class ClassyExistanceIndicator
{
/**
* @param string $name
*
* @return bool
*/
public function exists($name)
{
if (class_exists($name) || interface_exists($name) || trait_exists($name)) {

This comment has been minimized.

Copy link
@TomasVotruba

TomasVotruba Jul 14, 2017

This won't work when php-cs-fixer is installed as composer create-project ... and run on another code base, will it?

This comment has been minimized.

Copy link
@keradus

keradus Jul 15, 2017

Author Owner

it won't out of the box, it could if in config file you will inject autoloader. that's why I wrote in your PR about violating context-unawareness rule. (same, your PR is failing in that case as well)

This comment has been minimized.

Copy link
@TomasVotruba

TomasVotruba Jul 15, 2017

Why don't you use regular expression then? That would solve it.

$rc = new \ReflectionClass($name);

return $rc->getName() === $name;

This comment has been minimized.

Copy link
@TomasVotruba

TomasVotruba Jul 14, 2017

What is this check for?

This comment has been minimized.

Copy link
@keradus

keradus Jul 15, 2017

Author Owner

if you would do only class_exists("EXCEPTION"), you will get true. with this, you will get false.

This comment has been minimized.

Copy link
@TomasVotruba

TomasVotruba Jul 15, 2017

I see, pretty neat. Thanks

}

return false;
}
}

5 comments on commit 33be691

@keradus
Copy link
Owner Author

@keradus keradus commented on 33be691 Jul 15, 2017

Choose a reason for hiding this comment

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

(answering:

)

All rules are suppose to be context unaware. That means the fixer must rely only on code of single file passed for fixing. This fixer is violating that rule (that's why it's not in main repo), as it requires to be aware about whole fixing project files and deps to detect if given class exists or not.

@TomasVotruba
Copy link

Choose a reason for hiding this comment

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

Could you explain that in specific code? I don't understand this.

@keradus
Copy link
Owner Author

Choose a reason for hiding this comment

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

let assume you have 2 files - Foo.php (with Foo class) and Bar.php (with Bar class),
then while fixing Foo.php class, you have no idea does Bar class exists or not in text-based file modifications. you even don't know that Bar class exists.
This is being context-unaware. If you would know whole project files and code - you would be context-aware.

And then keep in mind that some context is vendor. so Bar could come from vendor, that needs to be first installed from external source

@TomasVotruba
Copy link

@TomasVotruba TomasVotruba commented on 33be691 Jul 16, 2017

Choose a reason for hiding this comment

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

I see.

I'd still have context unaware fixer that can fail in 1 % of cases and fixes the rest than my manuall work on 99 % of them.

@keradus
Copy link
Owner Author

Choose a reason for hiding this comment

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

then it's good for one-time job, but not for continuous verification.

Please sign in to comment.