From 5909fb2dc78cbee46927c2cb23f7491dfef34165 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 20:55:14 +0200 Subject: [PATCH] Debugging function - `PHPStan\debugScope()` --- composer.json | 2 +- conf/config.neon | 1 + src/Analyser/MutatingScope.php | 2 +- src/Rules/Debug/DebugScopeRule.php | 66 +++++++++++++++++++ ...unctionStatementWithoutSideEffectsRule.php | 1 + src/debugScope.php | 14 ++++ .../Rules/Debug/DebugScopeRuleTest.php | 56 ++++++++++++++++ .../PHPStan/Rules/Debug/data/debug-scope.php | 17 +++++ 8 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/Rules/Debug/DebugScopeRule.php create mode 100644 src/debugScope.php create mode 100644 tests/PHPStan/Rules/Debug/DebugScopeRuleTest.php create mode 100644 tests/PHPStan/Rules/Debug/data/debug-scope.php diff --git a/composer.json b/composer.json index 11803e18a3..f6cb9c01db 100644 --- a/composer.json +++ b/composer.json @@ -132,7 +132,7 @@ "src/" ] }, - "files": ["src/dumpType.php", "src/autoloadFunctions.php", "src/Testing/functions.php"] + "files": ["src/debugScope.php", "src/dumpType.php", "src/autoloadFunctions.php", "src/Testing/functions.php"] }, "autoload-dev": { "psr-4": { diff --git a/conf/config.neon b/conf/config.neon index 9f62638b65..255be78dc2 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -278,6 +278,7 @@ extensions: validateExcludePaths: PHPStan\DependencyInjection\ValidateExcludePathsExtension rules: + - PHPStan\Rules\Debug\DebugScopeRule - PHPStan\Rules\Debug\DumpTypeRule - PHPStan\Rules\Debug\FileAssertRule diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index f10fc22508..cb4dd6b858 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -5400,7 +5400,7 @@ public function debug(): array $descriptions[$key] = $variableTypeHolder->getType()->describe(VerbosityLevel::precise()); } foreach ($this->nativeExpressionTypes as $exprString => $nativeTypeHolder) { - $key = sprintf('native %s', $exprString); + $key = sprintf('native %s (%s)', $exprString, $nativeTypeHolder->getCertainty()->describe()); $descriptions[$key] = $nativeTypeHolder->getType()->describe(VerbosityLevel::precise()); } diff --git a/src/Rules/Debug/DebugScopeRule.php b/src/Rules/Debug/DebugScopeRule.php new file mode 100644 index 0000000000..7f6a43930a --- /dev/null +++ b/src/Rules/Debug/DebugScopeRule.php @@ -0,0 +1,66 @@ + + */ +final class DebugScopeRule 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\debugscope') { + return []; + } + + if (!$scope instanceof MutatingScope) { + return []; + } + + $parts = []; + foreach ($scope->debug() as $key => $row) { + $parts[] = sprintf('%s: %s', $key, $row); + } + + if (count($parts) === 0) { + $parts[] = 'Scope is empty'; + } + + return [ + RuleErrorBuilder::message( + implode("\n", $parts), + )->nonIgnorable()->identifier('phpstan.debugScope')->build(), + ]; + } + +} diff --git a/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php b/src/Rules/Functions/CallToFunctionStatementWithoutSideEffectsRule.php index 2df15b78f0..3ecbecaa7f 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\\debugScope', 'PHPStan\\Testing\\assertType', 'PHPStan\\Testing\\assertNativeType', 'PHPStan\\Testing\\assertVariableCertainty', diff --git a/src/debugScope.php b/src/debugScope.php new file mode 100644 index 0000000000..6f331c97ba --- /dev/null +++ b/src/debugScope.php @@ -0,0 +1,14 @@ + + */ +class DebugScopeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DebugScopeRule($this->createReflectionProvider()); + } + + public function testRuleInPhpStanNamespace(): void + { + $this->analyse([__DIR__ . '/data/debug-scope.php'], [ + [ + 'Scope is empty', + 7, + ], + [ + implode("\n", [ + '$a (Yes): int', + '$b (Yes): int', + '$debug (Yes): bool', + 'native $a (Yes): int', + 'native $b (Yes): int', + 'native $debug (Yes): bool', + ]), + 10, + ], + [ + implode("\n", [ + '$a (Yes): int', + '$b (Yes): int', + '$debug (Yes): bool', + '$c (Maybe): 1', + 'native $a (Yes): int', + 'native $b (Yes): int', + 'native $debug (Yes): bool', + 'native $c (Maybe): 1', + 'condition about $c #1: if $debug=false then $c is *ERROR* (No)', + 'condition about $c #2: if $debug=true then $c is 1 (Yes)', + ]), + 16, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Debug/data/debug-scope.php b/tests/PHPStan/Rules/Debug/data/debug-scope.php new file mode 100644 index 0000000000..0e7b8663aa --- /dev/null +++ b/tests/PHPStan/Rules/Debug/data/debug-scope.php @@ -0,0 +1,17 @@ +