Skip to content

Commit 31cc54f

Browse files
committed
Fix #1768
* correctly handle all current and future string types * correctly handle additional int types * fix non-empty-array not correctly set as native type but set with hyphens * fix numeric also contains string (numeric-string specifically)
1 parent b4f9f02 commit 31cc54f

10 files changed

+129
-34
lines changed

SlevomatCodingStandard/Helpers/AnnotationHelper.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,7 @@ public static function isAnnotationUseless(
293293
return $enableStandaloneNullTrueFalseTypeHints;
294294
}
295295

296-
if (in_array(
297-
strtolower($annotationType->name),
298-
['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string', 'positive-int', 'negative-int'],
299-
true,
300-
)) {
296+
if (TypeHintHelper::isSimpleUnofficialTypeHints(strtolower($annotationType->name)) && strtolower($annotationType->name) !== 'mixed') {
301297
return false;
302298
}
303299
}

SlevomatCodingStandard/Helpers/AnnotationTypeHelper.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace SlevomatCodingStandard\Helpers;
44

5+
use InvalidArgumentException;
56
use PHP_CodeSniffer\Files\File;
67
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
78
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
@@ -22,6 +23,7 @@
2223
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
2324
use function count;
2425
use function in_array;
26+
use function preg_match;
2527
use function strtolower;
2628

