diff --git a/.gitignore b/.gitignore index 650c8a0b..92ba894c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor/ composer.lock phpcs.xml phpunit.xml +/lsp/ diff --git a/.travis.yml b/.travis.yml index 4d44dfd4..c62bf6e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,3 +18,4 @@ install: script: - composer tests - composer coding-style + - composer types diff --git a/composer.json b/composer.json index fd0706b7..1da7bb82 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ } ], "require": { - "php": "~7" + "php": "~7", + "vimeo/psalm": "^3.7" }, "require-dev": { "phpunit/phpunit": "~7", @@ -124,6 +125,7 @@ }, "scripts": { "tests": "vendor/bin/phpunit", + "types": "vendor/bin/psalm", "coding-style": "vendor/bin/phpcs && vendor/bin/php-cs-fixer fix --dry-run --diff --config=.php_cs.dist", "clear": "rm -rf vendor/" } diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..42f355b2 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Functional/Average.php b/src/Functional/Average.php index b3029f34..d0e4adbf 100644 --- a/src/Functional/Average.php +++ b/src/Functional/Average.php @@ -17,7 +17,7 @@ * Returns the average of all numeric values in the array or null if no numeric value was found * * @param Traversable|array $collection - * @return null|float|int + * @return numeric|null */ function average($collection) { @@ -28,7 +28,7 @@ function average($collection) foreach ($collection as $element) { if (\is_numeric($element)) { - $sum += $element; + $sum = ($sum === null) ? $element : $sum + $element; ++$divisor; } } diff --git a/src/Functional/Concat.php b/src/Functional/Concat.php index 64fe8d57..c607e08f 100644 --- a/src/Functional/Concat.php +++ b/src/Functional/Concat.php @@ -13,7 +13,7 @@ /** * Concatenates zero or more strings * - * @param string[] ...$strings + * @param array $strings * @return string */ function concat(string ...$strings) diff --git a/src/Functional/Curry.php b/src/Functional/Curry.php index 97bb835e..e473a5a8 100644 --- a/src/Functional/Curry.php +++ b/src/Functional/Curry.php @@ -24,8 +24,10 @@ */ function curry(callable $function, $required = true) { + /** @psalm-suppress ArgumentTypeCoercion */ if (\method_exists('Closure', 'fromCallable')) { // Closure::fromCallable was introduced in PHP 7.1 + /** @psalm-suppress InvalidArgument */ $reflection = new ReflectionFunction(Closure::fromCallable($function)); } else { if (\is_string($function) && \strpos($function, '::', 1) !== false) { @@ -35,6 +37,7 @@ function curry(callable $function, $required = true) } elseif (\is_object($function) && \method_exists($function, '__invoke')) { $reflection = new ReflectionMethod($function, '__invoke'); } else { + /** @psalm-suppress InvalidArgument */ $reflection = new ReflectionFunction($function); } } diff --git a/src/Functional/Difference.php b/src/Functional/Difference.php index 0c906f17..469ae0c1 100644 --- a/src/Functional/Difference.php +++ b/src/Functional/Difference.php @@ -17,8 +17,8 @@ * Takes a collection and returns the difference of all elements * * @param Traversable|array $collection - * @param integer|float $initial - * @return integer|float + * @param numeric $initial + * @return numeric */ function difference($collection, $initial = 0) { diff --git a/src/Functional/Exceptions/InvalidArgumentException.php b/src/Functional/Exceptions/InvalidArgumentException.php index 5a64a3ac..7d9a9301 100644 --- a/src/Functional/Exceptions/InvalidArgumentException.php +++ b/src/Functional/Exceptions/InvalidArgumentException.php @@ -22,7 +22,7 @@ public static function assertCallback($callback, $callee, $parameterPosition) { if (!\is_callable($callback)) { if (!\is_array($callback) && !\is_string($callback)) { - throw new static( + throw new self( \sprintf( '%s() expected parameter %d to be a valid callback, no array, string, closure or functor given', $callee, @@ -51,7 +51,7 @@ public static function assertCallback($callback, $callee, $parameterPosition) break; } - throw new static( + throw new self( \sprintf( "%s() expects parameter %d to be a valid callback, %s '%s' not found or invalid %s name", $callee, @@ -77,7 +77,7 @@ public static function assertArrayAccess($collection, $callee, $parameterPositio public static function assertMethodName($methodName, $callee, $parameterPosition) { if (!\is_string($methodName)) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be string, %s given', $callee, @@ -102,7 +102,7 @@ public static function assertPropertyName($propertyName, $callee, $parameterPosi !\is_float($propertyName) && !\is_null($propertyName) ) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be a valid property name or array index, %s given', $callee, @@ -119,7 +119,7 @@ public static function assertPositiveInteger($value, $callee, $parameterPosition $type = self::getType($value); $type = $type === 'integer' ? 'negative integer' : $type; - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be positive integer, %s given', $callee, @@ -133,7 +133,7 @@ public static function assertPositiveInteger($value, $callee, $parameterPosition /** * @param mixed $key * @param string $callee - * @throws static + * @throws InvalidArgumentException */ public static function assertValidArrayKey($key, $callee) { @@ -142,7 +142,7 @@ public static function assertValidArrayKey($key, $callee) $keyType = \gettype($key); if (!\in_array($keyType, $keyTypes, true)) { - throw new static( + throw new self( \sprintf( '%s(): callback returned invalid array key of type "%s". Expected %4$s or %3$s', $callee, @@ -157,7 +157,7 @@ public static function assertValidArrayKey($key, $callee) public static function assertArrayKeyExists($collection, $key, $callee) { if (!isset($collection[$key])) { - throw new static( + throw new self( \sprintf( '%s(): unknown key "%s"', $callee, @@ -176,7 +176,7 @@ public static function assertArrayKeyExists($collection, $key, $callee) public static function assertBoolean($value, $callee, $parameterPosition) { if (!\is_bool($value)) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be boolean, %s given', $callee, @@ -196,7 +196,7 @@ public static function assertBoolean($value, $callee, $parameterPosition) public static function assertInteger($value, $callee, $parameterPosition) { if (!\is_int($value)) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be integer, %s given', $callee, @@ -217,7 +217,7 @@ public static function assertInteger($value, $callee, $parameterPosition) public static function assertIntegerGreaterThanOrEqual($value, $limit, $callee, $parameterPosition) { if (!\is_int($value) || $value < $limit) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be an integer greater than or equal to %d', $callee, @@ -238,7 +238,7 @@ public static function assertIntegerGreaterThanOrEqual($value, $limit, $callee, public static function assertIntegerLessThanOrEqual($value, $limit, $callee, $parameterPosition) { if (!\is_int($value) || $value > $limit) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be an integer less than or equal to %d', $callee, @@ -252,7 +252,7 @@ public static function assertIntegerLessThanOrEqual($value, $limit, $callee, $pa public static function assertResolvablePlaceholder(array $args, $position) { if (\count($args) === 0) { - throw new static( + throw new self( \sprintf('Cannot resolve parameter placeholder at position %d. Parameter stack is empty.', $position) ); } @@ -268,7 +268,7 @@ public static function assertResolvablePlaceholder(array $args, $position) private static function assertCollectionAlike($collection, $className, $callee, $parameterPosition) { if (!\is_array($collection) && !$collection instanceof $className) { - throw new static( + throw new self( \sprintf( '%s() expects parameter %d to be array or instance of %s, %s given', $callee, diff --git a/src/Functional/Match.php b/src/Functional/Match.php index 83d3b2a3..3370954a 100644 --- a/src/Functional/Match.php +++ b/src/Functional/Match.php @@ -21,7 +21,7 @@ * * @param array $conditions the conditions to check against * - * @return callable|null the function that calls the callable of the first truthy condition + * @return callable the function that calls the callable of the first truthy condition */ function match(array $conditions) { diff --git a/src/Functional/Maximum.php b/src/Functional/Maximum.php index f2c4596f..15097581 100644 --- a/src/Functional/Maximum.php +++ b/src/Functional/Maximum.php @@ -17,7 +17,7 @@ * Returns the maximum value of a collection * * @param Traversable|array $collection - * @return integer|float + * @return numeric|null */ function maximum($collection) { diff --git a/src/Functional/Minimum.php b/src/Functional/Minimum.php index d0d67fb8..3898a348 100644 --- a/src/Functional/Minimum.php +++ b/src/Functional/Minimum.php @@ -17,7 +17,7 @@ * Returns the minimum value of a collection * * @param Traversable|array $collection - * @return integer|float + * @return numeric|null */ function minimum($collection) { diff --git a/src/Functional/Poll.php b/src/Functional/Poll.php index ca48fcb3..667d4a17 100644 --- a/src/Functional/Poll.php +++ b/src/Functional/Poll.php @@ -33,6 +33,7 @@ function poll(callable $callback, $timeout, Traversable $delaySequence = null) $delays = new AppendIterator(); if ($delaySequence) { + /** @psalm-suppress ArgumentTypeCoercion */ $delays->append(new InfiniteIterator($delaySequence)); } $delays->append(new InfiniteIterator(new ArrayIterator([0]))); diff --git a/src/Functional/Product.php b/src/Functional/Product.php index ccd3a87e..697f204d 100644 --- a/src/Functional/Product.php +++ b/src/Functional/Product.php @@ -17,8 +17,8 @@ * Takes a collection and returns the product of all elements * * @param Traversable|array $collection - * @param integer|float $initial - * @return integer|float + * @param numeric $initial + * @return numeric */ function product($collection, $initial = 1) { diff --git a/src/Functional/Retry.php b/src/Functional/Retry.php index 02a74bf6..6382ee4c 100644 --- a/src/Functional/Retry.php +++ b/src/Functional/Retry.php @@ -34,6 +34,7 @@ function retry(callable $callback, $retries, Traversable $delaySequence = null) if ($delaySequence) { $delays = new AppendIterator(); + /** @psalm-suppress ArgumentTypeCoercion */ $delays->append(new InfiniteIterator($delaySequence)); $delays->append(new InfiniteIterator(new ArrayIterator([0]))); $delays = new LimitIterator($delays, $retries); diff --git a/src/Functional/Sum.php b/src/Functional/Sum.php index f79f5de5..642daceb 100644 --- a/src/Functional/Sum.php +++ b/src/Functional/Sum.php @@ -17,8 +17,8 @@ * Takes a collection and returns the sum of the elements * * @param Traversable|array $collection - * @param integer|float $initial - * @return integer|float + * @param numeric $initial + * @return numeric */ function sum($collection, $initial = 0) { diff --git a/src/Functional/ZipAll.php b/src/Functional/ZipAll.php index 2c7658b8..80002e0f 100644 --- a/src/Functional/ZipAll.php +++ b/src/Functional/ZipAll.php @@ -27,6 +27,7 @@ function zip_all(...$args) /** @var callable|null $callback */ $callback = null; if (\is_callable(\end($args))) { + /** @var callable $callback */ $callback = \array_pop($args); } diff --git a/tests/Functional/MatchTest.php b/tests/Functional/MatchTest.php index 323fc1bb..e52fb187 100644 --- a/tests/Functional/MatchTest.php +++ b/tests/Functional/MatchTest.php @@ -18,14 +18,19 @@ class MatchTest extends AbstractTestCase { public function testMatch() { - $test = match([ - [equal('foo'), const_function('is foo')], - [equal('bar'), const_function('is bar')], - [equal('baz'), const_function('is baz')], - [const_function(true), function ($x) { - return 'default is ' . $x; - }], - ]); + $test = match( + [ + [equal('foo'), const_function('is foo')], + [equal('bar'), const_function('is bar')], + [equal('baz'), const_function('is baz')], + [ + const_function(true), + function ($x) { + return 'default is ' . $x; + }, + ], + ] + ); $this->assertEquals('is foo', $test('foo')); $this->assertEquals('is bar', $test('bar')); @@ -35,10 +40,12 @@ public function testMatch() public function testNothingMatch() { - $test = match([ - [equal('foo'), const_function('is foo')], - [equal('bar'), const_function('is bar')], - ]); + $test = match( + [ + [equal('foo'), const_function('is foo')], + [equal('bar'), const_function('is bar')], + ] + ); $this->assertNull($test('baz')); } @@ -50,36 +57,46 @@ public function testMatchConditionIsArray() $callable = function () { }; - $test = match([ - [$callable, $callable], - '', - ]); + $test = match( + [ + [$callable, $callable], + '', + ] + ); } public function testMatchConditionLength() { - $this->expectArgumentError('Functional\match() expects size of condition at key 1 to be greater than or equals to 2, 1 given'); + $this->expectArgumentError( + 'Functional\match() expects size of condition at key 1 to be greater than or equals to 2, 1 given' + ); $callable = function () { }; - $test = match([ - [$callable, $callable], - [''], - ]); + $test = match( + [ + [$callable, $callable], + [''], + ] + ); } public function testMatchConditionCallables() { $this->expectException(\Functional\Exceptions\InvalidArgumentException::class); - $this->expectExceptionMessage('Functional\match() expects first two items of condition at key 1 to be callables'); + $this->expectExceptionMessage( + 'Functional\match() expects first two items of condition at key 1 to be callables' + ); $callable = function () { }; - $test = match([ - [$callable, $callable], - [$callable, ''], - ]); + $test = match( + [ + [$callable, $callable], + [$callable, ''], + ] + ); } }