From 8f4bc334e68bd5c493c8fe41708004419fa54f5e Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 17:47:32 +0900 Subject: [PATCH 1/4] Add PHPStan\dumpPhpDocType() function --- conf/config.neon | 1 + src/Rules/Debug/DumpPhpDocTypeRule.php | 58 ++++++++++++ ...unctionStatementWithoutSideEffectsRule.php | 1 + src/dumpType.php | 12 +++ .../Rules/Debug/DumpPhpDocTypeRuleTest.php | 89 +++++++++++++++++++ .../Rules/Debug/data/dump-phpdoc-type.php | 35 ++++++++ 6 files changed, 196 insertions(+) create mode 100644 src/Rules/Debug/DumpPhpDocTypeRule.php create mode 100644 tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php create mode 100644 tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php diff --git a/conf/config.neon b/conf/config.neon index aa096cc686..6b334a1322 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -282,6 +282,7 @@ extensions: rules: - PHPStan\Rules\Debug\DebugScopeRule + - PHPStan\Rules\Debug\DumpPhpDocTypeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule diff --git a/src/Rules/Debug/DumpPhpDocTypeRule.php b/src/Rules/Debug/DumpPhpDocTypeRule.php new file mode 100644 index 0000000000..07ff3a1c63 --- /dev/null +++ b/src/Rules/Debug/DumpPhpDocTypeRule.php @@ -0,0 +1,58 @@ + + */ +final class DumpPhpDocTypeRule implements Rule +{ + + public function __construct(private ReflectionProvider $reflectionProvider) + { + } + + public function getNodeType(): string + { + return Node\Expr\FuncCall::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$node->name instanceof Node\Name) { + return []; + } + + $functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope); + if ($functionName === null) { + return []; + } + + if (strtolower($functionName) !== 'phpstan\dumpphpdoctype') { + return []; + } + + if (count($node->getArgs()) === 0) { + return []; + } + + return [ + RuleErrorBuilder::message( + sprintf( + 'Dumped type: %s', + $scope->getType($node->getArgs()[0]->value)->toPhpDocNode(), + ), + )->nonIgnorable()->identifier('phpstan.dumpPhpDocType')->build(), + ]; + } + +} diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 3ecbecaa7f..23338924eb 100644 --- a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php +++ b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php @@ -29,6 +29,7 @@ final class CallToFunctionStatementWithoutSideEffectsRule implements Rule public const PHPSTAN_TESTING_FUNCTIONS = [ 'PHPStan\\dumpType', + 'PHPStan\\dumpPhpDocType', 'PHPStan\\debugScope', 'PHPStan\\Testing\\assertType', 'PHPStan\\Testing\\assertNativeType', diff --git a/src/dumpType.php b/src/dumpType.php index 7fa89bc896..3d4cda24f7 100644 --- a/src/dumpType.php +++ b/src/dumpType.php @@ -13,3 +13,15 @@ function dumpType($value) // phpcs:ignore Squiz.Functions.GlobalFunction.Found { return null; } + +/** + * @phpstan-pure + * @param mixed $value + * @return mixed + * + * @throws void + */ +function dumpPhpDocType($value) // phpcs:ignore Squiz.Functions.GlobalFunction.Found +{ + return null; +} diff --git a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php new file mode 100644 index 0000000000..688be1312c --- /dev/null +++ b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php @@ -0,0 +1,89 @@ + + */ +class DumpPhpDocTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DumpPhpDocTypeRule($this->createReflectionProvider()); + } + + public function testRuleSymbols(): void + { + $this->analyse([__DIR__ . '/data/dump-phpdoc-type.php'], [ + [ + "Dumped type: array{'': ''}", + 5, + ], + [ + "Dumped type: array{'\0': 'NUL', NUL: '\0'}", + 6, + ], + [ + "Dumped type: array{' ': 'SP', SP: ' '}", + 9, + ], + [ + "Dumped type: array{'foo ': 'ends with SP', ' foo': 'starts with SP', ' foo ': 'surrounded by SP', foo: 'no SP'}", + 10, + ], + [ + "Dumped type: array{'foo?': 'foo?'}", + 13, + ], + [ + "Dumped type: array{shallwedance: 'yes'}", + 14, + ], + [ + "Dumped type: array{'shallwedance?': 'yes'}", + 15, + ], + [ + "Dumped type: array{'Shall we dance': 'yes'}", + 16, + ], + [ + "Dumped type: array{'Shall we dance?': 'yes'}", + 17, + ], + [ + "Dumped type: array{shall_we_dance: 'yes'}", + 18, + ], + [ + "Dumped type: array{'shall_we_dance?': 'yes'}", + 19, + ], + [ + "Dumped type: array{shall-we-dance: 'yes'}", + 20, + ], + [ + "Dumped type: array{'shall-we-dance?': 'yes'}", + 21, + ], + [ + "Dumped type: array{'Let\'s go': 'Let\'s go'}", + 22, + ], + [ + "Dumped type: array{Foo\\Bar: 'Foo\\\\Bar'}", + 23, + ], + [ + "Dumped type: T", + 32, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php new file mode 100644 index 0000000000..7165f5eaf9 --- /dev/null +++ b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php @@ -0,0 +1,35 @@ + '']); +dumpPhpDocType(["\0" => 'NUL', 'NUL' => "\0"]); + +// Space +dumpPhpDocType([" " => 'SP', 'SP' => ' ']); +dumpPhpDocType(["foo " => 'ends with SP', " foo" => 'starts with SP', " foo " => 'surrounded by SP', 'foo' => 'no SP']); + +// Punctuation marks +dumpPhpDocType(["foo?" => 'foo?']); +dumpPhpDocType(["shallwedance" => 'yes']); +dumpPhpDocType(["shallwedance?" => 'yes']); +dumpPhpDocType(["Shall we dance" => 'yes']); +dumpPhpDocType(["Shall we dance?" => 'yes']); +dumpPhpDocType(["shall_we_dance" => 'yes']); +dumpPhpDocType(["shall_we_dance?" => 'yes']); +dumpPhpDocType(["shall-we-dance" => 'yes']); +dumpPhpDocType(["shall-we-dance?" => 'yes']); +dumpPhpDocType(['Let\'s go' => "Let's go"]); +dumpPhpDocType(['Foo\\Bar' => 'Foo\\Bar']); + +/** + * @template T + * @param T $value + * @return T + */ +function id(mixed $value): mixed +{ + dumpPhpDocType($value); + + return $value; +} From ff0b69a93b0b2939d17cde5a54af4afb28962206 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 18:26:46 +0900 Subject: [PATCH 2/4] Add test cases --- .../Rules/Debug/DumpPhpDocTypeRuleTest.php | 44 +++++++++++++------ .../Rules/Debug/data/dump-phpdoc-type.php | 4 ++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php index 688be1312c..06c39926e9 100644 --- a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php @@ -27,61 +27,77 @@ public function testRuleSymbols(): void "Dumped type: array{'\0': 'NUL', NUL: '\0'}", 6, ], + [ + "Dumped type: array{'\001': 'SOH', SOH: '\001'}", + 7, + ], + [ + "Dumped type: array{'\t': 'HT', HT: '\t'}", + 8, + ], [ "Dumped type: array{' ': 'SP', SP: ' '}", - 9, + 11, ], [ "Dumped type: array{'foo ': 'ends with SP', ' foo': 'starts with SP', ' foo ': 'surrounded by SP', foo: 'no SP'}", - 10, + 12, ], [ "Dumped type: array{'foo?': 'foo?'}", - 13, + 15, ], [ "Dumped type: array{shallwedance: 'yes'}", - 14, + 16, ], [ "Dumped type: array{'shallwedance?': 'yes'}", - 15, + 17, ], [ "Dumped type: array{'Shall we dance': 'yes'}", - 16, + 18, ], [ "Dumped type: array{'Shall we dance?': 'yes'}", - 17, + 19, ], [ "Dumped type: array{shall_we_dance: 'yes'}", - 18, + 20, ], [ "Dumped type: array{'shall_we_dance?': 'yes'}", - 19, + 21, ], [ "Dumped type: array{shall-we-dance: 'yes'}", - 20, + 22, ], [ "Dumped type: array{'shall-we-dance?': 'yes'}", - 21, + 23, ], [ "Dumped type: array{'Let\'s go': 'Let\'s go'}", - 22, + 24, ], [ "Dumped type: array{Foo\\Bar: 'Foo\\\\Bar'}", - 23, + 25, + ], + [ + "Dumped type: array{'3.14': 3.14}", + 26, + ], + [ + "Dumped type: array{1: true, 0: false}", + 27, ], [ "Dumped type: T", - 32, + 36, ], ]); } diff --git a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php index 7165f5eaf9..e8d009fe0f 100644 --- a/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php +++ b/tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php @@ -4,6 +4,8 @@ dumpPhpDocType(['' => '']); dumpPhpDocType(["\0" => 'NUL', 'NUL' => "\0"]); +dumpPhpDocType(["\x01" => 'SOH', 'SOH' => "\x01"]); +dumpPhpDocType(["\t" => 'HT', 'HT' => "\t"]); // Space dumpPhpDocType([" " => 'SP', 'SP' => ' ']); @@ -21,6 +23,8 @@ dumpPhpDocType(["shall-we-dance?" => 'yes']); dumpPhpDocType(['Let\'s go' => "Let's go"]); dumpPhpDocType(['Foo\\Bar' => 'Foo\\Bar']); +dumpPhpDocType(['3.14' => 3.14]); +dumpPhpDocType([true => true, false => false]); /** * @template T From f553432dab633b7c3120adf0f47287a73d9e9a82 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 18:26:59 +0900 Subject: [PATCH 3/4] Fix --- conf/config.neon | 3 +++ src/Rules/Debug/DumpPhpDocTypeRule.php | 6 ++++-- tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/conf/config.neon b/conf/config.neon index 6b334a1322..3146a8fd3f 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -434,6 +434,9 @@ services: usedAttributes: lines: %featureToggles.phpDocParserIncludeLines% + - + class: PHPStan\PhpDocParser\Printer\Printer + - class: PHPStan\PhpDoc\ConstExprParserFactory arguments: diff --git a/src/Rules/Debug/DumpPhpDocTypeRule.php b/src/Rules/Debug/DumpPhpDocTypeRule.php index 07ff3a1c63..34673997b3 100644 --- a/src/Rules/Debug/DumpPhpDocTypeRule.php +++ b/src/Rules/Debug/DumpPhpDocTypeRule.php @@ -4,6 +4,8 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; +use PHPStan\PhpDocParser\Printer\Printer; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; @@ -17,7 +19,7 @@ final class DumpPhpDocTypeRule implements Rule { - public function __construct(private ReflectionProvider $reflectionProvider) + public function __construct(private ReflectionProvider $reflectionProvider, private Printer $printer) { } @@ -49,7 +51,7 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message( sprintf( 'Dumped type: %s', - $scope->getType($node->getArgs()[0]->value)->toPhpDocNode(), + $this->printer->print($scope->getType($node->getArgs()[0]->value)->toPhpDocNode()), ), )->nonIgnorable()->identifier('phpstan.dumpPhpDocType')->build(), ]; diff --git a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php index 06c39926e9..1af92bf982 100644 --- a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\Debug; +use PHPStan\PhpDocParser\Printer\Printer; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -13,7 +14,7 @@ class DumpPhpDocTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new DumpPhpDocTypeRule($this->createReflectionProvider()); + return new DumpPhpDocTypeRule($this->createReflectionProvider(), new Printer()); } public function testRuleSymbols(): void From 9985e477bd3e0ab6001ace6a95d88553d0daac21 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 9 Oct 2024 18:42:58 +0900 Subject: [PATCH 4/4] Fix --- src/Rules/Debug/DumpPhpDocTypeRule.php | 1 - tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Rules/Debug/DumpPhpDocTypeRule.php b/src/Rules/Debug/DumpPhpDocTypeRule.php index 34673997b3..be867366b0 100644 --- a/src/Rules/Debug/DumpPhpDocTypeRule.php +++ b/src/Rules/Debug/DumpPhpDocTypeRule.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\PhpDocParser\Printer\Printer; -use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; diff --git a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php index 1af92bf982..ed56b46bd7 100644 --- a/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php @@ -93,11 +93,11 @@ public function testRuleSymbols(): void 26, ], [ - "Dumped type: array{1: true, 0: false}", + 'Dumped type: array{1: true, 0: false}', 27, ], [ - "Dumped type: T", + 'Dumped type: T', 36, ], ]);