diff --git a/composer.json b/composer.json index 3c07436d..39d7a030 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.18" + "phpstan/phpstan": "^2.1.32" }, "conflict": { "phpunit/phpunit": "<7.0" diff --git a/extension.neon b/extension.neon index 70831227..c6ce9bf4 100644 --- a/extension.neon +++ b/extension.neon @@ -49,9 +49,16 @@ services: class: PHPStan\Rules\PHPUnit\CoversHelper - class: PHPStan\Rules\PHPUnit\AnnotationHelper + - class: PHPStan\Rules\PHPUnit\PHPUnitVersionDetector + - + class: PHPStan\Rules\PHPUnit\TestMethodsHelper + factory: @PHPStan\Rules\PHPUnit\TestMethodsHelperFactory::create() + - + class: PHPStan\Rules\PHPUnit\TestMethodsHelperFactory + - class: PHPStan\Rules\PHPUnit\DataProviderHelper factory: @PHPStan\Rules\PHPUnit\DataProviderHelperFactory::create() diff --git a/rules.neon b/rules.neon index 84b71498..63e10b47 100644 --- a/rules.neon +++ b/rules.neon @@ -13,6 +13,9 @@ conditionalTags: PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule: phpstan.rules.rule: [%strictRulesInstalled%, %featureToggles.bleedingEdge%] + PHPStan\Rules\PHPUnit\DataProviderDataRule: + phpstan.rules.rule: %featureToggles.bleedingEdge% + services: - class: PHPStan\Rules\PHPUnit\DataProviderDeclarationRule @@ -24,3 +27,6 @@ services: - class: PHPStan\Rules\PHPUnit\AssertEqualsIsDiscouragedRule + + - + class: PHPStan\Rules\PHPUnit\DataProviderDataRule diff --git a/src/Rules/PHPUnit/DataProviderDataRule.php b/src/Rules/PHPUnit/DataProviderDataRule.php new file mode 100644 index 00000000..e241053e --- /dev/null +++ b/src/Rules/PHPUnit/DataProviderDataRule.php @@ -0,0 +1,246 @@ + + */ +class DataProviderDataRule implements Rule +{ + + private TestMethodsHelper $testMethodsHelper; + + private DataProviderHelper $dataProviderHelper; + + public function __construct( + TestMethodsHelper $testMethodsHelper, + DataProviderHelper $dataProviderHelper + ) + { + $this->testMethodsHelper = $testMethodsHelper; + $this->dataProviderHelper = $dataProviderHelper; + } + + public function getNodeType(): string + { + return Node::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if ( + !$node instanceof Node\Stmt\Return_ + && !$node instanceof Node\Expr\Yield_ + && !$node instanceof Node\Expr\YieldFrom + ) { + return []; + } + + if ($scope->getFunction() === null) { + return []; + } + if ($scope->isInAnonymousFunction()) { + return []; + } + + $arraysTypes = $this->buildArrayTypesFromNode($node, $scope); + if ($arraysTypes === []) { + return []; + } + + $method = $scope->getFunction(); + $classReflection = $scope->getClassReflection(); + if ( + $classReflection === null + || !$classReflection->is(TestCase::class) + ) { + return []; + } + + $testsWithProvider = []; + $testMethods = $this->testMethodsHelper->getTestMethods($classReflection, $scope); + foreach ($testMethods as $testMethod) { + foreach ($this->dataProviderHelper->getDataProviderMethods($scope, $testMethod, $classReflection) as [, $providerMethodName]) { + if ($providerMethodName === $method->getName()) { + $testsWithProvider[] = $testMethod; + continue 2; + } + } + } + + if (count($testsWithProvider) === 0) { + return []; + } + + $maxNumberOfParameters = $testsWithProvider[0]->getNumberOfParameters(); + if (count($testsWithProvider) > 1) { + foreach ($testsWithProvider as $testMethod) { + if ($testMethod->isVariadic()) { + $maxNumberOfParameters = PHP_INT_MAX; + break; + } + + $maxNumberOfParameters = max($maxNumberOfParameters, $testMethod->getNumberOfParameters()); + } + } + + foreach ($testsWithProvider as $testMethod) { + $numberOfParameters = $testMethod->getNumberOfParameters(); + + foreach ($arraysTypes as [$startLine, $arraysType]) { + $args = $this->arrayItemsToArgs($arraysType, $maxNumberOfParameters); + if ($args === null) { + continue; + } + + if ( + !$testMethod->isVariadic() + && $numberOfParameters !== $maxNumberOfParameters + ) { + $args = array_slice($args, 0, $numberOfParameters); + } + + $scope->invokeNodeCallback(new Node\Expr\MethodCall( + new TypeExpr(new ObjectType($classReflection->getName())), + $testMethod->getName(), + $args, + ['startLine' => $startLine], + )); + } + } + + return []; + } + + /** + * @return array + */ + private function arrayItemsToArgs(Type $array, int $numberOfParameters): ?array + { + $args = []; + + $constArrays = $array->getConstantArrays(); + if ($constArrays !== [] && count($constArrays) === 1) { + $keyTypes = $constArrays[0]->getKeyTypes(); + $valueTypes = $constArrays[0]->getValueTypes(); + } elseif ($array->isArray()->yes()) { + $keyTypes = []; + $valueTypes = []; + for ($i = 0; $i < $numberOfParameters; ++$i) { + $keyTypes[$i] = $array->getIterableKeyType(); + $valueTypes[$i] = $array->getIterableValueType(); + } + } else { + return null; + } + + foreach ($valueTypes as $i => $valueType) { + $key = $keyTypes[$i]->getConstantStrings(); + if (count($key) > 1) { + return null; + } + + if (count($key) === 0) { + $arg = new Node\Arg(new TypeExpr($valueType)); + $args[] = $arg; + continue; + + } + + $arg = new Node\Arg( + new TypeExpr($valueType), + false, + false, + [], + new Node\Identifier($key[0]->getValue()), + ); + $args[] = $arg; + } + + return $args; + } + + /** + * @param Node\Stmt\Return_|Node\Expr\Yield_|Node\Expr\YieldFrom $node + * + * @return list + */ + private function buildArrayTypesFromNode(Node $node, Scope $scope): array + { + $arraysTypes = []; + + // special case for providers only containing static data, so we get more precise error lines + if ( + ($node instanceof Node\Stmt\Return_ && $node->expr instanceof Node\Expr\Array_) + || ($node instanceof Node\Expr\YieldFrom && $node->expr instanceof Node\Expr\Array_) + ) { + foreach ($node->expr->items as $item) { + if (!$item->value instanceof Node\Expr\Array_) { + $arraysTypes = []; + break; + } + + $constArrays = $scope->getType($item->value)->getConstantArrays(); + if ($constArrays === []) { + $arraysTypes = []; + break; + } + + foreach ($constArrays as $constArray) { + $arraysTypes[] = [$item->value->getStartLine(), $constArray]; + } + } + + if ($arraysTypes !== []) { + return $arraysTypes; + } + } + + // general case with less precise error message lines + if ($node instanceof Node\Stmt\Return_ || $node instanceof Node\Expr\YieldFrom) { + if ($node->expr === null) { + return []; + } + + $exprType = $scope->getType($node->expr); + $exprConstArrays = $exprType->getConstantArrays(); + foreach ($exprConstArrays as $constArray) { + foreach ($constArray->getValueTypes() as $valueType) { + foreach ($valueType->getConstantArrays() as $constValueArray) { + $arraysTypes[] = [$node->getStartLine(), $constValueArray]; + } + } + } + + if ($arraysTypes === []) { + foreach ($exprType->getIterableValueType()->getArrays() as $arrayType) { + $arraysTypes[] = [$node->getStartLine(), $arrayType]; + } + } + } elseif ($node instanceof Node\Expr\Yield_) { + if ($node->value === null) { + return []; + } + + $exprType = $scope->getType($node->value); + foreach ($exprType->getConstantArrays() as $constValueArray) { + $arraysTypes[] = [$node->getStartLine(), $constValueArray]; + } + } + + return $arraysTypes; + } + +} diff --git a/src/Rules/PHPUnit/TestMethodsHelper.php b/src/Rules/PHPUnit/TestMethodsHelper.php new file mode 100644 index 00000000..d0984442 --- /dev/null +++ b/src/Rules/PHPUnit/TestMethodsHelper.php @@ -0,0 +1,94 @@ +fileTypeMapper = $fileTypeMapper; + $this->phpunit10OrNewer = $phpunit10OrNewer; + } + + /** + * @return array + */ + public function getTestMethods(ClassReflection $classReflection, Scope $scope): array + { + $testMethods = []; + foreach ($classReflection->getNativeReflection()->getMethods() as $reflectionMethod) { + if (!$reflectionMethod->isPublic()) { + continue; + } + + if (str_starts_with(strtolower($reflectionMethod->getName()), 'test')) { + $testMethods[] = $reflectionMethod; + continue; + } + + $docComment = $reflectionMethod->getDocComment(); + if ($docComment !== false) { + $methodPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $reflectionMethod->getName(), + $docComment, + ); + + if ($this->hasTestAnnotation($methodPhpDoc)) { + $testMethods[] = $reflectionMethod; + continue; + } + } + + if (!$this->phpunit10OrNewer) { + continue; + } + + $testAttributes = $reflectionMethod->getAttributes('PHPUnit\Framework\Attributes\Test'); // @phpstan-ignore argument.type + if ($testAttributes === []) { + continue; + } + + $testMethods[] = $reflectionMethod; + } + + return $testMethods; + } + + private function hasTestAnnotation(?ResolvedPhpDocBlock $phpDoc): bool + { + if ($phpDoc === null) { + return false; + } + + $phpDocNodes = $phpDoc->getPhpDocNodes(); + + foreach ($phpDocNodes as $docNode) { + $tags = $docNode->getTagsByName('@test'); + if ($tags !== []) { + return true; + } + } + + return false; + } + +} diff --git a/src/Rules/PHPUnit/TestMethodsHelperFactory.php b/src/Rules/PHPUnit/TestMethodsHelperFactory.php new file mode 100644 index 00000000..202f88cb --- /dev/null +++ b/src/Rules/PHPUnit/TestMethodsHelperFactory.php @@ -0,0 +1,28 @@ +fileTypeMapper = $fileTypeMapper; + $this->PHPUnitVersionDetector = $PHPUnitVersionDetector; + } + + public function create(): TestMethodsHelper + { + return new TestMethodsHelper($this->fileTypeMapper, $this->PHPUnitVersionDetector->isPHPUnit10OrNewer()); + } + +} diff --git a/tests/Rules/PHPUnit/DataProviderDataRuleTest.php b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php new file mode 100644 index 00000000..fa3aa9fc --- /dev/null +++ b/tests/Rules/PHPUnit/DataProviderDataRuleTest.php @@ -0,0 +1,273 @@ + + */ +class DataProviderDataRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + $reflectionProvider = $this->createReflectionProvider(); + + /** @var list> $rules */ + $rules = [ + new DataProviderDataRule( + new TestMethodsHelper( + self::getContainer()->getByType(FileTypeMapper::class), + true + ), + new DataProviderHelper( + $reflectionProvider, + self::getContainer()->getByType(FileTypeMapper::class), + self::getContainer()->getService('defaultAnalysisParser'), + true + ), + + ), + self::getContainer()->getByType(CallMethodsRule::class) /** @phpstan-ignore phpstanApi.classConstant */ + ]; + + return new CompositeRule($rules); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/data-provider-data.php'], [ + [ + 'Parameter #2 $input of method DataProviderDataTest\FooTest::testWithAttribute() expects string, int given.', + 24, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\FooTest::testWithAttribute() expects string, false given.', + 28, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\BarTest::testWithAnnotation() expects string, int given.', + 51, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\BarTest::testWithAnnotation() expects string, false given.', + 55, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldTest::myTestMethod() expects string, int given.', + 80, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldTest::myTestMethod() expects string, false given.', + 86, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldFromTest::myTestMethod() expects string, int given.', + 112, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldFromTest::myTestMethod() expects string, false given.', + 116, + ], + [ + 'Method DataProviderDataTest\DifferentArgumentCount::testFoo() invoked with 3 parameters, 2 required.', + 141, + ], + [ + 'Method DataProviderDataTest\DifferentArgumentCount::testFoo() invoked with 1 parameter, 2 required.', + 146, + ], + [ + 'Method DataProviderDataTest\DifferentArgumentCountWithReusedDataprovider::testFoo() invoked with 3 parameters, 2 required.', + 177, + ], + [ + 'Method DataProviderDataTest\DifferentArgumentCountWithReusedDataprovider::testFoo() invoked with 1 parameter, 2 required.', + 182, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\UnionTypeReturnTest::testFoo() expects string, int given.', + 216, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldFromExpr::testFoo() expects string, int given.', + 236, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\YieldFromExpr::testFoo() expects string, true given.', + 238, + ], + [ + 'Parameter #1 $si of method DataProviderDataTest\TestInvalidVariadic::testBar() expects int, string given.', + 295, + ], + [ + 'Parameter #1 $s of method DataProviderDataTest\TestInvalidVariadic::testFoo() expects string, int given.', + 296, + ], + [ + 'Parameter #1 $si of method DataProviderDataTest\TestInvalidVariadic2::testBar() expects int, string given.', + 317, + ], + [ + 'Parameter #2 ...$moreS of method DataProviderDataTest\TestInvalidVariadic2::testFoo() expects int, string given.', + 317, + ], + [ + 'Parameter #4 ...$moreS of method DataProviderDataTest\TestInvalidVariadic2::testFoo() expects int, string given.', + 317, + ], + [ + 'Parameter #1 $s of method DataProviderDataTest\TestInvalidVariadic2::testFoo() expects string, int given.', + 318, + ], + [ + 'Parameter #1 $i of method DataProviderDataTest\TestArrayIterator::testBar() expects int, int|string given.', + 362, + ], + [ + 'Parameter #1 $i of method DataProviderDataTest\TestArrayIterator::testFoo() expects int, int|string given.', + 362, + ], + [ + 'Parameter #1 $s1 of method DataProviderDataTest\TestArrayIterator::testFooBar() expects string, int|string given.', + 362, + ], + [ + 'Parameter #1 $si of method DataProviderDataTest\TestWrongTypedIterable::testBar() expects int, string given.', + 380, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\AbstractBaseTest::testWithAttribute() expects string, int given.', + 407, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\AbstractBaseTest::testWithAttribute() expects string, false given.', + 411, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\ConstantArrayUnionTypeReturnTest::testFoo() expects string, int given.', + 446, + ], + [ + 'Method DataProviderDataTest\ConstantArrayDifferentLengthUnionTypeReturnTest::testFoo() invoked with 3 parameters, 2 required.', + 484, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\ConstantArrayDifferentLengthUnionTypeReturnTest::testFoo() expects string, int given.', + 484, + ], + [ + 'Parameter #2 $input of method DataProviderDataTest\ConstantArrayUnionWithDifferentValueTypeReturnTest::testFoo() expects string, int|string given.', + 517, + ], + ]); + } + + public function testRulePhp8(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped(); + } + + $this->analyse([__DIR__ . '/data/data-provider-data-named.php'], [ + [ + 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, int given.', + 44 + ], + [ + 'Parameter $input of method DataProviderDataTestPhp8\NamedArgsInProvider::testFoo() expects string, false given.', + 44 + ], + [ + 'Unknown parameter $wrong in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', + 58 + ], + [ + 'Missing parameter $si (int) in call to method DataProviderDataTestPhp8\TestWrongOffsetNameArrayShapeIterable::testBar().', + 58 + ], + [ + 'Parameter $si of method DataProviderDataTestPhp8\TestWrongTypeInArrayShapeIterable::testBar() expects int, string given.', + 79 + ], + ]); + } + + + public function testVariadicMethod(): void + { + $this->analyse([__DIR__ . '/data/data-provider-variadic-method.php'], [ + [ + 'Method DataProviderVariadicMethod\FooTest::testProvide2() invoked with 1 parameter, at least 2 required.', + 12, + ], + [ + 'Parameter #1 $a of method DataProviderVariadicMethod\FooTest::testProvide() expects int, string given.', + 13, + ], + [ + 'Method DataProviderVariadicMethod\FooTest::testProvide2() invoked with 1 parameter, at least 2 required.', + 13, + ], + [ + 'Parameter #1 $a of method DataProviderVariadicMethod\FooTest::testProvide2() expects int, string given.', + 13, + ], + [ + 'Parameter #2 ...$rest of method DataProviderVariadicMethod\FooTest::testProvide() expects string, int given.', + 15, + ], + [ + 'Parameter #3 ...$rest of method DataProviderVariadicMethod\FooTest::testProvide() expects string, int given.', + 15, + ], + [ + 'Parameter #2 $two of method DataProviderVariadicMethod\FooTest::testProvide2() expects string, int given.', + 15, + ], + [ + 'Parameter #3 ...$rest of method DataProviderVariadicMethod\FooTest::testProvide2() expects string, int given.', + 15, + ], + ]); + } + + public function testTrimmingArgs(): void + { + $this->analyse([__DIR__ . '/data/data-provider-trimming-args.php'], [ + [ + 'Method DataProviderTrimmingArgs\FooTest::testProvide() invoked with 2 parameters, 1 required.', + 12, + ], + [ + 'Method DataProviderTrimmingArgs\FooTest::testProvide2() invoked with 2 parameters, 1 required.', + 12, + ], + [ + 'Method DataProviderTrimmingArgs\FooTest::testProvide() invoked with 2 parameters, 1 required.', + 13, + ], + [ + 'Method DataProviderTrimmingArgs\FooTest::testProvide2() invoked with 2 parameters, 1 required.', + 13, + ], + ]); + } + + /** + * @return string[] + */ + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../extension.neon', + ]; + } +} diff --git a/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php b/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php index cbe40bf1..67f55ed4 100644 --- a/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php +++ b/tests/Rules/PHPUnit/DataProviderDeclarationRuleTest.php @@ -17,7 +17,12 @@ protected function getRule(): Rule $reflection = $this->createReflectionProvider(); return new DataProviderDeclarationRule( - new DataProviderHelper($reflection, self::getContainer()->getByType(FileTypeMapper::class), self::getContainer()->getService('defaultAnalysisParser'), true), + new DataProviderHelper( + $reflection, + self::getContainer()->getByType(FileTypeMapper::class), + self::getContainer()->getService('defaultAnalysisParser'), + true + ), true, true ); diff --git a/tests/Rules/PHPUnit/data/data-provider-data-named.php b/tests/Rules/PHPUnit/data/data-provider-data-named.php new file mode 100644 index 00000000..bea3e50e --- /dev/null +++ b/tests/Rules/PHPUnit/data/data-provider-data-named.php @@ -0,0 +1,109 @@ + 'Hello World', + "expectedResult" => " Hello World \n" + ] + ]; + + if (rand(0,1)) { + $arr = [ + [ + "input" => 123, + "expectedResult" => " Hello World \n" + ] + ]; + } + if (rand(0,1)) { + $arr = [ + [ + "input" => false, + "expectedResult" => " Hello World \n" + ] + ]; + } + + return $arr; + } +} + + +class TestWrongOffsetNameArrayShapeIterable extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + public function aProvider(): iterable + { + return $this->data(); + } + + /** + * @return iterable + */ + public function data(): iterable + { + } +} + + +class TestWrongTypeInArrayShapeIterable extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + public function aProvider(): iterable + { + return $this->data(); + } + + /** + * @return iterable + */ + public function data(): iterable + { + } +} + + +class TestValidArrayShapeIterable extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + public function aProvider(): iterable + { + return $this->data(); + } + + /** + * @return iterable + */ + public function data(): iterable + { + } +} diff --git a/tests/Rules/PHPUnit/data/data-provider-data.php b/tests/Rules/PHPUnit/data/data-provider-data.php new file mode 100644 index 00000000..d684df79 --- /dev/null +++ b/tests/Rules/PHPUnit/data/data-provider-data.php @@ -0,0 +1,519 @@ +moreData(); + + yield [ + 'Hello World', + true, + ]; + } + + /** + * @return array{array{'Hello World', 123}} + */ + private function moreData(): array + { + return [ + [ + 'Hello World', + 123, + ] + ]; + } +} + +class TestValidVariadic extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(string $s): void + { + } + + /** @dataProvider aProvider */ + public function testFoo(string $s, string ...$moreS): void + { + } + + public function aProvider(): iterable + { + return [ + ["hello", "world", "foo", "bar"], + ["hi", "ho"], + ["nope"] + ]; + } +} + +class TestInvalidVariadic extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + /** @dataProvider aProvider */ + public function testFoo(string $s, string ...$moreS): void + { + } + + public function aProvider(): iterable + { + return [ + ["hello", "world", "foo", "bar"], + [123] + ]; + } +} + + +class TestInvalidVariadic2 extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + /** @dataProvider aProvider */ + public function testFoo(string $s, int ...$moreS): void + { + } + + public function aProvider(): iterable + { + return [ + ["hello", "world", 5, "bar"], + [123] + ]; + } +} + +class TestTypedIterable extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + public function aProvider(): iterable + { + return $this->data(); + } + + /** + * @return iterable> + */ + public function data(): iterable + { + } +} + +class TestArrayIterator extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $i): void + { + } + + /** @dataProvider aProvider */ + public function testFoo(int $i, string $si): void + { + } + + /** @dataProvider aProvider */ + public function testFooBar(string $s1, string $s2): void + { + } + + public function aProvider(): iterable + { + return new \ArrayIterator([ + [1], + [2, "hello"], + ["no"], + ["no", "yes"], + ]); + } +} + +class TestWrongTypedIterable extends TestCase +{ + /** @dataProvider aProvider */ + public function testBar(int $si): void + { + } + + public function aProvider(): iterable + { + return $this->data(); + } + + /** + * @return iterable> + */ + public function data(): iterable + { + } +} + + +abstract class AbstractBaseTest extends TestCase +{ + + #[DataProvider('aProvider')] + public function testWithAttribute(string $expectedResult, string $input): void + { + } + + static public function aProvider(): array + { + return [ + [ + 'Hello World', + " Hello World \n", + ], + [ + 'Hello World', + 123, + ], + [ + 'Hello World', + false, + ], + ]; + } +} + + +class ConstantArrayUnionTypeReturnTest extends TestCase +{ + + /** @dataProvider aProvider */ + public function testFoo(string $expectedResult, string $input): void + { + } + + public function aProvider(): array + { + if (rand(0,1)) { + $arr = [ + [ + 'Hello World', + 123 + ] + ]; + } else { + $arr = [ + [ + 'Hello World', + " Hello World \n" + ] + ]; + } + + return $arr; + } +} + +class ConstantArrayDifferentLengthUnionTypeReturnTest extends TestCase +{ + + /** @dataProvider aProvider */ + public function testFoo(string $expectedResult, string $input): void + { + } + + public function aProvider(): array + { + if (rand(0,1)) { + $arr = [ + [ + 'Hello World', + 123 + ] + ]; + } elseif (rand(0,1)) { + $arr = [ + [ + 'Hello World', + 'Hello World', + ] + ]; + } else { + $arr = [ + [ + 'Hello World', + " Hello World \n", + " Too much \n", + ] + ]; + } + + return $arr; + } +} + +class ConstantArrayUnionWithDifferentValueTypeReturnTest extends TestCase +{ + + /** @dataProvider aProvider */ + public function testFoo(string $expectedResult, string $input): void + { + } + + public function aProvider(): array + { + if (rand(0,1)) { + $arr = [ + [ + 'Hellooo', + ' World', + ] + ]; + } else { + $a = rand(0,1) ? 'Hello' : 'World'; + $b = rand(0,1) ? " Hello World \n" : 123; + + $arr = [ + [ + $a, + $b + ] + ]; + } + + return $arr; + } +} diff --git a/tests/Rules/PHPUnit/data/data-provider-trimming-args.php b/tests/Rules/PHPUnit/data/data-provider-trimming-args.php new file mode 100644 index 00000000..60fc7cf4 --- /dev/null +++ b/tests/Rules/PHPUnit/data/data-provider-trimming-args.php @@ -0,0 +1,32 @@ +