Skip to content

Commit 443837c

Browse files
b1rdexondrejmirtes
authored andcommitted
Check datetime instantiation
1 parent fb95fab commit 443837c

File tree

6 files changed

+146
-0
lines changed

6 files changed

+146
-0
lines changed

conf/bleedingEdge.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ parameters:
77
fileWhitespace: true
88
unusedClassElements: true
99
readComposerPhpVersion: true
10+
dateTimeInstantiation: true
1011
stubFiles:
1112
- ../stubs/SplObjectStorage.stub

conf/config.level5.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ includes:
44
conditionalTags:
55
PHPStan\Rules\Functions\RandomIntParametersRule:
66
phpstan.rules.rule: %featureToggles.randomIntParameters%
7+
PHPStan\Rules\DateTimeInstantiationRule:
8+
phpstan.rules.rule: %featureToggles.dateTimeInstantiation%
79

810
parameters:
911
checkFunctionArgumentTypes: true
@@ -15,3 +17,5 @@ services:
1517
class: PHPStan\Rules\Functions\RandomIntParametersRule
1618
arguments:
1719
reportMaybes: %reportMaybes%
20+
-
21+
class: PHPStan\Rules\DateTimeInstantiationRule

conf/config.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ parameters:
1919
fileWhitespace: false
2020
unusedClassElements: false
2121
readComposerPhpVersion: false
22+
dateTimeInstantiation: false
2223
fileExtensions:
2324
- php
2425
checkAlwaysTrueCheckTypeFunctionCall: false
@@ -151,6 +152,7 @@ parametersSchema:
151152
fileWhitespace: bool(),
152153
unusedClassElements: bool(),
153154
readComposerPhpVersion: bool()
155+
dateTimeInstantiation: bool()
154156
])
155157
fileExtensions: listOf(string())
156158
checkAlwaysTrueCheckTypeFunctionCall: bool()
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules;
4+
5+
use DateTime;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr\New_;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Type\Constant\ConstantStringType;
10+
11+
/**
12+
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\New_>
13+
*/
14+
class DateTimeInstantiationRule implements \PHPStan\Rules\Rule
15+
{
16+
17+
public function getNodeType(): string
18+
{
19+
return New_::class;
20+
}
21+
22+
/**
23+
* @param New_ $node
24+
*/
25+
public function processNode(Node $node, Scope $scope): array
26+
{
27+
if (
28+
!($node->class instanceof \PhpParser\Node\Name)
29+
|| \count($node->args) === 0
30+
|| !\in_array(strtolower((string) $node->class), ['datetime', 'datetimeimmutable'], true)
31+
) {
32+
return [];
33+
}
34+
35+
$arg = $scope->getType($node->args[0]->value);
36+
if (!($arg instanceof ConstantStringType)) {
37+
return [];
38+
}
39+
40+
$errors = [];
41+
$dateString = $arg->getValue();
42+
try {
43+
new DateTime($dateString);
44+
} catch (\Throwable $e) {
45+
// an exception is thrown for errors only but we want to catch warnings too
46+
}
47+
$lastErrors = DateTime::getLastErrors();
48+
if ($lastErrors !== false) {
49+
foreach ($lastErrors['warnings'] as $warning) {
50+
$errors[] = RuleErrorBuilder::message(sprintf(
51+
'Instantiating %s with %s produces a warning: %s',
52+
(string) $node->class,
53+
$dateString,
54+
$warning
55+
))->build();
56+
}
57+
foreach ($lastErrors['errors'] as $error) {
58+
$errors[] = RuleErrorBuilder::message(sprintf(
59+
'Instantiating %s with %s produces an error: %s',
60+
(string) $node->class,
61+
$dateString,
62+
$error
63+
))->build();
64+
}
65+
}
66+
67+
return $errors;
68+
}
69+
70+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules;
4+
5+
/**
6+
* @extends \PHPStan\Testing\RuleTestCase<DateTimeInstantiationRule>
7+
*/
8+
class DateTimeInstantiationRuleTest extends \PHPStan\Testing\RuleTestCase
9+
{
10+
11+
protected function getRule(): \PHPStan\Rules\Rule
12+
{
13+
return new DateTimeInstantiationRule();
14+
}
15+
16+
public function test(): void
17+
{
18+
$this->analyse(
19+
[__DIR__ . '/data/datetime-instantiation.php'],
20+
[
21+
[
22+
'Instantiating DateTime with 2020.11.17 produces an error: Double time specification',
23+
3,
24+
],
25+
[
26+
'Instantiating DateTimeImmutable with asdfasdf produces a warning: Double timezone specification',
27+
5,
28+
],
29+
[
30+
'Instantiating DateTimeImmutable with asdfasdf produces an error: The timezone could not be found in the database',
31+
5,
32+
],
33+
[
34+
'Instantiating DateTimeImmutable with 2020.11.17 produces an error: Double time specification',
35+
10,
36+
],
37+
[
38+
'Instantiating DateTimeImmutable with 2020.11.18 produces an error: Double time specification',
39+
17,
40+
],
41+
[
42+
'Instantiating DateTime with 2020-04-31 produces a warning: The parsed date was invalid',
43+
20,
44+
],
45+
]
46+
);
47+
}
48+
49+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
new DateTime('2020.11.17');
4+
5+
new DateTimeImmutable('asdfasdf');
6+
7+
new DateTime('');
8+
9+
$test = '2020.11.17';
10+
new DateTimeImmutable($test);
11+
12+
/**
13+
* @param '2020.11.18' $date2
14+
*/
15+
function foo(string $date, string $date2): void {
16+
new DateTime($date);
17+
new DateTimeImmutable($date2);
18+
}
19+
20+
new DateTime('2020-04-31');

0 commit comments

Comments
 (0)