Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ protected function scoreSubject(string $subject, array $prefixes): int
*
* @return array<int, string>
*/
protected function normalizeInnerPrefixes(string|array|null $value): array
protected function normalizeInnerPrefixes(array|string|null $value): array
{
if ($value === null || $value === '') {
return [];
Expand Down
117 changes: 116 additions & 1 deletion PhpCollective/Sniffs/Commenting/TypeHintSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
use const T_FUNCTION;
use const T_TYPE_UNION;

/**
* Verifies order of types in type hints. Also removes duplicates.
Expand Down Expand Up @@ -91,7 +93,10 @@ class TypeHintSniff extends AbstractSniff
*/
public function register(): array
{
return [T_DOC_COMMENT_OPEN_TAG];
return [
T_DOC_COMMENT_OPEN_TAG,
T_FUNCTION,
];
}

/**
Expand All @@ -101,6 +106,12 @@ public function register(): array
*/
public function process(File $phpcsFile, $stackPtr): void
{
if ($phpcsFile->getTokens()[$stackPtr]['code'] === T_FUNCTION) {
$this->processNativeTypeHints($phpcsFile, $stackPtr);

return;
}

$tokens = $phpcsFile->getTokens();

if (!isset($tokens[$stackPtr]['comment_closer'])) {
Expand Down Expand Up @@ -244,6 +255,81 @@ public function process(File $phpcsFile, $stackPtr): void
}
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $stackPtr
*
* @return void
*/
protected function processNativeTypeHints(File $phpcsFile, int $stackPtr): void
{
foreach ($phpcsFile->getMethodParameters($stackPtr) as $param) {
if ($param['type_hint'] === '' || $param['type_hint_token'] === false || $param['type_hint_end_token'] === false) {
continue;
}

$this->processNativeTypeHint(
$phpcsFile,
$param['type_hint_token'],
$param['type_hint_end_token'],
$param['type_hint'],
);
}

$methodProperties = $phpcsFile->getMethodProperties($stackPtr);
if (
$methodProperties['return_type'] === '' ||
$methodProperties['return_type_token'] === false ||
$methodProperties['return_type_end_token'] === false
) {
return;
}

$this->processNativeTypeHint(
$phpcsFile,
$methodProperties['return_type_token'],
$methodProperties['return_type_end_token'],
$methodProperties['return_type'],
);
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $typeHintToken
* @param int $typeHintEndToken
* @param string $typeHint
*
* @return void
*/
protected function processNativeTypeHint(File $phpcsFile, int $typeHintToken, int $typeHintEndToken, string $typeHint): void
{
if (!str_contains($typeHint, '|') || !$this->contains($phpcsFile, T_TYPE_UNION, $typeHintToken, $typeHintEndToken, false)) {
return;
}

$sortedTypeHint = $this->getSortedNativeTypeHint(explode('|', $typeHint));
if ($sortedTypeHint === $typeHint) {
return;
}

$fix = $phpcsFile->addFixableError(
'Native type hint is not formatted properly, expected "%s"',
$typeHintToken,
'IncorrectNativeFormat',
[$sortedTypeHint],
);
if (!$fix) {
return;
}

$phpcsFile->fixer->beginChangeset();
$phpcsFile->fixer->replaceToken($typeHintToken, $sortedTypeHint);
for ($i = $typeHintToken + 1; $i <= $typeHintEndToken; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
$phpcsFile->fixer->endChangeset();
}

/**
* @param array<\PHPStan\PhpDocParser\Ast\Type\TypeNode> $types node types
*
Expand Down Expand Up @@ -301,6 +387,35 @@ protected function getSortedTypeHint(array $types): string
return $this->renderUnionTypes($types);
}

/**
* @param array<string> $types
*
* @return string
*/
protected function getSortedNativeTypeHint(array $types): string
{
$sortable = array_fill_keys(static::$sortMap, []);
$unsortable = [];
foreach ($types as $type) {
$sortName = strtolower(ltrim($type, '\\'));
if (in_array($sortName, static::$sortMap, true)) {
$sortable[$sortName][] = $type;
} else {
$unsortable[] = $type;
}
}

$sorted = [];
array_walk($sortable, function ($types) use (&$sorted): void {
$sorted = array_merge($sorted, $types);
});

$types = array_merge($unsortable, $sorted);
$types = $this->makeUnique($types);

return implode('|', $types);
}

/**
* @param array<\PHPStan\PhpDocParser\Ast\Type\TypeNode|string> $types
*
Expand Down
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ To enable checking for (and auto-fixing) invalid `: void` return types on these
</rule>
```

## Union type order
`PhpCollective.Commenting.TypeHint` sorts docblock union types by the standard type order.
Native function parameter and return union types are sorted with the same order, keeping signatures such as `string|int $modelId` aligned with `@param string|int $modelId`.

## Customize DocBlock tag ordering
`PhpCollective.Commenting.DocBlockTagOrder` enforces a consistent tag order in docblocks for functions, methods, classes, interfaces, and traits. Three properties can be overridden via XML.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TypeHintSniffTest extends TestCase
*/
public function testTypeHintSniffer(): void
{
$this->assertSnifferFindsErrors(new TypeHintSniff(), 10);
$this->assertSnifferFindsErrors(new TypeHintSniff(), 12);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions tests/_data/TypeHint/after.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public function second(array $test): array
return [];
}

/**
* @param string|int $modelId
*/
public function nativeUnionOrder(string|int $modelId): void
{
}

public function nativeUnionReturn(): string|int
{
return 1;
}

/**
* @param \ArrayObject|int[] $array
*
Expand Down
12 changes: 12 additions & 0 deletions tests/_data/TypeHint/before.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ public function second(array $test): array
return [];
}

/**
* @param string|int $modelId
*/
public function nativeUnionOrder(int|string $modelId): void
{
}

public function nativeUnionReturn(): int|string
{
return 1;
}

/**
* @param \ArrayObject|int[] $array
*
Expand Down
Loading