Skip to content

Commit 4895cc8

Browse files
authored
fix: PhpdocVarAnnotationToAssertFixer - do not convert types from phpstan-type, phpstan-import-type, psalm-type and psalm-import-type (#1061)
1 parent 0763868 commit 4895cc8

File tree

4 files changed

+204
-10
lines changed

4 files changed

+204
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# CHANGELOG for PHP CS Fixer: custom fixers
22

3+
## v3.31.0
4+
- Update minimum PHP CS Fixer version to 3.84.0
5+
36
## v3.30.0
47
- Update minimum PHP CS Fixer version to 3.82.0
58

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"php": "^7.4 || ^8.0",
1414
"ext-filter": "*",
1515
"ext-tokenizer": "*",
16-
"friendsofphp/php-cs-fixer": "^3.82"
16+
"friendsofphp/php-cs-fixer": "^3.84"
1717
},
1818
"require-dev": {
1919
"phpunit/phpunit": "^9.6.22 || 10.5.45 || ^11.5.7"

src/Fixer/PhpdocVarAnnotationToAssertFixer.php

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace PhpCsFixerCustomFixers\Fixer;
1313

14+
use PhpCsFixer\AbstractPhpdocToTypeDeclarationFixer;
1415
use PhpCsFixer\DocBlock\Annotation;
1516
use PhpCsFixer\DocBlock\DocBlock;
1617
use PhpCsFixer\FixerDefinition\CodeSample;
@@ -57,17 +58,18 @@ public function isRisky(): bool
5758

5859
public function fix(\SplFileInfo $file, Tokens $tokens): void
5960
{
60-
for ($docCommentIndex = $tokens->count() - 1; $docCommentIndex > 0; $docCommentIndex--) {
61-
if (!$tokens[$docCommentIndex]->isGivenKind([\T_DOC_COMMENT])) {
62-
continue;
63-
}
61+
$tokensToInsert = [];
62+
$typesToExclude = [];
63+
64+
foreach ($tokens->findGivenKind(\T_DOC_COMMENT) as $index => $token) {
65+
$typesToExclude = \array_merge($typesToExclude, self::getTypesToExclude($token->getContent()));
6466

65-
$variableIndex = self::getVariableIndex($tokens, $docCommentIndex);
67+
$variableIndex = self::getVariableIndex($tokens, $index);
6668
if ($variableIndex === null) {
6769
continue;
6870
}
6971

70-
$assertTokens = self::getAssertTokens($tokens, $docCommentIndex, $tokens[$variableIndex]->getContent());
72+
$assertTokens = self::getAssertTokens($tokens, $index, $tokens[$variableIndex]->getContent(), $typesToExclude);
7173
if ($assertTokens === null) {
7274
continue;
7375
}
@@ -82,10 +84,32 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
8284
\array_unshift($assertTokens, new Token([\T_WHITESPACE, $tokens[$variableIndex - 1]->getContent()]));
8385
}
8486

85-
$tokens->insertAt($expressionEndIndex + 1, $assertTokens);
87+
$tokensToInsert[$expressionEndIndex + 1] = $assertTokens;
88+
89+
TokenRemover::removeWithLinesIfPossible($tokens, $index);
90+
}
91+
92+
$tokens->insertSlices($tokensToInsert);
93+
}
8694

87-
TokenRemover::removeWithLinesIfPossible($tokens, $docCommentIndex);
95+
/**
96+
* @return list<string>
97+
*/
98+
private static function getTypesToExclude(string $content): array
99+
{
100+
/** @var null|\Closure(string): list<string> $getTypesToExclude */
101+
static $getTypesToExclude = null;
102+
103+
if ($getTypesToExclude === null) {
104+
/** @var \Closure(string): list<string> $getTypesToExclude */
105+
$getTypesToExclude = \Closure::bind(
106+
static fn (string $content): array => AbstractPhpdocToTypeDeclarationFixer::getTypesToExclude($content),
107+
null,
108+
AbstractPhpdocToTypeDeclarationFixer::class,
109+
);
88110
}
111+
112+
return $getTypesToExclude($content);
89113
}
90114

91115
private static function getVariableIndex(Tokens $tokens, int $docCommentIndex): ?int
@@ -114,9 +138,11 @@ private static function getVariableIndex(Tokens $tokens, int $docCommentIndex):
114138
}
115139

