Skip to content

Commit 37d0a83

Browse files
authored
Implement DeclareStrictTypesRule
1 parent c6898a1 commit 37d0a83

14 files changed

+258
-0
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ lint:
6464
--exclude tests/PHPStan/Rules/Types/data/invalid-union-with-never.php \
6565
--exclude tests/PHPStan/Rules/Types/data/invalid-union-with-void.php \
6666
--exclude tests/PHPStan/Rules/Constants/data/dynamic-class-constant-fetch.php \
67+
--exclude tests/PHPStan/Rules/Keywords/data/declare-position.php \
68+
--exclude tests/PHPStan/Rules/Keywords/data/declare-position2.php \
69+
--exclude tests/PHPStan/Rules/Keywords/data/declare-position-nested.php \
70+
--exclude tests/PHPStan/Rules/Keywords/data/declare-strict-nonsense.php \
71+
--exclude tests/PHPStan/Rules/Keywords/data/declare-strict-nonsense-bool.php \
6772
src tests
6873

6974
cs:

conf/config.level0.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ rules:
8181
- PHPStan\Rules\Functions\ReturnNullsafeByRefRule
8282
- PHPStan\Rules\Functions\VariadicParametersDeclarationRule
8383
- PHPStan\Rules\Keywords\ContinueBreakInLoopRule
84+
- PHPStan\Rules\Keywords\DeclareStrictTypesRule
8485
- PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule
8586
- PHPStan\Rules\Methods\AbstractPrivateMethodRule
8687
- PHPStan\Rules\Methods\CallMethodsRule

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,11 @@ services:
644644
tags:
645645
- phpstan.parser.richParserNodeVisitor
646646

