Skip to content

Commit

Permalink
feat: add custom php cs fixers
Browse files Browse the repository at this point in the history
  • Loading branch information
k0d3r1s committed Aug 20, 2024
0 parents commit 0fa206e
Show file tree
Hide file tree
Showing 18 changed files with 3,198 additions and 0 deletions.
107 changes: 107 additions & 0 deletions Fixer/DeclareAfterOpeningTagFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php declare(strict_types = 1);

/*
* This file is part of the Vairogs package.
*
* (c) Dāvis Zālītis (k0d3r1s) <davis@vairogs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Vairogs\PhpCsFixerCustomFixers\Fixer;

use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
use Vairogs\Component\Functions\Preg\_Replace;
use Vairogs\PhpCsFixerCustomFixers\PhpCsFixer\AbstractFixer;

use function assert;
use function is_int;
use function stripos;
use function substr;

use const T_DECLARE;
use const T_OPEN_TAG;
use const T_WHITESPACE;

/**
* @internal
*/
final class DeclareAfterOpeningTagFixer extends AbstractFixer
{
public function applyFix(
SplFileInfo $file,
Tokens $tokens,
): void {
if (!$tokens[0]->isGivenKind(T_OPEN_TAG)) {
return;
}

$openingTagTokenContent = $tokens[0]->getContent();

$declareIndex = $tokens->getNextTokenOfKind(0, [[T_DECLARE]]);
assert(is_int($declareIndex));

$openParenthesisIndex = $tokens->getNextMeaningfulToken($declareIndex);
assert(is_int($openParenthesisIndex));

$closeParenthesisIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesisIndex);

if (false === stripos($tokens->generatePartialCode($openParenthesisIndex, $closeParenthesisIndex), 'strict_types')) {
return;
}

$tokens[0] = new Token([T_OPEN_TAG, substr($openingTagTokenContent, 0, 5) . ' ']);

if ($declareIndex <= 2) {
$tokens->clearRange(1, $declareIndex - 1);

return;
}

$semicolonIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex);
assert(is_int($semicolonIndex));

$tokensToInsert = [];
for ($index = $declareIndex; $index <= $semicolonIndex; $index++) {
$tokensToInsert[] = $tokens[$index];
}

if ($tokens[1]->isGivenKind(T_WHITESPACE)) {
$tokens[1] = new Token([T_WHITESPACE, substr($openingTagTokenContent, 5) . $tokens[1]->getContent()]);
} else {
$tokensToInsert[] = new Token([T_WHITESPACE, substr($openingTagTokenContent, 5)]);
}

if ($tokens[$semicolonIndex + 1]->isGivenKind(T_WHITESPACE)) {
$content = (new class {
use _Replace;
})::replace('/^(\\R?)(?=\\R)/', '', $tokens[$semicolonIndex + 1]->getContent());

$tokens->ensureWhitespaceAtIndex($semicolonIndex + 1, 0, $content);
}

$tokens->clearRange($declareIndex + 1, $semicolonIndex);
self::removeWithLinesIfPossible($tokens, $declareIndex);

$tokens->insertAt(1, $tokensToInsert);
}

public function getDocumentation(): string
{
return 'Declare statement for strict types must be placed on the same line, after the opening tag.';
}

public function getSampleCode(): string
{
return "<?php\n\$foo;\ndeclare(strict_types=1);\n\$bar;\n";
}

public function isCandidate(
Tokens $tokens,
): bool {
return $tokens->isTokenKindFound(T_DECLARE);
}
}
118 changes: 118 additions & 0 deletions Fixer/DoctrineMigrationsFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php declare(strict_types = 1);

/*
* This file is part of the Vairogs package.
*
* (c) Dāvis Zālītis (k0d3r1s) <davis@vairogs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Vairogs\PhpCsFixerCustomFixers\Fixer;

use Doctrine\Migrations\AbstractMigration;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
use Vairogs\PhpCsFixerCustomFixers\PhpCsFixer\AbstractFixer;

use function class_exists;
use function explode;
use function implode;
use function in_array;
use function trim;

use const T_COMMENT;

/**
* @internal
*/
final class DoctrineMigrationsFixer extends AbstractFixer
{
public function getDocumentation(): string
{
return 'Unnecessary comments MUST BE removed from Doctrine migrations';
}

public function getSampleCode(): string
{
return <<<'SPEC'
<?php declare(strict_types=1);
namespace Doctrine\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class VersionTest extends AbstractMigration
{
public function getDescription()
{
return '';
}
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
}
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
}
}
SPEC;
}

