diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8459a8e..3d2a1bb 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,11 +7,11 @@ jobs: composer-require-checker: strategy: matrix: - php: [ "8.1", "8.2" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -23,27 +23,26 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - name: Composer Require Checker run: composer check-deps - phpcs: + code-style: strategy: matrix: - php: [ "8.1", "8.2" ] - deps: [ "--prefer-lowest", "" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -55,27 +54,26 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - - name: PHPCS - run: composer cs-check + - name: PHP Code Style + run: PHP_CS_FIXER_IGNORE_ENV=1 composer cs-check phpstan: strategy: matrix: - php: [ "8.1", "8.2" ] - deps: [ "--prefer-lowest", "" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -87,14 +85,14 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - name: PHPStan run: composer phpstan @@ -102,12 +100,11 @@ jobs: psalm: strategy: matrix: - php: [ "8.1", "8.2" ] - deps: [ "--prefer-lowest", "" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -119,27 +116,27 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - name: Psalm run: composer psalm infection: + strategy: matrix: - php: [ "8.1", "8.2" ] - deps: [ "--prefer-lowest", "" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -151,14 +148,14 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - name: Infection coverage of changed lines if: "!contains(github.event.pull_request.labels.*.name, 'skip-infection')" @@ -174,21 +171,20 @@ jobs: -jmax - name: Save Infection result - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: - name: infection-log-${{ matrix.php }}-${{ matrix.deps }}.txt + name: infection-log-${{ matrix.php }}.txt path: infection-log.txt phpunit: strategy: matrix: - php: [ "8.1", "8.2" ] - deps: [ "--prefer-lowest", "" ] + php: ["8.3", "8.4"] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -200,14 +196,14 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}- + key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.php }}- - name: Install dependencies - run: composer update --no-interaction --no-progress --no-suggest ${{ matrix.deps }} + run: composer update --no-interaction --no-progress --no-suggest - name: Tests run: vendor/bin/phpunit --coverage-clover=coverage.xml --stop-on-failure diff --git a/composer.json b/composer.json index 97ff953..48d11ab 100644 --- a/composer.json +++ b/composer.json @@ -1,32 +1,25 @@ { "name": "eventjet/json", - "type": "library", "description": "Type-safe JSON encoding and decoding", "license": "MIT", + "type": "library", "require": { - "php": ">=8.1", + "php": ">=8.3", "ext-json": "*" }, "require-dev": { - "eventjet/coding-standard": "^3.15", - "infection/infection": "^0.26.20", - "maglnet/composer-require-checker": "^4.6", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "^10.1", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.23" - }, - "config": { - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "infection/extension-installer": true, - "phpstan/extension-installer": true - }, - "sort-packages": true + "eventjet/coding-standard": "^3.18", + "infection/infection": "^0.31.2", + "maglnet/composer-require-checker": "^4.16.1", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.7", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpunit/phpunit": "^12.0.7", + "psalm/plugin-phpunit": "^0.19.2", + "vimeo/psalm": "^6.8.8" }, + "minimum-stability": "stable", "autoload": { "psr-4": { "Eventjet\\Json\\": "src" @@ -37,7 +30,14 @@ "Eventjet\\Test\\Unit\\Json\\": "tests/unit" } }, - "minimum-stability": "stable", + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "infection/extension-installer": true, + "phpstan/extension-installer": true + }, + "sort-packages": true + }, "scripts": { "check": [ "@check-deps", @@ -47,12 +47,13 @@ "@phpunit", "@infection" ], - "check-deps": "vendor/bin/composer-require-checker", + "check-deps": "composer-require-checker", "cs-check": "php-cs-fixer fix --dry-run", "cs-fix": "php-cs-fixer fix", - "infection": "vendor/bin/infection -jmax", - "phpstan": "vendor/bin/phpstan", - "phpunit": "vendor/bin/phpunit", - "psalm": "vendor/bin/psalm" + "infection": "infection -jmax", + "infection-diff": "infection -jmax --git-diff-lines --min-msi=100 --min-covered-msi=100 --ignore-msi-with-no-mutations", + "phpstan": "phpstan analyze", + "phpunit": "phpunit", + "psalm": "psalm" } } diff --git a/infection.json b/infection.json index fbf03ce..7bc85ba 100644 --- a/infection.json +++ b/infection.json @@ -1,4 +1,5 @@ { + "staticAnalysisTool":"phpstan", "timeout": 10, "source": { "directories": [ diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e691e35..4548ef5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,6 @@ parameters: level: max + rememberPossiblyImpureFunctionValues: false paths: - src/ - tests/ diff --git a/psalm.xml b/psalm.xml index 2a2a87f..2cbaac8 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,8 +5,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + ensureOverrideAttribute="false" findUnusedBaselineEntry="true" findUnusedCode="true" + findUnusedPsalmSuppress="true" > diff --git a/src/Json.php b/src/Json.php index 56e3be0..f8f7a7d 100644 --- a/src/Json.php +++ b/src/Json.php @@ -62,12 +62,10 @@ public static function decode(string $json, object|string $value): object } /** - * @psalm-suppress MixedInferredReturnType * @return string|int|float|bool|array|null */ private static function preparePropertyValueForEncoding(mixed $value): string|int|float|bool|array|null { - /** @psalm-suppress MixedReturnStatement */ return match (true) { is_string($value), is_int($value), is_float($value), is_bool($value), $value === null => $value, is_array($value) => self::encodeArray($value), @@ -119,7 +117,6 @@ private static function getJsonKeyForProperty(ReflectionProperty $property): str * @template T of object * @param T | class-string $value * @return T - * @psalm-suppress InvalidReturnType */ private static function decodeClass(string $json, object|string $value): object { @@ -128,7 +125,7 @@ private static function decodeClass(string $json, object|string $value): object throw JsonError::decodeFailed(error_get_last()['message'] ?? null); } if (!is_array($data)) { - throw JsonError::decodeFailed(sprintf("Expected JSON object, got %s", gettype($data))); + throw JsonError::decodeFailed(sprintf('Expected JSON object, got %s', gettype($data))); } /** @psalm-suppress DocblockTypeContradiction */ if (!is_string($value)) { @@ -485,13 +482,13 @@ private static function parseUseStatements(string $file): array if ($result !== 1) { continue; } - $useStatements[$matches['alias'] ?? $matches['class']] = ($matches['ns'] ?? '') . $matches['class']; + $useStatements[$matches['alias'] ?? $matches['class']] = $matches['ns'] . $matches['class']; } return $useStatements; } /** - * @return list | array + * @return list | array */ private static function createConstructorArgumentForArrayType( ReflectionParameter $parameter, @@ -615,16 +612,8 @@ private static function createConstructorArgumentForEnumType( ), ); } - /** - * @psalm-suppress MixedAssignment - * @psalm-suppress MixedMethodCall - */ $case = $typeName::tryFrom($value); if ($case === null) { - /** - * @psalm-suppress MixedArgument - * @psalm-suppress MixedMethodCall - */ throw JsonError::decodeFailed( sprintf( '"%s" is not a valid value for enum %s. Valid values are: %s', diff --git a/src/JsonError.php b/src/JsonError.php index 83ddb3a..63ee9d4 100644 --- a/src/JsonError.php +++ b/src/JsonError.php @@ -9,7 +9,7 @@ final class JsonError extends RuntimeException { - private function __construct(string $message = "", int $code = 0, Throwable|null $previous = null) + private function __construct(string $message = '', int $code = 0, Throwable|null $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Type/Array_.php b/src/Type/Array_.php index 0c938d5..9709120 100644 --- a/src/Type/Array_.php +++ b/src/Type/Array_.php @@ -26,7 +26,7 @@ public function __toString(): string public function validateValue(mixed $value, string $path = ''): ValidationResult { if (!is_array($value)) { - return ValidationResult::error(sprintf('Expected array, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected array, got %s.', (string)json_encode($value)), $path); } if (!array_is_list($value)) { return ValidationResult::error('Expected array, got object.', $path); diff --git a/src/Type/Boolean.php b/src/Type/Boolean.php index f1cd0bf..7021606 100644 --- a/src/Type/Boolean.php +++ b/src/Type/Boolean.php @@ -54,7 +54,7 @@ public function validateValue(mixed $value, string $path = ''): ValidationResult return ValidationResult::valid(); } return ValidationResult::error( - sprintf('Expected %s, got %s.', json_encode($this->value), json_encode($value)), + sprintf('Expected %s, got %s.', (string)json_encode($this->value), (string)json_encode($value)), $path, ); } diff --git a/src/Type/JsonType.php b/src/Type/JsonType.php index d55efde..12cf0fe 100644 --- a/src/Type/JsonType.php +++ b/src/Type/JsonType.php @@ -70,7 +70,7 @@ public static function union(self $first, self $second, self ...$other): Union protected static function joinPath(string $prefix, string|int $key): string { - return $prefix === '' ? (string)$key : sprintf("%s.%d", $prefix, $key); + return $prefix === '' ? (string)$key : sprintf('%s.%d', $prefix, $key); } /** diff --git a/src/Type/Null_.php b/src/Type/Null_.php index 03db7b7..0bc81b9 100644 --- a/src/Type/Null_.php +++ b/src/Type/Null_.php @@ -26,6 +26,6 @@ public function validateValue(mixed $value, string $path = ''): ValidationResult if ($value === null) { return ValidationResult::valid(); } - return ValidationResult::error(sprintf('Expected null, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected null, got %s.', (string)json_encode($value)), $path); } } diff --git a/src/Type/Number.php b/src/Type/Number.php index 4065668..ab26783 100644 --- a/src/Type/Number.php +++ b/src/Type/Number.php @@ -28,6 +28,6 @@ public function validateValue(mixed $value, string $path = ''): ValidationResult if (is_int($value) || is_float($value)) { return ValidationResult::valid(); } - return ValidationResult::error(sprintf('Expected number, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected number, got %s.', (string)json_encode($value)), $path); } } diff --git a/src/Type/Object_.php b/src/Type/Object_.php index 6ea44cb..0690bca 100644 --- a/src/Type/Object_.php +++ b/src/Type/Object_.php @@ -34,10 +34,10 @@ public function __toString(): string public function validateValue(mixed $value, string $path = ''): ValidationResult { if (!is_array($value)) { - return ValidationResult::error(sprintf('Expected object, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected object, got %s.', (string)json_encode($value)), $path); } if ($value !== [] && array_is_list($value)) { - return ValidationResult::error(sprintf('Expected object, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected object, got %s.', (string)json_encode($value)), $path); } $results = []; foreach ($this->members as $name => $member) { diff --git a/src/Type/String_.php b/src/Type/String_.php index a9bf930..33202b4 100644 --- a/src/Type/String_.php +++ b/src/Type/String_.php @@ -25,7 +25,7 @@ public function __toString(): string public function validateValue(mixed $value, string $path = ''): ValidationResult { if (!is_string($value)) { - return ValidationResult::error(sprintf('Expected string, got %s.', json_encode($value)), $path); + return ValidationResult::error(sprintf('Expected string, got %s.', (string)json_encode($value)), $path); } return ValidationResult::valid(); } diff --git a/src/Type/Union.php b/src/Type/Union.php index dabc61c..83fc1a4 100644 --- a/src/Type/Union.php +++ b/src/Type/Union.php @@ -56,7 +56,7 @@ public function validateValue(mixed $value, string $path = ''): ValidationResult } $expected = self::disjunction($this->canonicalize()->types); return ValidationResult::error( - sprintf('Expected %s, got %s.', $expected, json_encode($value)), + sprintf('Expected %s, got %s.', $expected, (string)json_encode($value)), $path, ); } diff --git a/tests/unit/Fixtures/HasImportedListItemType.php b/tests/unit/Fixtures/HasImportedListItemType.php index 6efbcd7..5aebb9f 100644 --- a/tests/unit/Fixtures/HasImportedListItemType.php +++ b/tests/unit/Fixtures/HasImportedListItemType.php @@ -11,7 +11,7 @@ final class HasImportedListItemType { /** * @param list $items1 - * @param list $items2 The second set of items + * @param list $items2 The second set of items * @param list $items3 The third set of items */ public function __construct( diff --git a/tests/unit/Fixtures/InvalidArrayConstructorParamTag.php b/tests/unit/Fixtures/InvalidArrayConstructorParamTag.php index f8c4ee9..32f35a0 100644 --- a/tests/unit/Fixtures/InvalidArrayConstructorParamTag.php +++ b/tests/unit/Fixtures/InvalidArrayConstructorParamTag.php @@ -9,10 +9,11 @@ final class InvalidArrayConstructorParamTag { /** + * @phpstan-ignore-next-line phpDoc.parseError * @param class-string items This is not a valid param tag * @param list $items */ - public function __construct(public readonly array $items) // @phpstan-ignore-line + public function __construct(public readonly array $items) { } } diff --git a/tests/unit/JsonTest.php b/tests/unit/JsonTest.php index ed7719e..f5bcb55 100644 --- a/tests/unit/JsonTest.php +++ b/tests/unit/JsonTest.php @@ -39,6 +39,7 @@ use Eventjet\Test\Unit\Json\Fixtures\Worldline\AccountOnFileDisplayHints; use Eventjet\Test\Unit\Json\Fixtures\Worldline\LabelTemplateElement; use Eventjet\Test\Unit\Json\Fixtures\WrongArrayDocblockType; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use ThisClassDoesNotExist; @@ -519,9 +520,7 @@ public function __construct(public DoesNotExist|null $nested = null) ]; } - /** - * @dataProvider encodeCases - */ + #[DataProvider('encodeCases')] public function testEncode(mixed $value, string $expected): void { $encoded = Json::encode($value); @@ -532,8 +531,8 @@ public function testEncode(mixed $value, string $expected): void /** * @param object | class-string $object * @param callable(object): void $test - * @dataProvider decodeCases */ + #[DataProvider('decodeCases')] public function testDecode(string $json, object|string $object, callable $test): void { $object = Json::decode($json, $object); @@ -541,9 +540,7 @@ public function testDecode(string $json, object|string $object, callable $test): $test($object); } - /** - * @dataProvider roundtripsCases - */ + #[DataProvider('roundtripsCases')] public function testRoundtrips(object $value): void { $encoded1 = Json::encode($value); @@ -553,9 +550,7 @@ public function testRoundtrips(object $value): void self::assertJsonStringEqualsJsonString($encoded1, $encoded2); } - /** - * @dataProvider failingEncodeCases - */ + #[DataProvider('failingEncodeCases')] public function testFailingEncode(mixed $value): void { $this->expectException(JsonError::class); @@ -566,8 +561,8 @@ public function testFailingEncode(mixed $value): void /** * @param object | class-string $object - * @dataProvider failingDecodeCases */ + #[DataProvider('failingDecodeCases')] public function testFailingDecode(string $json, object|string $object, string|null $expectedMessage = null): void { $this->expectException(JsonError::class); diff --git a/tests/unit/Type/ValidationIssueTest.php b/tests/unit/Type/ValidationIssueTest.php index 31f5b2b..c2465da 100644 --- a/tests/unit/Type/ValidationIssueTest.php +++ b/tests/unit/Type/ValidationIssueTest.php @@ -5,6 +5,7 @@ namespace Eventjet\Test\Unit\Json\Type; use Eventjet\Json\Type\ValidationIssue; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function sprintf; @@ -31,9 +32,7 @@ public static function equalsCases(): iterable } } - /** - * @dataProvider equalsCases - */ + #[DataProvider('equalsCases')] public function testEquals(ValidationIssue $a, ValidationIssue $b, bool $expected): void { if ($expected) { diff --git a/tests/unit/TypesTest.php b/tests/unit/TypesTest.php index 9ef414f..079467d 100644 --- a/tests/unit/TypesTest.php +++ b/tests/unit/TypesTest.php @@ -8,6 +8,7 @@ use Eventjet\Json\Type\Member; use Eventjet\Json\Type\ValidationIssue; use Eventjet\Json\Type\ValidationResult; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function count; @@ -233,8 +234,8 @@ private static function expectIssues(ValidationResult $actual, array $expectedIs /** * @param JsonType | callable(): JsonType $type * @param list $expectedIssues - * @dataProvider validateCases */ + #[DataProvider('validateCases')] public function testValidate(JsonType|callable $type, string $json, array $expectedIssues = []): void { if (!$type instanceof JsonType) { @@ -258,17 +259,13 @@ public function testValidate(JsonType|callable $type, string $json, array $expec } } - /** - * @dataProvider toStringCases - */ + #[DataProvider('toStringCases')] public function testToString(JsonType $type, string $expected): void { self::assertSame($expected, (string)$type); } - /** - * @dataProvider equalsCases - */ + #[DataProvider('equalsCases')] public function testEquals(JsonType $a, JsonType $b, bool $expected): void { $aString = (string)$a->canonicalize();