647+
-
648+
class: PHPStan\Parser\DeclarePositionVisitor
649+
tags:
650+
- phpstan.parser.richParserNodeVisitor
651+
647652
-
648653
class: PHPStan\Parallel\ParallelAnalyser
649654
arguments:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\NodeVisitorAbstract;
7+
8+
class DeclarePositionVisitor extends NodeVisitorAbstract
9+
{
10+
11+
private bool $isFirstStatement = true;
12+
13+
public const ATTRIBUTE_NAME = 'isFirstStatement';
14+
15+
public function beforeTraverse(array $nodes): ?array
16+
{
17+
$this->isFirstStatement = true;
18+
return null;
19+
}
20+
21+
public function enterNode(Node $node): ?Node
22+
{
23+
if ($node instanceof Node\Stmt) {
24+
if ($node instanceof Node\Stmt\Declare_) {
25+
$node->setAttribute(self::ATTRIBUTE_NAME, $this->isFirstStatement);
26+
}
27+
28+
$this->isFirstStatement = false;
29+
}
30+
31+
return null;
32+
}
33+
34+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Keywords;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Node\Printer\ExprPrinter;
9+
use PHPStan\Parser\DeclarePositionVisitor;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
use function in_array;
13+
use function sprintf;
14+
15+
/**
16+
* @implements Rule<Stmt\Declare_>
17+
*/
18+
class DeclareStrictTypesRule implements Rule
19+
{
20+
21+
public function __construct(
22+
private readonly ExprPrinter $exprPrinter,
23+
)
24+
{
25+
}
26+
27+
public function getNodeType(): string
28+
{
29+
return Stmt\Declare_::class;
30+
}
31+
32+
public function processNode(Node $node, Scope $scope): array
33+
{
34+
$declaresStrictTypes = false;
35+
foreach ($node->declares as $declare) {
36+
if (
37+
$declare->key->name !== 'strict_types'
38+
) {
39+
continue;
40+
}
41+
42+
if (
43+
!$declare->value instanceof Node\Scalar\LNumber
44+
|| !in_array($declare->value->value, [0, 1], true)
45+
) {
46+
return [
47+
RuleErrorBuilder::message(sprintf(
48+
sprintf(
49+
'Declare strict_types must have 0 or 1 as its value, %s given.',
50+
$this->exprPrinter->printExpr($declare->value),
51+
),
52+
))->nonIgnorable()->build(),
53+
];
54+
}
55+
56+
$declaresStrictTypes = true;
57+
break;
58+
}
59+
60+
if ($declaresStrictTypes === false) {
61+
return [];
62+
}
63+
64+
if (!$node->hasAttribute(DeclarePositionVisitor::ATTRIBUTE_NAME)) {
65+
return [];
66+
}
67+
68+
$isFirstStatement = (bool) $node->getAttribute(DeclarePositionVisitor::ATTRIBUTE_NAME);
69+
if ($isFirstStatement) {
70+
return [];
71+
}
72+
73+
return [
74+
RuleErrorBuilder::message(sprintf(
75+
'Declare strict_types must be the very first statement.',
76+
))->nonIgnorable()->build(),
77+
];
78+
}
79+
80+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Keywords;
4+
5+
use PHPStan\Node\Printer\ExprPrinter;
6+
use PHPStan\Node\Printer\Printer;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Testing\RuleTestCase;
9+
10+
/**
11+
* @extends RuleTestCase<DeclareStrictTypesRule>
12+
*/
13+
class DeclareStrictTypesRuleTest extends RuleTestCase
14+
{
15+
16+
protected function getRule(): Rule
17+
{
18+
return new DeclareStrictTypesRule(new ExprPrinter(new Printer()));
19+
}
20+
21+
public function testRule(): void
22+
{
23+
$this->analyse([__DIR__ . '/data/declare-position.php'], [
24+
[
25+
'Declare strict_types must be the very first statement.',
26+
5,
27+
],
28+
]);
29+
}
30+
31+
public function testRule2(): void
32+
{
33+
$this->analyse([__DIR__ . '/data/declare-position2.php'], [
34+
[
35+
'Declare strict_types must be the very first statement.',
36+
1,
37+
],
38+
]);
39+
}
40+
41+
public function testNested(): void
42+
{
43+
$this->analyse([__DIR__ . '/data/declare-position-nested.php'], [
44+
[
45+
'Declare strict_types must be the very first statement.',
46+
7,
47+
],
48+
[
49+
'Declare strict_types must be the very first statement.',
50+
12,
51+
],
52+
]);
53+
}
54+
55+
public function testValidPosition(): void
56+
{
57+
$this->analyse([__DIR__ . '/data/declare-position-valid.php'], []);
58+
}
59+
60+
public function testTicks(): void
61+
{
62+
$this->analyse([__DIR__ . '/data/declare-ticks.php'], []);
63+
}
64+
65+
public function testMulti(): void
66+
{
67+
$this->analyse([__DIR__ . '/data/declare-multi.php'], []);
68+
}
69+
70+
public function testNonsense(): void
71+
{
72+
$this->analyse([__DIR__ . '/data/declare-strict-nonsense.php'], [
73+
[
74+
"Declare strict_types must have 0 or 1 as its value, 'foo' given.",
75+
1,
76+
],
77+
]);
78+
}
79+
80+
public function testNonsenseBool(): void
81+
{
82+
$this->analyse([__DIR__ . '/data/declare-strict-nonsense-bool.php'], [
83+
[
84+
'Declare strict_types must have 0 or 1 as its value, \true given.',
85+
1,
86+
],
87+
]);
88+
}
89+
90+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php declare(ticks=1, strict_types = 1);
2+
3+
namespace ValidMultiDeclare;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace DeclarePositionDeep;
4+
5+
class X {
6+
public function doFoo() {
7+
declare(strict_types = 1);
8+
}
9+
}
10+
11+
function doFoo() {
12+
declare(strict_types = 1);
13+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ValidDeclarePosition;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php declare(strict_types = 1);
2+
3+
use \stdClass;
4+
5+
declare(strict_types=1);
6+

0 commit comments

Comments
 (0)