* @license http://www.apache.org/licenses/LICENSE-2.0 * @link http://phpsx.org */ class ChangelogGenerator { public function generate(DefinitionsInterface $left, ?DefinitionsInterface $right = null): \Generator { if (empty($right)) { yield SemVer::PATCH => 'Initial release'; return; } foreach ($left->getAllTypes() as $leftName => $leftType) { if ($right->hasType($leftName)) { yield from $this->generateType($leftType, $right->getType($leftName), $leftName); } else { yield SemVer::MAJOR => $this->getMessageRemoved($leftName, null); } } foreach ($right->getAllTypes() as $rightName => $rightType) { if (!$left->hasType($rightName)) { yield SemVer::PATCH => $this->getMessageAdded($rightName, null); } } } private function generateType( TypeInterface $leftType, TypeInterface $rightType, string $typeName, ?string $propertyName = null, ): \Generator { if (get_class($leftType) !== get_class($rightType)) { yield SemVer::MAJOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'type', TypeUtil::getTypeName($leftType), TypeUtil::getTypeName($rightType)); return; } if ($leftType instanceof TypeAbstract && $rightType instanceof TypeAbstract) { yield from $this->generateCommon($leftType, $rightType, $typeName, $propertyName); } if ($leftType instanceof StructType && $rightType instanceof StructType) { yield from $this->generateStruct($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof MapType && $rightType instanceof MapType) { yield from $this->generateMap($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof ArrayType && $rightType instanceof ArrayType) { yield from $this->generateArray($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof NumberType && $rightType instanceof NumberType) { yield from $this->generateNumber($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof BooleanType && $rightType instanceof BooleanType) { // nothing to diff here } elseif ($leftType instanceof StringType && $rightType instanceof StringType) { yield from $this->generateString($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof IntersectionType && $rightType instanceof IntersectionType) { yield from $this->generateIntersection($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof UnionType && $rightType instanceof UnionType) { yield from $this->generateUnion($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof ReferenceType && $rightType instanceof ReferenceType) { yield from $this->generateReference($leftType, $rightType, $typeName, $propertyName); } elseif ($leftType instanceof AnyType && $rightType instanceof AnyType) { // nothing to diff here } elseif ($leftType instanceof GenericType && $rightType instanceof GenericType) { // nothing to diff here } } private function generateCommon( TypeAbstract $leftType, TypeAbstract $rightType, string $typeName, ?string $propertyName = null, ): \Generator { if ($leftType->getDescription() !== $rightType->getDescription()) { yield SemVer::PATCH => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'description', $leftType->getDescription(), $rightType->getDescription()); } if ($leftType->isNullable() !== $rightType->isNullable()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'nullable', $leftType->isNullable(), $rightType->isNullable()); } if ($leftType->isDeprecated() !== $rightType->isDeprecated()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'deprecated', $leftType->isDeprecated(), $rightType->isDeprecated()); } if ($leftType->isReadonly() !== $rightType->isReadonly()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'readonly', $leftType->isReadonly(), $rightType->isReadonly()); } } private function generateStruct( StructType $leftType, StructType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { $left = $leftType->getProperties(); $right = $rightType->getProperties(); foreach ($left as $key => $property) { if (isset($right[$key])) { yield from $this->generateType($property, $right[$key], $typeName, $key); } else { yield SemVer::MAJOR => $this->getMessageRemoved($typeName, $key); } } foreach ($right as $key => $value) { if (!isset($left[$key])) { yield SemVer::PATCH => $this->getMessageAdded($typeName, $key); } } } private function generateMap( MapType $leftType, MapType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { $left = $leftType->getAdditionalProperties(); $right = $rightType->getAdditionalProperties(); if ($left instanceof TypeInterface && $right instanceof TypeInterface) { yield from $this->generateType($left, $right, $typeName); } } private function generateArray( ArrayType $leftType, ArrayType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { $left = $leftType->getItems(); $right = $rightType->getItems(); if ($left instanceof TypeInterface && $right instanceof TypeInterface) { yield from $this->generateType($left, $right, $typeName); } } private function generateIntersection( IntersectionType $leftType, IntersectionType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { $left = $leftType->getAllOf(); $right = $rightType->getAllOf(); foreach ($left as $index => $value) { if (isset($right[$index])) { yield from $this->generateType($value, $right[$index], $typeName, $propertyName); } else { yield $this->getMessageRemoved($typeName, $propertyName . '[' . $index . ']'); } } foreach ($right as $index => $value) { if (!isset($left[$index])) { yield $this->getMessageAdded($typeName, $propertyName . '[' . $index . ']'); } } } private function generateUnion( UnionType $leftType, UnionType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { $left = $leftType->getOneOf(); $right = $rightType->getOneOf(); foreach ($left as $index => $value) { if (isset($right[$index])) { yield from $this->generateType($value, $right[$index], $typeName, $propertyName); } else { yield $this->getMessageRemoved($typeName, $propertyName . '[' . $index . ']'); } } foreach ($right as $index => $value) { if (!isset($left[$index])) { yield $this->getMessageAdded($typeName, $propertyName . '[' . $index . ']'); } } } private function generateReference( ReferenceType $leftType, ReferenceType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { if ($leftType->getRef() !== $rightType->getRef()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'ref', $leftType->getRef(), $rightType->getRef()); } } private function generateString( StringType $leftType, StringType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { if ($leftType->getPattern() !== $rightType->getPattern()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'pattern', $leftType->getPattern(), $rightType->getPattern()); } if ($leftType->getMinLength() !== $rightType->getMinLength()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'min length', $leftType->getMinLength(), $rightType->getMinLength()); } if ($leftType->getMaxLength() !== $rightType->getMaxLength()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'max length', $leftType->getMaxLength(), $rightType->getMaxLength()); } } private function generateNumber( NumberType $leftType, NumberType $rightType, string $typeName, ?string $propertyName = null, ): \Generator { if ($leftType->getMinimum() !== $rightType->getMinimum()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'minimum', $leftType->getMinimum(), $rightType->getMinimum()); } if ($leftType->getMaximum() !== $rightType->getMaximum()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'maximum', $leftType->getMaximum(), $rightType->getMaximum()); } if ($leftType->getExclusiveMinimum() !== $rightType->getExclusiveMinimum()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'exclusive minimum', $leftType->getExclusiveMinimum(), $rightType->getExclusiveMinimum()); } if ($leftType->getExclusiveMaximum() !== $rightType->getExclusiveMaximum()) { yield SemVer::MINOR => $this->getMessageChanged($typeName, $propertyName, TypeUtil::getTypeName($leftType), 'exclusive maximum', $leftType->getExclusiveMaximum(), $rightType->getExclusiveMaximum()); } } private function getMessageAdded(string $typeName, ?string $propertyName): string { [$ns, $name] = TypeUtil::split($typeName); if ($propertyName === null) { return 'Type "' . $name . '" was added'; } else { return 'Property "' . $name . '.' . $propertyName . '" was added'; } } private function getMessageRemoved(string $typeName, ?string $propertyName): string { [$ns, $name] = TypeUtil::split($typeName); if ($propertyName === null) { return 'Type "' . $name . '" was removed'; } else { return 'Property "' . $name . '.' . $propertyName . '" was removed'; } } private function getMessageChanged( string $typeName, ?string $propertyName, string $type, string $description, $from, $to, ): string { $from = $from ?? 'NULL'; $to = $to ?? 'NULL'; [$ns, $name] = TypeUtil::split($typeName); if ($propertyName === null) { return 'Property "' . $name . '.' . $propertyName . '" (' . $type . ') ' . $description . ' has changed from "' . $from . '" to "' . $to . '"'; } else { return 'Type "' . $name . '" (' . $type . ') ' . $description . ' has changed from "' . $from . '" to "' . $to . '"'; } } } __halt_compiler();----SIGNATURE:----REdEJL/Tlw7vQF7OejBdXMjSgfZ/ivJLZtrgrlineYhEq4JlR4l1WXY9VKAaEvHICFYB//zfQfXDx+0h1zVmsDFUpnVZd7Qdmu6kilJWPN4Cr6RvnDZ4Z/woylNNXzM+g6HcnU4Px08BOO9RH+DGLdu8conVwmmtsBil84UqxG1rigZbVCsFF0BtoYz7eP6Dga9MJ9FCLef82zRlBZtmGC0SomsD8q8CL0jsrhULno3a/uKrqbYeO/tSjeGsOrsh/f/4PUdz5H3l/ONXxRM5lOBQkpLxon33UqnOtYEgy0sLDN2Z/WUW/okgidst4nIaIHPcDo/h5fzj7vARwMfcLYFEWdfUJLavbrxDORau+NpEdrSwcPeyLRaXLVCqTkrZxtTty7Sx83GvuEKGgBrk4fA1Qt7I8RhImCven5rz159Twc4cbLEbOfTWBLqXG4HvqauwiZjb8RluU9ZxoQrzUi+sx1n26c04G2S8mQ5Ki8GaUBaSK5LxY/S79SEyRMxk3EN6WbMxaJlsGilyv0ExyKq5FlIVWlTuugjZIp24jwiUieBH4t3w1tBod0C7IQTy1a17KZ9+fi5VxSFrPHmhBI1k3pyaPxSf4J5/qqm2Y6pLB9kgJnT2TnaAPHwW+iuL1wNP0UeQeSRaGpF16qIfHOyKBIbXTJ8PtwyAizFihvA=----ATTACHMENT:----NjczNDEwNzU3MDc2ODkyNCA1NzE3OTUwNzQxMDQwMjY1IDg4Mjc0MjUzOTU1MzAyODY=