From 050dfe63995c00938675990fbbea8b105a069b3c Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 24 Mar 2023 00:39:56 +0100 Subject: [PATCH] Revisit BlackLineAroundClassBodyFixer --- .../BlankLineAroundClassBodyFixer.php | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Fixer/Whitespace/BlankLineAroundClassBodyFixer.php b/src/Fixer/Whitespace/BlankLineAroundClassBodyFixer.php index dacdc57..86feed5 100644 --- a/src/Fixer/Whitespace/BlankLineAroundClassBodyFixer.php +++ b/src/Fixer/Whitespace/BlankLineAroundClassBodyFixer.php @@ -16,14 +16,26 @@ use PhpCsFixer\Preg; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use PhpCsFixer\Tokenizer\TokensAnalyzer; +use SplFileInfo; /** - * This is copy of the PR https://github.com/FriendsOfPHP/PHP-CS-Fixer/pull/3688 + * This fixer conflicts with the CurlyBracesPositionFixer (which is part of the BracesFixer), + * because CurlyBracesPositionFixer always tries to remove any new lines between class beginning + * and the first meaningful statement. And then this fixer restores those spaces back. * - * @author ErickSkrauch + * That is the reason, why you always see a "braces, Ely/blank_line_around_class_body" in verbose output. */ final class BlankLineAroundClassBodyFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface { + /** + * @internal + */ + public const C_BLANK_LINES_COUNT = 'blank_lines_count'; + /** + * @internal + */ + public const C_APPLY_TO_ANONYMOUS_CLASSES = 'apply_to_anonymous_classes'; + public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Ensure that class body contains one blank line after class definition and before its end.', @@ -48,7 +60,7 @@ new class extends Foo { }; ', - ['apply_to_anonymous_classes' => false], + [self::C_APPLY_TO_ANONYMOUS_CLASSES => false], ), new CodeSample( ' true], + [self::C_APPLY_TO_ANONYMOUS_CLASSES => true], ), ], ); } - public function getPriority(): int { - // should be run after the BracesFixer - return -26; - } - public function isCandidate(Tokens $tokens): bool { return $tokens->isAnyTokenKindsFound(Token::getClassyTokenKinds()); } - protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { + protected function applyFix(SplFileInfo $file, Tokens $tokens): void { $analyzer = new TokensAnalyzer($tokens); + /** @var Token $token */ foreach ($tokens as $index => $token) { if (!$token->isClassy()) { continue; } - $countLines = $this->configuration['blank_lines_count']; - if (!$this->configuration['apply_to_anonymous_classes'] && $analyzer->isAnonymousClass($index)) { + $countLines = $this->configuration[self::C_BLANK_LINES_COUNT]; + if (!$this->configuration[self::C_APPLY_TO_ANONYMOUS_CLASSES] && $analyzer->isAnonymousClass($index)) { $countLines = 0; } $startBraceIndex = $tokens->getNextTokenOfKind($index, ['{']); - if ($tokens[$startBraceIndex + 1]->isWhitespace()) { + /** @var Token $nextAfterBraceToken */ + $nextAfterBraceToken = $tokens[$startBraceIndex + 1]; + if ($nextAfterBraceToken->isWhitespace()) { $nextStatementIndex = $tokens->getNextMeaningfulToken($startBraceIndex); - // Traits should be placed right after a class opening brace, - if ($tokens[$nextStatementIndex]->getContent() !== 'use') { - $this->fixBlankLines($tokens, $startBraceIndex + 1, $countLines); + /** @var Token $nextStatementToken */ + $nextStatementToken = $tokens[$nextStatementIndex]; + // Traits should be placed right after a class opening brace + if ($nextStatementToken->getContent() !== 'use') { + $this->ensureBlankLines($tokens, $startBraceIndex + 1, $countLines); } } $endBraceIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startBraceIndex); if ($tokens[$endBraceIndex - 1]->isWhitespace()) { - $this->fixBlankLines($tokens, $endBraceIndex - 1, $countLines); + $this->ensureBlankLines($tokens, $endBraceIndex - 1, $countLines); } } } protected function createConfigurationDefinition(): FixerConfigurationResolverInterface { return new FixerConfigurationResolver([ - (new FixerOptionBuilder('blank_lines_count', 'adjusts an amount of the blank lines.')) + (new FixerOptionBuilder(self::C_BLANK_LINES_COUNT, 'adjusts the number of blank lines.')) ->setAllowedTypes(['int']) ->setDefault(1) ->getOption(), - (new FixerOptionBuilder('apply_to_anonymous_classes', 'whether this fixer should be applied to anonymous classes.')) + (new FixerOptionBuilder(self::C_APPLY_TO_ANONYMOUS_CLASSES, 'whether this fixer should be applied to anonymous classes.')) ->setAllowedTypes(['bool']) ->setDefault(true) ->getOption(), ]); } - private function fixBlankLines(Tokens $tokens, int $index, int $countLines): void { + private function ensureBlankLines(Tokens $tokens, int $index, int $countLines): void { $content = $tokens[$index]->getContent(); - // Apply fix only in the case when the count lines do not equals to expected + // Apply fix only when the lines count doesn't equal to expected + // Don't check for \r\n sequence since it's still contains \n part if (substr_count($content, "\n") === $countLines + 1) { return; } - // The final bit of the whitespace must be the next statement's indentation + // Use regexp to extract contents between line breaks Preg::matchAll('/[^\n\r]+[\r\n]*/', $content, $matches); $lines = $matches[0]; $eol = $this->whitespacesConfig->getLineEnding(); - $tokens[$index] = new Token([T_WHITESPACE, str_repeat($eol, $countLines + 1) . end($lines)]); + $tokens->ensureWhitespaceAtIndex($index, 0, str_repeat($eol, $countLines + 1) . end($lines)); } }