Skip to content

Commit e30b538

Browse files
committed
Refactor and adapt the rules to the new PHPStan APIs
1 parent b99a895 commit e30b538

File tree

5 files changed

+106
-46
lines changed

5 files changed

+106
-46
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace TheCodingMachine\Safe\PHPStan\Rules\Error;
4+
5+
use PhpParser\Node\Name;
6+
7+
class SafeClassRuleError extends SafeRuleError
8+
{
9+
public function __construct(Name $className, int $line)
10+
{
11+
parent::__construct(
12+
"Class $className is unsafe to use. Its methods can return FALSE instead of throwing an exception. Please add 'use Safe\\$className;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library.",
13+
$line,
14+
);
15+
}
16+
17+
public function getIdentifier(): string
18+
{
19+
return self::IDENTIFIER_PREFIX . 'class';
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace TheCodingMachine\Safe\PHPStan\Rules\Error;
4+
5+
use PhpParser\Node\Name;
6+
7+
class SafeFunctionRuleError extends SafeRuleError
8+
{
9+
public function __construct(Name $nodeName, int $line)
10+
{
11+
$functionName = $nodeName->toString();
12+
13+
parent::__construct(
14+
"Function $functionName is unsafe to use. It can return FALSE instead of throwing an exception. Please add 'use function Safe\\$functionName;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library.",
15+
$line,
16+
);
17+
}
18+
19+
public function getIdentifier(): string
20+
{
21+
return self::IDENTIFIER_PREFIX . 'class';
22+
}
23+
}

src/Rules/Error/SafeRuleError.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace TheCodingMachine\Safe\PHPStan\Rules\Error;
4+
5+
use PHPStan\Rules\IdentifierRuleError;
6+
use PHPStan\Rules\LineRuleError;
7+
use PHPStan\Rules\RuleError;
8+
9+
abstract class SafeRuleError implements RuleError, LineRuleError, IdentifierRuleError
10+
{
11+
protected const IDENTIFIER_PREFIX = 'theCodingMachineSafe.';
12+
13+
private string $message;
14+
private int $line;
15+
16+
public function __construct(string $message, int $line)
17+
{
18+
$this->message = $message;
19+
$this->line = $line;
20+
}
21+
22+
public function getMessage(): string
23+
{
24+
return $this->message;
25+
}
26+
27+
public function getLine(): int
28+
{
29+
return $this->line;
30+
}
31+
}

src/Rules/UseSafeClassesRule.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,9 @@
55

66
use PhpParser\Node;
77
use PHPStan\Analyser\Scope;
8-
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\MethodReflection;
108
use PHPStan\Rules\Rule;
11-
use PHPStan\ShouldNotHappenException;
9+
use TheCodingMachine\Safe\PHPStan\Rules\Error\SafeClassRuleError;
1210
use TheCodingMachine\Safe\PHPStan\Utils\ClassListLoader;
13-
use TheCodingMachine\Safe\PHPStan\Utils\FunctionListLoader;
14-
use PhpParser\Node\Arg;
15-
use PhpParser\Node\Expr;
16-
use PhpParser\Node\Scalar;
1711

1812
/**
1913
* This rule checks that no "unsafe" classes are instantiated in code.
@@ -27,11 +21,6 @@ public function getNodeType(): string
2721
return Node\Expr\New_::class;
2822
}
2923

30-
/**
31-
* @param Node\Expr\New_ $node
32-
* @param \PHPStan\Analyser\Scope $scope
33-
* @return string[]
34-
*/
3524
public function processNode(Node $node, Scope $scope): array
3625
{
3726
$classNode = $node->class;
@@ -43,7 +32,9 @@ public function processNode(Node $node, Scope $scope): array
4332
$unsafeClasses = ClassListLoader::getClassList();
4433

4534
if (isset($unsafeClasses[$className])) {
46-
return ["Class $className is unsafe to use. Its methods can return FALSE instead of throwing an exception. Please add 'use Safe\\$className;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library."];
35+
return [
36+
new SafeClassRuleError($classNode, $node->getStartLine()),
37+
];
4738
}
4839

4940
return [];

src/Rules/UseSafeFunctionsRule.php

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
namespace TheCodingMachine\Safe\PHPStan\Rules;
55

66
use PhpParser\Node;
7-
use PHPStan\Analyser\Scope;
8-
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\MethodReflection;
10-
use PHPStan\Rules\Rule;
11-
use PHPStan\ShouldNotHappenException;
12-
use TheCodingMachine\Safe\PHPStan\Utils\FunctionListLoader;
137
use PhpParser\Node\Arg;
148
use PhpParser\Node\Expr;
159
use PhpParser\Node\Scalar;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Rules\Rule;
12+
use TheCodingMachine\Safe\PHPStan\Rules\Error\SafeFunctionRuleError;
13+
use TheCodingMachine\Safe\PHPStan\Utils\FunctionListLoader;
1614

1715
/**
1816
* This rule checks that no "unsafe" functions are used in code.
@@ -26,11 +24,6 @@ public function getNodeType(): string
2624
return Node\Expr\FuncCall::class;
2725
}
2826

29-
/**
30-
* @param Node\Expr\FuncCall $node
31-
* @param \PHPStan\Analyser\Scope $scope
32-
* @return string[]
33-
*/
3427
public function processNode(Node $node, Scope $scope): array
3528
{
3629
if (!$node->name instanceof Node\Name) {
@@ -40,45 +33,46 @@ public function processNode(Node $node, Scope $scope): array
4033
$unsafeFunctions = FunctionListLoader::getFunctionList();
4134

4235
if (isset($unsafeFunctions[$functionName])) {
43-
if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
44-
if ($functionName === "json_decode") {
45-
if (count($node->args) == 4) {
46-
if ($this->argValueIncludeJSONTHROWONERROR($node->args[3])) {
47-
return [];
48-
}
49-
}
50-
}
51-
if ($functionName === "json_encode") {
52-
if (count($node->args) >= 2) {
53-
if ($this->argValueIncludeJSONTHROWONERROR($node->args[1])) {
54-
return [];
55-
}
56-
}
57-
}
36+
if (
37+
$functionName === "json_decode"
38+
&& $this->argValueIncludeJSONTHROWONERROR($node->getArgs()[3] ?? null)
39+
) {
40+
return [];
41+
}
42+
43+
if (
44+
$functionName === "json_encode"
45+
&& $this->argValueIncludeJSONTHROWONERROR($node->getArgs()[1] ?? null)
46+
) {
47+
return [];
5848
}
5949

60-
return ["Function $functionName is unsafe to use. It can return FALSE instead of throwing an exception. Please add 'use function Safe\\$functionName;' at the beginning of the file to use the variant provided by the 'thecodingmachine/safe' library."];
50+
return [new SafeFunctionRuleError($node->name, $node->getStartLine())];
6151
}
6252

6353
return [];
6454
}
6555

66-
private function argValueIncludeJSONTHROWONERROR(Arg $arg): bool
56+
private function argValueIncludeJSONTHROWONERROR(?Arg $arg): bool
6757
{
68-
$parseValue = function ($expr, array $options) use (&$parseValue): array {
58+
if ($arg === null) {
59+
return false;
60+
}
61+
62+
$parseValue = static function ($expr, array $options) use (&$parseValue): array {
6963
if ($expr instanceof Expr\BinaryOp\BitwiseOr) {
7064
return array_merge($parseValue($expr->left, $options), $parseValue($expr->right, $options));
7165
} elseif ($expr instanceof Expr\ConstFetch) {
72-
return array_merge($options, $expr->name->parts);
73-
} elseif ($expr instanceof Scalar\LNumber) {
66+
return array_merge($options, $expr->name->getParts());
67+
} elseif ($expr instanceof Scalar\Int_) {
7468
return array_merge($options, [$expr->value]);
7569
} else {
7670
return $options;
7771
}
7872
};
7973
$options = $parseValue($arg->value, []);
8074

81-
if (in_array("JSON_THROW_ON_ERROR", $options)) {
75+
if (in_array("JSON_THROW_ON_ERROR", $options, true)) {
8276
return true;
8377
}
8478

@@ -87,6 +81,6 @@ private function argValueIncludeJSONTHROWONERROR(Arg $arg): bool
8781
return ($element & 4194304) == 4194304;
8882
}, array_filter($options, function ($element) {
8983
return is_int($element);
90-
})));
84+
})), true);
9185
}
9286
}

0 commit comments

Comments
 (0)