public function isCandidate(
Tokens $tokens,
): bool {
return class_exists(AbstractMigration::class) && $this->extendsClass($tokens, AbstractMigration::class);
}

protected function applyFix(
SplFileInfo $file,
Tokens $tokens,
): void {
$this->removeUselessComments($tokens);
}

private function removeUselessComments(
Tokens $tokens,
): void {
$blacklist = [
'Auto-generated Migration: Please modify to your needs!',
'this up() migration is auto-generated, please modify it to your needs',
'this down() migration is auto-generated, please modify it to your needs',
];

foreach ($this->getComments($tokens) as $position => $comment) {
$lines = explode("\n", $comment->getContent());
$changed = false;

foreach ($lines as $index => $line) {
if (in_array(trim($line, '/* '), $blacklist, true)) {
unset($lines[$index]);
$changed = true;
}
}

if (false === $changed) {
continue;
}

if (empty(trim(implode("\n", $lines), " /*\n"))) {
$tokens->clearAt($position);
$tokens->removeTrailingWhitespace($position);

continue;
}

$tokens[$position] = new Token([T_COMMENT, implode("\n", $lines)]);
}
}
}
103 changes: 103 additions & 0 deletions Fixer/IssetToArrayKeyExistsFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types = 1);

/*
* This file is part of the Vairogs package.
*
* (c) Dāvis Zālītis (k0d3r1s) <davis@vairogs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Vairogs\PhpCsFixerCustomFixers\Fixer;

use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
use Vairogs\PhpCsFixerCustomFixers\PhpCsFixer\AbstractFixer;

use function assert;
use function count;
use function is_int;

use const T_ISSET;
use const T_STRING;
use const T_WHITESPACE;

/**
* @internal
*/
final class IssetToArrayKeyExistsFixer extends AbstractFixer
{
public function applyFix(
SplFileInfo $file,
Tokens $tokens,
): void {
for ($index = $tokens->count() - 1; $index > 0; $index--) {
if (!$tokens[$index]->isGivenKind(T_ISSET)) {
continue;
}

if (1 !== count((new FunctionsAnalyzer())->getFunctionArguments($tokens, $index))) {
continue;
}

$openParenthesis = $tokens->getNextMeaningfulToken($index);
assert(is_int($openParenthesis));

$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);

$closeBrackets = $tokens->getPrevMeaningfulToken($closeParenthesis);
assert(is_int($closeBrackets));
if (!$tokens[$closeBrackets]->equals(']')) {
continue;
}

$openBrackets = $tokens->findBlockStart(Tokens::BLOCK_TYPE_ARRAY_SQUARE_BRACE, $closeBrackets);

$keyStartIndex = $tokens->getNextMeaningfulToken($openBrackets);
assert(is_int($keyStartIndex));
$keyEndIndex = $tokens->getPrevMeaningfulToken($closeBrackets);

$keyTokens = [];
for ($i = $keyStartIndex; $i <= $keyEndIndex; $i++) {
if ($tokens[$i]->equals('')) {
continue;
}
$keyTokens[] = $tokens[$i];
}
$keyTokens[] = new Token(',');
$keyTokens[] = new Token([T_WHITESPACE, ' ']);

$tokens->clearRange($openBrackets, $closeBrackets);
$tokens->insertAt($openParenthesis + 1, $keyTokens);
$tokens[$index] = new Token([T_STRING, 'array_key_exists']);
}
}

public function getDocumentation(): string
{
return 'Function `array_key_exists` must be used instead of `isset` when possible.';
}

public function getSampleCode(): string
{
return '<?php
if (isset($array[$key])) {
echo $array[$key];
}
';
}

public function isCandidate(
Tokens $tokens,
): bool {
return $tokens->isTokenKindFound(T_ISSET);
}

public function isRisky(): bool
{
return true;
}
}
Loading

0 comments on commit 0fa206e

Please sign in to comment.