2729
/**
@@ -303,18 +305,24 @@ public static function getTypeHintFromOneType(
303305
return $enableUnionTypeHint || $enableStandaloneNullTrueFalseTypeHints ? 'false' : 'bool';
304306
}
305307

306-
if (in_array(strtolower($typeNode->name), ['positive-int', 'negative-int'], true)) {
308+
if (in_array(strtolower($typeNode->name), ['positive-int', 'non-positive-int', 'negative-int', 'non-negative-int', 'literal-int', 'int-mask'], true)) {
307309
return 'int';
308310
}
309311

310-
if (in_array(
311-
strtolower($typeNode->name),
312-
['class-string', 'trait-string', 'callable-string', 'numeric-string', 'non-empty-string', 'non-falsy-string', 'literal-string'],
313-
true,
314-
)) {
312+
if (in_array(strtolower($typeNode->name), ['callable-array', 'callable-string'], true)) {
313+
return 'callable';
314+
}
315+
316+
// see https://psalm.dev/docs/annotating_code/type_syntax/scalar_types/#class-string-interface-string
317+
if ((bool) preg_match('/-string$/i', $typeNode->name)) {
315318
return 'string';
316319
}
317320

321+
// here when used literally e.g. as type non-empty-array|null
322+
if (in_array(strtolower($typeNode->name), ['non-empty-array', 'list', 'non-empty-list'], true)) {
323+
return 'array';
324+
}
325+
318326
return $typeNode->name;
319327
}
320328

@@ -348,7 +356,13 @@ public static function getTypeHintFromOneType(
348356
}
349357
}
350358

351-
return (string) $typeNode;
359+
if((bool) preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', (string) $typeNode)) {
360+
return (string) $typeNode;
361+
}
362+
363+
throw new InvalidArgumentException(
364+
sprintf('Invalid type %s given', (string) $typeNode),
365+
);
352366
}
353367

354368
/**

SlevomatCodingStandard/Helpers/TypeHintHelper.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use function count;
1414
use function implode;
1515
use function in_array;
16+
use function preg_match;
1617
use function preg_split;
1718
use function sort;
1819
use function sprintf;
@@ -79,7 +80,7 @@ public static function convertLongSimpleTypeHintToShort(string $typeHint): strin
7980

8081
public static function isUnofficialUnionTypeHint(string $typeHint): bool
8182
{
82-
return in_array($typeHint, ['scalar', 'numeric'], true);
83+
return in_array($typeHint, ['scalar', 'numeric', 'array-key'], true);
8384
}
8485

8586
public static function isVoidTypeHint(string $typeHint): bool
@@ -99,7 +100,8 @@ public static function convertUnofficialUnionTypeHintToOfficialTypeHints(string
99100
{
100101
$conversion = [
101102
'scalar' => ['string', 'int', 'float', 'bool'],
102-
'numeric' => ['int', 'float'],
103+
'numeric' => ['int', 'float', 'string'],
104+
'array-key' => ['int', 'string'],
103105
];
104106

105107
return $conversion[$typeHint];
@@ -170,6 +172,7 @@ public static function isSimpleUnofficialTypeHints(string $typeHint): bool
170172
static $simpleUnofficialTypeHints;
171173

172174
if ($simpleUnofficialTypeHints === null) {
175+
// see https://psalm.dev/docs/annotating_code/type_syntax/atomic_types/
173176
$simpleUnofficialTypeHints = [
174177
'null',
175178
'mixed',
@@ -180,24 +183,25 @@ public static function isSimpleUnofficialTypeHints(string $typeHint): bool
180183
'resource',
181184
'static',
182185
'$this',
183-
'class-string',
184-
'trait-string',
185-
'callable-string',
186-
'numeric-string',
187-
'non-empty-string',
188-
'non-falsy-string',
189-
'literal-string',
190186
'array-key',
191187
'list',
188+
'non-empty-array',
189+
'non-empty-list',
192190
'empty',
193191
'positive-int',
192+
'non-positive-int',
194193
'negative-int',
194+
'non-negative-int',
195+
'literal-int',
196+
'int-mask',
195197
'min',
196198
'max',
199+
'callable-array',
200+
'callable-string',
197201
];
198202
}
199203

200-
return in_array($typeHint, $simpleUnofficialTypeHints, true);
204+
return in_array($typeHint, $simpleUnofficialTypeHints, true) || (bool) preg_match('/-string$/', $typeHint);
201205
}
202206

203207
/**

SlevomatCodingStandard/Sniffs/PHP/RequireExplicitAssertionSniff.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use function count;
2828
use function implode;
2929
use function in_array;
30+
use function preg_match;
3031
use function sprintf;
3132
use function strpos;
3233
use function trim;
@@ -417,8 +418,7 @@ private function createConditions(string $variableName, TypeNode $typeNode): arr
417418

418419
if ($typeNode->name === 'numeric') {
419420
return [
420-
sprintf('\is_int(%s)', $variableName),
421-
sprintf('\is_float(%s)', $variableName),
421+
sprintf('\is_numeric(%s)', $variableName),
422422
];
423423
}
424424

@@ -432,29 +432,33 @@ private function createConditions(string $variableName, TypeNode $typeNode): arr
432432
}
433433

434434
if ($this->enableIntegerRanges) {
435-
if ($typeNode->name === 'positive-int') {
435+
if ($typeNode->name === 'positive-int' || $typeNode->name === 'non-negative-int') {
436436
return [sprintf('\is_int(%1$s) && %1$s > 0', $variableName)];
437437
}
438438

439-
if ($typeNode->name === 'negative-int') {
439+
if ($typeNode->name === 'negative-int' || $typeNode->name === 'non-positive-int') {
440440
return [sprintf('\is_int(%1$s) && %1$s < 0', $variableName)];
441441
}
442+
443+
if ($typeNode->name === 'literal-int') {
444+
return [sprintf('\is_int(%1$s)', $variableName)];
445+
}
442446
}
443447

444448
if (
445449
$this->enableAdvancedStringTypes
446-
&& in_array($typeNode->name, ['non-empty-string', 'non-falsy-string', 'callable-string', 'numeric-string'], true)
450+
&& (bool) preg_match('/-string$/i', $typeNode->name)
447451
) {
448452
$conditions = [sprintf('\is_string(%s)', $variableName)];
449453

450-
if ($typeNode->name === 'non-empty-string') {
451-
$conditions[] = sprintf("%s !== ''", $variableName);
452-
} elseif ($typeNode->name === 'non-falsy-string') {
453-
$conditions[] = sprintf('(bool) %s === true', $variableName);
454-
} elseif ($typeNode->name === 'callable-string') {
454+
if ($typeNode->name === 'callable-string') {
455455
$conditions[] = sprintf('\is_callable(%s)', $variableName);
456-
} else {
456+
} elseif ($typeNode->name === 'numeric-string') {
457457
$conditions[] = sprintf('\is_numeric(%s)', $variableName);
458+
} elseif ((bool) preg_match('/^non-empty-/i', $typeNode->name)) {
459+
$conditions[] = sprintf("%s !== ''", $variableName);
460+
} elseif ((bool) preg_match('/^non-falsy-/i', $typeNode->name)) {
461+
$conditions[] = sprintf( '(bool) %s === true', $variableName );
458462
}
459463

460464
return [implode(' && ', $conditions)];

tests/Helpers/TypeHintHelperTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ public static function dataIsSimpleUnofficialTypeHint(): array
8181
['resource', true],
8282
['static', true],
8383
['$this', true],
84+
['array-key', true],
85+
['list', true],
86+
['non-empty-array', true],
87+
['non-empty-list', true],
88+
['empty', true],
89+
['positive-int', true],
90+
['non-positive-int', true],
91+
['negative-int', true],
92+
['non-negative-int', true],
93+
['literal-int', true],
94+
['int-mask', true],
95+
['callable-array', true],
96+
['callable-string', true],
8497

8598
['\Traversable', false],
8699
['int', false],
@@ -379,6 +392,8 @@ public static function dataTypeHintEqualsAnnotation(): array
379392
return [
380393
['scalar', true],
381394
['unionIsNotIntersection', false],
395+
['fooFunctionWithReturnAnnotationComplexString', false],
396+
['fooFunctionWithReturnAnnotationSimpleHyphenedIterable', false],
382397
];
383398
}
384399

tests/Helpers/data/typeHintEqualsAnnotation.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,19 @@ function scalar(): int|bool|float|string
1414
function unionIsNotIntersection(): Foo|Bar
1515
{
1616
}
17+
18+
/**
19+
* @return non-empty-lowercase-string
20+
*/
21+
function fooFunctionWithReturnAnnotationComplexString(): string
22+
{
23+
24+
}
25+
26+
/**
27+
* @return non-empty-array|null
28+
*/
29+
function fooFunctionWithReturnAnnotationSimpleHyphenedIterable(): ?array
30+
{
31+
32+
}

