|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace Ticketswap\PhpCsFixerConfig\Fixer; |
| 6 | + |
| 7 | +use Override; |
| 8 | +use PhpCsFixer\AbstractFixer; |
| 9 | +use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface; |
| 10 | +use PhpCsFixer\FixerDefinition\FixerDefinition; |
| 11 | +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; |
| 12 | +use PhpCsFixer\FixerDefinition\VersionSpecification; |
| 13 | +use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample; |
| 14 | +use PhpCsFixer\Tokenizer\CT; |
| 15 | +use PhpCsFixer\Tokenizer\Tokens; |
| 16 | +use SplFileInfo; |
| 17 | + |
| 18 | +/** |
| 19 | + * TODO https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/8695 wait for this be merged, and then use the official version. |
| 20 | + */ |
| 21 | +final class AttributesNewLineFixer extends AbstractFixer implements WhitespacesAwareFixerInterface |
| 22 | +{ |
| 23 | + #[Override] |
| 24 | + public function isCandidate(Tokens $tokens) : bool |
| 25 | + { |
| 26 | + return $tokens->isTokenKindFound(T_ATTRIBUTE); |
| 27 | + } |
| 28 | + |
| 29 | + #[Override] |
| 30 | + public function getDefinition() : FixerDefinitionInterface |
| 31 | + { |
| 32 | + return new FixerDefinition( |
| 33 | + 'Attributes should be on their own line.', |
| 34 | + [ |
| 35 | + new VersionSpecificCodeSample( |
| 36 | + "<?php |
| 37 | +#[Foo] #[Bar] class Baz |
| 38 | +{ |
| 39 | +}\n", |
| 40 | + new VersionSpecification(8_00_00), |
| 41 | + ), |
| 42 | + new VersionSpecificCodeSample( |
| 43 | + "<?php |
| 44 | +#[Foo] class Bar |
| 45 | +{ |
| 46 | + #[Baz] public function foo() {} |
| 47 | +}\n", |
| 48 | + new VersionSpecification(8_00_00), |
| 49 | + ), |
| 50 | + new VersionSpecificCodeSample( |
| 51 | + "<?php |
| 52 | +#[Foo] class Bar |
| 53 | +{ |
| 54 | + #[Test] public const TEST = 'Test'; |
| 55 | +}\n", |
| 56 | + new VersionSpecification(8_00_00), |
| 57 | + ), |
| 58 | + ], |
| 59 | + ); |
| 60 | + } |
| 61 | + |
| 62 | + #[Override] |
| 63 | + protected function applyFix(SplFileInfo $file, Tokens $tokens) : void |
| 64 | + { |
| 65 | + $count = $tokens->count(); |
| 66 | + for ($index = $count - 1; $index >= 0; --$index) { |
| 67 | + if ($tokens[$index]->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { |
| 68 | + $this->fixNewline($tokens, $index); |
| 69 | + } |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + private function fixNewline(Tokens $tokens, int $endIndex) : void |
| 74 | + { |
| 75 | + $nextIndex = $endIndex + 1; |
| 76 | + |
| 77 | + if ($tokens[$nextIndex]->isWhitespace()) { |
| 78 | + $whitespace = $tokens[$nextIndex]->getContent(); |
| 79 | + |
| 80 | + if (str_contains($whitespace, "\n") || str_contains($whitespace, "\r")) { |
| 81 | + return; |
| 82 | + } |
| 83 | + |
| 84 | + $tokens->clearAt($nextIndex); |
| 85 | + } |
| 86 | + |
| 87 | + $indentation = $this->getIndentation($tokens, $endIndex); |
| 88 | + |
| 89 | + $tokens->ensureWhitespaceAtIndex( |
| 90 | + $endIndex + 1, |
| 91 | + 0, |
| 92 | + $this->whitespacesConfig->getLineEnding() . $indentation, |
| 93 | + ); |
| 94 | + } |
| 95 | + |
| 96 | + private function getIndentation(Tokens $tokens, int $attributeEndIndex) : string |
| 97 | + { |
| 98 | + $nextMeaningfulIndex = $tokens->getNextMeaningfulToken($attributeEndIndex); |
| 99 | + |
| 100 | + if ($nextMeaningfulIndex === null || $tokens[$nextMeaningfulIndex]->isGivenKind([T_CLASS])) { |
| 101 | + return ''; |
| 102 | + } |
| 103 | + |
| 104 | + $searchIndex = $nextMeaningfulIndex; |
| 105 | + |
| 106 | + do { |
| 107 | + $prevWhitespaceIndex = $tokens->getPrevTokenOfKind( |
| 108 | + $searchIndex, |
| 109 | + [[T_ENCAPSED_AND_WHITESPACE], [T_INLINE_HTML], [T_WHITESPACE]], |
| 110 | + ); |
| 111 | + |
| 112 | + $searchIndex = $prevWhitespaceIndex; |
| 113 | + } while ($prevWhitespaceIndex !== null |
| 114 | + && ! str_contains($tokens[$prevWhitespaceIndex]->getContent(), "\n") |
| 115 | + ); |
| 116 | + |
| 117 | + if ($prevWhitespaceIndex === null) { |
| 118 | + return ''; |
| 119 | + } |
| 120 | + |
| 121 | + $whitespaceContent = $tokens[$prevWhitespaceIndex]->getContent(); |
| 122 | + |
| 123 | + if (str_contains($whitespaceContent, "\n")) { |
| 124 | + $lastNewLinePos = strrpos($whitespaceContent, "\n"); |
| 125 | + |
| 126 | + if ($lastNewLinePos === false) { |
| 127 | + return ''; |
| 128 | + } |
| 129 | + |
| 130 | + return substr($whitespaceContent, $lastNewLinePos + 1); |
| 131 | + } |
| 132 | + |
| 133 | + return $whitespaceContent; |
| 134 | + } |
| 135 | +} |
0 commit comments