diff --git a/conf/bleedingEdge.neon b/conf/bleedingEdge.neon index a68d1a352a2..e8e0152c6a1 100644 --- a/conf/bleedingEdge.neon +++ b/conf/bleedingEdge.neon @@ -16,3 +16,4 @@ parameters: reportNestedTooWideType: false # tmp assignToByRefForeachExpr: true curlSetOptArrayTypes: true + checkDateIntervalConstructor: true diff --git a/conf/config.level5.neon b/conf/config.level5.neon index b4518ba7e2c..534d58dde0b 100644 --- a/conf/config.level5.neon +++ b/conf/config.level5.neon @@ -10,6 +10,8 @@ conditionalTags: phpstan.rules.rule: %featureToggles.checkParameterCastableToNumberFunctions% PHPStan\Rules\Functions\PrintfParameterTypeRule: phpstan.rules.rule: %featureToggles.checkPrintfParameterTypes% + PHPStan\Rules\DateIntervalInstantiationRule: + phpstan.rules.rule: %featureToggles.checkDateIntervalConstructor% autowiredAttributeServices: # registers rules with #[RegisteredRule] attribute @@ -22,3 +24,5 @@ services: class: PHPStan\Rules\Functions\PrintfParameterTypeRule arguments: checkStrictPrintfPlaceholderTypes: %checkStrictPrintfPlaceholderTypes% + - + class: PHPStan\Rules\DateIntervalInstantiationRule diff --git a/conf/config.neon b/conf/config.neon index 6c71dae9225..fcbb79b1d81 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -43,6 +43,7 @@ parameters: reportNestedTooWideType: false assignToByRefForeachExpr: false curlSetOptArrayTypes: false + checkDateIntervalConstructor: false fileExtensions: - php checkAdvancedIsset: false diff --git a/conf/parametersSchema.neon b/conf/parametersSchema.neon index bc79fe7c401..7cff420cc3f 100644 --- a/conf/parametersSchema.neon +++ b/conf/parametersSchema.neon @@ -45,6 +45,7 @@ parametersSchema: reportNestedTooWideType: bool() assignToByRefForeachExpr: bool() curlSetOptArrayTypes: bool() + checkDateIntervalConstructor: bool() ]) fileExtensions: listOf(string()) checkAdvancedIsset: bool() diff --git a/src/Rules/DateIntervalInstantiationRule.php b/src/Rules/DateIntervalInstantiationRule.php new file mode 100644 index 00000000000..97e5753aa1d --- /dev/null +++ b/src/Rules/DateIntervalInstantiationRule.php @@ -0,0 +1,60 @@ + + */ +final class DateIntervalInstantiationRule implements Rule +{ + + public function getNodeType(): string + { + return New_::class; + } + + /** + * @param New_ $node + */ + public function processNode(Node $node, Scope $scope): array + { + if (!$node->class instanceof Node\Name) { + return []; + } + + if ( + count($node->getArgs()) === 0 + || strtolower((string) $node->class) !== 'dateinterval' + ) { + return []; + } + + $arg = $scope->getType($node->getArgs()[0]->value); + $errors = []; + + foreach ($arg->getConstantStrings() as $constantString) { + $dateIntervalString = $constantString->getValue(); + try { + new DateInterval($dateIntervalString); + } catch (Throwable $e) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Instantiating DateInterval with %s produces an error: %s', + $dateIntervalString, + $e->getMessage(), + ))->identifier('new.dateInterval')->build(); + } + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/DateIntervalInstantiationRuleTest.php b/tests/PHPStan/Rules/DateIntervalInstantiationRuleTest.php new file mode 100644 index 00000000000..5d8bc57ef9f --- /dev/null +++ b/tests/PHPStan/Rules/DateIntervalInstantiationRuleTest.php @@ -0,0 +1,54 @@ + + */ +class DateIntervalInstantiationRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new DateIntervalInstantiationRule(); + } + + public function test(): void + { + if (PHP_VERSION_ID < 80100) { + $prefix = 'DateInterval::__construct(): '; + } else { + $prefix = ''; + } + + $this->analyse( + [__DIR__ . '/data/date-interval-instantiation.php'], + [ + [ + 'Instantiating DateInterval with 1M produces an error: ' . $prefix . 'Unknown or bad format (1M)', + 5, + ], + [ + 'Instantiating DateInterval with asdfasdf produces an error: ' . $prefix . 'Unknown or bad format (asdfasdf)', + 18, + ], + [ + 'Instantiating DateInterval with produces an error: ' . $prefix . 'Unknown or bad format ()', + 21, + ], + [ + 'Instantiating DateInterval with 1M produces an error: ' . $prefix . 'Unknown or bad format (1M)', + 30, + ], + [ + 'Instantiating DateInterval with invalid produces an error: ' . $prefix . 'Unknown or bad format (invalid)', + 37, + ], + ], + ); + } + +} diff --git a/tests/PHPStan/Rules/data/date-interval-instantiation.php b/tests/PHPStan/Rules/data/date-interval-instantiation.php new file mode 100644 index 00000000000..eae4ee12d1c --- /dev/null +++ b/tests/PHPStan/Rules/data/date-interval-instantiation.php @@ -0,0 +1,38 @@ +