tests/Sniffs/TypeHints/ReturnTypeHintSniffTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function testErrors(): void
3333
'traversableTypeHints' => ['Traversable', '\ArrayIterator'],
3434
]);
3535

36-
self::assertSame(62, $report->getErrorCount());
36+
self::assertSame(65, $report->getErrorCount());
3737

3838
self::assertSniffError($report, 6, ReturnTypeHintSniff::CODE_MISSING_ANY_TYPE_HINT);
3939
self::assertSniffError($report, 14, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
@@ -104,6 +104,9 @@ public function testErrors(): void
104104
self::assertSniffError($report, 368, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
105105

106106
self::assertSniffError($report, 373, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
107+
self::assertSniffError($report, 378, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
108+
self::assertSniffError($report, 383, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
109+
self::assertSniffError($report, 388, ReturnTypeHintSniff::CODE_MISSING_NATIVE_TYPE_HINT);
107110

108111
self::assertAllFixedInFile($report);
109112
}

tests/Sniffs/TypeHints/data/returnTypeHintErrors.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,18 @@ public function returnsObjectShape()
374374
{
375375
}
376376

377+
/** @return non-empty-lowercase-string */
378+
public function returnComplexString()
379+
{
380+
}
381+
382+
/** @return callable-array */
383+
public function returnCallableArray()
384+
{
385+
}
386+
387+
/** @return non-empty-array */
388+
public function returnNonEmptyArray()
389+
{
390+
}
377391
}

tests/Sniffs/TypeHints/data/returnTypeHintNoErrors.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,19 @@ public function withParameterConditionalArrayInElse($parameter): array
397397
{
398398
}
399399

400+
/** @return non-empty-lowercase-string */
401+
public function returnComplexString(): string
402+
{
403+
}
404+
405+
/** @return callable-array */
406+
public function returnCallableArray(): callable
407+
{
408+
}
409+
410+
/** @return non-empty-array */
411+
public function returnNonEmptyArray(): array
412+
{
413+
}
414+
400415
}

tests/Sniffs/TypeHints/data/returnTypeHintWithUnionNoErrors.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,18 @@ public function intersectionWithBroadNativeTypeHint(): A
3333
{
3434
}
3535

36+
/** @return non-empty-lowercase-string|null */
37+
public function returnComplexString(): ?string
38+
{
39+
}
40+
41+
/** @return callable-array|null */
42+
public function returnCallableArray(): ?callable
43+
{
44+
}
45+
46+
/** @return non-empty-array|null */
47+
public function returnNonEmptyArray(): ?array
48+
{
49+
}
3650
}

0 commit comments

Comments
 (0)