Skip to content

Commit

Permalink
Level 5 - ImplodeFunctionRule
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm committed Sep 22, 2021
1 parent ac7a60a commit be79bce
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 0 deletions.
2 changes: 2 additions & 0 deletions conf/config.level5.neon
Expand Up @@ -11,6 +11,8 @@ parameters:
checkFunctionArgumentTypes: true
checkArgumentsPassedByReference: true

rules:
- PHPStan\Rules\Functions\ImplodeFunctionRule

services:
-
Expand Down
82 changes: 82 additions & 0 deletions src/Rules/Functions/ImplodeFunctionRule.php
@@ -0,0 +1,82 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;

/**
* @implements \PHPStan\Rules\Rule<\PhpParser\Node\Expr\FuncCall>
*/
class ImplodeFunctionRule implements \PHPStan\Rules\Rule
{

private RuleLevelHelper $ruleLevelHelper;

private ReflectionProvider $reflectionProvider;

public function __construct(
ReflectionProvider $reflectionProvider,
RuleLevelHelper $ruleLevelHelper
)
{
$this->reflectionProvider = $reflectionProvider;
$this->ruleLevelHelper = $ruleLevelHelper;
}

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!($node->name instanceof \PhpParser\Node\Name)) {
return [];
}

$functionName = $this->reflectionProvider->resolveFunctionName($node->name, $scope);
if (!in_array($functionName, ['implode', 'join'], true)) {
return [];
}

$args = $node->getArgs();
if (count($args) === 1) {
$arrayArg = $args[0]->value;
$paramNo = 1;
} elseif (count($args) === 2) {
$arrayArg = $args[1]->value;
$paramNo = 2;
} else {
return [];
}

$typeResult = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$arrayArg,
'',
static function (Type $type): bool {
return !$type->getIterableValueType()->toString() instanceof ErrorType;
}
);

if ($typeResult->getType() instanceof ErrorType
|| !$typeResult->getType()->getIterableValueType()->toString() instanceof ErrorType) {
return [];
}

return [
RuleErrorBuilder::message(
sprintf('Parameter #%d $array of function %s expects array<string>, %s given.', $paramNo, $functionName, $typeResult->getType()->getIterableValueType()->describe(VerbosityLevel::typeOnly()))
)->build(),
];
}

}
5 changes: 5 additions & 0 deletions tests/PHPStan/Levels/data/acceptTypes-5.json
Expand Up @@ -198,5 +198,10 @@
"message": "Parameter #1 $nonEmpty of method Levels\\AcceptTypes\\AcceptNonEmpty::doBar() expects array&nonEmpty, array given.",
"line": 735,
"ignorable": true
},
{
"message": "Parameter #2 $pieces of function implode expects array, int given.",
"line": 763,
"ignorable": true
}
]
15 changes: 15 additions & 0 deletions tests/PHPStan/Levels/data/acceptTypes-6.json
Expand Up @@ -148,5 +148,20 @@
"message": "Method Levels\\AcceptTypes\\ArrayShapes::doBar() has no return type specified.",
"line": 603,
"ignorable": true
},
{
"message": "Method Levels\\AcceptTypes\\Implode::partlySupportedUnion() has no return type specified.",
"line": 755,
"ignorable": true
},
{
"message": "Method Levels\\AcceptTypes\\Implode::partlySupportedUnion() has parameter $union with no value type specified in iterable type array.",
"line": 755,
"ignorable": true
},
{
"message": "Method Levels\\AcceptTypes\\Implode::invalidType() has no return type specified.",
"line": 762,
"ignorable": true
}
]
5 changes: 5 additions & 0 deletions tests/PHPStan/Levels/data/acceptTypes-7.json
Expand Up @@ -153,5 +153,10 @@
"message": "Parameter #1 $min (int<-1, 1>) of function random_int expects lower number than parameter #2 $max (int<-1, 1>).",
"line": 692,
"ignorable": true
},
{
"message": "Parameter #2 $pieces of function implode expects array, array|int|string given.",
"line": 756,
"ignorable": true
}
]
16 changes: 16 additions & 0 deletions tests/PHPStan/Levels/data/acceptTypes.php
Expand Up @@ -747,3 +747,19 @@ public function doBar(
}

}

class Implode {
/**
* @param string|int|array $union
*/
public function partlySupportedUnion($union) {
$imploded = implode('abc', $union);
}

/**
* @param int $invalid
*/
public function invalidType($invalid) {
$imploded = implode('abc', $invalid);
}
}
49 changes: 49 additions & 0 deletions tests/PHPStan/Rules/Functions/ImplodeFunctionRuleTest.php
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Functions;

use PHPStan\Rules\RuleLevelHelper;

/**
* @extends \PHPStan\Testing\RuleTestCase<ImplodeFunctionRule>
*/
class ImplodeFunctionRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
$broker = $this->createReflectionProvider();
return new ImplodeFunctionRule($broker, new RuleLevelHelper($broker, true, false, true, false));
}

public function testFile(): void
{
$this->analyse([__DIR__ . '/data/implode.php'], [
[
'Parameter #2 $array of function implode expects array<string>, array<int, string>|string given.',
9,
],
[
'Parameter #1 $array of function implode expects array<string>, array<int, string> given.',
11,
],
[
'Parameter #1 $array of function implode expects array<string>, array<int, int> given.',
12,
],
[
'Parameter #1 $array of function implode expects array<string>, array<int, int|true> given.',
13,
],
[
'Parameter #2 $array of function implode expects array<string>, array<int, string> given.',
15,
],
[
'Parameter #2 $array of function join expects array<string>, array<int, string> given.',
16,
],
]);
}

}
25 changes: 25 additions & 0 deletions tests/PHPStan/Rules/Functions/data/implode.php
@@ -0,0 +1,25 @@
<?php declare(strict_types = 1);

namespace ImplodeFunction;

class Foo
{
public function invalidArgs(): void
{
implode('', ['12', '123', ['1234', '12345']]);

implode([['1234', '12345']]);
implode([[1234, 12345]]);
implode([[true, 12345]]);

implode('', [['1234', '12345']]);
join('', [['1234', '12345']]);
}

public function valid() {
implode(['12', '345']);

implode('', ['12', '345']);
join('', ['12', '345']);
}
}

0 comments on commit be79bce

Please sign in to comment.