Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions config/set/standalone-line.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePlainConstructorParamFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePromotedPropertyFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineRequiredParamFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return static function (ECSConfig $ecsConfig): void {
$ecsConfig->rules([
StandaloneLineSymfonyAttributeParamFixer::class,
StandaloneLinePromotedPropertyFixer::class,
StandaloneLinePlainConstructorParamFixer::class,
StandaloneLineRequiredParamFixer::class,
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

declare(strict_types=1);

namespace Symplify\CodingStandard\Fixer\Spacing;

use Override;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use SplFileInfo;
use Symplify\CodingStandard\Fixer\AbstractSymplifyFixer;
use Symplify\CodingStandard\TokenRunner\Enum\LineKind;
use Symplify\CodingStandard\TokenRunner\Transformer\FixerTransformer\TokensNewliner;
use Symplify\CodingStandard\TokenRunner\ValueObject\BlockInfo;

/**
* Every argument of a Symfony attribute must be on a standalone line, to ease git diffs when arguments change.
*
* Only attributes whose written name contains the "Symfony" namespace part are handled, so third-party attributes
* keep their original layout.
*
* @see \Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\StandaloneLineSymfonyAttributeParamFixerTest
*/
final class StandaloneLineSymfonyAttributeParamFixer extends AbstractSymplifyFixer
{
private const string ERROR_MESSAGE = 'Symfony attribute argument should be on a standalone line to ease git diffs on change';

private const string SYMFONY_NAMESPACE_PART = 'Symfony';

public function __construct(
private readonly TokensNewliner $tokensNewliner
) {
}

/**
* Must run before
*
* @see \PhpCsFixer\Fixer\Basic\BracesFixer::getPriority()
*/
#[Override]
public function getPriority(): int
{
return 40;
}

public function getDefinition(): FixerDefinitionInterface
{
return new FixerDefinition(self::ERROR_MESSAGE, [new CodeSample(
<<<'CODE_SAMPLE'
#[\Symfony\Component\Console\Attribute\AsCommand(name: 'app:some', description: 'Some description')]
final class SomeCommand
{
}
CODE_SAMPLE
)]);
}

/**
* @param Tokens<Token> $tokens
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(T_ATTRIBUTE);
}

/**
* @param Tokens<Token> $tokens
*/
public function fix(SplFileInfo $fileInfo, Tokens $tokens): void
{
// from the bottom up, as adding tokens shifts every position after them
for ($position = count($tokens) - 1; $position >= 0; --$position) {
/** @var Token $token */
$token = $tokens[$position];

if (! $token->isGivenKind(T_ATTRIBUTE)) {
continue;
}

$attributeEndPosition = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_ATTRIBUTE, $position);

// attribute without arguments, e.g. #[Override]; the next "(" belongs to a later statement
$openBracketPosition = $tokens->getNextTokenOfKind($position, ['(']);
if ($openBracketPosition === null) {
continue;
}

if ($openBracketPosition > $attributeEndPosition) {
continue;
}

if (! $this->isSymfonyAttribute($tokens, $position, $openBracketPosition)) {
continue;
}

$closeBracketPosition = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openBracketPosition);
if ($tokens->getNextMeaningfulToken($openBracketPosition) === $closeBracketPosition) {
// empty argument list, e.g. #[\Symfony\...\AsCommand()]
continue;
}

$blockInfo = new BlockInfo($openBracketPosition, $closeBracketPosition);
$this->tokensNewliner->breakItems($blockInfo, $tokens, LineKind::CALLS);
}
}

/**
* @param Tokens<Token> $tokens
*/
private function isSymfonyAttribute(Tokens $tokens, int $attributePosition, int $openBracketPosition): bool
{
for ($index = $attributePosition + 1; $index < $openBracketPosition; ++$index) {
/** @var Token $token */
$token = $tokens[$index];

if ($token->isGivenKind(T_STRING) && $token->getContent() === self::SYMFONY_NAMESPACE_PART) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\Fixture;

#[\Symfony\Component\Console\Attribute\AsCommand]
final class SkipNoArguments
{
#[\Override]
public function run(): void
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\Fixture;

#[\Doctrine\ORM\Mapping\Table(name: 'some_table', schema: 'public')]
final class SkipNonSymfonyAttribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\Fixture;

#[\Symfony\Component\Console\Attribute\AsCommand(name: 'mautic:entity:import', description: 'Import entity data from a ZIP file.')]
final class SymfonyAttribute
{
}

?>
-----
<?php

namespace Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\Fixture;

#[\Symfony\Component\Console\Attribute\AsCommand(
name: 'mautic:entity:import',
description: 'Import entity data from a ZIP file.'
)]
final class SymfonyAttribute
{
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\EasyCodingStandard\Testing\PHPUnit\AbstractCheckerTestCase;

final class StandaloneLineSymfonyAttributeParamFixerTest extends AbstractCheckerTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFiles(__DIR__ . '/Fixture');
}

public function provideConfig(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return static function (ECSConfig $ecsConfig): void {
$ecsConfig->rule(StandaloneLineSymfonyAttributeParamFixer::class);
};
2 changes: 0 additions & 2 deletions src/Config/Level/SpacesLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
use PhpCsFixer\Fixer\Whitespace\TypesSpacesFixer;
use Symplify\CodingStandard\Fixer\Spacing\MethodChainingNewlineFixer;
use Symplify\CodingStandard\Fixer\Spacing\SpaceAfterCommaHereNowDocFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePlainConstructorParamFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePromotedPropertyFixer;
use Symplify\CodingStandard\Fixer\Spacing\StandaloneLineRequiredParamFixer;
use Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer;
Expand Down Expand Up @@ -76,7 +75,6 @@ final class SpacesLevel
// most invasive structural changes
MethodChainingIndentationFixer::class,
StandaloneLinePromotedPropertyFixer::class,
StandaloneLinePlainConstructorParamFixer::class,
StandaloneLineRequiredParamFixer::class,
MethodArgumentSpaceFixer::class,

Expand Down
6 changes: 6 additions & 0 deletions src/Configuration/ECSConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ public function withPreparedSets(
bool $cleanup = false,
/** @see SetList::CLEAN_CODE */
bool $cleanCode = false,
/** @see SetList::STANDALONE_LINE */
bool $standaloneLine = false,
): self {
if (func_get_args() === []) {
throw new InitializationException(
Expand Down Expand Up @@ -330,6 +332,10 @@ public function withPreparedSets(
$this->sets[] = SetList::CLEAN_CODE;
}

if ($standaloneLine) {
$this->sets[] = SetList::STANDALONE_LINE;
}

if ($symplify) {
// soft-deprecated: rules moved to the "common" sets, still loaded for backward compatibility
trigger_error(
Expand Down
5 changes: 5 additions & 0 deletions src/ValueObject/Set/SetList.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ final class SetList
* @api
*/
public const string LARAVEL = __DIR__ . '/../../../config/set/laravel.php';

/**
* @api
*/
public const string STANDALONE_LINE = __DIR__ . '/../../../config/set/standalone-line.php';
}
Loading