Skip to content

Commit

Permalink
PHP 8 - report deprecated required parameter after optional
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 16, 2020
1 parent bbda299 commit cc864d2
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Php/PhpVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@ public function supportsNativeUnionTypes(): bool
return $this->versionId >= 80000;
}

public function deprecatesRequiredParameterAfterOptional(): bool
{
return $this->versionId >= 80000;
}

}
56 changes: 56 additions & 0 deletions src/Rules/FunctionDefinitionCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules;

use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
Expand Down Expand Up @@ -108,6 +109,7 @@ public function checkAnonymousFunction(
$errors[] = RuleErrorBuilder::message($unionTypesMessage)->line($param->getLine())->nonIgnorable()->build();
$unionTypeReported = true;
}

if (!$param->var instanceof Variable || !is_string($param->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
Expand All @@ -129,6 +131,10 @@ public function checkAnonymousFunction(
}
}

if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) {
$errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameters));
}

if ($returnTypeNode === null) {
return $errors;
}
Expand Down Expand Up @@ -221,6 +227,10 @@ private function checkParametersAcceptor(
}
}

if ($this->phpVersion->deprecatesRequiredParameterAfterOptional()) {
$errors = array_merge($errors, $this->checkRequiredParameterAfterOptional($parameterNodes));
}

$returnTypeNode = $functionNode->getReturnType() ?? $functionNode;
foreach ($parametersAcceptor->getParameters() as $parameter) {
$referencedClasses = $this->getParameterReferencedClasses($parameter);
Expand Down Expand Up @@ -294,6 +304,52 @@ private function checkParametersAcceptor(
return $errors;
}

/**
* @param Param[] $parameterNodes
* @return RuleError[]
*/
private function checkRequiredParameterAfterOptional(array $parameterNodes): array
{
/** @var string|null $optionalParameter */
$optionalParameter = null;
$errors = [];
foreach ($parameterNodes as $parameterNode) {
if (!$parameterNode->var instanceof Variable) {
throw new \PHPStan\ShouldNotHappenException();
}
if (!is_string($parameterNode->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
$parameterName = $parameterNode->var->name;
if ($optionalParameter !== null && $parameterNode->default === null) {
$errors[] = RuleErrorBuilder::message(sprintf('Deprecated in PHP 8.0: Required parameter $%s follows optional parameter $%s.', $parameterName, $optionalParameter))->line($parameterNode->getStartLine())->nonIgnorable()->build();
continue;
}
if ($parameterNode->default === null) {
continue;
}
if ($parameterNode->type === null) {
$optionalParameter = $parameterName;
continue;
}

$defaultValue = $parameterNode->default;
if (!$defaultValue instanceof ConstFetch) {
$optionalParameter = $parameterName;
continue;
}

$constantName = $defaultValue->name->toLowerString();
if ($constantName === 'null') {
continue;
}

$optionalParameter = $parameterName;
}

return $errors;
}

/**
* @param string $parameterName
* @param Param[] $parameterNodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,42 @@ public function testNativeUnionTypes(int $phpVersionId, array $errors): void
$this->analyse([__DIR__ . '/data/native-union-types.php'], $errors);
}

public function dataRequiredParameterAfterOptional(): array
{
return [
[
70400,
[],
],
[
80000,
[
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
5,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
9,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
11,
],
],
],
];
}

/**
* @dataProvider dataRequiredParameterAfterOptional
* @param int $phpVersionId
* @param mixed[] $errors
*/
public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void
{
$this->phpVersionId = $phpVersionId;
$this->analyse([__DIR__ . '/data/required-parameter-after-optional-arrow.php'], $errors);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,42 @@ public function testNativeUnionTypes(int $phpVersionId, array $errors): void
$this->analyse([__DIR__ . '/data/native-union-types.php'], $errors);
}

public function dataRequiredParameterAfterOptional(): array
{
return [
[
70400,
[],
],
[
80000,
[
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
5,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
13,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
17,
],
],
],
];
}

/**
* @dataProvider dataRequiredParameterAfterOptional
* @param int $phpVersionId
* @param mixed[] $errors
*/
public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void
{
$this->phpVersionId = $phpVersionId;
$this->analyse([__DIR__ . '/data/required-parameter-after-optional-closures.php'], $errors);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,42 @@ public function testNativeUnionTypes(int $phpVersionId, array $errors): void
$this->analyse([__DIR__ . '/data/native-union-types.php'], $errors);
}

public function dataRequiredParameterAfterOptional(): array
{
return [
[
70400,
[],
],
[
80000,
[
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
5,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
14,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
18,
],
],
],
];
}

/**
* @dataProvider dataRequiredParameterAfterOptional
* @param int $phpVersionId
* @param mixed[] $errors
*/
public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void
{
$this->phpVersionId = $phpVersionId;
$this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php // lint >= 7.4

namespace RequiredAfterOptional;

fn ($foo = null, $bar): int => 1; // not OK

fn (int $foo = null, $bar): int => 1; // is OK

fn (int $foo = 1, $bar): int => 1; // not OK

fn (bool $foo = true, $bar): int => 1; // not OK
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace RequiredAfterOptional;

function ($foo = null, $bar): void // not OK
{
};

function (int $foo = null, $bar): void // is OK
{
};

function (int $foo = 1, $bar): void // not OK
{
};

function(bool $foo = true, $bar): void // not OK
{
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace RequiredAfterOptional;

function doFoo($foo = null, $bar): void // not OK
{

}

function doBar(int $foo = null, $bar): void // is OK
{
}

function doBaz(int $foo = 1, $bar): void // not OK
{
}

function doLorem(bool $foo = true, $bar): void // not OK
{
}
38 changes: 38 additions & 0 deletions tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,42 @@ public function testNativeUnionTypes(int $phpVersionId, array $errors): void
$this->analyse([__DIR__ . '/data/native-union-types.php'], $errors);
}

public function dataRequiredParameterAfterOptional(): array
{
return [
[
70400,
[],
],
[
80000,
[
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
8,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
17,
],
[
'Deprecated in PHP 8.0: Required parameter $bar follows optional parameter $foo.',
21,
],
],
],
];
}

/**
* @dataProvider dataRequiredParameterAfterOptional
* @param int $phpVersionId
* @param mixed[] $errors
*/
public function testRequiredParameterAfterOptional(int $phpVersionId, array $errors): void
{
$this->phpVersionId = $phpVersionId;
$this->analyse([__DIR__ . '/data/required-parameter-after-optional.php'], $errors);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace RequiredAfterOptional;

class Foo
{

public function doFoo($foo = null, $bar): void // not OK
{

}

public function doBar(int $foo = null, $bar): void // is OK
{
}

public function doBaz(int $foo = 1, $bar): void // not OK
{
}

public function doLorem(bool $foo = true, $bar): void // not OK
{
}

}

0 comments on commit cc864d2

Please sign in to comment.