Skip to content

Commit d8b52fd

Browse files
committed
Replace Nullable ? in docblocks with |null verbosely.
1 parent 980f00c commit d8b52fd

File tree

2 files changed

+82
-35
lines changed

2 files changed

+82
-35
lines changed

PhpCollective/Sniffs/Commenting/DisallowShorthandNullableTypeHintSniff.php

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
1515
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
1616
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
17-
use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode;
1817
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
1918
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
2019
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
@@ -52,14 +51,25 @@ public function process(File $phpcsFile, $pointer): void
5251
$tokens = $phpcsFile->getTokens();
5352
$docCommentContent = $tokens[$pointer]['content'];
5453

55-
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode $valueNode */
54+
/** @var \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode|\PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode $valueNode */
5655
$valueNode = static::getValueNode($tokens[$pointer - 2]['content'], $docCommentContent);
57-
if ($valueNode instanceof InvalidTagValueNode || $valueNode instanceof TypelessParamTagValueNode) {
58-
return;
59-
}
6056

6157
$printer = new Printer();
6258
$before = $printer->print($valueNode);
59+
60+
// Check if the value node is invalid and handle it
61+
if ($valueNode instanceof InvalidTagValueNode) {
62+
// Attempt to clean up and process invalid types
63+
$fixedNode = $this->fixInvalidTagValueNode($valueNode);
64+
if ($fixedNode) {
65+
$valueNode = $fixedNode;
66+
}
67+
}
68+
69+
if ($valueNode instanceof InvalidTagValueNode) {
70+
return;
71+
}
72+
6373
// Traverse and fix the nullable types
6474
$this->traversePhpDocNode($valueNode);
6575

@@ -78,6 +88,64 @@ public function process(File $phpcsFile, $pointer): void
7888
}
7989
}
8090

91+
/**
92+
* Attempt to fix an InvalidTagValueNode by parsing and correcting the types manually.
93+
*
94+
* @param \PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode $invalidNode
95+
*
96+
* @return \PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode|null
97+
*/
98+
protected function fixInvalidTagValueNode(InvalidTagValueNode $invalidNode): ?PhpDocTagValueNode
99+
{
100+
$value = $invalidNode->value;
101+
$rest = '';
102+
if (str_contains($value, '$')) {
103+
$string = trim(substr($value, 0, (int)strpos($value, '$')));
104+
$rest = trim(substr($value, strlen($string)));
105+
$value = $string;
106+
}
107+
108+
// Try to parse and correct the invalid node's type (e.g., `?string|null`)
109+
if (str_contains($value, '|')) {
110+
// Split the types
111+
$types = explode('|', $value);
112+
113+
$transformedTypes = [];
114+
$hasNullable = false;
115+
116+
foreach ($types as $type) {
117+
$type = trim($type);
118+
119+
// Handle `?Type` shorthand
120+
if (str_starts_with($type, '?')) {
121+
$type = substr($type, 1); // Remove leading '?'
122+
$transformedTypes[] = new IdentifierTypeNode($type);
123+
$hasNullable = true; // Mark as nullable
124+
} elseif (strtolower($type) === 'null') {
125+
// If 'null' is encountered, mark as nullable but don't add now
126+
$hasNullable = true;
127+
} else {
128+
$transformedTypes[] = new IdentifierTypeNode($type);
129+
}
130+
}
131+
132+
// Add `null` at the end if the type is nullable
133+
if ($hasNullable) {
134+
$transformedTypes[] = new IdentifierTypeNode('null');
135+
}
136+
137+
// Create a new UnionTypeNode with the transformed types
138+
return new ParamTagValueNode(
139+
new UnionTypeNode($transformedTypes),
140+
false,
141+
$rest,
142+
'',
143+
);
144+
}
145+
146+
return null;
147+
}
148+
81149
/**
82150
* Traverse and transform the PHPDoc AST.
83151
*
@@ -92,9 +160,11 @@ protected function traversePhpDocNode(PhpDocTagValueNode $phpDocNode): void
92160
|| $phpDocNode instanceof ReturnTagValueNode
93161
|| $phpDocNode instanceof VarTagValueNode
94162
) {
95-
// Traverse the type node recursively
163+
echo PHP_EOL . 'processing...' . PHP_EOL;
96164
$phpDocNode->type = $this->transformNullableType($phpDocNode->type);
97165
}
166+
167+
echo PHP_EOL . PHP_EOL;
98168
}
99169

100170
/**
@@ -116,39 +186,16 @@ protected function transformNullableType(TypeNode $typeNode): TypeNode
116186
]);
117187
}
118188

119-
// Recursively handle UnionTypeNode (e.g., `Type|null`)
189+
// Handle UnionTypeNode (e.g., `Type|null`)
120190
if ($typeNode instanceof UnionTypeNode) {
121-
// Traverse each type in the union and transform nullable types
122-
foreach ($typeNode->types as &$subType) {
123-
$subType = $this->transformNullableType($subType);
191+
$transformedTypes = [];
192+
foreach ($typeNode->types as $subType) {
193+
$transformedTypes[] = $this->transformNullableType($subType); // Recursively transform
124194
}
125195

126-
return $typeNode;
127-
}
128-
129-
// Recursively handle other nodes that might contain nested types
130-
if (property_exists($typeNode, 'types') && is_array($typeNode->types)) {
131-
foreach ($typeNode->types as &$subType) {
132-
$subType = $this->transformNullableType($subType);
133-
}
196+
return new UnionTypeNode($transformedTypes);
134197
}
135198

136199
return $typeNode;
137200
}
138-
139-
/**
140-
* @param array<string> $types
141-
*
142-
* @return bool
143-
*/
144-
protected function containsShorthand(array $types): bool
145-
{
146-
foreach ($types as $type) {
147-
if (str_starts_with($type, '?')) {
148-
return true;
149-
}
150-
}
151-
152-
return false;
153-
}
154201
}

tests/PhpCollective/Sniffs/Commenting/DisallowShorthandNullableTypeHintSniffTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class DisallowShorthandNullableTypeHintSniffTest extends TestCase
1717
*/
1818
public function testDocBlockConstSniffer(): void
1919
{
20-
$this->assertSnifferFindsErrors(new DisallowShorthandNullableTypeHintSniff(), 3);
20+
$this->assertSnifferFindsErrors(new DisallowShorthandNullableTypeHintSniff(), 5);
2121
}
2222

2323
/**

0 commit comments

Comments
 (0)