diff --git a/README.md b/README.md index 648923c..3a7c1cd 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ parameters: - method: 'Redis::connect()' message: 'use our own Redis instead' + errorIdentifier: 'redis.connect' disallowedStaticCalls: - @@ -136,6 +137,8 @@ parameters: The `message` key is optional. Functions and methods can be specified with or without `()`. Omitting `()` is not recommended though to avoid confusing method calls with class constants. +The `errorIdentifier` key is optional. It can be used to provide a unique identifier to the PHPStan error. + Use wildcard (`*`) to ignore all functions, methods, namespaces starting with a prefix, for example: ```neon parameters: diff --git a/phpstan.neon b/phpstan.neon index c291f72..21b5801 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,7 +3,7 @@ parameters: - src level: max typeAliases: - ForbiddenCallsConfig: 'array, allowParamsInAllowedAnyValue?:array, allowParamsAnywhere?:array, allowParamsAnywhereAnyValue?:array, allowExceptParamsInAllowed?:array, allowExceptParams?:array, allowExceptCaseInsensitiveParams?:array}>' + ForbiddenCallsConfig: 'array, allowParamsInAllowedAnyValue?:array, allowParamsAnywhere?:array, allowParamsAnywhereAnyValue?:array, allowExceptParamsInAllowed?:array, allowExceptParams?:array, allowExceptCaseInsensitiveParams?:array, errorIdentifier?:string}>' includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon diff --git a/src/Calls/EchoCalls.php b/src/Calls/EchoCalls.php index cafbe6d..1d949ed 100644 --- a/src/Calls/EchoCalls.php +++ b/src/Calls/EchoCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Stmt\Echo_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -52,7 +53,7 @@ public function getNodeType(): string /** * @param Echo_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/EmptyCalls.php b/src/Calls/EmptyCalls.php index 36effb7..c6bfdcc 100644 --- a/src/Calls/EmptyCalls.php +++ b/src/Calls/EmptyCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Empty_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -52,7 +53,7 @@ public function getNodeType(): string /** * @param Empty_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/EvalCalls.php b/src/Calls/EvalCalls.php index adea695..376032b 100644 --- a/src/Calls/EvalCalls.php +++ b/src/Calls/EvalCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Eval_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -52,7 +53,7 @@ public function getNodeType(): string /** * @param Eval_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/ExitDieCalls.php b/src/Calls/ExitDieCalls.php index 4e91efc..e2f1d90 100644 --- a/src/Calls/ExitDieCalls.php +++ b/src/Calls/ExitDieCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Exit_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -52,7 +53,7 @@ public function getNodeType(): string /** * @param Exit_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/FunctionCalls.php b/src/Calls/FunctionCalls.php index 0099edd..6aabba1 100644 --- a/src/Calls/FunctionCalls.php +++ b/src/Calls/FunctionCalls.php @@ -8,6 +8,7 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -53,7 +54,7 @@ public function getNodeType(): string /** * @param FuncCall $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/MethodCalls.php b/src/Calls/MethodCalls.php index d8e5bbb..4bab78b 100644 --- a/src/Calls/MethodCalls.php +++ b/src/Calls/MethodCalls.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\ClassNotFoundException; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -55,7 +56,7 @@ public function getNodeType(): string /** * @param MethodCall $node * @param Scope $scope - * @return string[] + * @return RuleError[] * @throws ClassNotFoundException */ public function processNode(Node $node, Scope $scope): array diff --git a/src/Calls/NewCalls.php b/src/Calls/NewCalls.php index 3a97d56..56aeeed 100644 --- a/src/Calls/NewCalls.php +++ b/src/Calls/NewCalls.php @@ -9,6 +9,7 @@ use PhpParser\Node\Name; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ConstantScalarType; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; @@ -55,7 +56,7 @@ public function getNodeType(): string /** * @param New_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/PrintCalls.php b/src/Calls/PrintCalls.php index 5bea4ce..67e4c63 100644 --- a/src/Calls/PrintCalls.php +++ b/src/Calls/PrintCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\Print_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -52,7 +53,7 @@ public function getNodeType(): string /** * @param Print_ $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/ShellExecCalls.php b/src/Calls/ShellExecCalls.php index 6e5567b..f8ec431 100644 --- a/src/Calls/ShellExecCalls.php +++ b/src/Calls/ShellExecCalls.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\ShellExec; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -56,7 +57,7 @@ public function getNodeType(): string /** * @param ShellExec $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Calls/StaticCalls.php b/src/Calls/StaticCalls.php index e419ff6..c77275a 100644 --- a/src/Calls/StaticCalls.php +++ b/src/Calls/StaticCalls.php @@ -8,6 +8,7 @@ use PHPStan\Analyser\Scope; use PHPStan\Broker\ClassNotFoundException; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedCall; use Spaze\PHPStan\Rules\Disallowed\DisallowedCallFactory; @@ -55,7 +56,7 @@ public function getNodeType(): string /** * @param StaticCall $node * @param Scope $scope - * @return string[] + * @return RuleError[] * @throws ClassNotFoundException */ public function processNode(Node $node, Scope $scope): array diff --git a/src/DisallowedCall.php b/src/DisallowedCall.php index 5c7a00e..4ab1b67 100644 --- a/src/DisallowedCall.php +++ b/src/DisallowedCall.php @@ -32,6 +32,9 @@ class DisallowedCall /** @var array */ private $allowExceptParams; + /** @var string */ + private $errorIdentifier; + /** * DisallowedCall constructor. @@ -44,8 +47,9 @@ class DisallowedCall * @param array $allowParamsAnywhere * @param array $allowExceptParamsInAllowed * @param array $allowExceptParams + * @param string $errorIdentifier */ - public function __construct(string $call, ?string $message, array $allowIn, array $allowInCalls, array $allowParamsInAllowed, array $allowParamsAnywhere, array $allowExceptParamsInAllowed, array $allowExceptParams) + public function __construct(string $call, ?string $message, array $allowIn, array $allowInCalls, array $allowParamsInAllowed, array $allowParamsAnywhere, array $allowExceptParamsInAllowed, array $allowExceptParams, string $errorIdentifier) { $this->call = $call; $this->message = $message; @@ -55,6 +59,7 @@ public function __construct(string $call, ?string $message, array $allowIn, arra $this->allowParamsAnywhere = $allowParamsAnywhere; $this->allowExceptParamsInAllowed = $allowExceptParamsInAllowed; $this->allowExceptParams = $allowExceptParams; + $this->errorIdentifier = $errorIdentifier; } @@ -124,6 +129,12 @@ public function getAllowExceptParams(): array } + public function getErrorIdentifier(): string + { + return $this->errorIdentifier; + } + + public function getKey(): string { // The key consists of "initial" config values that would be overwritten with more specific details in a custom config. diff --git a/src/DisallowedCallFactory.php b/src/DisallowedCallFactory.php index 7181ec5..d4501cc 100644 --- a/src/DisallowedCallFactory.php +++ b/src/DisallowedCallFactory.php @@ -61,7 +61,8 @@ public function createFromConfig(array $config): array $allowParamsInAllowed, $allowParamsAnywhere, $allowExceptParamsInAllowed, - $allowExceptParams + $allowExceptParams, + $disallowedCall['errorIdentifier'] ?? '' ); $calls[$disallowedCall->getKey()] = $disallowedCall; } diff --git a/src/DisallowedConstant.php b/src/DisallowedConstant.php index dd5d541..ae76693 100644 --- a/src/DisallowedConstant.php +++ b/src/DisallowedConstant.php @@ -15,6 +15,9 @@ class DisallowedConstant /** @var string[] */ private $allowIn; + /** @var string */ + private $errorIdentifier; + /** * DisallowedCall constructor. @@ -22,12 +25,14 @@ class DisallowedConstant * @param string $constant * @param string|null $message * @param string[] $allowIn + * @param string $errorIdentifier */ - public function __construct(string $constant, ?string $message, array $allowIn) + public function __construct(string $constant, ?string $message, array $allowIn, string $errorIdentifier) { $this->constant = ltrim($constant, '\\'); $this->message = $message; $this->allowIn = $allowIn; + $this->errorIdentifier = $errorIdentifier; } @@ -51,4 +56,10 @@ public function getAllowIn(): array return $this->allowIn; } + + public function getErrorIdentifier(): string + { + return $this->errorIdentifier; + } + } diff --git a/src/DisallowedConstantFactory.php b/src/DisallowedConstantFactory.php index b837a8f..a336b6c 100644 --- a/src/DisallowedConstantFactory.php +++ b/src/DisallowedConstantFactory.php @@ -9,7 +9,7 @@ class DisallowedConstantFactory { /** - * @param array $config + * @param array $config * @return DisallowedConstant[] * @throws ShouldNotHappenException */ @@ -25,7 +25,8 @@ public function createFromConfig(array $config): array $disallowedConstant = new DisallowedConstant( $class ? "{$class}::{$constant}" : $constant, $disallowedConstant['message'] ?? null, - $disallowedConstant['allowIn'] ?? [] + $disallowedConstant['allowIn'] ?? [], + $disallowedConstant['errorIdentifier'] ?? '' ); $constants[$disallowedConstant->getConstant()] = $disallowedConstant; } diff --git a/src/DisallowedHelper.php b/src/DisallowedHelper.php index d772292..1c9728c 100644 --- a/src/DisallowedHelper.php +++ b/src/DisallowedHelper.php @@ -11,6 +11,8 @@ use PHPStan\Broker\ClassNotFoundException; use PHPStan\Reflection\FunctionReflection; use PHPStan\Reflection\MethodReflection; +use PHPStan\Rules\RuleError; +use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -111,19 +113,21 @@ private function getArgType(CallLike $node, Scope $scope, int $param): ?Type * @param string|null $displayName * @param DisallowedCall[] $disallowedCalls * @param string|null $message - * @return string[] + * @return RuleError[] */ public function getDisallowedMessage(?CallLike $node, Scope $scope, string $name, ?string $displayName, array $disallowedCalls, ?string $message = null): array { foreach ($disallowedCalls as $disallowedCall) { if ($this->callMatches($disallowedCall, $name) && !$this->isAllowed($scope, $node, $disallowedCall)) { return [ - sprintf( + RuleErrorBuilder::message(sprintf( $message ?? 'Calling %s is forbidden, %s%s', ($displayName && $displayName !== $name) ? "{$name}() (as {$displayName}())" : "{$name}()", $disallowedCall->getMessage(), $disallowedCall->getCall() !== $name ? " [{$name}() matches {$disallowedCall->getCall()}()]" : '' - ), + )) + ->identifier($disallowedCall->getErrorIdentifier()) + ->build(), ]; } } @@ -145,7 +149,7 @@ private function callMatches(DisallowedCall $disallowedCall, string $name): bool * @param CallLike $node * @param Scope $scope * @param DisallowedCall[] $disallowedCalls - * @return string[] + * @return RuleError[] * @throws ClassNotFoundException */ public function getDisallowedMethodMessage($class, CallLike $node, Scope $scope, array $disallowedCalls): array @@ -215,19 +219,21 @@ private function isAllowedPath(Scope $scope, DisallowedConstant $disallowedConst * @param Scope $scope * @param string|null $displayName * @param DisallowedConstant[] $disallowedConstants - * @return string[] + * @return RuleError[] */ public function getDisallowedConstantMessage(string $constant, Scope $scope, ?string $displayName, array $disallowedConstants): array { foreach ($disallowedConstants as $disallowedConstant) { if ($disallowedConstant->getConstant() === $constant && !$this->isAllowedPath($scope, $disallowedConstant)) { return [ - sprintf( + RuleErrorBuilder::message(sprintf( 'Using %s%s is forbidden, %s', $disallowedConstant->getConstant(), $displayName && $displayName !== $disallowedConstant->getConstant() ? ' (as ' . $displayName . ')' : '', $disallowedConstant->getMessage() - ), + )) + ->identifier($disallowedConstant->getErrorIdentifier()) + ->build(), ]; } } diff --git a/src/DisallowedNamespace.php b/src/DisallowedNamespace.php index ba53a7d..c15e136 100644 --- a/src/DisallowedNamespace.php +++ b/src/DisallowedNamespace.php @@ -15,17 +15,22 @@ class DisallowedNamespace /** @var string[] */ private $allowIn; + /** @var string */ + private $errorIdentifier; + /** * @param string $namespace * @param string|null $message * @param string[] $allowIn + * @param string $errorIdentifier */ - public function __construct(string $namespace, ?string $message, array $allowIn) + public function __construct(string $namespace, ?string $message, array $allowIn, string $errorIdentifier) { $this->namespace = ltrim($namespace, '\\'); $this->message = $message; $this->allowIn = $allowIn; + $this->errorIdentifier = $errorIdentifier; } @@ -49,4 +54,10 @@ public function getAllowIn(): array return $this->allowIn; } + + public function getErrorIdentifier(): string + { + return $this->errorIdentifier; + } + } diff --git a/src/DisallowedNamespaceFactory.php b/src/DisallowedNamespaceFactory.php index 6262d5c..40b42c7 100644 --- a/src/DisallowedNamespaceFactory.php +++ b/src/DisallowedNamespaceFactory.php @@ -7,7 +7,7 @@ class DisallowedNamespaceFactory { /** - * @param array, allowParamsAnywhere?:array}> $config + * @param array, allowParamsAnywhere?:array, errorIdentifier?:string}> $config * @return DisallowedNamespace[] */ public function createFromConfig(array $config): array @@ -17,7 +17,8 @@ public function createFromConfig(array $config): array $disallowed = new DisallowedNamespace( $disallowed['namespace'], $disallowed['message'] ?? null, - $disallowed['allowIn'] ?? [] + $disallowed['allowIn'] ?? [], + $disallowed['errorIdentifier'] ?? '' ); $disallowedNamespaces[$disallowed->getNamespace()] = $disallowed; } diff --git a/src/DisallowedNamespaceHelper.php b/src/DisallowedNamespaceHelper.php index 6bd393b..a887154 100644 --- a/src/DisallowedNamespaceHelper.php +++ b/src/DisallowedNamespaceHelper.php @@ -4,6 +4,8 @@ namespace Spaze\PHPStan\Rules\Disallowed; use PHPStan\Analyser\Scope; +use PHPStan\Rules\RuleError; +use PHPStan\Rules\RuleErrorBuilder; class DisallowedNamespaceHelper { @@ -39,7 +41,7 @@ private function isAllowed(Scope $scope, DisallowedNamespace $disallowedNamespac * @param string $namespace * @param Scope $scope * @param DisallowedNamespace[] $disallowedNamespaces - * @return string[] + * @return RuleError[] */ public function getDisallowedMessage(string $namespace, Scope $scope, array $disallowedNamespaces): array { @@ -53,12 +55,14 @@ public function getDisallowedMessage(string $namespace, Scope $scope, array $dis } return [ - sprintf( + RuleErrorBuilder::message(sprintf( 'Namespace %s is forbidden, %s%s', $namespace, $disallowedNamespace->getMessage(), $disallowedNamespace->getNamespace() !== $namespace ? " [{$namespace} matches {$disallowedNamespace->getNamespace()}]" : '' - ), + )) + ->identifier($disallowedNamespace->getErrorIdentifier()) + ->build(), ]; } diff --git a/src/Usages/ClassConstantUsages.php b/src/Usages/ClassConstantUsages.php index 92bd16e..17515b1 100644 --- a/src/Usages/ClassConstantUsages.php +++ b/src/Usages/ClassConstantUsages.php @@ -8,6 +8,8 @@ use PhpParser\Node\Identifier; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; +use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\TypeWithClassName; @@ -54,7 +56,7 @@ public function getNodeType(): string /** * @param ClassConstFetch $node * @param Scope $scope - * @return string[] + * @return RuleError[] * @throws ShouldNotHappenException */ public function processNode(Node $node, Scope $scope): array @@ -78,11 +80,11 @@ public function processNode(Node $node, Scope $scope): array } else { if ($usedOnType->hasConstant($constant)->no()) { return [ - sprintf( + RuleErrorBuilder::message(sprintf( 'Cannot access constant %s on %s', $constant, $usedOnType->describe(VerbosityLevel::getRecommendedLevelByType($usedOnType)) - ), + ))->build(), ]; } else { $className = $usedOnType->getConstant($constant)->getDeclaringClass()->getDisplayName(); diff --git a/src/Usages/ConstantUsages.php b/src/Usages/ConstantUsages.php index b40479f..24e81f9 100644 --- a/src/Usages/ConstantUsages.php +++ b/src/Usages/ConstantUsages.php @@ -7,6 +7,7 @@ use PhpParser\Node\Expr\ConstFetch; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use PHPStan\ShouldNotHappenException; use Spaze\PHPStan\Rules\Disallowed\DisallowedConstant; use Spaze\PHPStan\Rules\Disallowed\DisallowedConstantFactory; @@ -50,7 +51,7 @@ public function getNodeType(): string /** * @param ConstFetch $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array { diff --git a/src/Usages/NamespaceUsages.php b/src/Usages/NamespaceUsages.php index 58d23b9..ff5ba7c 100644 --- a/src/Usages/NamespaceUsages.php +++ b/src/Usages/NamespaceUsages.php @@ -13,6 +13,7 @@ use PhpParser\Node\Stmt\UseUse; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleError; use Spaze\PHPStan\Rules\Disallowed\DisallowedNamespace; use Spaze\PHPStan\Rules\Disallowed\DisallowedNamespaceFactory; use Spaze\PHPStan\Rules\Disallowed\DisallowedNamespaceHelper; @@ -51,7 +52,7 @@ public function getNodeType(): string /** * @param Node $node * @param Scope $scope - * @return string[] + * @return RuleError[] */ public function processNode(Node $node, Scope $scope): array {