diff --git a/CHANGELOG.md b/CHANGELOG.md index c91cac1..e46000d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* *Nothing* + +### Changed +* Migrate from PHPStan to Mago for static code analysis. + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [9.0.1] - 2026-06-14 ### Added * *Nothing* diff --git a/composer.json b/composer.json index 5651ac5..90ef7a6 100644 --- a/composer.json +++ b/composer.json @@ -42,11 +42,9 @@ "laminas/laminas-stratigility": "^4.3", "mezzio/mezzio-problem-details": "^1.19", "pagerfanta/core": "^3.8", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", "phpunit/phpunit": "^13.0", "psr/simple-cache": "^3.0", - "shlinkio/shlink-mago-config": "^1.0", + "shlinkio/shlink-mago-config": "^1.1", "symfony/string": "^8.0", "symfony/var-dumper": "^8.0" }, @@ -71,7 +69,7 @@ "scripts": { "ci": [ "@cs", - "@stan", + "@analyze", "@test:ci" ], "lint": "mago lint", @@ -80,7 +78,8 @@ "format:check": "mago format --check", "cs": ["@lint", "@format:check"], "cs:fix": ["@lint:fix", "@format"], - "stan": "phpstan analyse", + "analyze": "mago analyze", + "stan": "@analyze", "test": "phpunit --order-by=random --testdox --testdox-summary", "test:ci": "XDEBUG_MODE=coverage composer test -- --coverage-clover=build/clover.xml", "test:pretty": "XDEBUG_MODE=coverage composer test -- --coverage-html=build/coverage-html" @@ -89,7 +88,7 @@ "ci": "Alias for \"cs\", \"stan\" and \"test:ci\"", "cs": "Checks coding styles", "cs:fix": "Fixes coding styles, when possible", - "stan": "Inspects code with phpstan", + "analyze": "Inspects code with Mago analyze", "test": "Runs unit tests with no coverage reports", "test:ci": "Runs unit tests generating coverage reports and logs", "test:pretty": "Runs unit tests generating coverage reports in html" diff --git a/functions/functions.php b/functions/functions.php index bcaf338..730e54b 100644 --- a/functions/functions.php +++ b/functions/functions.php @@ -27,7 +27,8 @@ function buildDateRange(Chronos|null $startDate, Chronos|null $endDate): DateRan */ function parseOrderBy(string $orderBy): array { - return array_pad(explode('-', $orderBy), 2, null); // @phpstan-ignore-line + // @mago-expect analysis:invalid-return-statement + return array_pad(explode('-', $orderBy), 2, null); } /** diff --git a/mago-analyze-baseline.toml b/mago-analyze-baseline.toml new file mode 100644 index 0000000..d7dcb0d --- /dev/null +++ b/mago-analyze-baseline.toml @@ -0,0 +1,625 @@ +variant = "loose" + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Mercure\MercureOptions::__construct`: expected `bool`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `trim`: expected `string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Shlinkio\Shlink\Common\Mercure\MercureOptions::__construct`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #3 of `Shlinkio\Shlink\Common\Mercure\MercureOptions::__construct`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #4 of `Shlinkio\Shlink\Common\Mercure\MercureOptions::__construct`: expected `null|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #5 of `Shlinkio\Shlink\Common\Mercure\MercureOptions::__construct`: expected `non-empty-string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "config/mercure.config.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Cache/CacheFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Symfony\Component\Cache\Adapter\ApcuAdapter::__construct`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Cache/CacheFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Symfony\Component\Cache\Adapter\RedisAdapter::__construct`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Cache/CacheFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/Cache/CacheFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Cache/CacheFactory.php" +code = "mixed-operand" +message = "Casting `mixed` to `bool`." +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Cache\RedisFactory::resolveOptions`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Cache\RedisFactory::resolveServers`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-argument" +message = "Invalid argument type for argument #1 of `parse_str`: expected `string`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `array_map`: expected `array<('K.array_map() extends array-key), string>`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "possibly-invalid-argument" +message = "Possible argument type mismatch for argument #2 of `sprintf`: expected `Stringable|null|scalar`, but possibly received `array|string>|string`." +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "redundant-comparison" +message = "Redundant `!==` comparison: left-hand side is always not identical to right-hand side." +count = 1 + +[[issues]] +file = "src/Cache/RedisFactory.php" +code = "redundant-logical-operation" +message = "Redundant `&&` operation: left operand is evaluated and right operand is always truthy." +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `nonnull`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\DBAL\Types\Type::addType`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\DBAL\Types\Type::hasType`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\ORM\Configuration::setDefaultRepositoryClassName`: expected `class-string>`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\ORM\Configuration::setProxyDir`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Doctrine\ConfigurationFactory::registerTypes`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Doctrine\DBAL\Types\Type::addType`: expected `Doctrine\DBAL\Types\Type|class-string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Shlinkio\Shlink\Common\Doctrine\Mapping\EnhancedPHPDriver::__construct`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #3 of `Shlinkio\Shlink\Common\Doctrine\Mapping\EnhancedPHPDriver::__construct`: expected `bool`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 4 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 4 + +[[issues]] +file = "src/Doctrine/ConfigurationFactory.php" +code = "mixed-operand" +message = "Casting `mixed` to `bool`." +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #1 of `Doctrine\Common\EventManager::addEventListener`: expected `array|string`, but provided type `array-key` is less specific.' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-argument" +message = '''Invalid argument type for argument #1 of `Doctrine\DBAL\DriverManager::getConnection`: expected `array{'application_name'?: string, 'charset'?: string, 'dbname'?: string, 'defaultTableOptions'?: array, 'driver'?: string('ibm_db2')|string('mysqli')|string('oci8')|string('pdo_mysql')|string('pdo_oci')|string('pdo_pgsql')|string('pdo_sqlite')|string('pdo_sqlsrv')|string('pgsql')|string('sqlite3')|string('sqlsrv'), 'driverClass'?: class-string, 'driverOptions'?: array, 'host'?: string, 'keepReplica'?: bool, 'memory'?: bool, 'password'?: string, 'path'?: string, 'persistent'?: bool, 'port'?: int, 'primary'?: array{'application_name'?: string, 'charset'?: string, 'dbname'?: string, 'defaultTableOptions'?: array, 'driver'?: string('ibm_db2')|string('mysqli')|string('oci8')|string('pdo_mysql')|string('pdo_oci')|string('pdo_pgsql')|string('pdo_sqlite')|string('pdo_sqlsrv')|string('pgsql')|string('sqlite3')|string('sqlsrv'), 'driverClass'?: class-string, 'driverOptions'?: array, 'host'?: string, 'memory'?: bool, 'password'?: string, 'path'?: string, 'persistent'?: bool, 'port'?: int, 'serverVersion'?: string, 'sessionMode'?: int, 'unix_socket'?: string, 'user'?: string, 'wrapperClass'?: class-string}, 'replica'?: array, 'driver'?: string('ibm_db2')|string('mysqli')|string('oci8')|string('pdo_mysql')|string('pdo_oci')|string('pdo_pgsql')|string('pdo_sqlite')|string('pdo_sqlsrv')|string('pgsql')|string('sqlite3')|string('sqlsrv'), 'driverClass'?: class-string, 'driverOptions'?: array, 'host'?: string, 'memory'?: bool, 'password'?: string, 'path'?: string, 'persistent'?: bool, 'port'?: int, 'serverVersion'?: string, 'sessionMode'?: int, 'unix_socket'?: string, 'user'?: string, 'wrapperClass'?: class-string}>, 'serverVersion'?: string, 'sessionMode'?: int, 'unix_socket'?: string, 'user'?: string, 'wrapperClass'?: class-string}`, but found `nonnull`.''' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Psr\Container\ContainerInterface::get`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Doctrine\EntityManagerFactory::registerListeners`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #2 of `Doctrine\Common\EventManager::addEventListener`: expected `object`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 3 + +[[issues]] +file = "src/Doctrine/EntityManagerFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Doctrine/EntityRepositoryFactory.php" +code = "possibly-invalid-argument" +message = '''Possible argument type mismatch for argument #1 of `Doctrine\ORM\EntityManager::getClassMetadata`: expected `class-string<'T.doctrine\persistence\objectmanager::getclassmetadata() extends object>`, but possibly received `string`.''' +count = 1 + +[[issues]] +file = "src/Doctrine/EntityRepositoryFactory.php" +code = "unknown-class-instantiation" +message = "Cannot determine the concrete class for instantiation." +count = 1 + +[[issues]] +file = "src/Doctrine/Mapping/EnhancedPHPDriver.php" +code = "extend-final-class" +message = 'Class `Shlinkio\Shlink\Common\Doctrine\Mapping\EnhancedPHPDriver` cannot extend final class `Doctrine\Persistence\Mapping\Driver\PHPDriver`' +count = 1 + +[[issues]] +file = "src/Doctrine/Mapping/EnhancedPHPDriver.php" +code = "invalid-callable" +message = "Expression of type `mixed` cannot be called as a function or method." +count = 1 + +[[issues]] +file = "src/Doctrine/Mapping/EnhancedPHPDriver.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\Persistence\Mapping\Driver\PHPDriver::__construct`: expected `Doctrine\Persistence\Mapping\Driver\FileLocator|array|string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/Mapping/EnhancedPHPDriver.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Doctrine/ReopeningEntityManager.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Doctrine\ORM\Decorator\EntityManagerDecorator::__construct`: expected `Doctrine\ORM\EntityManagerInterface`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Doctrine/ReopeningEntityManager.php" +code = "mixed-property-type-coercion" +message = 'A value with a less specific type `mixed` is being assigned to property `$wrapped` (Doctrine\ORM\EntityManagerInterface).' +count = 1 + +[[issues]] +file = "src/Doctrine/Type/ChronosDateTimeType.php" +code = "possibly-null-argument" +message = 'Argument #1 of method `Cake\Chronos\Chronos::instance` is possibly `null`, but parameter type `DateTimeInterface` does not accept it.' +count = 1 + +[[issues]] +file = "src/Http/HttpClientFactory.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 2 + +[[issues]] +file = "src/Http/HttpClientFactory.php" +code = "less-specific-nested-argument-type" +message = 'Argument type mismatch for argument #1 of `GuzzleHttp\HandlerStack::push`: expected `(callable((callable(...mixed): mixed)): (callable(...mixed): mixed))`, but provided type `(callable(...mixed=): mixed)` is less specific.' +count = 2 + +[[issues]] +file = "src/Http/HttpClientFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 5 + +[[issues]] +file = "src/Http/HttpClientFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Lock/NamespacedStore.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `Shlinkio\Shlink\Common\Lock\NamespacedStore::namespaceKey`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `nonnull`, which is not iterable." +count = 1 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Laminas\Stratigility\Middleware\ErrorHandler::attachListener`: expected `(callable(...mixed=): mixed)`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Mezzio\ProblemDetails\ProblemDetailsMiddleware::attachListener`: expected `(callable(Throwable, Psr\Http\Message\RequestInterface, Psr\Http\Message\ResponseInterface): void)`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Psr\Container\ContainerInterface::get`: expected `string`, but found `mixed`.' +count = 2 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Logger/ErrorHandlerListenerAttachingDelegator.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Logger/LoggerAwareDelegatorFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "less-specific-nested-argument-type" +message = 'Argument type mismatch for argument #3 of `Monolog\Logger::__construct`: expected `array`, but provided type `array` is less specific.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Monolog\Formatter\JsonFormatter::__construct`: expected `bool`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Monolog\Formatter\LineFormatter::__construct`: expected `null|string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Monolog\Handler\RotatingFileHandler::__construct`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Monolog\Handler\StreamHandler::__construct`: expected `resource|string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Monolog\Level::tryFrom`: expected `int`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Logger\Exception\InvalidLoggerException::fromInvalidType`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Logger\LoggerFactory::buildHandler`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Logger\LoggerFactory::resolveProcessors`: expected `array`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `Shlinkio\Shlink\Common\Logger\LoggerType::tryFrom`: expected `string`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `array_map`: expected `array<('K.array_map() extends array-key), string>`, but found `nonnull`." +count = 1 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 2 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 6 + +[[issues]] +file = "src/Logger/LoggerFactory.php" +code = "mixed-operand" +message = "Invalid left operand: type `nonnull` cannot be reliably used in string concatenation." +count = 1 + +[[issues]] +file = "src/Middleware/AccessLogMiddleware.php" +code = "mixed-argument" +message = "Invalid argument type for argument #2 of `str_ends_with`: expected `string`, but found `mixed`." +count = 1 + +[[issues]] +file = "src/Middleware/AccessLogMiddleware.php" +code = "mixed-assignment" +message = "Assigning `mixed` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/ObjectMapper/MapperBuilderDelegatorFactory.php" +code = "invalid-argument" +message = 'Invalid argument type for argument #1 of `CuyZ\Valinor\MapperBuilder::registerConstructor`: expected `(pure-callable(...mixed): mixed)|class-string`, but found `(closure(string): Cake\Chronos\Chronos)`.' +count = 1 + +[[issues]] +file = "src/ObjectMapper/MapperBuilderDelegatorFactory.php" +code = "invalid-argument" +message = 'Invalid argument type for argument #1 of `CuyZ\Valinor\MapperBuilder::registerConverter`: expected `(pure-callable(...mixed): mixed)|class-string`, but found `(closure(string, (callable(...mixed=): mixed)): mixed)`.' +count = 1 + +[[issues]] +file = "src/ObjectMapper/MapperBuilderDelegatorFactory.php" +code = "invalid-argument" +message = '''Invalid argument type for argument #2 of `CuyZ\Valinor\MapperBuilder::infer`: expected `(pure-callable(...mixed): mixed)`, but found `(closure(): class-string('Cake\Chronos\Chronos'))`.''' +count = 1 + +[[issues]] +file = "src/ObjectMapper/MapperBuilderDelegatorFactory.php" +code = "mixed-return-statement" +message = "Could not infer a precise return type for function `{closure:src/ObjectMapper/MapperBuilderDelegatorFactory.php:29:17}`. Saw type `mixed`." +count = 1 + +[[issues]] +file = "src/ObjectMapper/MappingError.php" +code = "mixed-return-statement" +message = 'Could not infer a precise return type for function `Shlinkio\Shlink\Common\ObjectMapper\MappingError::body`. Saw type `mixed`.' +count = 1 + +[[issues]] +file = "src/ObjectMapper/TagsConverter.php" +code = "invalid-return-statement" +message = 'Invalid return type for function `Shlinkio\Shlink\Common\ObjectMapper\TagsConverter::map`: expected `array`, but found `array`.' +count = 1 + +[[issues]] +file = "src/Paginator/Paginator.php" +code = "incompatible-parameter-type" +message = 'Parameter `$maxPerPage` of `Shlinkio\Shlink\Common\Paginator\Paginator::setmaxperpage()` expects type `int(-1)|positive-int` but parent `Pagerfanta\Pagerfanta::setmaxperpage()` expects type `int`' +count = 1 + +[[issues]] +file = "src/Paginator/Util/PagerfantaUtils.php" +code = "less-specific-nested-argument-type" +message = '''Argument type mismatch for argument #2 of `Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils::serializeItems`: expected `(callable(('T.shlinkio\shlink\common\paginator\util\pagerfantautils extends mixed)): array)|null`, but provided type `(callable(('T.shlinkio\shlink\common\paginator\util\pagerfantautils extends mixed)): mixed)|null` is less specific.''' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setHost`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setIsSecure`: expected `bool`, but found `nonnull`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setPassword`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setPort`: expected `int`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setUser`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-argument" +message = 'Invalid argument type for argument #1 of `PhpAmqpLib\Connection\AMQPConnectionConfig::setVhost`: expected `string`, but found `mixed`.' +count = 1 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-array-access" +message = "Unsafe array access on type `nonnull`." +count = 5 + +[[issues]] +file = "src/RabbitMq/AMQPConfigFactory.php" +code = "mixed-assignment" +message = "Assigning `nonnull` type to a variable may lead to unexpected behavior." +count = 1 + +[[issues]] +file = "src/Response/PixelResponse.php" +code = "less-specific-argument" +message = '''Argument type mismatch for argument #3 of `Laminas\Diactoros\Response::__construct`: expected `array|string>`, but provided type `array{'content-type': string('image/gif'), ...}` is less specific.''' +count = 1 + +[[issues]] +file = "src/Response/PixelResponse.php" +code = "possibly-false-argument" +message = 'Argument #1 of method `Laminas\Diactoros\Stream::write` is possibly `false`, but parameter type `string` does not accept it.' +count = 1 + +[[issues]] +file = "src/Response/ResponseUtilsTrait.php" +code = "less-specific-argument" +message = 'Argument type mismatch for argument #3 of `Laminas\Diactoros\Response::__construct`: expected `array|string>`, but provided type `array` is less specific.' +count = 1 + +[[issues]] +file = "test/Cache/RedisFactoryTest.php" +code = "non-documented-property" +message = 'Ambiguous property access: $ssl on class `Predis\Connection\ParametersInterface`.' +count = 1 + +[[issues]] +file = "test/Doctrine/EntityManagerFactoryTest.php" +code = "invalid-iterator" +message = "The expression provided to `foreach` is not iterable. It resolved to type `mixed`, which is not iterable." +count = 1 + +[[issues]] +file = "test/Doctrine/ReopeningEntityManagerTest.php" +code = "redundant-comparison" +message = "Redundant `===` comparison: left-hand side is never identical to right-hand side." +count = 1 + +[[issues]] +file = "test/Lock/NamespacedStoreTest.php" +code = "too-few-arguments" +message = 'Too few arguments provided for method `Shlinkio\Shlink\Common\Lock\NamespacedStore::putOffExpiration`.' +count = 1 + +[[issues]] +file = "test/Mercure/MercureOptionsTest.php" +code = "deprecated-method" +message = 'Call to deprecated method: `Shlinkio\Shlink\Common\Mercure\MercureOptions::isEnabled`.' +count = 1 + +[[issues]] +file = "test/ObjectMapper/HostAndPortConverterTest.php" +code = "less-specific-nested-argument-type" +message = 'Argument type mismatch for argument #2 of `Shlinkio\Shlink\Common\ObjectMapper\HostAndPortConverter::map`: expected `(callable(mixed): string)`, but provided type `(closure(mixed): mixed)` is less specific.' +count = 1 + +[[issues]] +file = "test/ObjectMapper/TagsConverterTest.php" +code = "less-specific-nested-argument-type" +message = 'Argument type mismatch for argument #1 of `Shlinkio\Shlink\Common\ObjectMapper\TagsConverter::map`: expected `array`, but provided type `array` is less specific.' +count = 1 diff --git a/mago.toml b/mago.toml index da3cda3..ec175e8 100644 --- a/mago.toml +++ b/mago.toml @@ -8,7 +8,7 @@ extends = "vendor/shlinkio/shlink-mago-config/mago.toml" [source] workspace = "." paths = ["config", "functions", "src", "test"] -excludes = ["vendor"] +includes = ["vendor"] [source.glob] literal-separator = true @@ -18,3 +18,13 @@ no-literal-password = { exclude = ["test"] } no-empty-catch-clause = { exclude = ["test"] } no-empty-loop = { exclude = ["test"] } ambiguous-function-call = { exclude = ["functions"] } + +[analyzer] +baseline = "mago-analyze-baseline.toml" +ignore = [ + { code = "non-documented-method", in = "test/" }, + { code = "mixed-assignment", in = "test/" }, + { code = "mixed-argument", in = "test/" }, + { code = "string-member-selector", in = "test/" }, + { code = "property-type-coercion", in = "test/" }, +] diff --git a/test/Cache/RedisFactoryTest.php b/test/Cache/RedisFactoryTest.php index 68a25d5..51692ea 100644 --- a/test/Cache/RedisFactoryTest.php +++ b/test/Cache/RedisFactoryTest.php @@ -177,7 +177,7 @@ public function exceptionIsThrownIfServerUriHasInvalidFormat(): void ]); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage( + $this->expectExceptionMessageIsOrContains( 'Provided server "//" is not a valid URL with format schema://[[username]:password@]host:port', ); @@ -314,7 +314,7 @@ public function exceptionIsThrownIfDatabaseIsNotAnInt(string $database): void ]); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The redis database index should be an integer, ' . $database . ' provided'); + $this->expectExceptionMessageIs('The redis database index should be an integer, ' . $database . ' provided'); ($this->factory)($this->container); } diff --git a/test/Doctrine/EntityRepositoryFactoryTest.php b/test/Doctrine/EntityRepositoryFactoryTest.php index 6da50b3..4954191 100644 --- a/test/Doctrine/EntityRepositoryFactoryTest.php +++ b/test/Doctrine/EntityRepositoryFactoryTest.php @@ -44,7 +44,7 @@ public function setUp(): void } /** - * @param class-string $repoClass + * @param class-string $repoClass */ #[Test, DataProvider('provideValidRepoNames')] public function createsRequestedRepositoryClass(string $repoClass): void @@ -75,7 +75,7 @@ public function throwsExceptionWhenInvalidRepoIsRequested(): void $this->em->expects($this->never())->method('getClassMetadata'); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf( + $this->expectExceptionMessageIs(sprintf( '"%s" cannot create an instance of "%s", as it is neither an instance of or extends from "%s"', EntityRepositoryFactory::class, stdClass::class, diff --git a/test/Doctrine/Mapping/EnhancedPHPDriverTest.php b/test/Doctrine/Mapping/EnhancedPHPDriverTest.php index 2b7dda6..c985bdf 100644 --- a/test/Doctrine/Mapping/EnhancedPHPDriverTest.php +++ b/test/Doctrine/Mapping/EnhancedPHPDriverTest.php @@ -18,7 +18,7 @@ class EnhancedPHPDriverTest extends TestCase { private Stub&FileLocator $loader; - /** @var MockObject & ClassMetadata */ + /** @var MockObject&ClassMetadata */ private MockObject&ClassMetadata $meta; public function setUp(): void diff --git a/test/Logger/LoggerFactoryTest.php b/test/Logger/LoggerFactoryTest.php index 37ed75c..8d44ccf 100644 --- a/test/Logger/LoggerFactoryTest.php +++ b/test/Logger/LoggerFactoryTest.php @@ -40,7 +40,7 @@ public function anExceptionIsThrownWhenRequestedLoggerDoesNotHaveConfig(): void $this->container->expects($this->once())->method('get')->with('config')->willReturn(['logger' => []]); $this->expectException(InvalidLoggerException::class); - $this->expectExceptionMessage( + $this->expectExceptionMessageIs( 'Provided logger with name "foo" is not valid. Make sure to provide a value defined under the "logger" ' . 'config key.', ); @@ -60,7 +60,7 @@ public function anExceptionIsThrownWhenConfiguredTypeIsInvalid(array $config): v ]); $this->expectException(InvalidLoggerException::class); - $this->expectExceptionMessage('Expected one of ["file", "stream"]'); + $this->expectExceptionMessageIsOrContains('Expected one of ["file", "stream"]'); LoggerFactory::foo($this->container); // @phpstan-ignore-line } @@ -230,8 +230,10 @@ public function expectedFormatterIsCreated(array $config, string $expectedFormat ]); /** @var Logger $logger */ - $logger = LoggerFactory::foo($this->container); // @phpstan-ignore-line - $formatter = $logger->getHandlers()[0]->getFormatter(); // @phpstan-ignore-line + // @mago-ignore + $logger = LoggerFactory::foo($this->container); + // @mago-expect analysis:non-existent-method + $formatter = $logger->getHandlers()[0]->getFormatter(); self::assertInstanceOf($expectedFormatter, $formatter); } @@ -253,7 +255,7 @@ public function exceptionIsThrownIfInvalidFormatterIsConfigured(): void ]); $this->expectException(InvalidLoggerException::class); - $this->expectExceptionMessage( + $this->expectExceptionMessageIs( 'Provided formatter type "invalid" is not valid. Expected one of ["console", "json"]', ); diff --git a/test/Mercure/HubFactoryTest.php b/test/Mercure/HubFactoryTest.php index 184403f..739cb52 100644 --- a/test/Mercure/HubFactoryTest.php +++ b/test/Mercure/HubFactoryTest.php @@ -33,7 +33,7 @@ public function throwsExceptionWhenNoHubUrlIsConfigured(MercureOptions $options) $this->container->expects($this->once())->method('get')->with(MercureOptions::class)->willReturn($options); $this->expectException(MercureException::class); - $this->expectExceptionMessage( + $this->expectExceptionMessageIs( 'You have to provide mercure hub URL under mercure.internal_hub_url or mercure.public_hub_url', ); diff --git a/test/Mercure/JwtConfigFactoryTest.php b/test/Mercure/JwtConfigFactoryTest.php index 796be61..1135fb9 100644 --- a/test/Mercure/JwtConfigFactoryTest.php +++ b/test/Mercure/JwtConfigFactoryTest.php @@ -31,7 +31,7 @@ public function throwsExceptionWhenProperConfigCouldNotBeFound(MercureOptions $o $this->container->expects($this->once())->method('get')->with(MercureOptions::class)->willReturn($options); $this->expectException(MercureException::class); - $this->expectExceptionMessage( + $this->expectExceptionMessageIs( 'You have to provide a non-empty secret key for the JWT generation, under mercure.jwt_secret', ); diff --git a/test/ObjectMapper/HostAndPortConverterTest.php b/test/ObjectMapper/HostAndPortConverterTest.php index df82544..812a42a 100644 --- a/test/ObjectMapper/HostAndPortConverterTest.php +++ b/test/ObjectMapper/HostAndPortConverterTest.php @@ -32,7 +32,7 @@ protected function setUp(): void public function throwsWhenValueIsInvalid(string $value, string $expectedError): void { $this->expectException(MappingError::class); - $this->expectExceptionMessage($expectedError); + $this->expectExceptionMessageIs($expectedError); $this->converter->map($value, static fn () => ''); } diff --git a/test/ObjectMapper/LooseUriConverterTest.php b/test/ObjectMapper/LooseUriConverterTest.php index 44fad6b..16949e1 100644 --- a/test/ObjectMapper/LooseUriConverterTest.php +++ b/test/ObjectMapper/LooseUriConverterTest.php @@ -25,7 +25,7 @@ protected function setUp(): void public function throwsWhenInvalidValueIsProvided(string $value): void { $this->expectException(MappingError::class); - $this->expectExceptionMessage('Provided value does not match a loose URI pattern'); + $this->expectExceptionMessageIs('Provided value does not match a loose URI pattern'); $this->converter->map($value); } diff --git a/test/Util/IpAddressTest.php b/test/Util/IpAddressTest.php index 9105ad7..ae9acac 100644 --- a/test/Util/IpAddressTest.php +++ b/test/Util/IpAddressTest.php @@ -20,7 +20,7 @@ class IpAddressTest extends TestCase public function exceptionIsThrownWhenTryingToParseInvalidIp(string $invalidAddress): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Provided IP "%s" is invalid', $invalidAddress)); + $this->expectExceptionMessageIs(sprintf('Provided IP "%s" is invalid', $invalidAddress)); IpAddress::fromString($invalidAddress); }