diff --git a/CHANGELOG.md b/CHANGELOG.md index f3853c08..3a3d62e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased -For a full diff see [`3.0.0...main`][3.0.0...main]. +For a full diff see [`3.0.1...main`][3.0.1...main]. + +## [`3.0.1`][3.0.1] + +For a full diff see [`3.0.0...3.0.1`][3.0.0...3.0.1]. + +### Fixed + +- Adjusted `ConfigHashNormalizer` to sort keys correctly ([#723]), by [@fredded] ### Changed @@ -385,6 +393,7 @@ For a full diff see [`5d8b3e2...0.1.0`][5d8b3e2...0.1.0]. [2.1.0]: https://github.com/ergebnis/json-normalizer/releases/tag/2.1.0 [2.2.0]: https://github.com/ergebnis/json-normalizer/releases/tag/2.2.0 [3.0.0]: https://github.com/ergebnis/json-normalizer/releases/tag/3.0.0 +[3.0.1]: https://github.com/ergebnis/json-normalizer/releases/tag/3.0.1 [5d8b3e2...0.1.0]: https://github.com/ergebnis/json-normalizer/compare/5d8b3e2...0.1.0 [0.1.0...0.2.0]: https://github.com/ergebnis/json-normalizer/compare/0.1.0...0.2.0 @@ -413,7 +422,8 @@ For a full diff see [`5d8b3e2...0.1.0`][5d8b3e2...0.1.0]. [2.0.0...2.1.0]: https://github.com/ergebnis/json-normalizer/compare/2.0.0...2.1.0 [2.1.0...2.2.0]: https://github.com/ergebnis/json-normalizer/compare/2.1.0...2.2.0 [2.2.0...3.0.0]: https://github.com/ergebnis/json-normalizer/compare/2.2.0...3.0.0 -[3.0.0...main]: https://github.com/ergebnis/json-normalizer/compare/3.0.0...main +[3.0.0...3.0.1]: https://github.com/ergebnis/json-normalizer/compare/3.0.0...3.0.1 +[3.0.1...main]: https://github.com/ergebnis/json-normalizer/compare/3.0.1...main [#1]: https://github.com/ergebnis/json-normalizer/pull/1 [#2]: https://github.com/ergebnis/json-normalizer/pull/2 @@ -504,8 +514,10 @@ For a full diff see [`5d8b3e2...0.1.0`][5d8b3e2...0.1.0]. [#673]: https://github.com/ergebnis/json-normalizer/pull/673 [#697]: https://github.com/ergebnis/json-normalizer/pull/697 [#698]: https://github.com/ergebnis/json-normalizer/pull/698 +[#723]: https://github.com/ergebnis/json-normalizer/pull/723 [@BackEndTea]: https://github.com/BackEndTea [@dependabot]: https://github.com/dependabot [@ergebnis]: https://github.com/ergebnis +[@fredden]: https://github.com/fredden [@localheinz]: https://github.com/localheinz diff --git a/src/Vendor/Composer/ConfigHashNormalizer.php b/src/Vendor/Composer/ConfigHashNormalizer.php index 6c9b01be..cf209d82 100644 --- a/src/Vendor/Composer/ConfigHashNormalizer.php +++ b/src/Vendor/Composer/ConfigHashNormalizer.php @@ -24,15 +24,6 @@ final class ConfigHashNormalizer implements Normalizer 'scripts-descriptions', ]; - /** - * @see https://getcomposer.org/doc/06-config.md#allow-plugins - * @see https://getcomposer.org/doc/06-config.md#preferred-install - */ - private const PROPERTY_PATHS_THAT_SHOULD_NOT_BE_SORTED = [ - 'config.allow-plugins', - 'config.preferred-install', - ]; - public function normalize(Json $json): Json { $decoded = $json->decoded(); @@ -72,10 +63,6 @@ private static function sortByKey( string $propertyPath, $value ) { - if (\in_array($propertyPath, self::PROPERTY_PATHS_THAT_SHOULD_NOT_BE_SORTED, true)) { - return $value; - } - if (!\is_object($value)) { return $value; } @@ -87,7 +74,12 @@ private static function sortByKey( return $value; } - \ksort($sorted); + \uksort($sorted, static function (string $a, string $b): int { + return \strcmp( + self::normalizeKey($a), + self::normalizeKey($b), + ); + }); $names = \array_keys($sorted); @@ -105,4 +97,22 @@ private static function sortByKey( }, $sorted, $names), ); } + + /** + * Replaces characters in keys to ensure the correct order. + * + * - '*' = ASCII 42 (i.e., before all letters, numbers, and dash) + * - '~' = ASCII 126 (i.e., after all letters, numbers, and dash) + * + * @see https://getcomposer.org/doc/06-config.md#allow-plugins + * @see https://getcomposer.org/doc/06-config.md#preferred-install + */ + private static function normalizeKey(string $key): string + { + return \str_replace( + '*', + '~', + $key, + ); + } } diff --git a/test/Unit/Vendor/Composer/ConfigHashNormalizerTest.php b/test/Unit/Vendor/Composer/ConfigHashNormalizerTest.php index b776c6fe..98335d42 100644 --- a/test/Unit/Vendor/Composer/ConfigHashNormalizerTest.php +++ b/test/Unit/Vendor/Composer/ConfigHashNormalizerTest.php @@ -199,7 +199,7 @@ public function testNormalizeSortsConfigHashRecursivelyIfPropertyExists(string $ /** * @see https://getcomposer.org/doc/06-config.md#allow-plugins */ - public function testNormalizeDoesNotSortAllowPluginsInConfig(): void + public function testNormalizeCorrectlySortsAllowPluginsInConfigWithoutWildcards(): void { $json = Json::fromEncoded( <<<'JSON' @@ -207,9 +207,9 @@ public function testNormalizeDoesNotSortAllowPluginsInConfig(): void "config": { "sort-packages": true, "allow-plugins": { - "foo/*": true, - "bar/*": false, - "*": true + "foo/bar": true, + "bar/qux": false, + "foo/baz": false } } } @@ -221,9 +221,9 @@ public function testNormalizeDoesNotSortAllowPluginsInConfig(): void { "config": { "allow-plugins": { - "foo/*": true, - "bar/*": false, - "*": true + "bar/qux": false, + "foo/bar": true, + "foo/baz": false }, "sort-packages": true } @@ -238,17 +238,21 @@ public function testNormalizeDoesNotSortAllowPluginsInConfig(): void self::assertJsonStringEqualsJsonStringNormalized($expected->encoded(), $normalized->encoded()); } - public function testNormalizeSortsAllowPluginsInOtherProperty(): void + /** + * @see https://github.com/ergebnis/composer-normalize/issues/644 + * @see https://getcomposer.org/doc/06-config.md#preferred-install + */ + public function testNormalizeCorrectlySortsPreferredInstallInConfigWithCatchAllWildcardAtEnd(): void { $json = Json::fromEncoded( <<<'JSON' { - "extra": { - "something": { - "allowed-plugins": { - "foo": true, - "bar": false - } + "config": { + "sort-packages": true, + "preferred-install": { + "foo/package-one": "source", + "bar/another-package": "source", + "*": "dist" } } } @@ -258,13 +262,56 @@ public function testNormalizeSortsAllowPluginsInOtherProperty(): void $expected = Json::fromEncoded( <<<'JSON' { - "extra": { - "something": { - "allowed-plugins": { - "bar": false, - "foo": true - } + "config": { + "preferred-install": { + "bar/another-package": "source", + "foo/package-one": "source", + "*": "dist" + }, + "sort-packages": true + } +} +JSON + ); + + $normalizer = new Vendor\Composer\ConfigHashNormalizer(); + + $normalized = $normalizer->normalize($json); + + self::assertJsonStringEqualsJsonStringNormalized($expected->encoded(), $normalized->encoded()); } + + /** + * @see https://github.com/ergebnis/composer-normalize/issues/644 + * @see https://getcomposer.org/doc/06-config.md#preferred-install + */ + public function testNormalizeCorrectlySortsPreferredInstallInConfigWithCatchAllWildcardAtStart(): void + { + $json = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "sort-packages": true, + "preferred-install": { + "*": "dist", + "foo/package-one": "source", + "bar/another-package": "source" + } + } +} +JSON + ); + + $expected = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "preferred-install": { + "bar/another-package": "source", + "foo/package-one": "source", + "*": "dist" + }, + "sort-packages": true } } JSON @@ -281,7 +328,7 @@ public function testNormalizeSortsAllowPluginsInOtherProperty(): void * @see https://github.com/ergebnis/composer-normalize/issues/644 * @see https://getcomposer.org/doc/06-config.md#preferred-install */ - public function testNormalizeDoesNotSortPreferredInstallInConfig(): void + public function testNormalizeCorrectlySortsPreferredInstallInConfigWithCatchAllWildcardInTheMiddle(): void { $json = Json::fromEncoded( <<<'JSON' @@ -289,9 +336,57 @@ public function testNormalizeDoesNotSortPreferredInstallInConfig(): void "config": { "sort-packages": true, "preferred-install": { - "foo/*": "source", - "bar/*": "source", + "foo/package-two": "source", + "foo/package-one": "source", + "*": "dist", + "bar/another-package-2": "source", + "bar/another-package-1": "source" + } + } +} +JSON + ); + + $expected = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "preferred-install": { + "bar/another-package-1": "source", + "bar/another-package-2": "source", + "foo/package-one": "source", + "foo/package-two": "source", "*": "dist" + }, + "sort-packages": true + } +} +JSON + ); + + $normalizer = new Vendor\Composer\ConfigHashNormalizer(); + + $normalized = $normalizer->normalize($json); + + self::assertJsonStringEqualsJsonStringNormalized($expected->encoded(), $normalized->encoded()); + } + + /** + * @see https://github.com/ergebnis/composer-normalize/issues/644 + * @see https://getcomposer.org/doc/06-config.md#preferred-install + */ + public function testNormalizeCorrectlySortsPreferredInstallInConfigWithVendorWildcardAtEnd(): void + { + $json = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "sort-packages": true, + "preferred-install": { + "foo/package-two": "dist", + "foo/package-one": "dist", + "*": "dist", + "foo/*": "source" } } } @@ -303,8 +398,9 @@ public function testNormalizeDoesNotSortPreferredInstallInConfig(): void { "config": { "preferred-install": { + "foo/package-one": "dist", + "foo/package-two": "dist", "foo/*": "source", - "bar/*": "source", "*": "dist" }, "sort-packages": true @@ -320,17 +416,18 @@ public function testNormalizeDoesNotSortPreferredInstallInConfig(): void self::assertJsonStringEqualsJsonStringNormalized($expected->encoded(), $normalized->encoded()); } - public function testNormalizeSortsPreferredInstallInOtherProperty(): void + public function testNormalizeCorrectlySortsPreferredInstallInConfigWhenMoreSpecificAfterWildcard(): void { $json = Json::fromEncoded( <<<'JSON' { - "extra": { - "something": { - "preferred-install": { - "foo": "bar", - "bar": "baz" - } + "config": { + "sort-packages": true, + "preferred-install": { + "foo/*": "source", + "foo/package-two": "dist", + "foo/package-one": "dist", + "*": "dist" } } } @@ -340,18 +437,62 @@ public function testNormalizeSortsPreferredInstallInOtherProperty(): void $expected = Json::fromEncoded( <<<'JSON' { - "extra": { - "something": { - "preferred-install": { - "bar": "baz", - "foo": "bar" - } + "config": { + "preferred-install": { + "foo/package-one": "dist", + "foo/package-two": "dist", + "foo/*": "source", + "*": "dist" + }, + "sort-packages": true + } +} +JSON + ); + + $normalizer = new Vendor\Composer\ConfigHashNormalizer(); + + $normalized = $normalizer->normalize($json); + + self::assertJsonStringEqualsJsonStringNormalized($expected->encoded(), $normalized->encoded()); + } + + public function testNormalizeCorrectlySortsPreferredInstallInConfigWhenMoreSpecificWildcardAfterWildcard(): void + { + $json = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "sort-packages": true, + "preferred-install": { + "foo/something-longer-but-alphabetically-after-package-*": "source", + "foo/*": "dist", + "foo/package-*": "source", + "foo/package-one": "dist", + "*": "dist" } } } JSON ); + $expected = Json::fromEncoded( + <<<'JSON' +{ + "config": { + "preferred-install": { + "foo/package-one": "dist", + "foo/package-*": "source", + "foo/something-longer-but-alphabetically-after-package-*": "source", + "foo/*": "dist", + "*": "dist" + }, + "sort-packages": true + } +} +JSON + ); + $normalizer = new Vendor\Composer\ConfigHashNormalizer(); $normalized = $normalizer->normalize($json);