116140
/**
141+
* @param list<string> $typesToExclude
142+
*
117143
* @return null|list<Token>
118144
*/
119-
private static function getAssertTokens(Tokens $tokens, int $docCommentIndex, string $variableName): ?array
145+
private static function getAssertTokens(Tokens $tokens, int $docCommentIndex, string $variableName, array $typesToExclude): ?array
120146
{
121147
$annotation = self::getAnnotationForVariable($tokens, $docCommentIndex, $variableName);
122148
if ($annotation === null) {
@@ -136,6 +162,9 @@ private static function getAssertTokens(Tokens $tokens, int $docCommentIndex, st
136162
$assertions['null'] = self::getCodeForType('null', $variableName);
137163
$type = \substr($type, 1);
138164
}
165+
if (\in_array($type, $typesToExclude, true)) {
166+
return null;
167+
}
139168
$assertions[$type] = self::getCodeForType($type, $variableName);
140169
}
141170

tests/Fixer/PhpdocVarAnnotationToAssertFixerTest.php

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,5 +399,167 @@ function ($x) { $x++; return $x + 6; },
399399
$z = 4;
400400
',
401401
];
402+
403+
yield 'types defined with `phpstan-type`' => [
404+
<<<'PHP'
405+
<?php
406+
/**
407+
* @phpstan-type _Pair array{int, int}
408+
*/
409+
class Foo {
410+
public function bar() {
411+
/** @var _Pair $x */
412+
$x = getValue();
413+
}
414+
}
415+
/**
416+
* @phpstan-type _Trio array{int, int, int}
417+
*/
418+
class Bar {
419+
public function bar() {
420+
/** @var _Trio $x */
421+
$x = getValue();
422+
}
423+
}
424+
PHP,
425+
];
426+
427+
yield 'types defined with `phpstan-import-type`' => [
428+
<<<'PHP'
429+
<?php
430+
/**
431+
* @phpstan-import-type _Pair from FooFoo
432+
*/
433+
class Foo {
434+
public function bar() {
435+
/** @var _Pair $x */
436+
$x = getValue();
437+
}
438+
}
439+
/**
440+
* @phpstan-import-type _Trio from BarBar
441+
*/
442+
class Bar {
443+
public function bar() {
444+
/** @var _Trio $x */
445+
$x = getValue();
446+
}
447+
}
448+
PHP,
449+
];
450+
451+
yield 'types defined with `phpstan-import-type` with alias' => [
452+
<<<'PHP'
453+
<?php
454+
/**
455+
* @phpstan-import-type ConflictingType from FooFoo as Not_ConflictingType
456+
*/
457+
class Foo {
458+
public function bar() {
459+
$x = getValue();
460+
assert($x instanceof ConflictingType);
461+
462+
/** @var Not_ConflictingType $y */
463+
$y = getValue();
464+
}
465+
}
466+
PHP,
467+
<<<'PHP'
468+
<?php
469+
/**
470+
* @phpstan-import-type ConflictingType from FooFoo as Not_ConflictingType
471+
*/
472+
class Foo {
473+
public function bar() {
474+
/** @var ConflictingType $x */
475+
$x = getValue();
476+
477+
/** @var Not_ConflictingType $y */
478+
$y = getValue();
479+
}
480+
}
481+
PHP,
482+
];
483+
484+
yield 'types defined with `psalm-type`' => [
485+
<<<'PHP'
486+
<?php
487+
/**
488+
* @psalm-type _Pair = array{int, int}
489+
*/
490+
class Foo {
491+
public function bar() {
492+
/** @var _Pair $x */
493+
$x = getValue();
494+
}
495+
}
496+
/**
497+
* @psalm-type _Trio array{int, int, int}
498+
*/
499+
class Bar {
500+
public function bar() {
501+
/** @var _Trio $x */
502+
$x = getValue();
503+
}
504+
}
505+
PHP,
506+
];
507+
508+
yield 'types defined with `psalm-import-type`' => [
509+
<<<'PHP'
510+
<?php
511+
/**
512+
* @psalm-import-type _Pair from FooFoo
513+
*/
514+
class Foo {
515+
public function bar() {
516+
/** @var _Pair $x */
517+
$x = getValue();
518+
}
519+
}
520+
/**
521+
* @psalm-import-type _Trio from BarBar
522+
*/
523+
class Bar {
524+
public function bar() {
525+
/** @var _Trio $x */
526+
$x = getValue();
527+
}
528+
}
529+
PHP,
530+
];
531+
532+
yield 'types defined with `psalm-import-type` with alias' => [
533+
<<<'PHP'
534+
<?php
535+
/**
536+
* @psalm-import-type Bar from FooFoo as BarAliased
537+
*/
538+
class Foo {
539+
public function bar() {
540+
$x = getValue();
541+
assert($x instanceof Bar);
542+
543+
/** @var BarAliased $y */
544+
$y = getValue();
545+
}
546+
}
547+
PHP,
548+
<<<'PHP'
549+
<?php
550+
/**
551+
* @psalm-import-type Bar from FooFoo as BarAliased
552+
*/
553+
class Foo {
554+
public function bar() {
555+
/** @var Bar $x */
556+
$x = getValue();
557+
558+
/** @var BarAliased $y */
559+
$y = getValue();
560+
}
561+
}
562+
PHP,
563+
];
402564
}
403565
}

0 commit comments

Comments
 (0)