From 3d8f294043de76e70dbfac79054ffcede42b5381 Mon Sep 17 00:00:00 2001 From: Dorian Villet Date: Wed, 1 Oct 2025 12:55:01 +0200 Subject: [PATCH 1/2] StandaloneLinePromotedPropertyFixer: properly deal with attributes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code 🤖 --- .../Analyzer/FixerAnalyzer/TokenSkipper.php | 32 ++++++- .../FixerTransformer/TokensNewliner.php | 4 +- .../Fixture/skip_attributes.php.inc | 96 +++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 tests/Fixer/Spacing/StandaloneLinePromotedPropertyFixer/Fixture/skip_attributes.php.inc diff --git a/src/TokenRunner/Analyzer/FixerAnalyzer/TokenSkipper.php b/src/TokenRunner/Analyzer/FixerAnalyzer/TokenSkipper.php index aa577dc2..54614f21 100644 --- a/src/TokenRunner/Analyzer/FixerAnalyzer/TokenSkipper.php +++ b/src/TokenRunner/Analyzer/FixerAnalyzer/TokenSkipper.php @@ -57,10 +57,18 @@ public function skipBlocksReversed(Tokens $tokens, int $position): int { /** @var Token $token */ $token = $tokens[$position]; - if (! $token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE) && ! $token->equals(')')) { + if (! $token->isGivenKind(CT::T_ARRAY_SQUARE_BRACE_CLOSE) && ! $token->equals(')') && ! $token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { return $position; } + // Check if this is an attribute closing bracket + if ($token->isGivenKind(CT::T_ATTRIBUTE_CLOSE)) { + $attributeStartPosition = $this->findAttributeStart($tokens, $position); + if ($attributeStartPosition !== null) { + return $attributeStartPosition; + } + } + $blockInfo = $this->blockFinder->findInTokensByEdge($tokens, $position); if (! $blockInfo instanceof BlockInfo) { throw new ShouldNotHappenException(); @@ -68,4 +76,26 @@ public function skipBlocksReversed(Tokens $tokens, int $position): int return $blockInfo->getStart(); } + + /** + * @param Tokens $tokens + */ + private function findAttributeStart(Tokens $tokens, int $closingBracketPosition): ?int + { + // Search backwards for T_ATTRIBUTE token (#[) + for ($i = $closingBracketPosition - 1; $i >= 0; --$i) { + $currentToken = $tokens[$i]; + + if ($currentToken->isGivenKind(T_ATTRIBUTE)) { + return $i; + } + + // If we hit another ] or reach a statement boundary, stop searching + if ($currentToken->equals(']') || $currentToken->equals(';') || $currentToken->equals('{') || $currentToken->equals('}')) { + break; + } + } + + return null; + } } diff --git a/src/TokenRunner/Transformer/FixerTransformer/TokensNewliner.php b/src/TokenRunner/Transformer/FixerTransformer/TokensNewliner.php index 95780d4b..eca2e39e 100644 --- a/src/TokenRunner/Transformer/FixerTransformer/TokensNewliner.php +++ b/src/TokenRunner/Transformer/FixerTransformer/TokensNewliner.php @@ -51,11 +51,11 @@ public function breakItems(BlockInfo $blockInfo, Tokens $tokens, int $kind): voi // again, from the bottom to the top for ($i = $blockInfo->getEnd() - 1; $i >= $blockInfo->getStart(); --$i) { + $i = $this->tokenSkipper->skipBlocksReversed($tokens, $i); + /** @var Token $currentToken */ $currentToken = $tokens[$i]; - $i = $this->tokenSkipper->skipBlocksReversed($tokens, $i); - // 2. new line after each comma ",", instead of just space if ($currentToken->getContent() === ',') { if ($this->isLastItem($tokens, $i)) { diff --git a/tests/Fixer/Spacing/StandaloneLinePromotedPropertyFixer/Fixture/skip_attributes.php.inc b/tests/Fixer/Spacing/StandaloneLinePromotedPropertyFixer/Fixture/skip_attributes.php.inc new file mode 100644 index 00000000..460eb0cb --- /dev/null +++ b/tests/Fixer/Spacing/StandaloneLinePromotedPropertyFixer/Fixture/skip_attributes.php.inc @@ -0,0 +1,96 @@ + +----- + From 07f7ae9b45d05399189475b54a2f4e72a13f0c9c Mon Sep 17 00:00:00 2001 From: Dorian Villet Date: Thu, 2 Oct 2025 08:13:04 +0200 Subject: [PATCH 2/2] CI: fix PHPStan and Rector issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sort named parameters in rector.php - Update PHPStan ignored errors to match new PHP version range - Add new PHPStan ignores for offset access and array filter issues - Remove obsolete PHPStan ignored error patterns Co-Authored-By: Claude Code 🤖 --- phpstan.neon | 20 +++++++++++++++----- rector.php | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index c3af9c45..4fa0bf43 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -13,20 +13,30 @@ parameters: - '*/tests/**/Fixture/*' ignoreErrors: - # partial enum - - '#Method Symplify\\CodingStandard\\TokenRunner\\Analyzer\\FixerAnalyzer\\BlockFinder\:\:(getBlockTypeByContent|getBlockTypeByToken)\(\) never returns \d+ so it can be removed from the return type#' - - path: tests/bootstrap.php message: '#Instantiated class PHP_CodeSniffer\\Util\\Tokens not found#' - '#Constant T_OPEN_CURLY_BRACKET|T_START_NOWDOC not found#' - - '#Method Symplify\\CodingStandard\\TokenRunner\\Traverser\\ArrayBlockInfoFinder\:\:reverseTokens\(\) should return array but returns array#' # unused generics - '#Class (.*?) implements generic interface PhpCsFixer\\Fixer\\ConfigurableFixerInterface but does not specify its types\: TFixerInputConfig, TFixerComputedConfig#' # conditional check to allow various php versions - - message: '#Comparison operation ">\=" between int<80200, 80499> and (.*?) is always true#' + message: '#Comparison operation ">\=" between int<80200, 80599> and (.*?) is always true#' path: src/TokenAnalyzer/DocblockRelatedParamNamesResolver.php + + # offset access issues + - + message: '#Offset int\|null might not exist on PhpCsFixer\\Tokenizer\\Tokens#' + path: src/Fixer/Naming/ClassNameResolver.php + + - + message: '#Offset int\|null might not exist on PhpCsFixer\\Tokenizer\\Tokens#' + path: src/Fixer/Naming/MethodNameResolver.php + + # array filter issue + - + message: '#Parameter \#1 \$array \(array\) to function array_filter does not contain falsy values, the array will always stay the same#' + path: src/TokenRunner/Traverser/TokenReverser.php diff --git a/rector.php b/rector.php index 117979ed..b65f0b5b 100644 --- a/rector.php +++ b/rector.php @@ -8,7 +8,7 @@ ->withPaths([__DIR__ . '/config', __DIR__ . '/src', __DIR__ . '/tests']) ->withRootFiles() ->withPhpSets() - ->withPreparedSets(codeQuality: true, codingStyle: true, naming: true, earlyReturn: true, privatization: true) + ->withPreparedSets(codeQuality: true, codingStyle: true, privatization: true, naming: true, earlyReturn: true) ->withImportNames() ->withSkip([ '*/Source/*',