From 1ebc564f40bd50f8c2238ea9b979e8457f0c22ab Mon Sep 17 00:00:00 2001 From: Ilario Pierbattista Date: Fri, 30 May 2025 08:08:39 +0200 Subject: [PATCH 1/5] Upgrade dev deps and bump php min to 8.1 --- Makefile | 20 +- composer.json | 10 +- phpstan-baseline.neon | 582 +++++++++++++++++- phpstan.neon | 5 +- psalm-baseline.xml | 12 - psalm.xml | 20 - rector.php | 2 +- src/Internal/Arrays/ListOfDecoder.php | 9 +- .../Combinators/ArrayPropsDecoder.php | 9 +- src/Internal/Combinators/ComposeDecoder.php | 16 +- .../Combinators/IntersectionDecoder.php | 14 +- src/Internal/Combinators/MapDecoder.php | 4 +- src/Internal/Combinators/UnionDecoder.php | 20 +- src/Internal/Primitives/UndefinedDecoder.php | 6 +- src/Internal/Undefined.php | 2 +- .../Useful/DateTimeFromStringDecoder.php | 13 +- src/Internal/Useful/RegexDecoder.php | 5 +- .../Useful/StringMatchingRegexDecoder.php | 5 +- src/Reporters/PathReporter.php | 2 +- src/Reporters/SimplePathReporter.php | 2 +- src/Utils/ConcreteDecoder.php | 4 +- src/Validation/Context.php | 4 +- src/Validation/ContextEntry.php | 15 +- src/Validation/VError.php | 15 +- src/Validation/ValidationFailures.php | 6 +- src/Validation/ValidationSuccess.php | 8 +- tests/architecture/ArchitectureTest.php | 10 +- .../DecodeApiResponse/Coordinates.php | 7 +- .../DecodeApiResponse/OpenWeatherResponse.php | 14 +- tests/examples/DecodeApiResponse/Sys.php | 9 +- tests/examples/DecodeApiResponse/Weather.php | 9 +- .../examples/DecodePartialPropertiesTest.php | 11 +- tests/examples/DecoderForSumType/A.php | 9 +- tests/examples/DecoderForSumType/B.php | 9 +- tests/examples/ParseCsv/City.php | 14 +- .../Combinators/ArrayPropsDecoderTest.php | 36 -- tests/type-assertions/TypeAssertion.php | 32 - tests/unit/DecodersTest.php | 5 +- 38 files changed, 653 insertions(+), 322 deletions(-) delete mode 100644 psalm-baseline.xml delete mode 100644 psalm.xml delete mode 100644 tests/type-assertions/Internal/Combinators/ArrayPropsDecoderTest.php delete mode 100644 tests/type-assertions/TypeAssertion.php diff --git a/Makefile b/Makefile index 6476e3d..1b9e2f8 100644 --- a/Makefile +++ b/Makefile @@ -22,19 +22,9 @@ run-php8.1: run-php8.2: @# Help: It creates and runs a docker image with PHP 8.2 docker-compose run --rm php82 bash -c "rm composer.lock || true; composer install --no-interaction; bash" -run: run-php7.4 +run: run-php8.1 @# Help: It creates and runs a docker image with the lowest supported PHP version -.PHONY: psalm psalm-update-baseline -psalm: - @# Help: It runs Psalm - ./vendor/bin/psalm --no-cache - -psalm-update-baseline: - @# Help: It updates the Psalm baseline - ./vendor/bin/psalm --update-baseline - - .PHONY: phpstan phpstan-update-baseline phpstan: @# Help: It runs PHPStan @@ -46,10 +36,6 @@ phpstan-update-baseline: .PHONY: type-assertions test -type-assertions: - @# Help: It runs tests on Psalm types - ./vendor/bin/psalm tests/type-assertions --no-cache - test: @# Help: It runs PHPUnit tests XDEBUG_MODE=coverage ./vendor/bin/phpunit @@ -63,10 +49,10 @@ cs-check: @# Help: It runs the code style check ./vendor/bin/php-cs-fixer fix --ansi --verbose --dry-run -ci: test phpstan psalm type-assertions cs-fix +ci: test phpstan cs-fix @# Help: It runs all tests and code style fix -ci-check: test phpstan psalm type-assertions cs-check +ci-check: test phpstan cs-check @# Help: It runs all tests and code style check .PHONY: rector diff --git a/composer.json b/composer.json index 25ef517..5f1eb7c 100644 --- a/composer.json +++ b/composer.json @@ -5,11 +5,10 @@ "require-dev": { "phpunit/phpunit": "^9", "giorgiosironi/eris": "^0.14.0", - "phpat/phpat": "^0.10.18", + "phpat/phpat": "^0.11", "facile-it/facile-coding-standard": "^1.2", - "vimeo/psalm": "4.30.0", - "phpstan/phpstan": "^1.8", - "rector/rector": "^1.2" + "phpstan/phpstan": "^2", + "rector/rector": "^2" }, "license": "MIT", "authors": [ @@ -27,12 +26,11 @@ "psr-4": { "Tests\\Facile\\PhpCodec\\": "tests/unit/", "Examples\\Facile\\PhpCodec\\": "tests/examples/", - "TypeAssertions\\Facile\\PhpCodec\\": "tests/type-assertions/", "ArchitectureAssertions\\Facile\\PhpCodec\\": "tests/architecture/" } }, "require": { - "php": "^7.4 | ^8.0" + "php": "^8.1" }, "prefer-stable": true, "archive": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d3515c0..79ba775 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,31 +1,601 @@ parameters: ignoreErrors: - - message: "#^Template type ClassFactory of method Facile\\\\PhpCodec\\\\Decoders\\:\\:classFromArrayPropsDecoder\\(\\) is not referenced in a parameter\\.$#" + message: '#^Method Facile\\PhpCodec\\Decoders\:\:pipe\(\) return type with generic interface Facile\\PhpCodec\\Decoder does not specify its types\: I, A$#' + identifier: missingType.generics count: 1 path: src/Decoders.php - - message: "#^Template type T of method Facile\\\\PhpCodec\\\\Decoders\\:\\:classFromArrayPropsDecoder\\(\\) is not referenced in a parameter\\.$#" + message: '#^Method Facile\\PhpCodec\\Decoders\:\:union\(\) return type with generic interface Facile\\PhpCodec\\Decoder does not specify its types\: I, A$#' + identifier: missingType.generics count: 1 path: src/Decoders.php - - message: "#^Template type VP of method Facile\\\\PhpCodec\\\\Validation\\\\ListOfValidation\\:\\:reduceToIndexedSuccessOrAllFailures\\(\\) is not referenced in a parameter\\.$#" + message: '#^PHPDoc tag @param for parameter \$factory with type Facile\\PhpCodec\\ClassFactory is not subtype of native type callable\.$#' + identifier: parameter.phpDocType + count: 1 + path: src/Decoders.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type Facile\\PhpCodec\\Internal\\Combinators\\IntersectionDecoder\\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Decoders.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type Facile\\PhpCodec\\Internal\\Combinators\\ComposeDecoder\\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Decoders.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\, T of object\> is not subtype of native type Facile\\PhpCodec\\Internal\\Combinators\\MapDecoder\\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Decoders.php + + - + message: '#^Parameter \#1 \$db of class Facile\\PhpCodec\\Internal\\Combinators\\ComposeDecoder constructor expects Facile\\PhpCodec\\Decoder\<\(A of IB\)\|IB, B\>, Facile\\PhpCodec\\Decoder\ given\.$#' + identifier: argument.type + count: 1 + path: src/Decoders.php + + - + message: '#^Parameter \#1 \$db of static method Facile\\PhpCodec\\Decoders\:\:compose\(\) expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\ given\.$#' + identifier: argument.type + count: 1 + path: src/Decoders.php + + - + message: '#^Parameter \#1 \$literal of class Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder constructor expects T of Facile\\PhpCodec\\Internal\\Combinators\\literable, T of bool\|int\|string given\.$#' + identifier: argument.type + count: 1 + path: src/Decoders.php + + - + message: '#^Parameter \#2 \$da of class Facile\\PhpCodec\\Internal\\Combinators\\ComposeDecoder constructor expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\ given\.$#' + identifier: argument.type + count: 1 + path: src/Decoders.php + + - + message: '#^Parameter \$factory of method Facile\\PhpCodec\\Decoders\:\:classFromArrayPropsDecoder\(\) has invalid type Facile\\PhpCodec\\ClassFactory\.$#' + identifier: class.notFound + count: 1 + path: src/Decoders.php + + - + message: '#^Template type T of method Facile\\PhpCodec\\Decoders\:\:classFromArrayPropsDecoder\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter + count: 1 + path: src/Decoders.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Arrays\\ListOfDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Arrays/ListOfDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\ArrayPropsDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\Validation\\>\>\>\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/ArrayPropsDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\ArrayPropsDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/ArrayPropsDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\IntersectionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/IntersectionDecoder.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type \$this\(Facile\\PhpCodec\\Internal\\Combinators\\IntersectionDecoder\\)\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Internal/Combinators/IntersectionDecoder.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Validation\\Validation\ is not subtype of native type Facile\\PhpCodec\\Validation\\ValidationFailures\.$#' + identifier: varTag.nativeType + count: 2 + path: src/Internal/Combinators/IntersectionDecoder.php + + - + message: '#^PHPDoc tag @var with type T1&T2 is not subtype of native type array\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Internal/Combinators/IntersectionDecoder.php + + - + message: '#^PHPDoc tag @var with type T1&T2 is not subtype of native type stdClass\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Internal/Combinators/IntersectionDecoder.php + + - + message: '#^Call to function is_bool\(\) with Facile\\PhpCodec\\Internal\\Combinators\\literable will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Call to function is_string\(\) with Facile\\PhpCodec\\Internal\\Combinators\\literable will always evaluate to false\.$#' + identifier: function.impossibleType + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Class Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder implements generic interface Facile\\PhpCodec\\Decoder but does not specify its types\: I, A$#' + identifier: missingType.generics + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Parameter \$literal of method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:__construct\(\) has invalid type Facile\\PhpCodec\\Internal\\Combinators\\literable\.$#' + identifier: class.notFound + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Parameter \$x of method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:literalName\(\) has invalid type Facile\\PhpCodec\\Internal\\Combinators\\literable\.$#' + identifier: class.notFound + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Property Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:\$literal has unknown class Facile\\PhpCodec\\Internal\\Combinators\\literable as its type\.$#' + identifier: class.notFound + count: 1 + path: src/Internal/Combinators/LiteralDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\Validation\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/UnionDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\Validation\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/UnionDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Combinators/UnionDecoder.php + + - + message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type \$this\(Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\\)\.$#' + identifier: varTag.nativeType + count: 1 + path: src/Internal/Combinators/UnionDecoder.php + + - + message: '#^Cannot cast mixed to string\.$#' + identifier: cast.string + count: 1 + path: src/Internal/FunctionUtils.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\FunctionUtils\:\:nameFromProps\(\) has parameter \$props with generic interface Facile\\PhpCodec\\Decoder but does not specify its types\: I, A$#' + identifier: missingType.generics + count: 1 + path: src/Internal/FunctionUtils.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\BoolDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/BoolDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\CallableDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/CallableDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\FloatDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/FloatDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\IntDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/IntDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\NullDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/NullDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\StringDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/StringDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Primitives\\UndefinedDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Primitives/UndefinedDecoder.php + + - + message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Internal/Useful/DateTimeFromStringDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Useful\\DateTimeFromStringDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 2 + path: src/Internal/Useful/DateTimeFromStringDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Useful\\IntFromStringDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\|Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Useful/IntFromStringDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Useful\\RegexDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Internal/Useful/RegexDecoder.php + + - + message: '#^Call to function is_string\(\) with string will always evaluate to true\.$#' + identifier: function.alreadyNarrowedType + count: 1 + path: src/Internal/Useful/StringMatchingRegexDecoder.php + + - + message: '#^Method Facile\\PhpCodec\\Internal\\Useful\\StringMatchingRegexDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 2 + path: src/Internal/Useful/StringMatchingRegexDecoder.php + + - + message: '#^Class Facile\\PhpCodec\\Validation\\Context implements generic interface Iterator but does not specify its types\: TKey, TValue$#' + identifier: missingType.generics + count: 1 + path: src/Validation/Context.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\Context\:\:__construct\(\) has parameter \$decoder with generic interface Facile\\PhpCodec\\Decoder but does not specify its types\: I, A$#' + identifier: missingType.generics + count: 1 + path: src/Validation/Context.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\ContextEntry\:\:getDecoder\(\) return type with generic interface Facile\\PhpCodec\\Decoder does not specify its types\: I, A$#' + identifier: missingType.generics + count: 1 + path: src/Validation/ContextEntry.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:reduceToIndexedSuccessOrAllFailures\(\) should return VP of Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\>\.$#' + identifier: return.type + count: 1 + path: src/Validation/ListOfValidation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:reduceToSuccessOrAllFailures\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type count: 1 path: src/Validation/ListOfValidation.php - - message: "#^Template type T of method Facile\\\\PhpCodec\\\\Validation\\\\Validation\\:\\:failure\\(\\) is not referenced in a parameter\\.$#" + message: '#^Method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:reduceToSuccessOrAllFailures\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\>\>\.$#' + identifier: return.type + count: 1 + path: src/Validation/ListOfValidation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:sequence\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Validation/ListOfValidation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:sequence\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\>\>\.$#' + identifier: return.type + count: 1 + path: src/Validation/ListOfValidation.php + + - + message: '#^Template type VP of method Facile\\PhpCodec\\Validation\\ListOfValidation\:\:reduceToIndexedSuccessOrAllFailures\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter + count: 1 + path: src/Validation/ListOfValidation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\Validation\:\:failure\(\) should return Facile\\PhpCodec\\Validation\\ValidationFailures\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Validation/Validation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\Validation\:\:failures\(\) should return Facile\\PhpCodec\\Validation\\ValidationFailures\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' + identifier: return.type + count: 1 + path: src/Validation/Validation.php + + - + message: '#^Method Facile\\PhpCodec\\Validation\\Validation\:\:success\(\) should return Facile\\PhpCodec\\Validation\\ValidationSuccess\ but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type + count: 1 + path: src/Validation/Validation.php + + - + message: '#^Template type T of method Facile\\PhpCodec\\Validation\\Validation\:\:failure\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter count: 1 path: src/Validation/Validation.php - - message: "#^Template type T of method Facile\\\\PhpCodec\\\\Validation\\\\Validation\\:\\:failures\\(\\) is not referenced in a parameter\\.$#" + message: '#^Template type T of method Facile\\PhpCodec\\Validation\\Validation\:\:failures\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter count: 1 path: src/Validation/Validation.php - - message: "#^Template type R of method Tests\\\\Facile\\\\PhpCodec\\\\BaseTestCase\\:\\:asserSuccessSameTo\\(\\) is not referenced in a parameter\\.$#" + message: '#^PHPDoc tag @param references unknown parameter\: \$a$#' + identifier: parameter.notFound + count: 1 + path: src/Validation/ValidationSuccess.php + + - + message: '#^Method Examples\\Facile\\PhpCodec\\DecodeApiResponse\\OpenWeatherResponse\:\:__construct\(\) has parameter \$weather with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/examples/DecodeApiResponse/OpenWeatherResponse.php + + - + message: '#^Method Examples\\Facile\\PhpCodec\\DecodeApiResponse\\OpenWeatherResponse\:\:getWeather\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/examples/DecodeApiResponse/OpenWeatherResponse.php + + - + message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{foo\: Facile\\PhpCodec\\Decoder\, bar\: Facile\\PhpCodec\\Decoder\} given\.$#' + identifier: argument.type + count: 1 + path: tests/examples/DecodePartialPropertiesTest.php + + - + message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{type\: Facile\\PhpCodec\\Decoder\, subType\: Facile\\PhpCodec\\Decoder, propA\: Facile\\PhpCodec\\Decoder\, propB\: Facile\\PhpCodec\\Decoder\\} given\.$#' + identifier: argument.type + count: 1 + path: tests/examples/DecoderForSumType/DecoderForSumType.php + + - + message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{type\: Facile\\PhpCodec\\Decoder\, case\: Facile\\PhpCodec\\Decoder, amount\: Facile\\PhpCodec\\Decoder\, flag\: Facile\\PhpCodec\\Decoder\\} given\.$#' + identifier: argument.type + count: 1 + path: tests/examples/DecoderForSumType/DecoderForSumType.php + + - + message: '#^Cannot call method getId\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/examples/ParseCsv/ParseCsvTest.php + + - + message: '#^Cannot call method getItalianLandRegistryCode\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/examples/ParseCsv/ParseCsvTest.php + + - + message: '#^Cannot call method getName\(\) on mixed\.$#' + identifier: method.nonObject + count: 3 + path: tests/examples/ParseCsv/ParseCsvTest.php + + - + message: '#^Template type R of method Tests\\Facile\\PhpCodec\\BaseTestCase\:\:asserSuccessSameTo\(\) is not referenced in a parameter\.$#' + identifier: method.templateTypeNotInParameter count: 1 path: tests/unit/BaseTestCase.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\GeneratorUtils\:\:scalar\(\) return type with generic interface Eris\\Generator does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/GeneratorUtils.php + + - + message: '#^Parameter \#1 \$f of static method Facile\\PhpCodec\\Internal\\FunctionUtils\:\:destructureIn\(\) expects callable\(mixed \.\.\.\)\: void, Closure\(int, string\)\: void given\.$#' + identifier: argument.type + count: 1 + path: tests/unit/Internal/Useful/StringMatchingRegexDecoderTest.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\A has an unused parameter \$a\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/A.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\A has an unused parameter \$b\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/A.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\A has an unused parameter \$c\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/A.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\SampleClass has an unused parameter \$amount\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/SampleClass.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\SampleClass has an unused parameter \$flag\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/SampleClass.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\SampleClass has an unused parameter \$name\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/SampleClass.php + + - + message: '#^Constructor of class Tests\\Facile\\PhpCodec\\Reporters\\Models\\SampleClass has an unused parameter \$number\.$#' + identifier: constructor.unusedParameter + count: 1 + path: tests/unit/Reporters/Models/SampleClass.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:assertReports\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:assertReports\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:assertReports\(\) has parameter \$validation with generic class Facile\\PhpCodec\\Validation\\Validation but does not specify its types\: A$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideIntersectionReport\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideListOfClassReport\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideNestedArrayPropsReport\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideReportRootClassError\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideReportRootErrors\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:provideUnionReport\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testIntersectionReport\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testIntersectionReport\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testListOfClassReport\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testListOfClassReport\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testNestedArrayPropsReport\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testNestedArrayPropsReport\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testReportRootClassError\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testReportRootClassError\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testReportRootError\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testReportRootError\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testUnionReport\(\) has parameter \$expected with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: tests/unit/Reporters/ReportersTest.php + + - + message: '#^Method Tests\\Facile\\PhpCodec\\Reporters\\ReportersTest\:\:testUnionReport\(\) has parameter \$reporter with generic interface Facile\\PhpCodec\\Reporter but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: tests/unit/Reporters/ReportersTest.php diff --git a/phpstan.neon b/phpstan.neon index edf254a..433411e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,7 +3,8 @@ includes: - phpat.neon - phpstan-baseline.neon parameters: - level: 0 + level: 9 paths: - src - - tests + - tests/examples + - tests/unit diff --git a/psalm-baseline.xml b/psalm-baseline.xml deleted file mode 100644 index 5605487..0000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - Generators::oneOf(Generators::int(), Generators::float(), Generators::bool()) - Generators::oneOf(Generators::string(), Generators::float(), Generators::bool()) - - - diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 1fe9e97..0000000 --- a/psalm.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - diff --git a/rector.php b/rector.php index ec0d046..e4e11a3 100644 --- a/rector.php +++ b/rector.php @@ -15,6 +15,6 @@ // register a single rule $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); $rectorConfig->sets([ - LevelSetList::UP_TO_PHP_74, + LevelSetList::UP_TO_PHP_81, ]); }; diff --git a/src/Internal/Arrays/ListOfDecoder.php b/src/Internal/Arrays/ListOfDecoder.php index 94e31b6..d3045c2 100644 --- a/src/Internal/Arrays/ListOfDecoder.php +++ b/src/Internal/Arrays/ListOfDecoder.php @@ -22,15 +22,14 @@ */ final class ListOfDecoder implements Decoder { - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $elementDecoder; - /** * @psalm-param Decoder $elementDecoder */ - public function __construct(Decoder $elementDecoder) + public function __construct( + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $elementDecoder + ) { - $this->elementDecoder = $elementDecoder; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Combinators/ArrayPropsDecoder.php b/src/Internal/Combinators/ArrayPropsDecoder.php index 015c776..75d87ab 100644 --- a/src/Internal/Combinators/ArrayPropsDecoder.php +++ b/src/Internal/Combinators/ArrayPropsDecoder.php @@ -24,15 +24,14 @@ */ final class ArrayPropsDecoder implements Decoder { - /** @var PD */ - private array $props; - /** * @psalm-param PD $props */ - public function __construct(array $props) + public function __construct( + /** @var PD */ + private readonly array $props + ) { - $this->props = $props; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Combinators/ComposeDecoder.php b/src/Internal/Combinators/ComposeDecoder.php index 06cfbde..8a2b56c 100644 --- a/src/Internal/Combinators/ComposeDecoder.php +++ b/src/Internal/Combinators/ComposeDecoder.php @@ -20,21 +20,17 @@ */ final class ComposeDecoder implements Decoder { - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $db; - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $da; - /** * @psalm-param Decoder $db * @psalm-param Decoder $da */ public function __construct( - Decoder $db, - Decoder $da - ) { - $this->db = $db; - $this->da = $da; + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $db, + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $da + ) + { } /** diff --git a/src/Internal/Combinators/IntersectionDecoder.php b/src/Internal/Combinators/IntersectionDecoder.php index 53cfe01..7b6f209 100644 --- a/src/Internal/Combinators/IntersectionDecoder.php +++ b/src/Internal/Combinators/IntersectionDecoder.php @@ -24,19 +24,17 @@ */ final class IntersectionDecoder implements Decoder { - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $a; - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $b; - /** * @psalm-param Decoder $a * @psalm-param Decoder $b */ - public function __construct(Decoder $a, Decoder $b) + public function __construct( + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $a, + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $b + ) { - $this->a = $a; - $this->b = $b; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Combinators/MapDecoder.php b/src/Internal/Combinators/MapDecoder.php index 33689c6..6bc7fe8 100644 --- a/src/Internal/Combinators/MapDecoder.php +++ b/src/Internal/Combinators/MapDecoder.php @@ -21,15 +21,13 @@ final class MapDecoder implements Decoder { /** @var callable(A):B */ private $f; - private string $name; /** * @psalm-param callable(A):B $f */ - public function __construct(callable $f, string $name = 'map') + public function __construct(callable $f, private readonly string $name = 'map') { $this->f = $f; - $this->name = $name; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Combinators/UnionDecoder.php b/src/Internal/Combinators/UnionDecoder.php index 05d8f9a..4f8ac7a 100644 --- a/src/Internal/Combinators/UnionDecoder.php +++ b/src/Internal/Combinators/UnionDecoder.php @@ -23,24 +23,18 @@ */ final class UnionDecoder implements Decoder { - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $a; - /** @var Decoder */ - private \Facile\PhpCodec\Decoder $b; - private int $indexBegin; - /** * @psalm-param Decoder $a * @psalm-param Decoder $b */ public function __construct( - Decoder $a, - Decoder $b, - int $indexBegin = 0 - ) { - $this->a = $a; - $this->b = $b; - $this->indexBegin = $indexBegin; + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $a, + /** @var Decoder */ + private readonly \Facile\PhpCodec\Decoder $b, + private readonly int $indexBegin = 0 + ) + { } public function validate($i, Context $context): Validation diff --git a/src/Internal/Primitives/UndefinedDecoder.php b/src/Internal/Primitives/UndefinedDecoder.php index 3c9dd6b..9fdc9e3 100644 --- a/src/Internal/Primitives/UndefinedDecoder.php +++ b/src/Internal/Primitives/UndefinedDecoder.php @@ -19,17 +19,13 @@ */ final class UndefinedDecoder implements Decoder { - /** @var U */ - private $default; - /** * @psalm-param U $default * * @param mixed $default */ - public function __construct($default) + public function __construct(private $default) { - $this->default = $default; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Undefined.php b/src/Internal/Undefined.php index cf10c3e..4314a31 100644 --- a/src/Internal/Undefined.php +++ b/src/Internal/Undefined.php @@ -4,7 +4,7 @@ namespace Facile\PhpCodec\Internal; -final class Undefined +final class Undefined implements \Stringable { public function __toString(): string { diff --git a/src/Internal/Useful/DateTimeFromStringDecoder.php b/src/Internal/Useful/DateTimeFromStringDecoder.php index 525a108..337a78d 100644 --- a/src/Internal/Useful/DateTimeFromStringDecoder.php +++ b/src/Internal/Useful/DateTimeFromStringDecoder.php @@ -16,14 +16,13 @@ */ final class DateTimeFromStringDecoder implements Decoder { - /** - * @psalm-readonly - */ - private string $format; - - public function __construct(string $format = \DATE_ATOM) + public function __construct( + /** + * @psalm-readonly + */ + private readonly string $format = \DATE_ATOM + ) { - $this->format = $format; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Useful/RegexDecoder.php b/src/Internal/Useful/RegexDecoder.php index 1649f4c..2674264 100644 --- a/src/Internal/Useful/RegexDecoder.php +++ b/src/Internal/Useful/RegexDecoder.php @@ -16,11 +16,8 @@ */ final class RegexDecoder implements Decoder { - private string $regex; - - public function __construct(string $regex) + public function __construct(private readonly string $regex) { - $this->regex = $regex; } public function validate($i, Context $context): Validation diff --git a/src/Internal/Useful/StringMatchingRegexDecoder.php b/src/Internal/Useful/StringMatchingRegexDecoder.php index 733bc34..f90a826 100644 --- a/src/Internal/Useful/StringMatchingRegexDecoder.php +++ b/src/Internal/Useful/StringMatchingRegexDecoder.php @@ -16,11 +16,8 @@ */ final class StringMatchingRegexDecoder implements Decoder { - private string $regex; - - public function __construct(string $regex) + public function __construct(private readonly string $regex) { - $this->regex = $regex; } public function validate($i, Context $context): Validation diff --git a/src/Reporters/PathReporter.php b/src/Reporters/PathReporter.php index 77d2059..16c5558 100644 --- a/src/Reporters/PathReporter.php +++ b/src/Reporters/PathReporter.php @@ -27,7 +27,7 @@ public function report(Validation $validation): array { return Validation::fold( fn(array $errors): array => \array_map( - [self::class, 'getMessage'], + self::getMessage(...), $errors ), fn(): array => ['No errors!'], diff --git a/src/Reporters/SimplePathReporter.php b/src/Reporters/SimplePathReporter.php index aa1dcf8..f4bd78a 100644 --- a/src/Reporters/SimplePathReporter.php +++ b/src/Reporters/SimplePathReporter.php @@ -23,7 +23,7 @@ public function report(Validation $validation): array { return Validation::fold( static fn(array $errors): array => array_map( - [self::class, 'getMessage'], + self::getMessage(...), $errors ), static fn(): array => ['No errors'], diff --git a/src/Utils/ConcreteDecoder.php b/src/Utils/ConcreteDecoder.php index d33a6e6..d218541 100644 --- a/src/Utils/ConcreteDecoder.php +++ b/src/Utils/ConcreteDecoder.php @@ -19,17 +19,15 @@ final class ConcreteDecoder implements Decoder { /** @var callable(I, Context):Validation */ private $validateFunc; - private string $name; /** * @psalm-param callable(I, Context):Validation $validate */ public function __construct( callable $validate, - string $name + private readonly string $name ) { $this->validateFunc = $validate; - $this->name = $name; } public function validate($i, Context $context): Validation diff --git a/src/Validation/Context.php b/src/Validation/Context.php index 6ccdb9b..caf071d 100644 --- a/src/Validation/Context.php +++ b/src/Validation/Context.php @@ -11,18 +11,16 @@ final class Context implements \Iterator /** @var ContextEntry[] */ private array $entries; private int $currentIndex = 0; - private \Facile\PhpCodec\Decoder $decoder; /** * @psalm-param Decoder $decoder * @psalm-param ContextEntry ...$entries */ public function __construct( - Decoder $decoder, + private readonly \Facile\PhpCodec\Decoder $decoder, ContextEntry ...$entries ) { $this->entries = $entries; - $this->decoder = $decoder; } public function appendEntries(ContextEntry ...$entries): self diff --git a/src/Validation/ContextEntry.php b/src/Validation/ContextEntry.php index de8296c..5dea0ad 100644 --- a/src/Validation/ContextEntry.php +++ b/src/Validation/ContextEntry.php @@ -8,11 +8,6 @@ final class ContextEntry { - private string $key; - private \Facile\PhpCodec\Decoder $decoder; - /** @var mixed */ - private $actual; - /** * @psalm-param string $key * @psalm-param Decoder $decoder @@ -20,14 +15,8 @@ final class ContextEntry * * @param mixed $actual */ - public function __construct( - string $key, - Decoder $decoder, - $actual - ) { - $this->key = $key; - $this->decoder = $decoder; - $this->actual = $actual; + public function __construct(private readonly string $key, private readonly \Facile\PhpCodec\Decoder $decoder, private $actual) + { } public function getKey(): string diff --git a/src/Validation/VError.php b/src/Validation/VError.php index d2660cd..167d6d8 100644 --- a/src/Validation/VError.php +++ b/src/Validation/VError.php @@ -6,11 +6,6 @@ final class VError { - /** @var mixed */ - private $value; - private \Facile\PhpCodec\Validation\Context $context; - private ?string $message = null; - /** * @psalm-param mixed $value * @psalm-param Context $context @@ -18,14 +13,8 @@ final class VError * * @param mixed $value */ - public function __construct( - $value, - Context $context, - ?string $message = null - ) { - $this->value = $value; - $this->context = $context; - $this->message = $message; + public function __construct(private $value, private readonly \Facile\PhpCodec\Validation\Context $context, private readonly ?string $message = null) + { } /** diff --git a/src/Validation/ValidationFailures.php b/src/Validation/ValidationFailures.php index a28ae54..eb97e25 100644 --- a/src/Validation/ValidationFailures.php +++ b/src/Validation/ValidationFailures.php @@ -11,17 +11,13 @@ */ final class ValidationFailures extends Validation { - /** @var list */ - private array $errors; - /** * @psalm-param list $errors * * @param VError[] $errors */ - public function __construct(array $errors) + public function __construct(private readonly array $errors) { - $this->errors = $errors; } /** diff --git a/src/Validation/ValidationSuccess.php b/src/Validation/ValidationSuccess.php index 8b2d1ac..bd575fd 100644 --- a/src/Validation/ValidationSuccess.php +++ b/src/Validation/ValidationSuccess.php @@ -11,17 +11,13 @@ */ final class ValidationSuccess extends Validation { - /** @var A */ - private $value; - /** * @psalm-param A $a * - * @param mixed $a + * @param mixed $value */ - public function __construct($a) + public function __construct(private $value) { - $this->value = $a; } /** diff --git a/tests/architecture/ArchitectureTest.php b/tests/architecture/ArchitectureTest.php index f26f40a..9daa307 100644 --- a/tests/architecture/ArchitectureTest.php +++ b/tests/architecture/ArchitectureTest.php @@ -16,12 +16,12 @@ class ArchitectureTest public function testAnyInternalClassShouldNotDependFromAnythingOutsideExceptDefinitionInterfaces(): Rule { return PHPat::rule() - ->classes(Selector::namespace('Facile\PhpCodec\Internal')) + ->classes(Selector::inNamespace('Facile\PhpCodec\Internal')) ->shouldNotDependOn() - ->classes(Selector::namespace('Facile\PhpCodec')) + ->classes(Selector::inNamespace('Facile\PhpCodec')) ->excluding( - Selector::namespace('Facile\PhpCodec\Internal'), - Selector::namespace('Facile\PhpCodec\Validation'), + Selector::inNamespace('Facile\PhpCodec\Internal'), + Selector::inNamespace('Facile\PhpCodec\Validation'), Selector::classname(Decoder::class), ); } @@ -29,7 +29,7 @@ public function testAnyInternalClassShouldNotDependFromAnythingOutsideExceptDefi public function testEndpointClasses(): Rule { return PHPat::rule() - ->classes(Selector::namespace('Facile\PhpCodec\*')) + ->classes(Selector::inNamespace('Facile\PhpCodec\*')) ->shouldNotDependOn() ->classes( Selector::classname(Decoders::class), diff --git a/tests/examples/DecodeApiResponse/Coordinates.php b/tests/examples/DecodeApiResponse/Coordinates.php index eff6c6e..ecaa718 100644 --- a/tests/examples/DecodeApiResponse/Coordinates.php +++ b/tests/examples/DecodeApiResponse/Coordinates.php @@ -9,13 +9,8 @@ */ class Coordinates { - private float $longitude; - private float $latitude; - - public function __construct(float $longitude, float $latitude) + public function __construct(private readonly float $longitude, private readonly float $latitude) { - $this->longitude = $longitude; - $this->latitude = $latitude; } public function getLatitude(): float diff --git a/tests/examples/DecodeApiResponse/OpenWeatherResponse.php b/tests/examples/DecodeApiResponse/OpenWeatherResponse.php index ac48f6c..06056ec 100644 --- a/tests/examples/DecodeApiResponse/OpenWeatherResponse.php +++ b/tests/examples/DecodeApiResponse/OpenWeatherResponse.php @@ -9,18 +9,8 @@ */ class OpenWeatherResponse { - private \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates $coordinates; - private array $weather; - private \Examples\Facile\PhpCodec\DecodeApiResponse\Sys $sys; - - public function __construct( - \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates $coordinates, - array $weathers, - Sys $sys - ) { - $this->coordinates = $coordinates; - $this->weather = $weathers; - $this->sys = $sys; + public function __construct(private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates $coordinates, private readonly array $weather, private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Sys $sys) + { } public function getCoordinates(): \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates diff --git a/tests/examples/DecodeApiResponse/Sys.php b/tests/examples/DecodeApiResponse/Sys.php index 4c83ba6..b0bb965 100644 --- a/tests/examples/DecodeApiResponse/Sys.php +++ b/tests/examples/DecodeApiResponse/Sys.php @@ -9,15 +9,8 @@ */ class Sys { - private string $country; - private \DateTimeInterface $sunrise; - private \DateTimeInterface $sunset; - - public function __construct(string $country, \DateTimeInterface $sunrise, \DateTimeInterface $sunset) + public function __construct(private readonly string $country, private readonly \DateTimeInterface $sunrise, private readonly \DateTimeInterface $sunset) { - $this->country = $country; - $this->sunrise = $sunrise; - $this->sunset = $sunset; } public function getCountry(): string diff --git a/tests/examples/DecodeApiResponse/Weather.php b/tests/examples/DecodeApiResponse/Weather.php index 42b902e..e44798c 100644 --- a/tests/examples/DecodeApiResponse/Weather.php +++ b/tests/examples/DecodeApiResponse/Weather.php @@ -9,15 +9,8 @@ */ class Weather { - private int $id; - private string $main; - private string $description; - - public function __construct(int $id, string $main, string $description) + public function __construct(private readonly int $id, private readonly string $main, private readonly string $description) { - $this->id = $id; - $this->main = $main; - $this->description = $description; } public function getId(): int diff --git a/tests/examples/DecodePartialPropertiesTest.php b/tests/examples/DecodePartialPropertiesTest.php index d63886c..741c2b0 100644 --- a/tests/examples/DecodePartialPropertiesTest.php +++ b/tests/examples/DecodePartialPropertiesTest.php @@ -35,15 +35,8 @@ public function test(): void class A { - private string $foo; - private int $bar; - - public function __construct( - string $foo, - int $bar - ) { - $this->foo = $foo; - $this->bar = $bar; + public function __construct(private readonly string $foo, private readonly int $bar) + { } public function getFoo(): string diff --git a/tests/examples/DecoderForSumType/A.php b/tests/examples/DecoderForSumType/A.php index 36570fd..b5e3f9c 100644 --- a/tests/examples/DecoderForSumType/A.php +++ b/tests/examples/DecoderForSumType/A.php @@ -12,15 +12,8 @@ final class A extends P public const SUB_foo = 'foo'; public const SUB_bar = 'bar'; - private string $subType; - private int $propertyA; - private string $propertyB; - - public function __construct(string $subType, int $propertyA, string $propertyB) + public function __construct(private readonly string $subType, private readonly int $propertyA, private readonly string $propertyB) { - $this->subType = $subType; - $this->propertyA = $propertyA; - $this->propertyB = $propertyB; } public function getType(): string diff --git a/tests/examples/DecoderForSumType/B.php b/tests/examples/DecoderForSumType/B.php index 38c9025..e842b5f 100644 --- a/tests/examples/DecoderForSumType/B.php +++ b/tests/examples/DecoderForSumType/B.php @@ -13,15 +13,8 @@ final class B extends P public const CASE_B2 = 2; public const CASE_B3 = 3; - private int $case; - private float $amount; - private bool $flag; - - public function __construct(int $case, float $amount, bool $flag) + public function __construct(private readonly int $case, private readonly float $amount, private readonly bool $flag) { - $this->case = $case; - $this->amount = $amount; - $this->flag = $flag; } public function getType(): string diff --git a/tests/examples/ParseCsv/City.php b/tests/examples/ParseCsv/City.php index c5a90e9..914d00f 100644 --- a/tests/examples/ParseCsv/City.php +++ b/tests/examples/ParseCsv/City.php @@ -6,18 +6,8 @@ class City { - private int $id; - private string $name; - private string $italianLandRegistryCode; - - public function __construct( - int $id, - string $name, - string $italianLandRegistryCode - ) { - $this->id = $id; - $this->name = $name; - $this->italianLandRegistryCode = $italianLandRegistryCode; + public function __construct(private readonly int $id, private readonly string $name, private readonly string $italianLandRegistryCode) + { } public function getId(): int diff --git a/tests/type-assertions/Internal/Combinators/ArrayPropsDecoderTest.php b/tests/type-assertions/Internal/Combinators/ArrayPropsDecoderTest.php deleted file mode 100644 index 90cd32e..0000000 --- a/tests/type-assertions/Internal/Combinators/ArrayPropsDecoderTest.php +++ /dev/null @@ -1,36 +0,0 @@ - Decoders::string(), - 'b' => Decoders::int(), - ]); - - /** @var mixed $i */ - $i = null; - - $v = $d->decode($i); - - /** - * @psalm-type K = 'a' | 'b' - * @psalm-type V = string | int - * - * @psalm-param Validation> $v - */ - $assert1 = function (Validation $v): void {}; - - $assert1($v); - } -} diff --git a/tests/type-assertions/TypeAssertion.php b/tests/type-assertions/TypeAssertion.php deleted file mode 100644 index ce17415..0000000 --- a/tests/type-assertions/TypeAssertion.php +++ /dev/null @@ -1,32 +0,0 @@ -v = $v; } public function getValue(): int From c657ded0a905759eaca28700f86f255944d5f349 Mon Sep 17 00:00:00 2001 From: Ilario Pierbattista Date: Fri, 30 May 2025 08:15:29 +0200 Subject: [PATCH 2/5] up --- .github/workflows/ci.yaml | 2 +- .github/workflows/static-analysis.yaml | 6 +----- Makefile | 14 +++++++------- docker-compose.yml | 22 +++++++++++----------- docker/Dockerfile | 2 +- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 313c2cc..ef6c8d6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [7.4, 8.0, 8.1] + php: [8.1, 8.2, 8.3, 8.4] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index aa49540..3582d6c 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -17,10 +17,6 @@ jobs: script: make cs-check - description: PHPStan script: make phpstan - - description: PSalm - script: make psalm - - description: Type assertions - script: make type-assertions name: ${{ matrix.description }} runs-on: ubuntu-latest @@ -30,7 +26,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 - name: Install dependencies uses: ramsey/composer-install@v2 - run: ${{ matrix.script }} diff --git a/Makefile b/Makefile index 1b9e2f8..fba159b 100644 --- a/Makefile +++ b/Makefile @@ -9,19 +9,19 @@ help: | xargs -I _ sh -c 'printf "%-40s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' -.PHONY: run run-php7.4 run-php8.0 run-php8.1 run-php8.2 -run-php7.4: - @# Help: It creates and runs a docker image with PHP 7.4 - docker-compose run --rm php74 bash -c "rm composer.lock || true; composer install --no-interaction; bash" -run-php8.0: - @# Help: It creates and runs a docker image with PHP 8.0 - docker-compose run --rm php80 bash -c "rm composer.lock || true; composer install --no-interaction; bash" +.PHONY: run run-php8.1 run-php8.2 run-php8.3 run-php8.4 run-php8.1: @# Help: It creates and runs a docker image with PHP 8.1 docker-compose run --rm php81 bash -c "rm composer.lock || true; composer install --no-interaction; bash" run-php8.2: @# Help: It creates and runs a docker image with PHP 8.2 docker-compose run --rm php82 bash -c "rm composer.lock || true; composer install --no-interaction; bash" +run-php8.3: + @# Help: It creates and runs a docker image with PHP 8.3 + docker-compose run --rm php83 bash -c "rm composer.lock || true; composer install --no-interaction; bash" +run-php8.4: + @# Help: It creates and runs a docker image with PHP 8.4 + docker-compose run --rm php84 bash -c "rm composer.lock || true; composer install --no-interaction; bash" run: run-php8.1 @# Help: It creates and runs a docker image with the lowest supported PHP version diff --git a/docker-compose.yml b/docker-compose.yml index 4482d1a..4e33d82 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,32 @@ -version: "3.8" - services: - php74: &base + php81: &base build: context: docker/ args: - PHP_IMAGE: php:7.4 - XDEBUG: xdebug-3.1.5 + PHP_IMAGE: php:8.1 volumes: - .:/home/dev/lib tty: true user: dev working_dir: /home/dev/lib - php80: + + php82: <<: *base build: context: docker/ args: - PHP_IMAGE: php:8.0 - php81: + PHP_IMAGE: php:8.2 + + php83: <<: *base build: context: docker/ args: - PHP_IMAGE: php:8.1 - php82: + PHP_IMAGE: php:8.3 + + php84: <<: *base build: context: docker/ args: - PHP_IMAGE: php:8.2 + PHP_IMAGE: php:8.4 diff --git a/docker/Dockerfile b/docker/Dockerfile index 2233983..abd7bd6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG PHP_IMAGE +ARG PHP_IMAGE=php:8.1 FROM $PHP_IMAGE COPY --from=composer /usr/bin/composer /usr/bin/composer From bbd999b07d94003d8ea05d37c951b4705e109d71 Mon Sep 17 00:00:00 2001 From: Ilario Pierbattista Date: Fri, 30 May 2025 08:18:24 +0200 Subject: [PATCH 3/5] up cs --- composer.json | 2 +- src/Internal/Arrays/ListOfDecoder.php | 4 +--- src/Internal/Combinators/ArrayPropsDecoder.php | 4 +--- src/Internal/Combinators/ComposeDecoder.php | 4 +--- src/Internal/Combinators/IntersectionDecoder.php | 4 +--- src/Internal/Combinators/UnionDecoder.php | 4 +--- src/Internal/Primitives/UndefinedDecoder.php | 4 +--- src/Internal/Useful/DateTimeFromStringDecoder.php | 4 +--- src/Internal/Useful/RegexDecoder.php | 4 +--- src/Internal/Useful/StringMatchingRegexDecoder.php | 4 +--- src/Validation/ContextEntry.php | 4 +--- src/Validation/VError.php | 4 +--- src/Validation/ValidationFailures.php | 4 +--- src/Validation/ValidationSuccess.php | 4 +--- tests/examples/DecodeApiResponse/Coordinates.php | 4 +--- tests/examples/DecodeApiResponse/OpenWeatherResponse.php | 4 +--- tests/examples/DecodeApiResponse/Sys.php | 4 +--- tests/examples/DecodeApiResponse/Weather.php | 4 +--- tests/examples/DecodePartialPropertiesTest.php | 4 +--- tests/examples/DecoderForSumType/A.php | 4 +--- tests/examples/DecoderForSumType/B.php | 4 +--- tests/examples/ParseCsv/City.php | 4 +--- tests/unit/DecodersTest.php | 4 +--- 23 files changed, 23 insertions(+), 67 deletions(-) diff --git a/composer.json b/composer.json index 5f1eb7c..a59607e 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "phpunit/phpunit": "^9", "giorgiosironi/eris": "^0.14.0", "phpat/phpat": "^0.11", - "facile-it/facile-coding-standard": "^1.2", + "facile-it/facile-coding-standard": "^1.3", "phpstan/phpstan": "^2", "rector/rector": "^2" }, diff --git a/src/Internal/Arrays/ListOfDecoder.php b/src/Internal/Arrays/ListOfDecoder.php index d3045c2..7900a34 100644 --- a/src/Internal/Arrays/ListOfDecoder.php +++ b/src/Internal/Arrays/ListOfDecoder.php @@ -28,9 +28,7 @@ final class ListOfDecoder implements Decoder public function __construct( /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $elementDecoder - ) - { - } + ) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Combinators/ArrayPropsDecoder.php b/src/Internal/Combinators/ArrayPropsDecoder.php index 75d87ab..d7cce51 100644 --- a/src/Internal/Combinators/ArrayPropsDecoder.php +++ b/src/Internal/Combinators/ArrayPropsDecoder.php @@ -30,9 +30,7 @@ final class ArrayPropsDecoder implements Decoder public function __construct( /** @var PD */ private readonly array $props - ) - { - } + ) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Combinators/ComposeDecoder.php b/src/Internal/Combinators/ComposeDecoder.php index 8a2b56c..34d8cd4 100644 --- a/src/Internal/Combinators/ComposeDecoder.php +++ b/src/Internal/Combinators/ComposeDecoder.php @@ -29,9 +29,7 @@ public function __construct( private readonly \Facile\PhpCodec\Decoder $db, /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $da - ) - { - } + ) {} /** * @psalm-param IA $i diff --git a/src/Internal/Combinators/IntersectionDecoder.php b/src/Internal/Combinators/IntersectionDecoder.php index 7b6f209..a189698 100644 --- a/src/Internal/Combinators/IntersectionDecoder.php +++ b/src/Internal/Combinators/IntersectionDecoder.php @@ -33,9 +33,7 @@ public function __construct( private readonly \Facile\PhpCodec\Decoder $a, /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $b - ) - { - } + ) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Combinators/UnionDecoder.php b/src/Internal/Combinators/UnionDecoder.php index 4f8ac7a..da16d6f 100644 --- a/src/Internal/Combinators/UnionDecoder.php +++ b/src/Internal/Combinators/UnionDecoder.php @@ -33,9 +33,7 @@ public function __construct( /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $b, private readonly int $indexBegin = 0 - ) - { - } + ) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Primitives/UndefinedDecoder.php b/src/Internal/Primitives/UndefinedDecoder.php index 9fdc9e3..18fc787 100644 --- a/src/Internal/Primitives/UndefinedDecoder.php +++ b/src/Internal/Primitives/UndefinedDecoder.php @@ -24,9 +24,7 @@ final class UndefinedDecoder implements Decoder * * @param mixed $default */ - public function __construct(private $default) - { - } + public function __construct(private $default) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Useful/DateTimeFromStringDecoder.php b/src/Internal/Useful/DateTimeFromStringDecoder.php index 337a78d..e938a58 100644 --- a/src/Internal/Useful/DateTimeFromStringDecoder.php +++ b/src/Internal/Useful/DateTimeFromStringDecoder.php @@ -21,9 +21,7 @@ public function __construct( * @psalm-readonly */ private readonly string $format = \DATE_ATOM - ) - { - } + ) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Useful/RegexDecoder.php b/src/Internal/Useful/RegexDecoder.php index 2674264..e59c452 100644 --- a/src/Internal/Useful/RegexDecoder.php +++ b/src/Internal/Useful/RegexDecoder.php @@ -16,9 +16,7 @@ */ final class RegexDecoder implements Decoder { - public function __construct(private readonly string $regex) - { - } + public function __construct(private readonly string $regex) {} public function validate($i, Context $context): Validation { diff --git a/src/Internal/Useful/StringMatchingRegexDecoder.php b/src/Internal/Useful/StringMatchingRegexDecoder.php index f90a826..4dea3ef 100644 --- a/src/Internal/Useful/StringMatchingRegexDecoder.php +++ b/src/Internal/Useful/StringMatchingRegexDecoder.php @@ -16,9 +16,7 @@ */ final class StringMatchingRegexDecoder implements Decoder { - public function __construct(private readonly string $regex) - { - } + public function __construct(private readonly string $regex) {} public function validate($i, Context $context): Validation { diff --git a/src/Validation/ContextEntry.php b/src/Validation/ContextEntry.php index 5dea0ad..c8f5476 100644 --- a/src/Validation/ContextEntry.php +++ b/src/Validation/ContextEntry.php @@ -15,9 +15,7 @@ final class ContextEntry * * @param mixed $actual */ - public function __construct(private readonly string $key, private readonly \Facile\PhpCodec\Decoder $decoder, private $actual) - { - } + public function __construct(private readonly string $key, private readonly \Facile\PhpCodec\Decoder $decoder, private $actual) {} public function getKey(): string { diff --git a/src/Validation/VError.php b/src/Validation/VError.php index 167d6d8..a0e864a 100644 --- a/src/Validation/VError.php +++ b/src/Validation/VError.php @@ -13,9 +13,7 @@ final class VError * * @param mixed $value */ - public function __construct(private $value, private readonly \Facile\PhpCodec\Validation\Context $context, private readonly ?string $message = null) - { - } + public function __construct(private $value, private readonly \Facile\PhpCodec\Validation\Context $context, private readonly ?string $message = null) {} /** * @return mixed diff --git a/src/Validation/ValidationFailures.php b/src/Validation/ValidationFailures.php index eb97e25..74eb376 100644 --- a/src/Validation/ValidationFailures.php +++ b/src/Validation/ValidationFailures.php @@ -16,9 +16,7 @@ final class ValidationFailures extends Validation * * @param VError[] $errors */ - public function __construct(private readonly array $errors) - { - } + public function __construct(private readonly array $errors) {} /** * @psalm-return list diff --git a/src/Validation/ValidationSuccess.php b/src/Validation/ValidationSuccess.php index bd575fd..47cc4a8 100644 --- a/src/Validation/ValidationSuccess.php +++ b/src/Validation/ValidationSuccess.php @@ -16,9 +16,7 @@ final class ValidationSuccess extends Validation * * @param mixed $value */ - public function __construct(private $value) - { - } + public function __construct(private $value) {} /** * @psalm-return A diff --git a/tests/examples/DecodeApiResponse/Coordinates.php b/tests/examples/DecodeApiResponse/Coordinates.php index ecaa718..6be6f91 100644 --- a/tests/examples/DecodeApiResponse/Coordinates.php +++ b/tests/examples/DecodeApiResponse/Coordinates.php @@ -9,9 +9,7 @@ */ class Coordinates { - public function __construct(private readonly float $longitude, private readonly float $latitude) - { - } + public function __construct(private readonly float $longitude, private readonly float $latitude) {} public function getLatitude(): float { diff --git a/tests/examples/DecodeApiResponse/OpenWeatherResponse.php b/tests/examples/DecodeApiResponse/OpenWeatherResponse.php index 06056ec..dcd7268 100644 --- a/tests/examples/DecodeApiResponse/OpenWeatherResponse.php +++ b/tests/examples/DecodeApiResponse/OpenWeatherResponse.php @@ -9,9 +9,7 @@ */ class OpenWeatherResponse { - public function __construct(private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates $coordinates, private readonly array $weather, private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Sys $sys) - { - } + public function __construct(private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates $coordinates, private readonly array $weather, private readonly \Examples\Facile\PhpCodec\DecodeApiResponse\Sys $sys) {} public function getCoordinates(): \Examples\Facile\PhpCodec\DecodeApiResponse\Coordinates { diff --git a/tests/examples/DecodeApiResponse/Sys.php b/tests/examples/DecodeApiResponse/Sys.php index b0bb965..acff8a2 100644 --- a/tests/examples/DecodeApiResponse/Sys.php +++ b/tests/examples/DecodeApiResponse/Sys.php @@ -9,9 +9,7 @@ */ class Sys { - public function __construct(private readonly string $country, private readonly \DateTimeInterface $sunrise, private readonly \DateTimeInterface $sunset) - { - } + public function __construct(private readonly string $country, private readonly \DateTimeInterface $sunrise, private readonly \DateTimeInterface $sunset) {} public function getCountry(): string { diff --git a/tests/examples/DecodeApiResponse/Weather.php b/tests/examples/DecodeApiResponse/Weather.php index e44798c..fb950a5 100644 --- a/tests/examples/DecodeApiResponse/Weather.php +++ b/tests/examples/DecodeApiResponse/Weather.php @@ -9,9 +9,7 @@ */ class Weather { - public function __construct(private readonly int $id, private readonly string $main, private readonly string $description) - { - } + public function __construct(private readonly int $id, private readonly string $main, private readonly string $description) {} public function getId(): int { diff --git a/tests/examples/DecodePartialPropertiesTest.php b/tests/examples/DecodePartialPropertiesTest.php index 741c2b0..a5e8d7e 100644 --- a/tests/examples/DecodePartialPropertiesTest.php +++ b/tests/examples/DecodePartialPropertiesTest.php @@ -35,9 +35,7 @@ public function test(): void class A { - public function __construct(private readonly string $foo, private readonly int $bar) - { - } + public function __construct(private readonly string $foo, private readonly int $bar) {} public function getFoo(): string { diff --git a/tests/examples/DecoderForSumType/A.php b/tests/examples/DecoderForSumType/A.php index b5e3f9c..31f1717 100644 --- a/tests/examples/DecoderForSumType/A.php +++ b/tests/examples/DecoderForSumType/A.php @@ -12,9 +12,7 @@ final class A extends P public const SUB_foo = 'foo'; public const SUB_bar = 'bar'; - public function __construct(private readonly string $subType, private readonly int $propertyA, private readonly string $propertyB) - { - } + public function __construct(private readonly string $subType, private readonly int $propertyA, private readonly string $propertyB) {} public function getType(): string { diff --git a/tests/examples/DecoderForSumType/B.php b/tests/examples/DecoderForSumType/B.php index e842b5f..6429015 100644 --- a/tests/examples/DecoderForSumType/B.php +++ b/tests/examples/DecoderForSumType/B.php @@ -13,9 +13,7 @@ final class B extends P public const CASE_B2 = 2; public const CASE_B3 = 3; - public function __construct(private readonly int $case, private readonly float $amount, private readonly bool $flag) - { - } + public function __construct(private readonly int $case, private readonly float $amount, private readonly bool $flag) {} public function getType(): string { diff --git a/tests/examples/ParseCsv/City.php b/tests/examples/ParseCsv/City.php index 914d00f..0417b32 100644 --- a/tests/examples/ParseCsv/City.php +++ b/tests/examples/ParseCsv/City.php @@ -6,9 +6,7 @@ class City { - public function __construct(private readonly int $id, private readonly string $name, private readonly string $italianLandRegistryCode) - { - } + public function __construct(private readonly int $id, private readonly string $name, private readonly string $italianLandRegistryCode) {} public function getId(): int { diff --git a/tests/unit/DecodersTest.php b/tests/unit/DecodersTest.php index a302ba9..963f54f 100644 --- a/tests/unit/DecodersTest.php +++ b/tests/unit/DecodersTest.php @@ -39,9 +39,7 @@ public function testMap(): void class A { - public function __construct(private readonly int $v) - { - } + public function __construct(private readonly int $v) {} public function getValue(): int { From 62a7c1583121c05b86880859389afd71c6caec1f Mon Sep 17 00:00:00 2001 From: Ilario Pierbattista Date: Fri, 30 May 2025 08:41:18 +0200 Subject: [PATCH 4/5] upgrade phpunit --- Makefile | 2 +- composer.json | 4 +- phpstan-baseline.neon | 54 +----------- src/Decoder.php | 19 ++--- src/Decoders.php | 13 +-- src/Internal/Combinators/LiteralDecoder.php | 27 +----- .../Useful/DateTimeFromStringDecoder.php | 1 - .../Useful/StringMatchingRegexDecoder.php | 1 - src/Reporter.php | 6 +- src/Reporters/PathReporter.php | 2 +- src/Validation/Validation.php | 82 +++++++++---------- src/Validation/ValidationFailures.php | 2 +- src/Validation/ValidationSuccess.php | 8 +- .../DecodeApiResponseTest.php | 1 - .../examples/DecodePartialPropertiesTest.php | 1 - .../DecoderForSumType/DecoderForSumType.php | 3 - tests/examples/ParseCsv/ParseCsvTest.php | 1 - tests/unit/BaseTestCase.php | 1 - tests/unit/DecodersTest.php | 2 - tests/unit/GeneratorUtils.php | 7 -- .../Combinators/ArrayPropsDecoderTest.php | 2 - .../Combinators/IntersectionDecoderTest.php | 4 - .../Internal/Combinators/UnionDecoderTest.php | 1 - .../Primitives/UndefinedDecoderTest.php | 5 -- .../Useful/DateTimeFromStringDecoderTest.php | 4 - .../Useful/StringMatchingRegexDecoderTest.php | 1 - tests/unit/Reporters/ReportersTest.php | 2 - 27 files changed, 66 insertions(+), 190 deletions(-) diff --git a/Makefile b/Makefile index fba159b..755a8ad 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ phpstan-update-baseline: ./vendor/bin/phpstan analyse src tests --generate-baseline -.PHONY: type-assertions test +.PHONY: test test: @# Help: It runs PHPUnit tests XDEBUG_MODE=coverage ./vendor/bin/phpunit diff --git a/composer.json b/composer.json index a59607e..60430d5 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,8 @@ "description": "A partial porting of io-ts in PHP", "type": "library", "require-dev": { - "phpunit/phpunit": "^9", - "giorgiosironi/eris": "^0.14.0", + "phpunit/phpunit": "^10", + "giorgiosironi/eris": "^1", "phpat/phpat": "^0.11", "facile-it/facile-coding-standard": "^1.3", "phpstan/phpstan": "^2", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 79ba775..81ad350 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -48,12 +48,6 @@ parameters: count: 1 path: src/Decoders.php - - - message: '#^Parameter \#1 \$literal of class Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder constructor expects T of Facile\\PhpCodec\\Internal\\Combinators\\literable, T of bool\|int\|string given\.$#' - identifier: argument.type - count: 1 - path: src/Decoders.php - - message: '#^Parameter \#2 \$da of class Facile\\PhpCodec\\Internal\\Combinators\\ComposeDecoder constructor expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\ given\.$#' identifier: argument.type @@ -121,44 +115,14 @@ parameters: path: src/Internal/Combinators/IntersectionDecoder.php - - message: '#^Call to function is_bool\(\) with Facile\\PhpCodec\\Internal\\Combinators\\literable will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: src/Internal/Combinators/LiteralDecoder.php - - - - message: '#^Call to function is_string\(\) with Facile\\PhpCodec\\Internal\\Combinators\\literable will always evaluate to false\.$#' - identifier: function.impossibleType - count: 1 - path: src/Internal/Combinators/LiteralDecoder.php - - - - message: '#^Class Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder implements generic interface Facile\\PhpCodec\\Decoder but does not specify its types\: I, A$#' - identifier: missingType.generics - count: 1 - path: src/Internal/Combinators/LiteralDecoder.php - - - - message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' identifier: return.type count: 1 path: src/Internal/Combinators/LiteralDecoder.php - - message: '#^Parameter \$literal of method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:__construct\(\) has invalid type Facile\\PhpCodec\\Internal\\Combinators\\literable\.$#' - identifier: class.notFound - count: 1 - path: src/Internal/Combinators/LiteralDecoder.php - - - - message: '#^Parameter \$x of method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:literalName\(\) has invalid type Facile\\PhpCodec\\Internal\\Combinators\\literable\.$#' - identifier: class.notFound - count: 1 - path: src/Internal/Combinators/LiteralDecoder.php - - - - message: '#^Property Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:\$literal has unknown class Facile\\PhpCodec\\Internal\\Combinators\\literable as its type\.$#' - identifier: class.notFound + message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\LiteralDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' + identifier: return.type count: 1 path: src/Internal/Combinators/LiteralDecoder.php @@ -342,12 +306,6 @@ parameters: count: 1 path: src/Validation/Validation.php - - - message: '#^Method Facile\\PhpCodec\\Validation\\Validation\:\:success\(\) should return Facile\\PhpCodec\\Validation\\ValidationSuccess\ but returns Facile\\PhpCodec\\Validation\\ValidationSuccess\\.$#' - identifier: return.type - count: 1 - path: src/Validation/Validation.php - - message: '#^Template type T of method Facile\\PhpCodec\\Validation\\Validation\:\:failure\(\) is not referenced in a parameter\.$#' identifier: method.templateTypeNotInParameter @@ -360,12 +318,6 @@ parameters: count: 1 path: src/Validation/Validation.php - - - message: '#^PHPDoc tag @param references unknown parameter\: \$a$#' - identifier: parameter.notFound - count: 1 - path: src/Validation/ValidationSuccess.php - - message: '#^Method Examples\\Facile\\PhpCodec\\DecodeApiResponse\\OpenWeatherResponse\:\:__construct\(\) has parameter \$weather with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue diff --git a/src/Decoder.php b/src/Decoder.php index 9b9dfa5..9b0a8fc 100644 --- a/src/Decoder.php +++ b/src/Decoder.php @@ -8,29 +8,24 @@ use Facile\PhpCodec\Validation\Validation; /** - * @psalm-template I - * @psalm-template A + * @template I + * @template A */ interface Decoder { /** - * @psalm-param I $i - * @psalm-param Context $context + * @param I $i * * @psalm-return Validation - * - * @param mixed $i */ - public function validate($i, Context $context): Validation; + public function validate(mixed $i, Context $context): Validation; /** - * @psalm-param I $i - * - * @psalm-return Validation + * @param I $i * - * @param mixed $i + * @return Validation */ - public function decode($i): Validation; + public function decode(mixed $i): Validation; public function getName(): string; } diff --git a/src/Decoders.php b/src/Decoders.php index d0d41ca..206c39d 100644 --- a/src/Decoders.php +++ b/src/Decoders.php @@ -26,7 +26,6 @@ use Facile\PhpCodec\Utils\ConcreteDecoder; use Facile\PhpCodec\Validation\Context; use Facile\PhpCodec\Validation\Validation; -use Psalm\Internal\Codebase\Properties; final class Decoders { @@ -61,7 +60,6 @@ public static function compose(Decoder $db, Decoder $da): Decoder { // TODO Fix this /** @psalm-var Decoder */ - /** @psalm-suppress InvalidArgument */ return new ComposeDecoder($db, $da); } @@ -100,7 +98,6 @@ public static function pipe( ): Decoder { // Order is important: composition is not commutative // TODO fix this - /** @psalm-suppress InvalidArgument */ return $c instanceof Decoder ? self::compose(self::pipe($b, $c, $d, $e), $a) : self::compose($b, $a); @@ -198,15 +195,13 @@ public static function transformValidationSuccess(callable $f, Decoder $da): Dec } /** - * @psalm-template T of bool | string | int + * @template T of bool | string | int * - * @psalm-param T $l + * @param T $l * - * @psalm-return Decoder - * - * @param mixed $l + * @return Decoder */ - public static function literal($l): Decoder + public static function literal(bool|string|int $l): Decoder { return new LiteralDecoder($l); } diff --git a/src/Internal/Combinators/LiteralDecoder.php b/src/Internal/Combinators/LiteralDecoder.php index 0fe5d8b..f0e2996 100644 --- a/src/Internal/Combinators/LiteralDecoder.php +++ b/src/Internal/Combinators/LiteralDecoder.php @@ -10,10 +10,8 @@ use Facile\PhpCodec\Validation\Validation; /** - * @psalm-type literable = bool | string | int - * * @template I of mixed - * @template T of literable + * @template T of bool | string | int * * @template-implements Decoder * @@ -22,21 +20,9 @@ final class LiteralDecoder implements Decoder { /** - * @var T - * - * @readonly - */ - private $literal; - - /** - * @psalm-param T $literal - * - * @param mixed $literal + * @param T $literal */ - public function __construct($literal) - { - $this->literal = $literal; - } + public function __construct(private readonly string|bool|int $literal) {} public function validate($i, Context $context): Validation { @@ -57,12 +43,7 @@ public function getName(): string return self::literalName($this->literal); } - /** - * @psalm-param literable $x - * - * @param mixed $x - */ - private static function literalName($x): string + private static function literalName(string|bool|int $x): string { if (\is_string($x)) { return "'{$x}'"; diff --git a/src/Internal/Useful/DateTimeFromStringDecoder.php b/src/Internal/Useful/DateTimeFromStringDecoder.php index e938a58..5ac55aa 100644 --- a/src/Internal/Useful/DateTimeFromStringDecoder.php +++ b/src/Internal/Useful/DateTimeFromStringDecoder.php @@ -25,7 +25,6 @@ public function __construct( public function validate($i, Context $context): Validation { - /** @psalm-suppress DocblockTypeContradiction */ if (! \is_string($i)) { return Validation::failure($i, $context); } diff --git a/src/Internal/Useful/StringMatchingRegexDecoder.php b/src/Internal/Useful/StringMatchingRegexDecoder.php index 4dea3ef..6d23b15 100644 --- a/src/Internal/Useful/StringMatchingRegexDecoder.php +++ b/src/Internal/Useful/StringMatchingRegexDecoder.php @@ -20,7 +20,6 @@ public function __construct(private readonly string $regex) {} public function validate($i, Context $context): Validation { - /** @psalm-suppress DocblockTypeContradiction */ if (! \is_string($i)) { return Validation::failure($i, $context); } diff --git a/src/Reporter.php b/src/Reporter.php index 2d02a7b..1e36f58 100644 --- a/src/Reporter.php +++ b/src/Reporter.php @@ -7,14 +7,14 @@ use Facile\PhpCodec\Validation\Validation; /** - * @psalm-template T + * @template T */ interface Reporter { /** - * @psalm-template A + * @template A * - * @psalm-param Validation $validation + * @param Validation $validation * * @return T */ diff --git a/src/Reporters/PathReporter.php b/src/Reporters/PathReporter.php index 16c5558..7d332b3 100644 --- a/src/Reporters/PathReporter.php +++ b/src/Reporters/PathReporter.php @@ -21,7 +21,7 @@ public static function create(): self } /** - * @psalm-return list + * @return list */ public function report(Validation $validation): array { diff --git a/src/Validation/Validation.php b/src/Validation/Validation.php index 965cf44..e4a3cb2 100644 --- a/src/Validation/Validation.php +++ b/src/Validation/Validation.php @@ -10,13 +10,11 @@ abstract class Validation { /** - * @psalm-template T + * @template T * - * @psalm-param T $a + * @param T $a * - * @psalm-return ValidationSuccess - * - * @param mixed $a + * @return ValidationSuccess */ public static function success($a): self { @@ -24,13 +22,11 @@ public static function success($a): self } /** - * @psalm-template T - * - * @psalm-param list $errors + * @template T * - * @psalm-return ValidationFailures + * @param list $errors * - * @param VError[] $errors + * @return ValidationFailures */ public static function failures(array $errors): self { @@ -38,15 +34,11 @@ public static function failures(array $errors): self } /** - * @psalm-template T - * - * @psalm-param mixed $value - * @psalm-param Context $context - * @psalm-param string|null $message - * - * @psalm-return ValidationFailures + * @template T * * @param mixed $value + * + * @return ValidationFailures */ public static function failure($value, Context $context, ?string $message = null): self { @@ -56,14 +48,14 @@ public static function failure($value, Context $context, ?string $message = null } /** - * @psalm-template T - * @psalm-template R + * @template T + * @template R * - * @psalm-param callable(list):R $onFailures - * @psalm-param callable(T):R $onSuccess - * @psalm-param Validation $v + * @param callable(list):R $onFailures + * @param callable(T):R $onSuccess + * @param Validation $v * - * @psalm-return R + * @return R */ public static function fold(callable $onFailures, callable $onSuccess, self $v) { @@ -79,11 +71,11 @@ public static function fold(callable $onFailures, callable $onSuccess, self $v) } /** - * @psalm-template T + * @template T * - * @psalm-param Validation $v + * @param Validation $v * - * @psalm-assert-if-true ValidationSuccess $v + * @phpstan-assert-if-true ValidationSuccess $v */ private static function isSuccess(self $v): bool { @@ -91,11 +83,11 @@ private static function isSuccess(self $v): bool } /** - * @psalm-template T + * @template T * - * @psalm-param Validation $v + * @param Validation $v * - * @psalm-assert-if-true ValidationFailures $v + * @phpstan-assert-if-true ValidationFailures $v */ private static function isFailures(self $v): bool { @@ -103,11 +95,11 @@ private static function isFailures(self $v): bool } /** - * @psalm-template T + * @template T * - * @psalm-param list> $validations + * @param list> $validations * - * @psalm-return Validation> + * @return Validation> * * @deprecated use sequence in ListOfValidation * @see ListOfValidation::sequence() @@ -118,11 +110,11 @@ public static function sequence(array $validations): self } /** - * @psalm-template T + * @template T * - * @psalm-param list> $validations + * @param list> $validations * - * @psalm-return Validation> + * @return Validation> * * @deprecated use ListOfValidation instead * @see ListOfValidation::reduceToSuccessOrAllFailures() @@ -133,13 +125,13 @@ public static function reduceToSuccessOrAllFailures(array $validations): self } /** - * @psalm-template T1 - * @psalm-template T2 + * @template T1 + * @template T2 * - * @psalm-param callable(T1):T2 $f - * @psalm-param Validation $v + * @param callable(T1):T2 $f + * @param Validation $v * - * @psalm-return Validation + * @return Validation */ public static function map(callable $f, self $v): self { @@ -152,13 +144,13 @@ public static function map(callable $f, self $v): self } /** - * @psalm-template T1 - * @psalm-template T2 + * @template T1 + * @template T2 * - * @psalm-param callable(T1):Validation $f - * @psalm-param Validation $v + * @param callable(T1):Validation $f + * @param Validation $v * - * @psalm-return Validation + * @return Validation */ public static function bind(callable $f, self $v): self { diff --git a/src/Validation/ValidationFailures.php b/src/Validation/ValidationFailures.php index 74eb376..f910be7 100644 --- a/src/Validation/ValidationFailures.php +++ b/src/Validation/ValidationFailures.php @@ -5,7 +5,7 @@ namespace Facile\PhpCodec\Validation; /** - * @psalm-template A + * @template A * * @extends Validation */ diff --git a/src/Validation/ValidationSuccess.php b/src/Validation/ValidationSuccess.php index 47cc4a8..0533ccd 100644 --- a/src/Validation/ValidationSuccess.php +++ b/src/Validation/ValidationSuccess.php @@ -5,18 +5,16 @@ namespace Facile\PhpCodec\Validation; /** - * @psalm-template A + * @template A * * @extends Validation */ final class ValidationSuccess extends Validation { /** - * @psalm-param A $a - * - * @param mixed $value + * @param A $value */ - public function __construct(private $value) {} + public function __construct(private readonly mixed $value) {} /** * @psalm-return A diff --git a/tests/examples/DecodeApiResponse/DecodeApiResponseTest.php b/tests/examples/DecodeApiResponse/DecodeApiResponseTest.php index 0f9ec5f..f2dc1ed 100644 --- a/tests/examples/DecodeApiResponse/DecodeApiResponseTest.php +++ b/tests/examples/DecodeApiResponse/DecodeApiResponseTest.php @@ -7,7 +7,6 @@ use Facile\PhpCodec\Decoders; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class DecodeApiResponseTest extends BaseTestCase { public function testJsonDecoding(): void diff --git a/tests/examples/DecodePartialPropertiesTest.php b/tests/examples/DecodePartialPropertiesTest.php index a5e8d7e..3dfbc59 100644 --- a/tests/examples/DecodePartialPropertiesTest.php +++ b/tests/examples/DecodePartialPropertiesTest.php @@ -7,7 +7,6 @@ use Facile\PhpCodec\Decoders; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class DecodePartialPropertiesTest extends BaseTestCase { public function test(): void diff --git a/tests/examples/DecoderForSumType/DecoderForSumType.php b/tests/examples/DecoderForSumType/DecoderForSumType.php index e7b3c9c..14a944c 100644 --- a/tests/examples/DecoderForSumType/DecoderForSumType.php +++ b/tests/examples/DecoderForSumType/DecoderForSumType.php @@ -9,7 +9,6 @@ use Facile\PhpCodec\Decoders; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class DecoderForSumType extends BaseTestCase { use TestTrait; @@ -46,7 +45,6 @@ public function testSumTypes(): void ) ); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::associative([ @@ -66,7 +64,6 @@ public function testSumTypes(): void self::assertSame($i['propB'], $a->getPropertyB()); }); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::associative([ diff --git a/tests/examples/ParseCsv/ParseCsvTest.php b/tests/examples/ParseCsv/ParseCsvTest.php index 407b83b..8f2546b 100644 --- a/tests/examples/ParseCsv/ParseCsvTest.php +++ b/tests/examples/ParseCsv/ParseCsvTest.php @@ -7,7 +7,6 @@ use Facile\PhpCodec\Decoders; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class ParseCsvTest extends BaseTestCase { public function test(): void diff --git a/tests/unit/BaseTestCase.php b/tests/unit/BaseTestCase.php index 16c3145..85680f0 100644 --- a/tests/unit/BaseTestCase.php +++ b/tests/unit/BaseTestCase.php @@ -9,7 +9,6 @@ use Facile\PhpCodec\Validation\ValidationSuccess; use PHPUnit\Framework\TestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class BaseTestCase extends TestCase { /** diff --git a/tests/unit/DecodersTest.php b/tests/unit/DecodersTest.php index 963f54f..d5896e6 100644 --- a/tests/unit/DecodersTest.php +++ b/tests/unit/DecodersTest.php @@ -8,7 +8,6 @@ use Eris\TestTrait; use Facile\PhpCodec\Decoders; -/** @psalm-suppress PropertyNotSetInConstructor */ class DecodersTest extends BaseTestCase { use TestTrait; @@ -20,7 +19,6 @@ public function testMap(): void Decoders::int() ); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::int() diff --git a/tests/unit/GeneratorUtils.php b/tests/unit/GeneratorUtils.php index 60b4910..d50dd74 100644 --- a/tests/unit/GeneratorUtils.php +++ b/tests/unit/GeneratorUtils.php @@ -9,15 +9,8 @@ final class GeneratorUtils { - /** - * @psalm-suppress MixedInferredReturnType - */ public static function scalar(): g { - /** - * @psalm-suppress UndefinedFunction - * @psalm-suppress MixedReturnStatement - */ return Generators::oneOf( Generators::int(), Generators::float(), diff --git a/tests/unit/Internal/Combinators/ArrayPropsDecoderTest.php b/tests/unit/Internal/Combinators/ArrayPropsDecoderTest.php index 3d11ecb..a3c8635 100644 --- a/tests/unit/Internal/Combinators/ArrayPropsDecoderTest.php +++ b/tests/unit/Internal/Combinators/ArrayPropsDecoderTest.php @@ -10,7 +10,6 @@ use Facile\PhpCodec\Reporters\PathReporter; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class ArrayPropsDecoderTest extends BaseTestCase { use TestTrait; @@ -24,7 +23,6 @@ public function testProps(): void 'd' => Decoders::literal('hello'), ]); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::associative([ diff --git a/tests/unit/Internal/Combinators/IntersectionDecoderTest.php b/tests/unit/Internal/Combinators/IntersectionDecoderTest.php index 3aaeb70..28ac11b 100644 --- a/tests/unit/Internal/Combinators/IntersectionDecoderTest.php +++ b/tests/unit/Internal/Combinators/IntersectionDecoderTest.php @@ -10,7 +10,6 @@ use Facile\PhpCodec\Validation\ValidationFailures; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class IntersectionDecoderTest extends BaseTestCase { use TestTrait; @@ -48,7 +47,6 @@ public function testIntersectionOfProps(): void Decoders::arrayProps(['b' => Decoders::int()]) ); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::associative([ @@ -64,7 +62,6 @@ public function testIntersectionOfProps(): void self::assertIsInt($a['b']); }); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::oneOf( @@ -102,7 +99,6 @@ public function testIntersectionOfUnionOfProps(): void ) ); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::oneOf( diff --git a/tests/unit/Internal/Combinators/UnionDecoderTest.php b/tests/unit/Internal/Combinators/UnionDecoderTest.php index fd2d029..5e53a0c 100644 --- a/tests/unit/Internal/Combinators/UnionDecoderTest.php +++ b/tests/unit/Internal/Combinators/UnionDecoderTest.php @@ -8,7 +8,6 @@ use Facile\PhpCodec\Reporters\PathReporter; use PHPUnit\Framework\TestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class UnionDecoderTest extends TestCase { public function testValidate(): void diff --git a/tests/unit/Internal/Primitives/UndefinedDecoderTest.php b/tests/unit/Internal/Primitives/UndefinedDecoderTest.php index e541180..84fe690 100644 --- a/tests/unit/Internal/Primitives/UndefinedDecoderTest.php +++ b/tests/unit/Internal/Primitives/UndefinedDecoderTest.php @@ -10,17 +10,12 @@ use Tests\Facile\PhpCodec\BaseTestCase; use Tests\Facile\PhpCodec\GeneratorUtils; -/** - * @psalm-suppress PropertyNotSetInConstructor - * @psalm-suppress UndefinedFunction - */ class UndefinedDecoderTest extends BaseTestCase { use TestTrait; public function testDefault(): void { - /** @psalm-suppress DeprecatedMethod */ $this ->forAll(GeneratorUtils::scalar()) ->then( diff --git a/tests/unit/Internal/Useful/DateTimeFromStringDecoderTest.php b/tests/unit/Internal/Useful/DateTimeFromStringDecoderTest.php index 42c3251..ea6350f 100644 --- a/tests/unit/Internal/Useful/DateTimeFromStringDecoderTest.php +++ b/tests/unit/Internal/Useful/DateTimeFromStringDecoderTest.php @@ -9,7 +9,6 @@ use Facile\PhpCodec\Decoders; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class DateTimeFromStringDecoderTest extends BaseTestCase { use TestTrait; @@ -22,7 +21,6 @@ public function test(): void $decoder->decode('2021-03-12T06:22:48+01:00') ); - /** @psalm-suppress UndefinedFunction */ $this ->forAll( Generators::date() @@ -34,7 +32,6 @@ public function test(): void ); }); - /** @psalm-suppress UndefinedFunction */ $this ->limitTo(1_000) ->forAll( @@ -55,7 +52,6 @@ public function test(): void ]) ) ->then(function (\DateTimeInterface $date, string $format): void { - /** @psalm-suppress InternalClass */ $decoder = Decoders::dateTimeFromString($format); self::assertSuccessInstanceOf( diff --git a/tests/unit/Internal/Useful/StringMatchingRegexDecoderTest.php b/tests/unit/Internal/Useful/StringMatchingRegexDecoderTest.php index ef8e6b1..3cd5e2d 100644 --- a/tests/unit/Internal/Useful/StringMatchingRegexDecoderTest.php +++ b/tests/unit/Internal/Useful/StringMatchingRegexDecoderTest.php @@ -11,7 +11,6 @@ use Facile\PhpCodec\Validation\ValidationFailures; use Tests\Facile\PhpCodec\BaseTestCase; -/** @psalm-suppress PropertyNotSetInConstructor */ class StringMatchingRegexDecoderTest extends BaseTestCase { use TestTrait; diff --git a/tests/unit/Reporters/ReportersTest.php b/tests/unit/Reporters/ReportersTest.php index 286b955..826aac0 100644 --- a/tests/unit/Reporters/ReportersTest.php +++ b/tests/unit/Reporters/ReportersTest.php @@ -14,7 +14,6 @@ use Tests\Facile\PhpCodec\BaseTestCase; use Tests\Facile\PhpCodec\Reporters\Models\SampleClass; -/** @psalm-suppress PropertyNotSetInConstructor */ class ReportersTest extends BaseTestCase { use TestTrait; @@ -149,7 +148,6 @@ public function testReportClass(): void $pathReporter = Reporters::path(); $simplePathReporter = Reporters::simplePath(); - /** @psalm-suppress TooManyArguments */ $this ->forAll( Generators::associative([ From 761b2f063f895f2f2a7d6b9ca32685214f136956 Mon Sep 17 00:00:00 2001 From: Ilario Pierbattista Date: Fri, 30 May 2025 10:41:28 +0200 Subject: [PATCH 5/5] type testing and pipe --- phpstan-baseline.neon | 98 +++++------- phpstan.neon | 3 +- src/Decoder.php | 2 +- src/Decoders.php | 181 +++++++++------------- src/Internal/Combinators/UnionDecoder.php | 29 ++-- tests/unit/DecodersTest.php | 46 ------ tests/unit/DecodersTest/A.php | 15 ++ tests/unit/DecodersTest/DecodersTest.php | 83 ++++++++++ tests/unit/TypeAssertions.php | 30 ++++ 9 files changed, 256 insertions(+), 231 deletions(-) delete mode 100644 tests/unit/DecodersTest.php create mode 100644 tests/unit/DecodersTest/A.php create mode 100644 tests/unit/DecodersTest/DecodersTest.php create mode 100644 tests/unit/TypeAssertions.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 81ad350..a0bd2d5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,32 +1,44 @@ parameters: ignoreErrors: - - message: '#^Method Facile\\PhpCodec\\Decoders\:\:pipe\(\) return type with generic interface Facile\\PhpCodec\\Decoder does not specify its types\: I, A$#' - identifier: missingType.generics + message: '#^Method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) should return Facile\\PhpCodec\\Decoder\\> but returns Facile\\PhpCodec\\Internal\\Combinators\\ArrayPropsDecoder\<\(int\|string\), mixed, mixed, MapOfDecoders of non\-empty\-array\\>\>\.$#' + identifier: return.type count: 1 path: src/Decoders.php - - message: '#^Method Facile\\PhpCodec\\Decoders\:\:union\(\) return type with generic interface Facile\\PhpCodec\\Decoder does not specify its types\: I, A$#' - identifier: missingType.generics + message: '#^Method Facile\\PhpCodec\\Decoders\:\:pipe\(\) never returns Facile\\PhpCodec\\Decoder\ so it can be removed from the return type\.$#' + identifier: return.unusedType count: 1 path: src/Decoders.php - - message: '#^PHPDoc tag @param for parameter \$factory with type Facile\\PhpCodec\\ClassFactory is not subtype of native type callable\.$#' - identifier: parameter.phpDocType + message: '#^Method Facile\\PhpCodec\\Decoders\:\:pipe\(\) never returns Facile\\PhpCodec\\Decoder\ so it can be removed from the return type\.$#' + identifier: return.unusedType count: 1 path: src/Decoders.php - - message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type Facile\\PhpCodec\\Internal\\Combinators\\IntersectionDecoder\\.$#' - identifier: varTag.nativeType + message: '#^Method Facile\\PhpCodec\\Decoders\:\:pipe\(\) never returns Facile\\PhpCodec\\Decoder\ so it can be removed from the return type\.$#' + identifier: return.unusedType count: 1 path: src/Decoders.php - - message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type Facile\\PhpCodec\\Internal\\Combinators\\ComposeDecoder\\.$#' - identifier: varTag.nativeType + message: '#^Method Facile\\PhpCodec\\Decoders\:\:regex\(\) should return Facile\\PhpCodec\\Decoder\\> but returns Facile\\PhpCodec\\Internal\\Useful\\RegexDecoder\.$#' + identifier: return.type + count: 1 + path: src/Decoders.php + + - + message: '#^PHPDoc tag @param for parameter \$factory with type Facile\\PhpCodec\\ClassFactory is not subtype of native type callable\.$#' + identifier: parameter.phpDocType + count: 1 + path: src/Decoders.php + + - + message: '#^PHPDoc tag @template has invalid value \(ClassFactory of callable\(\.\.\.mixed\)\:T\)\: Unexpected token "\(", expected TOKEN_HORIZONTAL_WS at offset 139 on line 4$#' + identifier: phpDoc.parseError count: 1 path: src/Decoders.php @@ -43,7 +55,7 @@ parameters: path: src/Decoders.php - - message: '#^Parameter \#1 \$db of static method Facile\\PhpCodec\\Decoders\:\:compose\(\) expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\ given\.$#' + message: '#^Parameter \#1 \$db of static method Facile\\PhpCodec\\Decoders\:\:compose\(\) expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\\|Facile\\PhpCodec\\Decoder\\|Facile\\PhpCodec\\Decoder\ given\.$#' identifier: argument.type count: 1 path: src/Decoders.php @@ -66,6 +78,12 @@ parameters: count: 1 path: src/Decoders.php + - + message: '#^Unable to resolve the template type E in call to method static method Facile\\PhpCodec\\Decoders\:\:pipe\(\)$#' + identifier: argument.templateType + count: 1 + path: src/Decoders.php + - message: '#^Method Facile\\PhpCodec\\Internal\\Arrays\\ListOfDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\\> but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' identifier: return.type @@ -126,30 +144,6 @@ parameters: count: 1 path: src/Internal/Combinators/LiteralDecoder.php - - - message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\Validation\\.$#' - identifier: return.type - count: 1 - path: src/Internal/Combinators/UnionDecoder.php - - - - message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\Validation\\.$#' - identifier: return.type - count: 1 - path: src/Internal/Combinators/UnionDecoder.php - - - - message: '#^Method Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\:\:validate\(\) should return Facile\\PhpCodec\\Validation\\Validation\ but returns Facile\\PhpCodec\\Validation\\ValidationFailures\\.$#' - identifier: return.type - count: 1 - path: src/Internal/Combinators/UnionDecoder.php - - - - message: '#^PHPDoc tag @var with type Facile\\PhpCodec\\Decoder\ is not subtype of native type \$this\(Facile\\PhpCodec\\Internal\\Combinators\\UnionDecoder\\)\.$#' - identifier: varTag.nativeType - count: 1 - path: src/Internal/Combinators/UnionDecoder.php - - message: '#^Cannot cast mixed to string\.$#' identifier: cast.string @@ -331,39 +325,27 @@ parameters: path: tests/examples/DecodeApiResponse/OpenWeatherResponse.php - - message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{foo\: Facile\\PhpCodec\\Decoder\, bar\: Facile\\PhpCodec\\Decoder\} given\.$#' - identifier: argument.type - count: 1 - path: tests/examples/DecodePartialPropertiesTest.php - - - - message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{type\: Facile\\PhpCodec\\Decoder\, subType\: Facile\\PhpCodec\\Decoder, propA\: Facile\\PhpCodec\\Decoder\, propB\: Facile\\PhpCodec\\Decoder\\} given\.$#' - identifier: argument.type - count: 1 - path: tests/examples/DecoderForSumType/DecoderForSumType.php - - - - message: '#^Parameter \#1 \$props of static method Facile\\PhpCodec\\Decoders\:\:arrayProps\(\) expects non\-empty\-array\\>, array\{type\: Facile\\PhpCodec\\Decoder\, case\: Facile\\PhpCodec\\Decoder, amount\: Facile\\PhpCodec\\Decoder\, flag\: Facile\\PhpCodec\\Decoder\\} given\.$#' - identifier: argument.type - count: 1 - path: tests/examples/DecoderForSumType/DecoderForSumType.php + message: '#^Call to an undefined method object\:\:getId\(\)\.$#' + identifier: method.notFound + count: 3 + path: tests/examples/ParseCsv/ParseCsvTest.php - - message: '#^Cannot call method getId\(\) on mixed\.$#' - identifier: method.nonObject + message: '#^Call to an undefined method object\:\:getItalianLandRegistryCode\(\)\.$#' + identifier: method.notFound count: 3 path: tests/examples/ParseCsv/ParseCsvTest.php - - message: '#^Cannot call method getItalianLandRegistryCode\(\) on mixed\.$#' - identifier: method.nonObject + message: '#^Call to an undefined method object\:\:getName\(\)\.$#' + identifier: method.notFound count: 3 path: tests/examples/ParseCsv/ParseCsvTest.php - - message: '#^Cannot call method getName\(\) on mixed\.$#' - identifier: method.nonObject - count: 3 + message: '#^Parameter \#2 \$b of static method Facile\\PhpCodec\\Decoders\:\:pipe\(\) expects Facile\\PhpCodec\\Decoder\, Facile\\PhpCodec\\Decoder\\> given\.$#' + identifier: argument.type + count: 1 path: tests/examples/ParseCsv/ParseCsvTest.php - diff --git a/phpstan.neon b/phpstan.neon index 433411e..eedc7cf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,8 +3,9 @@ includes: - phpat.neon - phpstan-baseline.neon parameters: - level: 9 + level: 10 paths: - src - tests/examples - tests/unit + reportUnmatchedIgnoredErrors: false \ No newline at end of file diff --git a/src/Decoder.php b/src/Decoder.php index 9b0a8fc..3513c0b 100644 --- a/src/Decoder.php +++ b/src/Decoder.php @@ -16,7 +16,7 @@ interface Decoder /** * @param I $i * - * @psalm-return Validation + * @return Validation */ public function validate(mixed $i, Context $context): Validation; diff --git a/src/Decoders.php b/src/Decoders.php index 206c39d..67916b3 100644 --- a/src/Decoders.php +++ b/src/Decoders.php @@ -35,10 +35,9 @@ private function __construct() {} * @template I * @template A * - * @psalm-param callable(I, Context):Validation $f - * @psalm-param string $name + * @param callable(I, Context):Validation $f * - * @psalm-return Decoder + * @return Decoder */ public static function make(callable $f, string $name = 'anon'): Decoder { @@ -46,48 +45,20 @@ public static function make(callable $f, string $name = 'anon'): Decoder } /** - * @template IA - * @template IB - * @template A of IB + * @template I + * @template A * @template B + * @template C + * @template D + * @template E * - * @psalm-param Decoder $db - * @psalm-param Decoder $da + * @param Decoder $a + * @param Decoder $b + * @param Decoder | null $c + * @param Decoder | null $d + * @param Decoder | null $e * - * @psalm-return Decoder - */ - public static function compose(Decoder $db, Decoder $da): Decoder - { - // TODO Fix this - /** @psalm-var Decoder */ - return new ComposeDecoder($db, $da); - } - - /** - * @psalm-template IA of mixed - * @psalm-template IB of mixed - * @psalm-template IC of mixed - * @psalm-template ID of mixed - * @psalm-template IE of mixed - * @psalm-template A - * @psalm-template B - * @psalm-template C - * @psalm-template D - * @psalm-template E - * - * // TODO provide better types for input - * - * @psalm-param Decoder $a - * @psalm-param Decoder $b - * @psalm-param Decoder | null $c - * @psalm-param Decoder | null $d - * @psalm-param Decoder | null $e - * - * @psalm-return (func_num_args() is 2 ? Decoder - * : (func_num_args() is 3 ? Decoder - * : (func_num_args() is 4 ? Decoder - * : (func_num_args() is 5 ? Decoder : Decoder) - * ))) + * @return ($c is null ? Decoder : ($d is null ? Decoder : ($e is null ? Decoder : Decoder))) */ public static function pipe( Decoder $a, @@ -97,35 +68,43 @@ public static function pipe( ?Decoder $e = null ): Decoder { // Order is important: composition is not commutative - // TODO fix this + /** @phpstan-ignore return.type */ return $c instanceof Decoder ? self::compose(self::pipe($b, $c, $d, $e), $a) : self::compose($b, $a); } /** - * @psalm-template IA - * @psalm-template A - * @psalm-template IB - * @psalm-template B - * @psalm-template IC - * @psalm-template C - * @psalm-template ID - * @psalm-template D - * @psalm-template IE - * @psalm-template E + * @template IA + * @template IB + * @template A of IB + * @template B * - * @psalm-param Decoder $a - * @psalm-param Decoder $b - * @psalm-param Decoder | null $c - * @psalm-param Decoder | null $d - * @psalm-param Decoder | null $e + * @psalm-param Decoder $db + * @psalm-param Decoder $da + * + * @return Decoder + */ + public static function compose(Decoder $db, Decoder $da): Decoder + { + return new ComposeDecoder($db, $da); + } + + /** + * @template I input type + * @template A + * @template B + * @template C + * @template D + * @template E + * + * @psalm-param Decoder $a + * @psalm-param Decoder $b + * @psalm-param Decoder | null $c + * @psalm-param Decoder | null $d + * @psalm-param Decoder | null $e * - * @psalm-return (func_num_args() is 2 ? Decoder - * : (func_num_args() is 3 ? Decoder - * : (func_num_args() is 4 ? Decoder - * : (func_num_args() is 5 ? Decoder : Decoder) - * ))) + * @return ($c is null ? Decoder : ($d is null ? Decoder : ($e is null ? Decoder : Decoder))) */ public static function union(Decoder $a, Decoder $b, ?Decoder $c = null, ?Decoder $d = null, ?Decoder $e = null): Decoder { @@ -149,24 +128,23 @@ public static function union(Decoder $a, Decoder $b, ?Decoder $c = null, ?Decode ); } + /** @phpstan-ignore return.type */ return $res; } /** - * @psalm-template IA - * @psalm-template IB - * @psalm-template A - * @psalm-template B + * @template IA + * @template IB + * @template A + * @template B * * @psalm-param Decoder $a * @psalm-param Decoder $b * - * @psalm-return Decoder + * @return Decoder */ public static function intersection(Decoder $a, Decoder $b): Decoder { - // Intersection seems to mess up implements annotation - /** @var Decoder */ return new IntersectionDecoder($a, $b); } @@ -184,7 +162,7 @@ public static function intersection(Decoder $a, Decoder $b): Decoder * @psalm-param callable(A):B $f * @psalm-param Decoder $da * - * @psalm-return Decoder + * @return Decoder */ public static function transformValidationSuccess(callable $f, Decoder $da): Decoder { @@ -207,8 +185,8 @@ public static function literal(bool|string|int $l): Decoder } /** - * @psalm-template I - * @psalm-template T + * @template I + * @template T * * @param Decoder $elementDecoder * @@ -220,19 +198,11 @@ public static function listOf(Decoder $elementDecoder): Decoder } /** - * @psalm-template MapOfDecoders of non-empty-array> - * - * @psalm-param MapOfDecoders $props + * @template MapOfDecoders of non-empty-array> * - * @psalm-return Decoder> + * @param MapOfDecoders $props * - * @param Decoder[] $props - * - * @return Decoder - * - * Waiting for this feature to provide a better typing. I need something like mapped types from Typescript. - * - * @see https://github.com/vimeo/psalm/issues/3589 + * @return Decoder> */ public static function arrayProps(array $props): Decoder { @@ -240,15 +210,14 @@ public static function arrayProps(array $props): Decoder } /** - * @psalm-template T of object - * @psalm-template Properties of non-empty-array - * @psalm-template ClassFactory of callable(...mixed):T + * @template T of object + * @template Properties of non-empty-array + * @template ClassFactory of callable(...mixed):T * - * @psalm-param Decoder $propsDecoder - * @psalm-param ClassFactory $factory - * @psalm-param string $decoderName + * @param Decoder $propsDecoder + * @param ClassFactory $factory * - * @psalm-return Decoder + * @return Decoder */ public static function classFromArrayPropsDecoder( Decoder $propsDecoder, @@ -274,13 +243,11 @@ public static function classFromArrayPropsDecoder( # ########################################################### /** - * @psalm-template U - * - * @psalm-param U $default + * @template U * - * @psalm-return Decoder + * @param U $default * - * @param null|mixed $default + * @return Decoder */ public static function undefined($default = null): Decoder { @@ -296,7 +263,7 @@ public static function null(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function bool(): Decoder { @@ -304,7 +271,7 @@ public static function bool(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function int(): Decoder { @@ -312,7 +279,7 @@ public static function int(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function float(): Decoder { @@ -320,7 +287,7 @@ public static function float(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function string(): Decoder { @@ -328,7 +295,7 @@ public static function string(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function mixed(): Decoder { @@ -336,7 +303,7 @@ public static function mixed(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function callable(): Decoder { @@ -350,7 +317,7 @@ public static function callable(): Decoder # ########################################################### /** - * @psalm-return Decoder + * @return Decoder */ public static function intFromString(): Decoder { @@ -358,7 +325,7 @@ public static function intFromString(): Decoder } /** - * @psalm-return Decoder + * @return Decoder */ public static function dateTimeFromString(string $format = \DATE_ATOM): Decoder { @@ -366,7 +333,7 @@ public static function dateTimeFromString(string $format = \DATE_ATOM): Decoder } /** - * @psalm-return Decoder> + * @return Decoder> */ public static function regex(string $regex): Decoder { @@ -374,9 +341,7 @@ public static function regex(string $regex): Decoder } /** - * @psalm-param string $regex - * - * @psalm-return Decoder + * @return Decoder */ public static function stringMatchingRegex(string $regex): Decoder { diff --git a/src/Internal/Combinators/UnionDecoder.php b/src/Internal/Combinators/UnionDecoder.php index da16d6f..f362432 100644 --- a/src/Internal/Combinators/UnionDecoder.php +++ b/src/Internal/Combinators/UnionDecoder.php @@ -12,25 +12,20 @@ use Facile\PhpCodec\Validation\ValidationFailures; /** - * @psalm-template IA - * @psalm-template IB - * @psalm-template A - * @psalm-template B + * @template I + * @template A + * @template B * - * @template-implements Decoder - * - * @psalm-internal Facile\PhpCodec + * @implements Decoder */ final class UnionDecoder implements Decoder { /** - * @psalm-param Decoder $a - * @psalm-param Decoder $b + * @param Decoder $a + * @param Decoder $b */ public function __construct( - /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $a, - /** @var Decoder */ private readonly \Facile\PhpCodec\Decoder $b, private readonly int $indexBegin = 0 ) {} @@ -63,24 +58,24 @@ public function validate($i, Context $context): Validation ); if ($vb instanceof ValidationFailures) { - return Validation::failures( + /** @var Validation $validationFailures */ + $validationFailures = Validation::failures( [...$va->getErrors(), ...$vb->getErrors()] ); + + return $validationFailures; } + /** @var Validation $vb */ return $vb; } + /** @var Validation $va */ return $va; } public function decode($i): Validation { - /** - * @psalm-var IA&IB $i - * @psalm-var Decoder $this - */ - return FunctionUtils::standardDecode($this, $i); } diff --git a/tests/unit/DecodersTest.php b/tests/unit/DecodersTest.php deleted file mode 100644 index d5896e6..0000000 --- a/tests/unit/DecodersTest.php +++ /dev/null @@ -1,46 +0,0 @@ - new DecodersTest\A($v), - Decoders::int() - ); - - $this - ->forAll( - Generators::int() - ) - ->then(function (int $i) use ($decoder): void { - $a = self::assertSuccessInstanceOf( - DecodersTest\A::class, - $decoder->decode($i) - ); - self::assertSame($i, $a->getValue()); - }); - } -} - -namespace Tests\Facile\PhpCodec\DecodersTest; - -class A -{ - public function __construct(private readonly int $v) {} - - public function getValue(): int - { - return $this->v; - } -} diff --git a/tests/unit/DecodersTest/A.php b/tests/unit/DecodersTest/A.php new file mode 100644 index 0000000..fce351b --- /dev/null +++ b/tests/unit/DecodersTest/A.php @@ -0,0 +1,15 @@ +v; + } +} diff --git a/tests/unit/DecodersTest/DecodersTest.php b/tests/unit/DecodersTest/DecodersTest.php new file mode 100644 index 0000000..7f12109 --- /dev/null +++ b/tests/unit/DecodersTest/DecodersTest.php @@ -0,0 +1,83 @@ + new A($v), + Decoders::int() + ); + + $this + ->forAll( + Generators::int() + ) + ->then(function (int $i) use ($decoder): void { + $a = self::assertSuccessInstanceOf( + A::class, + $decoder->decode($i) + ); + self::assertSame($i, $a->getValue()); + }); + } + + public function testPipe(): void + { + $d = Decoders::pipe( + Decoders::string(), + Decoders::stringMatchingRegex('/^\d+$/'), + ); + TypeAssertions::decoderMixedString($d); + $validation = $d->decode('3'); + self::assertInstanceOf(ValidationSuccess::class, $validation); + self::assertSame('3', $validation->getValue()); + + $d = Decoders::pipe( + Decoders::string(), + Decoders::stringMatchingRegex('/^\d+$/'), + Decoders::intFromString(), + ); + TypeAssertions::decoderMixedInt($d); + $validation = $d->decode('3'); + self::assertInstanceOf(ValidationSuccess::class, $validation); + self::assertSame(3, $validation->getValue()); + + $d = Decoders::pipe( + Decoders::string(), + Decoders::stringMatchingRegex('/^\d+$/'), + Decoders::intFromString(), + Decoders::make(fn(int $i) => new ValidationSuccess((float) $i)), + ); + TypeAssertions::decoderMixedFloat($d); + $validation = $d->decode('3'); + self::assertInstanceOf(ValidationSuccess::class, $validation); + self::assertSame(3.0, $validation->getValue()); + + $d = Decoders::pipe( + Decoders::string(), + Decoders::stringMatchingRegex('/^\d+$/'), + Decoders::intFromString(), + Decoders::make(fn(int $i) => new ValidationSuccess((float) $i)), + Decoders::make(fn(float $i) => Validation::success($i > 0)) + ); + TypeAssertions::decoderMixedBool($d); + $validation = $d->decode('3'); + self::assertInstanceOf(ValidationSuccess::class, $validation); + self::assertTrue($validation->getValue()); + } +} diff --git a/tests/unit/TypeAssertions.php b/tests/unit/TypeAssertions.php new file mode 100644 index 0000000..71a008d --- /dev/null +++ b/tests/unit/TypeAssertions.php @@ -0,0 +1,30 @@ + $d + */ + public static function decoderMixedString(Decoder $d): void {} + + /** + * @param Decoder $d + */ + public static function decoderMixedInt(Decoder $d): void {} + + /** + * @param Decoder $d + */ + public static function decoderMixedFloat(Decoder $d): void {} + + /** + * @param Decoder $d + */ + public static function decoderMixedBool(Decoder $d): void {} +}