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
4 changes: 4 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ extensions:

rules:
- PHPStan\Rules\Debug\DebugScopeRule
- PHPStan\Rules\Debug\DumpPhpDocTypeRule
- PHPStan\Rules\Debug\DumpTypeRule
- PHPStan\Rules\Debug\FileAssertRule

Expand Down Expand Up @@ -433,6 +434,9 @@ services:
usedAttributes:
lines: %featureToggles.phpDocParserIncludeLines%

-
class: PHPStan\PhpDocParser\Printer\Printer

-
class: PHPStan\PhpDoc\ConstExprParserFactory
arguments:
Expand Down
59 changes: 59 additions & 0 deletions src/Rules/Debug/DumpPhpDocTypeRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Debug;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\PhpDocParser\Printer\Printer;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function count;
use function sprintf;
use function strtolower;

/**
* @implements Rule<Node\Expr\FuncCall>
*/
final class DumpPhpDocTypeRule implements Rule
{

public function __construct(private ReflectionProvider $reflectionProvider, private Printer $printer)
{
}

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',
$this->printer->print($scope->getType($node->getArgs()[0]->value)->toPhpDocNode()),
),
)->nonIgnorable()->identifier('phpstan.dumpPhpDocType')->build(),
];
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
12 changes: 12 additions & 0 deletions src/dumpType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is copied from dumpType, but I always wondered: Why does this even return something?

PHPStorm always complains that the result is not used.

Can't we make this return void? Then do the same for the other helpers?

/cc @ondrejmirtes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can’t make void functions pure, that’s why.

Personally I turn this off in PhpStorm, as PHPStan will tell me the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is because if declared as void, dumpType() would affect the purity of the embedded function.

}
106 changes: 106 additions & 0 deletions tests/PHPStan/Rules/Debug/DumpPhpDocTypeRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Debug;

use PHPStan\PhpDocParser\Printer\Printer;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<DumpPhpDocTypeRule>
*/
class DumpPhpDocTypeRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new DumpPhpDocTypeRule($this->createReflectionProvider(), new Printer());
}

public function testRuleSymbols(): void
{
$this->analyse([__DIR__ . '/data/dump-phpdoc-type.php'], [
[
"Dumped type: array{'': ''}",
5,
],
[
"Dumped type: array{'\0': 'NUL', NUL: '\0'}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also try some of the special multi-byte characters you did in the previous PR? (\x??)

6,
],
[
"Dumped type: array{'\001': 'SOH', SOH: '\001'}",
7,
],
[
"Dumped type: array{'\t': 'HT', HT: '\t'}",
8,
],
[
"Dumped type: array{' ': 'SP', SP: ' '}",
11,
],
[
"Dumped type: array{'foo ': 'ends with SP', ' foo': 'starts with SP', ' foo ': 'surrounded by SP', foo: 'no SP'}",
12,
],
[
"Dumped type: array{'foo?': 'foo?'}",
15,
],
[
"Dumped type: array{shallwedance: 'yes'}",
16,
],
[
"Dumped type: array{'shallwedance?': '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{shall-we-dance: 'yes'}",
22,
],
[
"Dumped type: array{'shall-we-dance?': 'yes'}",
23,
],
[
"Dumped type: array{'Let\'s go': 'Let\'s go'}",
24,
],
[
"Dumped type: array{Foo\\Bar: 'Foo\\\\Bar'}",
25,
],
[
"Dumped type: array{'3.14': 3.14}",
26,
],
[
'Dumped type: array{1: true, 0: false}',
27,
],
[
'Dumped type: T',
36,
],
]);
}

}
39 changes: 39 additions & 0 deletions tests/PHPStan/Rules/Debug/data/dump-phpdoc-type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace PHPStan;

dumpPhpDocType(['' => '']);
dumpPhpDocType(["\0" => 'NUL', 'NUL' => "\0"]);
dumpPhpDocType(["\x01" => 'SOH', 'SOH' => "\x01"]);
dumpPhpDocType(["\t" => 'HT', 'HT' => "\t"]);

// 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']);
dumpPhpDocType(['3.14' => 3.14]);
dumpPhpDocType([true => true, false => false]);

/**
* @template T
* @param T $value
* @return T
*/
function id(mixed $value): mixed
{
dumpPhpDocType($value);

return $value;
}
Loading