From 45bafad041eef3f53044d17c2fd0fc7917d3f2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Mon, 15 Jan 2018 22:12:06 +0100 Subject: [PATCH] Enhancement: Implement VersionConstraintNormalizer --- README.md | 20 ++ src/Normalizer/ComposerJsonNormalizer.php | 3 +- .../VersionConstraintNormalizer.php | 102 ++++++++ .../Normalizer/ComposerJsonNormalizerTest.php | 6 +- .../VersionConstraintNormalizerTest.php | 234 ++++++++++++++++++ 5 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 src/Normalizer/VersionConstraintNormalizer.php create mode 100644 test/Unit/Normalizer/VersionConstraintNormalizerTest.php diff --git a/README.md b/README.md index 88271a8c..d67ed2bf 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ as well as the following normalizers provided by this package: * [`Localheinz\Composer\Normalize\Normalizer\BinNormalizer`](#binnormalizer) * [`Localheinz\Composer\Normalize\Normalizer\ConfigHashNormalizer`](#confighashnormalizer) * [`Localheinz\Composer\Normalize\Normalizer\PackageHashNormalizer`](#packagehashnormalizer) +* [`Localheinz\Composer\Normalize\Normalizer\VersionOrConstraintNormalizer`](#versionconstraintnormalizer) ### `BinNormalizer` @@ -79,6 +80,25 @@ sections, the `PackageHashNormalizer` will sort the content of these sections. the `--sort-packages` flag and configuration at https://getcomposer.org/doc/06-config.md#sort-packages and https://getcomposer.org/doc/03-cli.md#require. +### `VersionConstraintNormalizer` + +If `composer.json` contains version constraints in the + +* `conflict` +* `provide` +* `replaces` +* `require` +* `require-dev` + +sections, the `VersionConstraintNormalizer` will ensure that + +* all constraints are trimmed +* *and* constraints are separated by a comma (`,`) +* *or* constraints are separated by double-pipe with a single space before and after (` || `) +* *range* constraints are separated by a single space (` `) + +:bulb: Find out more about version constraints at https://getcomposer.org/doc/articles/versions.md. + ## Contributing Please have a look at [`CONTRIBUTING.md`](.github/CONTRIBUTING.md). diff --git a/src/Normalizer/ComposerJsonNormalizer.php b/src/Normalizer/ComposerJsonNormalizer.php index ded83241..42214fe4 100644 --- a/src/Normalizer/ComposerJsonNormalizer.php +++ b/src/Normalizer/ComposerJsonNormalizer.php @@ -31,7 +31,8 @@ public function __construct(string $schemaUri = 'https://getcomposer.org/schema. new SchemaNormalizer($schemaUri), new BinNormalizer(), new ConfigHashNormalizer(), - new PackageHashNormalizer() + new PackageHashNormalizer(), + new VersionConstraintNormalizer() )); } diff --git a/src/Normalizer/VersionConstraintNormalizer.php b/src/Normalizer/VersionConstraintNormalizer.php new file mode 100644 index 00000000..e227919b --- /dev/null +++ b/src/Normalizer/VersionConstraintNormalizer.php @@ -0,0 +1,102 @@ + [ + '{\s*,\s*}', + ',', + ], + 'or' => [ + '{\s*\|\|?\s*}', + ' || ', + ], + 'range' => [ + '{\s+}', + ' ', + ], + ]; + + public function normalize(string $json): string + { + $decoded = \json_decode($json); + + if (null === $decoded && JSON_ERROR_NONE !== \json_last_error()) { + throw new \InvalidArgumentException(\sprintf( + '"%s" is not valid JSON.', + $json + )); + } + + $objectProperties = \array_intersect_key( + \get_object_vars($decoded), + \array_flip(self::$properties) + ); + + if (!\count($objectProperties)) { + return $json; + } + + foreach ($objectProperties as $name => $value) { + $packages = (array) $decoded->{$name}; + + if (!\count($packages)) { + continue; + } + + $decoded->{$name} = \array_map(function (string $versionConstraint) { + return $this->normalizeVersionConstraint($versionConstraint); + }, $packages); + } + + return \json_encode($decoded); + } + + private function normalizeVersionConstraint(string $versionConstraint): string + { + $normalized = $versionConstraint; + + foreach (self::$map as list($pattern, $glue)) { + $split = \preg_split( + $pattern, + $normalized + ); + + $normalized = \implode( + $glue, + $split + ); + } + + return \trim($normalized); + } +} diff --git a/test/Unit/Normalizer/ComposerJsonNormalizerTest.php b/test/Unit/Normalizer/ComposerJsonNormalizerTest.php index a0574961..04b9d7e6 100644 --- a/test/Unit/Normalizer/ComposerJsonNormalizerTest.php +++ b/test/Unit/Normalizer/ComposerJsonNormalizerTest.php @@ -17,6 +17,7 @@ use Localheinz\Composer\Normalize\Normalizer\ComposerJsonNormalizer; use Localheinz\Composer\Normalize\Normalizer\ConfigHashNormalizer; use Localheinz\Composer\Normalize\Normalizer\PackageHashNormalizer; +use Localheinz\Composer\Normalize\Normalizer\VersionConstraintNormalizer; use Localheinz\Json\Normalizer\AutoFormatNormalizer; use Localheinz\Json\Normalizer\ChainNormalizer; use Localheinz\Json\Normalizer\NormalizerInterface; @@ -41,6 +42,7 @@ public function testComposesNormalizers() BinNormalizer::class, ConfigHashNormalizer::class, PackageHashNormalizer::class, + VersionConstraintNormalizer::class, ]; $this->assertComposesNormalizers($normalizerClassNames, $chainNormalizer); @@ -84,7 +86,7 @@ public function testNormalizeNormalizes() "require-dev": { "localheinz/test-util": "0.6.1", "phpunit/phpunit": "^6.5.5", - "localheinz/php-cs-fixer-config": "~1.11.0" + "localheinz/php-cs-fixer-config": "~1.0.0|~1.11.0" }, "autoload": { "psr-4": { @@ -126,7 +128,7 @@ public function testNormalizeNormalizes() "localheinz/json-printer": "^1.0.0" }, "require-dev": { - "localheinz/php-cs-fixer-config": "~1.11.0", + "localheinz/php-cs-fixer-config": "~1.0.0 || ~1.11.0", "localheinz/test-util": "0.6.1", "phpunit/phpunit": "^6.5.5" }, diff --git a/test/Unit/Normalizer/VersionConstraintNormalizerTest.php b/test/Unit/Normalizer/VersionConstraintNormalizerTest.php new file mode 100644 index 00000000..f92130f9 --- /dev/null +++ b/test/Unit/Normalizer/VersionConstraintNormalizerTest.php @@ -0,0 +1,234 @@ +assertSame($json, $normalizer->normalize($json)); + } + + public function providerVersionConstraint(): \Generator + { + foreach (\array_keys($this->versionConstraints()) as $versionConstraint) { + yield [ + $versionConstraint, + ]; + } + } + + /** + * @dataProvider providerProperty + * + * @param string $property + */ + public function testNormalizeIgnoresEmptyPackageHash(string $property) + { + $json = <<assertSame(\json_encode(\json_decode($json)), $normalizer->normalize($json)); + } + + public function providerProperty(): \Generator + { + $properties = $this->properties(); + + foreach ($properties as $property) { + yield [ + $property, + ]; + } + } + + /** + * @dataProvider providerPropertyAndVersionConstraint + * + * @param string $property + * @param string $versionConstraint + * @param string $normalizedVersionConstraint + */ + public function testNormalizeNormalizesVersionConstraints(string $property, string $versionConstraint, string $normalizedVersionConstraint) + { + $json = <<assertJsonStringEqualsJsonString($normalized, $normalizer->normalize($json)); + } + + public function providerPropertyAndVersionConstraint(): \Generator + { + $properties = $this->properties(); + $versionConstraints = $this->versionConstraints(); + + foreach ($properties as $property) { + foreach ($versionConstraints as $versionConstraint => $normalizedVersionConstraint) { + yield [ + $property, + $versionConstraint, + $normalizedVersionConstraint, + ]; + } + } + } + + /** + * @dataProvider providerPropertyAndUntrimmedVersionConstraint + * + * @param string $property + * @param string $versionConstraint + * @param string $trimmedVersionConstraint + */ + public function testNormalizeNormalizesTrimsVersionConstraints(string $property, string $versionConstraint, string $trimmedVersionConstraint) + { + $json = <<assertJsonStringEqualsJsonString($normalized, $normalizer->normalize($json)); + } + + public function providerPropertyAndUntrimmedVersionConstraint(): \Generator + { + $spaces = [ + '', + ' ', + ]; + + $properties = $this->properties(); + $versionConstraints = \array_unique(\array_values($this->versionConstraints())); + + foreach ($properties as $property) { + foreach ($versionConstraints as $versionConstraint) { + foreach ($spaces as $prefix) { + foreach ($spaces as $suffix) { + yield [ + $property, + $prefix . $versionConstraint . $suffix, + $versionConstraint, + ]; + } + } + } + } + } + + private function properties(): array + { + return [ + 'conflict', + 'provide', + 'replaces', + 'require', + 'require-dev', + ]; + } + + /** + * @see https://getcomposer.org/doc/articles/versions.md#writing-version-constraints + * + * @return array + */ + private function versionConstraints(): array + { + return [ + /** + * @see https://getcomposer.org/doc/articles/versions.md#branches + */ + 'dev-master' => 'dev-master', + 'dev-my-feature' => 'dev-my-feature', + 'dev-master#bf2eeff' => 'dev-master#bf2eeff', + /** + * @see https://getcomposer.org/doc/articles/versions.md#exact-version-constraint + */ + '1.0.2' => '1.0.2', + /** + * @see https://getcomposer.org/doc/articles/versions.md#version-range + */ + '>=1.0' => '>=1.0', + '>=1.0 <2.0' => '>=1.0 <2.0', + '>=1.0,<2.0' => '>=1.0,<2.0', + '>=1.0 <2.0' => '>=1.0 <2.0', + '>=1.0 , <2.0' => '>=1.0,<2.0', + '>=1.0 <1.1 || >=1.2' => '>=1.0 <1.1 || >=1.2', + '>=1.0,<1.1 || >=1.2' => '>=1.0,<1.1 || >=1.2', + '>=1.0 <1.1||>=1.2' => '>=1.0 <1.1 || >=1.2', + /** + * @see https://getcomposer.org/doc/articles/versions.md#hyphenated-version-range- + */ + '1.0 - 2.0' => '1.0 - 2.0', + '1.0 - 2.0' => '1.0 - 2.0', + /** + * @see https://getcomposer.org/doc/articles/versions.md#next-significant-release-operators + */ + '~1.2' => '~1.2', + /** + * @see https://getcomposer.org/doc/articles/versions.md#caret-version-range- + */ + '^1.2.3' => '^1.2.3', + ]; + } +}