Skip to content

Commit

Permalink
Add MultilinePromotedPropertiesFixer (#617)
Browse files Browse the repository at this point in the history
  • Loading branch information
kubawerlos committed Oct 6, 2021
1 parent 434f8ab commit 479c935
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,6 +1,7 @@
# CHANGELOG for PHP CS Fixer: custom fixers

## v3.1.0
- Add MultilinePromotedPropertiesFixer
- Add PhpdocArrayStyleFixer
- Add PromotedConstructorPropertyFixer
- Restore PhpCsFixerCustomFixers\Analyzer\SwitchAnalyzer (as PhpCsFixer\Tokenizer\Analyzer\SwitchAnalyzer got removed in PHP CS Fixer 3.2.0)
Expand Down
15 changes: 14 additions & 1 deletion README.md
Expand Up @@ -3,7 +3,7 @@
[![Latest stable version](https://img.shields.io/packagist/v/kubawerlos/php-cs-fixer-custom-fixers.svg?label=current%20version)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)
[![PHP version](https://img.shields.io/packagist/php-v/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://php.net)
[![License](https://img.shields.io/github/license/kubawerlos/php-cs-fixer-custom-fixers.svg)](LICENSE)
![Tests](https://img.shields.io/badge/tests-2825-brightgreen.svg)
![Tests](https://img.shields.io/badge/tests-2855-brightgreen.svg)
[![Downloads](https://img.shields.io/packagist/dt/kubawerlos/php-cs-fixer-custom-fixers.svg)](https://packagist.org/packages/kubawerlos/php-cs-fixer-custom-fixers)

[![CI Status](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/kubawerlos/php-cs-fixer-custom-fixers/actions)
Expand Down Expand Up @@ -122,6 +122,19 @@ Multiline comments or PHPDocs must contain an opening and closing line with no a
*/;
```

#### MultilinePromotedPropertiesFixer
Constructor having promoted properties must have them in separate lines.
```diff
<?php class Foo {
- public function __construct(private array $a, private bool $b, private int $i) {}
+ public function __construct(
+ private array $a,
+ private bool $b,
+ private int $i
+ ) {}
}
```

#### NoCommentedOutCodeFixer
There can be no commented out code.
```diff
Expand Down
2 changes: 1 addition & 1 deletion dev-tools/infection.json
Expand Up @@ -5,7 +5,7 @@
"../src"
]
},
"timeout": 10,
"timeout": 5,
"logs": {
"text": "php://stdout",
"github": true,
Expand Down
1 change: 1 addition & 0 deletions dev-tools/src/Fixer/OrderedClassElementsInternalFixer.php
Expand Up @@ -27,6 +27,7 @@ final class OrderedClassElementsInternalFixer implements FixerInterface
'getDefinition',
'getConfigurationDefinition',
'configure',
'setWhitespacesConfig',
'name',
'getName',
'getPriority',
Expand Down
2 changes: 1 addition & 1 deletion dev-tools/src/InfectionConfigBuilder.php
Expand Up @@ -50,7 +50,7 @@ public function build(): array
$config = [
'$schema' => './vendor/infection/infection/resources/schema.json',
'source' => ['directories' => ['../src']],
'timeout' => 10,
'timeout' => 5,
'logs' => [
'text' => 'php://stdout',
'github' => true,
Expand Down
6 changes: 6 additions & 0 deletions dev-tools/src/Readme/ReadmeCommand.php
Expand Up @@ -16,9 +16,11 @@
use PhpCsFixer\Console\Command\HelpCommand;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\StdinFileInfo;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\WhitespacesFixerConfig;
use PhpCsFixerCustomFixers\Fixer\AbstractFixer;
use PhpCsFixerCustomFixers\Fixer\DeprecatingFixerInterface;
use PhpCsFixerCustomFixers\Fixers;
Expand Down Expand Up @@ -168,6 +170,10 @@ private function fixers(): string

/** @var AbstractFixer $fixer */
foreach (new Fixers() as $fixer) {
if ($fixer instanceof WhitespacesAwareFixerInterface) {
$fixer->setWhitespacesConfig(new WhitespacesFixerConfig());
}

$reflectionClass = new \ReflectionClass($fixer);

$output .= \sprintf(
Expand Down
5 changes: 5 additions & 0 deletions src/Analyzer/Analysis/ConstructorAnalysis.php
Expand Up @@ -33,6 +33,11 @@ public function __construct(Tokens $tokens, int $constructorIndex)
$this->constructorIndex = $constructorIndex;
}

public function getConstructorIndex(): int
{
return $this->constructorIndex;
}

/**
* @return array<int, string>
*/
Expand Down
153 changes: 153 additions & 0 deletions src/Fixer/MultilinePromotedPropertiesFixer.php
@@ -0,0 +1,153 @@
<?php

/*
* This file is part of PHP CS Fixer: custom fixers.
*
* (c) 2018 Kuba Werłos
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

declare(strict_types=1);

namespace PhpCsFixerCustomFixers\Fixer;

use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\FixerDefinition\VersionSpecification;
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
use PhpCsFixer\Tokenizer\Analyzer\WhitespacesAnalyzer;
use PhpCsFixer\Tokenizer\CT;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\WhitespacesFixerConfig;
use PhpCsFixerCustomFixers\Analyzer\ConstructorAnalyzer;

final class MultilinePromotedPropertiesFixer extends AbstractFixer implements WhitespacesAwareFixerInterface
{
/** @var WhitespacesFixerConfig */
private $whitespacesConfig;

public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(
'Constructor having promoted properties must have them in separate lines.',
[
new VersionSpecificCodeSample(
'<?php class Foo {
public function __construct(private array $a, private bool $b, private int $i) {}
}
',
new VersionSpecification(80000)
),
]
);
}

public function setWhitespacesConfig(WhitespacesFixerConfig $config): void
{
$this->whitespacesConfig = $config;
}

/**
* Must run after PromotedConstructorPropertyFixer.
*/
public function getPriority(): int
{
return 0;
}

public function isCandidate(Tokens $tokens): bool
{
return $tokens->isAnyTokenKindsFound([
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
]);
}

public function isRisky(): bool
{
return false;
}

public function fix(\SplFileInfo $file, Tokens $tokens): void
{
$constructorAnalyzer = new ConstructorAnalyzer();

for ($index = $tokens->count() - 1; $index > 0; $index--) {
if (!$tokens[$index]->isGivenKind(\T_CLASS)) {
continue;
}

$constructorAnalysis = $constructorAnalyzer->findNonAbstractConstructor($tokens, $index);
if ($constructorAnalysis === null) {
continue;
}

/** @var int $openParenthesis */
$openParenthesis = $tokens->getNextTokenOfKind($constructorAnalysis->getConstructorIndex(), ['(']);
$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);

if (!$this->shouldBeFixed($tokens, $openParenthesis, $closeParenthesis)) {
continue;
}

$this->fixParameters($tokens, $openParenthesis, $closeParenthesis);
}
}

private function shouldBeFixed(Tokens $tokens, int $openParenthesis, int $closeParenthesis): bool
{
for ($index = $openParenthesis + 1; $index < $closeParenthesis; $index++) {
if (
$tokens[$index]->isGivenKind([
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
])
) {
return true;
}
}

return false;
}

private function fixParameters(Tokens $tokens, int $openParenthesis, int $closeParenthesis): void
{
$indent = WhitespacesAnalyzer::detectIndent($tokens, $openParenthesis);

$tokens->ensureWhitespaceAtIndex(
$closeParenthesis - 1,
1,
$this->whitespacesConfig->getLineEnding() . $indent
);

/** @var int $index */
$index = $tokens->getPrevMeaningfulToken($closeParenthesis);

while ($index > $openParenthesis) {
/** @var int $index */
$index = $tokens->getPrevMeaningfulToken($index);

/** @var null|array{isStart: bool, type: int} $blockType */
$blockType = Tokens::detectBlockType($tokens[$index]);
if ($blockType !== null && !$blockType['isStart']) {
$index = $tokens->findBlockStart($blockType['type'], $index);
continue;
}

if (!$tokens[$index]->equalsAny(['(', ','])) {
continue;
}

$tokens->ensureWhitespaceAtIndex(
$index + 1,
0,
$this->whitespacesConfig->getLineEnding() . $indent . $this->whitespacesConfig->getIndent()
);
}
}
}
2 changes: 1 addition & 1 deletion src/Fixer/PromotedConstructorPropertyFixer.php
Expand Up @@ -50,7 +50,7 @@ public function __construct(string $bar) {
}

/**
* Must run before ClassAttributesSeparationFixer.
* Must run before ClassAttributesSeparationFixer, MultilinePromotedPropertiesFixer.
*/
public function getPriority(): int
{
Expand Down
1 change: 1 addition & 0 deletions tests/Analyzer/Analysis/ConstructorAnalysisTest.php
Expand Up @@ -34,6 +34,7 @@ public function testGettingConstructorPromotableParameters(array $expected, stri
$tokens = Tokens::fromCode($code);
$analysis = new ConstructorAnalysis($tokens, 11);

self::assertSame(11, $analysis->getConstructorIndex());
self::assertSame($expected, $analysis->getConstructorPromotableParameters());
}

Expand Down
5 changes: 1 addition & 4 deletions tests/Analyzer/ConstructorAnalyzerTest.php
Expand Up @@ -53,10 +53,7 @@ public function testFindingNonAbstractConstructor(array $expected, string $code)
self::assertNull($constructorAnalysis);
} else {
self::assertInstanceOf(ConstructorAnalysis::class, $constructorAnalysis);
self::assertSame(
\serialize(new ConstructorAnalysis($tokens, $nonAbstractConstructorIndex)),
\serialize($constructorAnalysis)
);
self::assertSame($nonAbstractConstructorIndex, $constructorAnalysis->getConstructorIndex());
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions tests/Fixer/AbstractFixerTestCase.php
Expand Up @@ -16,8 +16,10 @@
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\Fixer\DeprecatedFixerInterface;
use PhpCsFixer\Fixer\FixerInterface;
use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
use PhpCsFixer\Linter\Linter;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\WhitespacesFixerConfig;
use PHPUnit\Framework\TestCase;
use Tests\AssertRegExpTrait;
use Tests\AssertSameTokensTrait;
Expand All @@ -43,6 +45,9 @@ final protected function setUp(): void
$fixer = new $className();

$this->fixer = $fixer;
if ($this->fixer instanceof WhitespacesAwareFixerInterface) {
$this->fixer->setWhitespacesConfig(new WhitespacesFixerConfig());
}
}

final public function testFixerDefinitionSummaryStartWithCorrectCase(): void
Expand Down

0 comments on commit 479c935

Please sign in to comment.