From 071268f97a4ce6d8d0dcc041a569839a1d472ab4 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Sun, 12 Dec 2021 18:15:42 -0600 Subject: [PATCH] feat: provide --flatten option for hoisting selectors --- CHANGELOG.md | 1 + src/Console/Command/ExtractCommand.php | 11 + src/Descriptor.php | 5 + src/Extractor/MessageExtractor.php | 32 +++ src/Extractor/MessageExtractorOptions.php | 9 + src/Icu/MessageFormat/Manipulator.php | 79 +++++++ .../Parser/Type/AbstractElement.php | 6 +- .../Parser/Type/AbstractSkeleton.php | 2 + .../MessageFormat/Parser/Type/DeepCloner.php | 84 +++++++ .../Parser/Type/ElementCollection.php | 11 + .../MessageFormat/Parser/Type/Location.php | 2 + .../Type/NumberSkeletonTokenCollection.php | 11 + .../Parser/Type/PluralOrSelectOption.php | 2 + .../Parser/Type/PoundElement.php | 4 +- src/Icu/MessageFormat/Printer.php | 212 ++++++++++++++++++ tests/DescriptorTest.php | 8 + tests/Extractor/MessageExtractorTest.php | 57 +++++ tests/Icu/MessageFormat/ManipulatorTest.php | 69 ++++++ .../Parser/Type/ArgumentElementTest.php | 18 +- .../Parser/Type/DateElementTest.php | 31 ++- .../Parser/Type/DateTimeSkeletonTest.php | 19 +- .../Parser/Type/LiteralElementTest.php | 23 +- .../Parser/Type/LocationTest.php | 12 + .../Parser/Type/NumberElementTest.php | 36 ++- .../Parser/Type/NumberSkeletonTest.php | 28 ++- .../Parser/Type/PluralElementTest.php | 36 ++- .../Parser/Type/PluralOrSelectOptionTest.php | 25 ++- .../Parser/Type/PoundElementTest.php | 16 +- .../Parser/Type/SelectElementTest.php | 32 ++- .../Parser/Type/TagElementTest.php | 26 ++- .../Parser/Type/TimeElementTest.php | 31 ++- tests/Icu/MessageFormat/PrinterTest.php | 207 +++++++++++++++++ ...h data set hoist some random case 1__1.txt | 1 + ...with data set should hoist 1 plural__1.txt | 1 + ...should hoist plural select and tag__1.txt | 25 +++ ...tAst with data set basic_argument_1__1.txt | 1 + ...tAst with data set basic_argument_2__1.txt | 2 + ...t with data set date_arg_skeleton_1__1.txt | 1 + ...t with data set date_arg_skeleton_2__1.txt | 1 + ...t with data set date_arg_skeleton_3__1.txt | 1 + ...ntAst with data set escaped_pound_1__1.txt | 1 + ...th data set ignore_tag_number_arg_1__1.txt | 1 + ...rintAst with data set ignore_tags_1__1.txt | 1 + ... with data set left_angle_bracket_1__1.txt | 1 + ...tAst with data set less_than_sign_1__1.txt | 1 + ...Ast with data set negative_offset_1__1.txt | 1 + ...testPrintAst with data set nested_1__1.txt | 5 + ...rintAst with data set nested_tags_1__1.txt | 1 + ...t with data set not_escaped_pound_1__1.txt | 1 + ...t with data set not_quoted_string_2__1.txt | 1 + ...ith data set not_self_closing_tag_1__1.txt | 1 + ...with data set number_arg_skeleton_2__1.txt | 1 + ...with data set number_arg_skeleton_3__1.txt | 1 + ...st with data set number_arg_style_1__1.txt | 1 + ...rintAst with data set numeric_tag_1__1.txt | 1 + ...tAst with data set open_close_tag_1__1.txt | 1 + ...tAst with data set open_close_tag_2__1.txt | 1 + ...tAst with data set open_close_tag_3__1.txt | 1 + ...h data set open_close_tag_with_args__1.txt | 1 + ... set open_close_tag_with_nested_arg__1.txt | 1 + ...PrintAst with data set plural_arg_1__1.txt | 2 + ...PrintAst with data set plural_arg_2__1.txt | 2 + ...ral_arg_with_escaped_nested_message__1.txt | 2 + ...h data set plural_arg_with_offset_1__1.txt | 1 + ...t with data set quoted_pound_sign_1__1.txt | 1 + ...ntAst with data set quoted_string_3__1.txt | 1 + ...ntAst with data set quoted_string_4__1.txt | 1 + ...PrintAst with data set select_arg_1__1.txt | 2 + ...et select_arg_with_nested_arguments__1.txt | 2 + ...ntAst with data set selectordinal_1__1.txt | 1 + ...st with data set self_closing_tag_1__1.txt | 1 + ...st with data set self_closing_tag_2__1.txt | 1 + ...Ast with data set simple_argument_1__1.txt | 1 + ...Ast with data set simple_argument_2__1.txt | 1 + ...data set simple_date_and_time_arg_1__1.txt | 1 + ...t with data set simple_number_arg_1__1.txt | 1 + ...t with data set time_arg_skeleton_1__1.txt | 1 + ...et treat_unicode_nbsp_as_whitespace__1.txt | 5 + ...estPrintAst with data set trivial_1__1.txt | 1 + ...estPrintAst with data set trivial_2__1.txt | 1 + ...data set unescaped_string_literal_1__1.txt | 1 + ...ntAst with data set uppercase_tag_1__1.txt | 1 + 82 files changed, 1196 insertions(+), 37 deletions(-) create mode 100644 src/Icu/MessageFormat/Manipulator.php create mode 100644 src/Icu/MessageFormat/Parser/Type/DeepCloner.php create mode 100644 src/Icu/MessageFormat/Printer.php create mode 100644 tests/Icu/MessageFormat/ManipulatorTest.php create mode 100644 tests/Icu/MessageFormat/PrinterTest.php create mode 100644 tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set hoist some random case 1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist 1 plural__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist plural select and tag__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_3__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set escaped_pound_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tag_number_arg_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tags_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set left_angle_bracket_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set less_than_sign_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set negative_offset_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_tags_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_escaped_pound_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_quoted_string_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_self_closing_tag_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_3__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_style_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set numeric_tag_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_3__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_args__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_nested_arg__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_escaped_nested_message__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_offset_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_pound_sign_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_3__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_4__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_with_nested_arguments__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set selectordinal_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_date_and_time_arg_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_number_arg_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set time_arg_skeleton_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set treat_unicode_nbsp_as_whitespace__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_2__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set unescaped_string_literal_1__1.txt create mode 100644 tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set uppercase_tag_1__1.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b6a15..2be61dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Provide `--flatten` extraction option to tell the extractor to hoist selectors and flatten sentences as much as possible. For example, `I have {count, plural, one{a dog} other{many dogs}}` becomes `{count, plural, one{I have a dog} other{I have many dogs}}`. The goal is to provide as many full sentences as possible, since fragmented sentences are not translator-friendly. - Provide `--add-missing-ids` extraction option to update source code with auto-generated identifiers - Add `Util\FormatHelper` that provides `getReader()` and `getWriter()` methods - Introduce `Format\Format` final static class for format constants diff --git a/src/Console/Command/ExtractCommand.php b/src/Console/Command/ExtractCommand.php index 3cc4718..fd79378 100644 --- a/src/Console/Command/ExtractCommand.php +++ b/src/Console/Command/ExtractCommand.php @@ -167,6 +167,16 @@ protected function configure(): void 'Whether to preserve whitespace and newlines in extracted ' . 'messages.', ) + ->addOption( + 'flatten', + null, + InputOption::VALUE_NONE, + 'Whether to hoist selectors & flatten sentences as much as possible, ' + . 'e.g: "I have {count, plural, one{a dog} other{many dogs}}" ' + . 'becomes "{count, plural, one{I have a dog} other{I have many ' + . 'dogs}}". The goal is to provide as many full sentences as ' + . 'possible, since fragmented sentences are not translator-friendly.', + ) ->addOption( 'add-missing-ids', null, @@ -239,6 +249,7 @@ private function buildOptions(InputInterface $input): MessageExtractorOptions $options->extractSourceLocation = (bool) $input->getOption('extract-source-location'); $options->throws = (bool) $input->getOption('throws'); $options->preserveWhitespace = (bool) $input->getOption('preserve-whitespace'); + $options->flatten = (bool) $input->getOption('flatten'); $options->addGeneratedIdsToSourceCode = (bool) $input->getOption('add-missing-ids'); /** @var string $inputFunctionNames */ diff --git a/src/Descriptor.php b/src/Descriptor.php index c1bae42..9b58bd3 100644 --- a/src/Descriptor.php +++ b/src/Descriptor.php @@ -72,6 +72,11 @@ public function getDefaultMessage(): ?string return $this->defaultMessage; } + public function setDefaultMessage(string $defaultMessage): void + { + $this->defaultMessage = $defaultMessage; + } + public function getDescription(): ?string { return $this->description; diff --git a/src/Extractor/MessageExtractor.php b/src/Extractor/MessageExtractor.php index f48bc6a..3b7b118 100644 --- a/src/Extractor/MessageExtractor.php +++ b/src/Extractor/MessageExtractor.php @@ -23,7 +23,9 @@ namespace FormatPHP\Extractor; use Closure; +use FormatPHP\Descriptor; use FormatPHP\DescriptorCollection; +use FormatPHP\DescriptorInterface; use FormatPHP\Exception\FormatPHPExceptionInterface; use FormatPHP\Exception\ImproperContextException; use FormatPHP\Exception\InvalidArgumentException; @@ -32,6 +34,9 @@ use FormatPHP\Extractor\Parser\Descriptor\PhpParser; use FormatPHP\Extractor\Parser\DescriptorParserInterface; use FormatPHP\Extractor\Parser\ParserErrorCollection; +use FormatPHP\Icu\MessageFormat\Manipulator; +use FormatPHP\Icu\MessageFormat\Parser as MessageFormatParser; +use FormatPHP\Icu\MessageFormat\Printer; use FormatPHP\Util\FileSystemHelper; use FormatPHP\Util\FormatHelper; use FormatPHP\Util\Globber; @@ -56,6 +61,8 @@ class MessageExtractor private MessageExtractorOptions $options; private ParserErrorCollection $errors; private FormatHelper $formatHelper; + private Manipulator $manipulator; + private Printer $printer; public function __construct( MessageExtractorOptions $options, @@ -70,6 +77,8 @@ public function __construct( $this->file = $file; $this->formatHelper = $formatHelper; $this->errors = new ParserErrorCollection(); + $this->manipulator = new Manipulator(); + $this->printer = new Printer(); } /** @@ -201,6 +210,12 @@ private function loadDescriptorParser(string $parserNameOrScript): DescriptorPar */ private function write(callable $formatter, DescriptorCollection $descriptors): void { + if ($this->options->flatten === true) { + /** @var DescriptorInterface[] $flattened */ + $flattened = $descriptors->map($this->flattenMessage())->toArray(); + $descriptors = new DescriptorCollection($flattened); + } + $file = $this->options->outFile ?? 'php://output'; $messages = $formatter($descriptors, $this->options); @@ -238,4 +253,21 @@ public function __invoke( } }; } + + private function flattenMessage(): Closure + { + return function (Descriptor $descriptor): Descriptor { + $message = $descriptor->getDefaultMessage(); + $messageFormatParser = new MessageFormatParser((string) $message); + $result = $messageFormatParser->parse(); + + /** @var MessageFormatParser\Type\ElementCollection $messageAst */ + $messageAst = $result->val; + + $hoistedAst = $this->manipulator->hoistSelectors($messageAst); + $descriptor->setDefaultMessage($this->printer->printAst($hoistedAst)); + + return $descriptor; + }; + } } diff --git a/src/Extractor/MessageExtractorOptions.php b/src/Extractor/MessageExtractorOptions.php index 1ddf84b..8eee0e4 100644 --- a/src/Extractor/MessageExtractorOptions.php +++ b/src/Extractor/MessageExtractorOptions.php @@ -22,6 +22,8 @@ namespace FormatPHP\Extractor; +use FormatPHP\Icu\MessageFormat\Manipulator; + /** * MessageExtractor options */ @@ -77,6 +79,13 @@ class MessageExtractorOptions */ public bool $preserveWhitespace = false; + /** + * Whether to hoist selectors and flatten sentences as much as possible + * + * @see Manipulator::hoistSelectors() + */ + public bool $flatten = false; + /** * Function and method names to parse from the application source code * diff --git a/src/Icu/MessageFormat/Manipulator.php b/src/Icu/MessageFormat/Manipulator.php new file mode 100644 index 0000000..62f9b2d --- /dev/null +++ b/src/Icu/MessageFormat/Manipulator.php @@ -0,0 +1,79 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +declare(strict_types=1); + +namespace FormatPHP\Icu\MessageFormat; + +use FormatPHP\Icu\MessageFormat\Parser\Type\ElementCollection; +use FormatPHP\Icu\MessageFormat\Parser\Type\PluralElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\SelectElement; + +use function array_slice; +use function array_values; + +/** + * Provides functionality to manipulate a parsed AST + * + * @internal + */ +class Manipulator +{ + /** + * Hoist all selectors to the beginning of the AST & flatten the + * resulting options + * + * For example, + * + * I have {count, plural, one{a dog} other{many dogs}} + * + * becomes + * + * {count, plural, one{I have a dog} other{I have many dogs}} + * + * If there are multiple selectors, the order of which one is hoisted 1st + * is non-deterministic. + * + * The goal is to provide as many full sentences as possible since + * fragmented sentences are not translator-friendly. + */ + public function hoistSelectors(ElementCollection $ast): ElementCollection + { + for ($i = 0; $i < $ast->count(); $i++) { + $element = $ast[$i]; + + if ($element instanceof PluralElement || $element instanceof SelectElement) { + $cloned = clone $element; + $options = $cloned->options; + foreach ($options as $option) { + $option->value = $this->hoistSelectors(new ElementCollection([ + ...array_values(array_slice($ast->toArray(), 0, $i)), + ...array_values($option->value->toArray()), + ...array_values(array_slice($ast->toArray(), $i + 1)), + ])); + } + + return new ElementCollection([$cloned]); + } + } + + return $ast; + } +} diff --git a/src/Icu/MessageFormat/Parser/Type/AbstractElement.php b/src/Icu/MessageFormat/Parser/Type/AbstractElement.php index c9aeda9..eb6dc1e 100644 --- a/src/Icu/MessageFormat/Parser/Type/AbstractElement.php +++ b/src/Icu/MessageFormat/Parser/Type/AbstractElement.php @@ -24,7 +24,9 @@ abstract class AbstractElement implements ElementInterface { + use DeepCloner; + public ElementType $type; - public ?string $value = null; - public ?Location $location = null; + public string $value; + public Location $location; } diff --git a/src/Icu/MessageFormat/Parser/Type/AbstractSkeleton.php b/src/Icu/MessageFormat/Parser/Type/AbstractSkeleton.php index 24cdcf8..5cc80ed 100644 --- a/src/Icu/MessageFormat/Parser/Type/AbstractSkeleton.php +++ b/src/Icu/MessageFormat/Parser/Type/AbstractSkeleton.php @@ -24,6 +24,8 @@ abstract class AbstractSkeleton implements SkeletonInterface { + use DeepCloner; + public SkeletonType $type; public Location $location; } diff --git a/src/Icu/MessageFormat/Parser/Type/DeepCloner.php b/src/Icu/MessageFormat/Parser/Type/DeepCloner.php new file mode 100644 index 0000000..7d03668 --- /dev/null +++ b/src/Icu/MessageFormat/Parser/Type/DeepCloner.php @@ -0,0 +1,84 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +declare(strict_types=1); + +namespace FormatPHP\Icu\MessageFormat\Parser\Type; + +use ReflectionObject; + +use function is_array; +use function is_object; + +trait DeepCloner +{ + public function __clone() + { + $this->cloneMyProperties(); + } + + private function cloneMyProperties(): void + { + $reflection = new ReflectionObject($this); + + foreach ($reflection->getProperties() as $reflectionProperty) { + /** @var mixed $propertyValue */ + $propertyValue = $reflectionProperty->getValue($this); + $reflectionProperty->setValue($this, $this->cloneValue($propertyValue)); + } + } + + /** + * @param mixed $value + * + * @return mixed The cloned value + */ + private function cloneValue($value) + { + if (is_array($value)) { + return $this->cloneArray($value); + } + + if (is_object($value)) { + return clone $value; + } + + return $value; + } + + /** + * @param array $value + * + * @return mixed[] + * + * @psalm-suppress MixedAssignment + */ + private function cloneArray(array $value): array + { + /** @var mixed[] $clone */ + $clone = []; + + foreach ($value as $k => $v) { + $clone[$k] = $this->cloneValue($v); + } + + return $clone; + } +} diff --git a/src/Icu/MessageFormat/Parser/Type/ElementCollection.php b/src/Icu/MessageFormat/Parser/Type/ElementCollection.php index adc51c9..210b3ba 100644 --- a/src/Icu/MessageFormat/Parser/Type/ElementCollection.php +++ b/src/Icu/MessageFormat/Parser/Type/ElementCollection.php @@ -65,4 +65,15 @@ public function jsonSerialize() { return $this->toArray(); } + + public function __clone() + { + $items = []; + + foreach ($this->data as $datum) { + $items[] = clone $datum; + } + + $this->data = $items; + } } diff --git a/src/Icu/MessageFormat/Parser/Type/Location.php b/src/Icu/MessageFormat/Parser/Type/Location.php index a54ce1d..7aee906 100644 --- a/src/Icu/MessageFormat/Parser/Type/Location.php +++ b/src/Icu/MessageFormat/Parser/Type/Location.php @@ -24,6 +24,8 @@ class Location { + use DeepCloner; + public LocationDetails $start; public LocationDetails $end; diff --git a/src/Icu/MessageFormat/Parser/Type/NumberSkeletonTokenCollection.php b/src/Icu/MessageFormat/Parser/Type/NumberSkeletonTokenCollection.php index a234067..16931aa 100644 --- a/src/Icu/MessageFormat/Parser/Type/NumberSkeletonTokenCollection.php +++ b/src/Icu/MessageFormat/Parser/Type/NumberSkeletonTokenCollection.php @@ -48,4 +48,15 @@ public function jsonSerialize() { return $this->toArray(); } + + public function __clone() + { + $items = []; + + foreach ($this->data as $datum) { + $items[] = clone $datum; + } + + $this->data = $items; + } } diff --git a/src/Icu/MessageFormat/Parser/Type/PluralOrSelectOption.php b/src/Icu/MessageFormat/Parser/Type/PluralOrSelectOption.php index 37c3e3b..ec55435 100644 --- a/src/Icu/MessageFormat/Parser/Type/PluralOrSelectOption.php +++ b/src/Icu/MessageFormat/Parser/Type/PluralOrSelectOption.php @@ -24,6 +24,8 @@ class PluralOrSelectOption { + use DeepCloner; + public ElementCollection $value; public Location $location; diff --git a/src/Icu/MessageFormat/Parser/Type/PoundElement.php b/src/Icu/MessageFormat/Parser/Type/PoundElement.php index 23a537f..f076785 100644 --- a/src/Icu/MessageFormat/Parser/Type/PoundElement.php +++ b/src/Icu/MessageFormat/Parser/Type/PoundElement.php @@ -24,8 +24,10 @@ final class PoundElement implements ElementInterface { + use DeepCloner; + public ElementType $type; - public ?Location $location = null; + public Location $location; public function __construct(Location $location) { diff --git a/src/Icu/MessageFormat/Printer.php b/src/Icu/MessageFormat/Printer.php new file mode 100644 index 0000000..675b222 --- /dev/null +++ b/src/Icu/MessageFormat/Printer.php @@ -0,0 +1,212 @@ + + * @license https://opensource.org/licenses/MIT MIT License + */ + +declare(strict_types=1); + +namespace FormatPHP\Icu\MessageFormat; + +use FormatPHP\Icu\MessageFormat\Parser\Type\ArgumentElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\DateElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\DateTimeSkeleton; +use FormatPHP\Icu\MessageFormat\Parser\Type\ElementCollection; +use FormatPHP\Icu\MessageFormat\Parser\Type\ElementInterface; +use FormatPHP\Icu\MessageFormat\Parser\Type\LiteralElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\NumberElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\NumberSkeleton; +use FormatPHP\Icu\MessageFormat\Parser\Type\NumberSkeletonToken; +use FormatPHP\Icu\MessageFormat\Parser\Type\PluralElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\PoundElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\SelectElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\SkeletonInterface; +use FormatPHP\Icu\MessageFormat\Parser\Type\TagElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\TimeElement; + +use function array_filter; +use function array_keys; +use function array_map; +use function assert; +use function count; +use function implode; +use function is_string; +use function preg_replace; +use function str_replace; +use function strtolower; + +/** + * Prints an AST representation of ICU message format as a string + * + * @internal + */ +class Printer +{ + /** + * Returns the string form of the ICU message format for the given AST + */ + public function printAst(ElementCollection $ast): string + { + return $this->doPrintAst($ast, false); + } + + private function doPrintAst(ElementCollection $ast, bool $isInPlural): string + { + $printedNodes = array_map(function (ElementInterface $element) use ($isInPlural): string { + if ($element instanceof ArgumentElement) { + return $this->printArgumentElement($element); + } + + if ( + $element instanceof DateElement + || $element instanceof TimeElement + || $element instanceof NumberElement + ) { + return $this->printSimpleFormatElement($element); + } + + if ($element instanceof PluralElement) { + return $this->printPluralElement($element); + } + + if ($element instanceof SelectElement) { + return $this->printSelectElement($element); + } + + if ($element instanceof PoundElement) { + return '#'; + } + + if ($element instanceof TagElement) { + return $this->printTagElement($element); + } + + assert($element instanceof LiteralElement); + + return $this->printLiteralElement($element, $isInPlural); + }, $ast->toArray()); + + return implode('', $printedNodes); + } + + private function printEscapedMessage(string $message): string + { + return (string) preg_replace('/([{}](?:.*[{}])?)/su', '\'$1\'', $message); + } + + private function printTagElement(TagElement $element): string + { + return "<$element->value>" . $this->printAst($element->children) . "value>"; + } + + private function printLiteralElement(LiteralElement $element, bool $isInPlural): string + { + $escaped = $this->printEscapedMessage($element->value); + + return $isInPlural ? str_replace('#', "'#'", $escaped) : $escaped; + } + + private function printArgumentElement(ArgumentElement $element): string + { + return '{' . $element->value . '}'; + } + + /** + * @param DateElement | TimeElement | NumberElement $element + */ + private function printSimpleFormatElement($element): string + { + $style = ''; + if ($element->style !== null) { + $style = ', ' . $this->printArgumentStyle($element->style); + } + + return '{' + . $element->value + . ', ' + . strtolower($element->type->getKey()) + . $style + . '}'; + } + + /** + * @param string | SkeletonInterface $style + */ + private function printArgumentStyle($style): string + { + if (is_string($style)) { + return $this->printEscapedMessage($style); + } + + if ($style instanceof DateTimeSkeleton) { + return '::' . $this->printDateTimeSkeleton($style); + } + + assert($style instanceof NumberSkeleton); + + return '::' . implode(' ', array_map([$this, 'printNumberSkeletonToken'], $style->tokens->toArray())); + } + + private function printDateTimeSkeleton(DateTimeSkeleton $style): string + { + return $style->pattern; + } + + private function printNumberSkeletonToken(NumberSkeletonToken $token): string + { + $stem = $token->stem; + $options = $token->options; + + return count($options) === 0 + ? $stem + : $stem . implode('', array_map(fn (string $option): string => "/$option", $options)); + } + + private function printSelectElement(SelectElement $element): string + { + $msg = [ + $element->value, + 'select', + implode( + ' ', + array_map( + fn (string $id): string => "$id{" . $this->doPrintAst($element->options[$id]->value, false) . '}', + array_keys($element->options), + ), + ), + ]; + + return '{' . implode(', ', $msg) . '}'; + } + + private function printPluralElement(PluralElement $element): string + { + $msg = [ + $element->value, + $element->pluralType === 'cardinal' ? 'plural' : 'selectordinal', + implode(' ', array_filter([ + $element->offset ? "offset:$element->offset" : '', + ...array_map( + fn (string $id): string => "$id{" . $this->doPrintAst($element->options[$id]->value, true) . '}', + array_keys($element->options), + ), + ])), + ]; + + return '{' . implode(', ', $msg) . '}'; + } +} diff --git a/tests/DescriptorTest.php b/tests/DescriptorTest.php index 7de1a8e..a59e253 100644 --- a/tests/DescriptorTest.php +++ b/tests/DescriptorTest.php @@ -79,4 +79,12 @@ public function testSetId(): void $this->assertSame('aDescriptorId', $descriptor->getId()); } + + public function testSetDefaultMessage(): void + { + $descriptor = new Descriptor(); + $descriptor->setDefaultMessage('a default message'); + + $this->assertSame('a default message', $descriptor->getDefaultMessage()); + } } diff --git a/tests/Extractor/MessageExtractorTest.php b/tests/Extractor/MessageExtractorTest.php index 75e3d56..3b0e80b 100644 --- a/tests/Extractor/MessageExtractorTest.php +++ b/tests/Extractor/MessageExtractorTest.php @@ -684,4 +684,61 @@ public function testProcessThrowsExceptionWithCustomParserNotACallable(): void __DIR__ . '/Parser/Descriptor/fixtures/*.template', ]); } + + public function testProcessFlatten(): void + { + $logger = new NullLogger(); + $options = new MessageExtractorOptions(); + $options->functionNames = ['formatMessage', 'translate']; + $options->flatten = true; + + $extractor = new MessageExtractor( + $options, + $logger, + new Globber(new FileSystemHelper()), + new FileSystemHelper(), + new FormatHelper(new FileSystemHelper()), + ); + + ob_start(); + $extractor->process([__DIR__ . '/Parser/Descriptor/fixtures/*.ph*']); + $output = ob_get_contents(); + ob_end_clean(); + + $messages = json_decode((string) $output, true); + + $this->assertSame( + [ + 'aTestId' => [ + 'defaultMessage' => 'This is a default message', + 'description' => 'A simple description of a fixture for testing purposes.', + ], + 'OpKKos' => [ + 'defaultMessage' => 'Hello!', + ], + 'photos.count' => [ + 'defaultMessage' => '{numPhotos, plural, =0{You have no photos.} ' + . '=1{You have one photo.} other{You have # photos.}}', + 'description' => 'A description with multiple lines and extra whitespace.', + ], + 'welcome' => [ + 'defaultMessage' => 'Welcome!', + ], + 'goodbye' => [ + 'defaultMessage' => 'Goodbye!', + ], + 'Soex4s' => [ + 'defaultMessage' => 'This is a default message', + 'description' => 'A simple description of a fixture for testing purposes.', + ], + 'xgMWoP' => [ + 'defaultMessage' => 'This is a default message', + ], + 'Q+U0TW' => [ + 'defaultMessage' => 'Welcome!', + ], + ], + $messages, + ); + } } diff --git a/tests/Icu/MessageFormat/ManipulatorTest.php b/tests/Icu/MessageFormat/ManipulatorTest.php new file mode 100644 index 0000000..39e2deb --- /dev/null +++ b/tests/Icu/MessageFormat/ManipulatorTest.php @@ -0,0 +1,69 @@ +parse(); + + assert($result->val !== null); + + $manipulator = new Manipulator(); + $hoistedAst = $manipulator->hoistSelectors($result->val); + + $printer = new Printer(); + + $this->assertMatchesTextSnapshot($printer->printAst($hoistedAst)); + } + + /** + * @return array + */ + public function messageProvider(): array + { + return [ + 'should hoist 1 plural' => [ + 'message' => 'I have {count, plural, one{a dog} other{many dogs}}', + ], + 'hoist some random case 1' => [ + 'message' => '{p1, plural, one{one {foo, select, bar{two} baz{three} other{other}}} other{other}}', + ], + 'should hoist plural & select and tag' => [ + 'message' => <<<'EOD' + I have {count, plural, + one{a { + gender, select, + male{male} + female{female} + other{male} + } dog + } + other{many dogs}} and {count, plural, + one{a { + gender, select, + male{male} + female{female} + other{male} + } cat + } + other{many cats}} + EOD, + ], + ]; + } +} diff --git a/tests/Icu/MessageFormat/Parser/Type/ArgumentElementTest.php b/tests/Icu/MessageFormat/Parser/Type/ArgumentElementTest.php index 6977970..03c84c1 100644 --- a/tests/Icu/MessageFormat/Parser/Type/ArgumentElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/ArgumentElementTest.php @@ -12,7 +12,7 @@ class ArgumentElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -21,5 +21,21 @@ public function testType(): void $element = new ArgumentElement('argument value', $location); $this->assertEquals(ElementType::Argument(), $element->type); + $this->assertSame('argument value', $element->value); + $this->assertSame($location, $element->location); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $element = new ArgumentElement('argument value', $location); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/DateElementTest.php b/tests/Icu/MessageFormat/Parser/Type/DateElementTest.php index e1ed0d3..e073670 100644 --- a/tests/Icu/MessageFormat/Parser/Type/DateElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/DateElementTest.php @@ -5,6 +5,7 @@ namespace FormatPHP\Test\Icu\MessageFormat\Parser\Type; use FormatPHP\Icu\MessageFormat\Parser\Type\DateElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\DateTimeFormatOptions; use FormatPHP\Icu\MessageFormat\Parser\Type\DateTimeSkeleton; use FormatPHP\Icu\MessageFormat\Parser\Type\ElementType; use FormatPHP\Icu\MessageFormat\Parser\Type\Location; @@ -13,19 +14,45 @@ class DateElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); $location = new Location($start, $end); - $skeleton = new DateTimeSkeleton('date pattern', $location); + $parsedOptions = new DateTimeFormatOptions(); + $skeleton = new DateTimeSkeleton('date pattern', $location, $parsedOptions); $element = new DateElement('date element value', $location, $skeleton); $this->assertEquals(ElementType::Date(), $element->type); $this->assertSame('date element value', $element->value); $this->assertSame($location, $element->location); + $this->assertSame($start, $element->location->start); + $this->assertSame($end, $element->location->end); $this->assertSame($skeleton, $element->style); + $this->assertSame($parsedOptions, $element->style->parsedOptions); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $parsedOptions = new DateTimeFormatOptions(); + $skeleton = new DateTimeSkeleton('date pattern', $location, $parsedOptions); + + $element = new DateElement('date element value', $location, $skeleton); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); + $this->assertNotSame($skeleton, $clone->style); + $this->assertNotNull($clone->style); + $this->assertInstanceOf(DateTimeSkeleton::class, $clone->style); + $this->assertNotSame($location, $clone->style->location); + $this->assertNotSame($parsedOptions, $clone->style->parsedOptions); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/DateTimeSkeletonTest.php b/tests/Icu/MessageFormat/Parser/Type/DateTimeSkeletonTest.php index 939884c..ba4ae13 100644 --- a/tests/Icu/MessageFormat/Parser/Type/DateTimeSkeletonTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/DateTimeSkeletonTest.php @@ -13,7 +13,7 @@ class DateTimeSkeletonTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -28,4 +28,21 @@ public function testType(): void $this->assertSame($location, $skeleton->location); $this->assertSame($parsedOptions, $skeleton->parsedOptions); } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $parsedOptions = new DateTimeFormatOptions(); + + $skeleton = new DateTimeSkeleton('date pattern', $location, $parsedOptions); + $clone = clone $skeleton; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); + $this->assertNotSame($parsedOptions, $clone->parsedOptions); + } } diff --git a/tests/Icu/MessageFormat/Parser/Type/LiteralElementTest.php b/tests/Icu/MessageFormat/Parser/Type/LiteralElementTest.php index f89e8ff..4561603 100644 --- a/tests/Icu/MessageFormat/Parser/Type/LiteralElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/LiteralElementTest.php @@ -12,15 +12,30 @@ class LiteralElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { - $details1 = new LocationDetails(7, 4, 5); - $details2 = new LocationDetails(12, 4, 10); - $location = new Location($details1, $details2); + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + $element = new LiteralElement('a literal element', $location); $this->assertEquals(ElementType::Literal(), $element->type); $this->assertSame('a literal element', $element->value); $this->assertSame($location, $element->location); } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $element = new LiteralElement('a literal element', $location); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); + } } diff --git a/tests/Icu/MessageFormat/Parser/Type/LocationTest.php b/tests/Icu/MessageFormat/Parser/Type/LocationTest.php index 07b8571..1631984 100644 --- a/tests/Icu/MessageFormat/Parser/Type/LocationTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/LocationTest.php @@ -19,4 +19,16 @@ public function testConstructor(): void $this->assertSame($details1, $location->start); $this->assertSame($details2, $location->end); } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $clone = clone $location; + + $this->assertNotSame($start, $clone->start); + $this->assertNotSame($end, $clone->end); + } } diff --git a/tests/Icu/MessageFormat/Parser/Type/NumberElementTest.php b/tests/Icu/MessageFormat/Parser/Type/NumberElementTest.php index 39015b8..449605c 100644 --- a/tests/Icu/MessageFormat/Parser/Type/NumberElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/NumberElementTest.php @@ -8,6 +8,7 @@ use FormatPHP\Icu\MessageFormat\Parser\Type\Location; use FormatPHP\Icu\MessageFormat\Parser\Type\LocationDetails; use FormatPHP\Icu\MessageFormat\Parser\Type\NumberElement; +use FormatPHP\Icu\MessageFormat\Parser\Type\NumberFormatOptions; use FormatPHP\Icu\MessageFormat\Parser\Type\NumberSkeleton; use FormatPHP\Icu\MessageFormat\Parser\Type\NumberSkeletonToken; use FormatPHP\Icu\MessageFormat\Parser\Type\NumberSkeletonTokenCollection; @@ -15,19 +16,48 @@ class NumberElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); $location = new Location($start, $end); - $tokens = new NumberSkeletonTokenCollection([new NumberSkeletonToken('token1')]); - $skeleton = new NumberSkeleton($tokens, $location); + $options = new NumberFormatOptions(); + $token = new NumberSkeletonToken('token1'); + $tokens = new NumberSkeletonTokenCollection([$token]); + $skeleton = new NumberSkeleton($tokens, $location, $options); $element = new NumberElement('number value', $location, $skeleton); $this->assertEquals(ElementType::Number(), $element->type); $this->assertSame($location, $element->location); $this->assertSame($skeleton, $element->style); + $this->assertSame($skeleton, $element->style); + $this->assertSame($options, $element->style->parsedOptions); + $this->assertCount(1, $element->style->tokens); + $this->assertSame($token, $element->style->tokens[0]); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $options = new NumberFormatOptions(); + $token = new NumberSkeletonToken('token1'); + $tokens = new NumberSkeletonTokenCollection([$token]); + $skeleton = new NumberSkeleton($tokens, $location, $options); + + $element = new NumberElement('number value', $location, $skeleton); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($skeleton, $clone->style); + $this->assertNotSame($skeleton, $clone->style); + $this->assertInstanceOf(NumberSkeleton::class, $clone->style); + $this->assertNotSame($options, $clone->style->parsedOptions); + $this->assertCount(1, $clone->style->tokens); + $this->assertNotSame($token, $clone->style->tokens[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/NumberSkeletonTest.php b/tests/Icu/MessageFormat/Parser/Type/NumberSkeletonTest.php index f40dfd7..6a9e8c4 100644 --- a/tests/Icu/MessageFormat/Parser/Type/NumberSkeletonTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/NumberSkeletonTest.php @@ -15,7 +15,7 @@ class NumberSkeletonTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -23,12 +23,36 @@ public function testType(): void $options = new NumberFormatOptions(); - $tokens = new NumberSkeletonTokenCollection([new NumberSkeletonToken('token1')]); + $token = new NumberSkeletonToken('token1'); + $tokens = new NumberSkeletonTokenCollection([$token]); $skeleton = new NumberSkeleton($tokens, $location, $options); $this->assertEquals(SkeletonType::Number(), $skeleton->type); $this->assertSame($tokens, $skeleton->tokens); $this->assertSame($location, $skeleton->location); $this->assertSame($options, $skeleton->parsedOptions); + $this->assertCount(1, $skeleton->tokens); + $this->assertSame($token, $skeleton->tokens[0]); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $options = new NumberFormatOptions(); + + $token = new NumberSkeletonToken('token1'); + $tokens = new NumberSkeletonTokenCollection([$token]); + $skeleton = new NumberSkeleton($tokens, $location, $options); + + $clone = clone $skeleton; + + $this->assertNotSame($tokens, $clone->tokens); + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($options, $clone->parsedOptions); + $this->assertCount(1, $clone->tokens); + $this->assertNotSame($token, $clone->tokens[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/PluralElementTest.php b/tests/Icu/MessageFormat/Parser/Type/PluralElementTest.php index d38eb1b..b5ac25c 100644 --- a/tests/Icu/MessageFormat/Parser/Type/PluralElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/PluralElementTest.php @@ -15,7 +15,7 @@ class PluralElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -23,9 +23,11 @@ public function testType(): void $formatElement = $this->mockery(ElementInterface::class); + $option = new PluralOrSelectOption(new ElementCollection([$formatElement]), $location); + $options = [ - 'one' => new PluralOrSelectOption(new ElementCollection([$formatElement]), $location), - 'two' => new PluralOrSelectOption(new ElementCollection([$formatElement]), $location), + 'one' => $option, + 'two' => $option, ]; $element = new PluralElement('plural value', $options, 56, 'cardinal', $location); @@ -36,5 +38,33 @@ public function testType(): void $this->assertSame(56, $element->offset); $this->assertSame('cardinal', $element->pluralType); $this->assertSame($location, $element->location); + $this->assertArrayHasKey('one', $element->options); + $this->assertSame($option, $element->options['one']); + $this->assertSame($formatElement, $element->options['one']->value[0]); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $formatElement = $this->mockery(ElementInterface::class); + + $option = new PluralOrSelectOption(new ElementCollection([$formatElement]), $location); + + $options = [ + 'one' => $option, + 'two' => $option, + ]; + + $element = new PluralElement('plural value', $options, 56, 'cardinal', $location); + $clone = clone $element; + + $this->assertNotSame($options, $clone->options); + $this->assertNotSame($location, $clone->location); + $this->assertArrayHasKey('one', $clone->options); + $this->assertNotSame($option, $clone->options['one']); + $this->assertNotSame($formatElement, $clone->options['one']->value[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/PluralOrSelectOptionTest.php b/tests/Icu/MessageFormat/Parser/Type/PluralOrSelectOptionTest.php index 8c7cc0f..3ab660b 100644 --- a/tests/Icu/MessageFormat/Parser/Type/PluralOrSelectOptionTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/PluralOrSelectOptionTest.php @@ -15,11 +15,8 @@ class PluralOrSelectOptionTest extends TestCase { public function testConstructor(): void { - $elements = new ElementCollection([ - $this->mockery(ElementInterface::class), - $this->mockery(ElementInterface::class), - $this->mockery(ElementInterface::class), - ]); + $element = $this->mockery(ElementInterface::class); + $elements = new ElementCollection([$element, $element]); $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -29,5 +26,23 @@ public function testConstructor(): void $this->assertSame($elements, $option->value); $this->assertSame($location, $option->location); + $this->assertSame($element, $option->value[0]); + } + + public function testDeepClone(): void + { + $element = $this->mockery(ElementInterface::class); + $elements = new ElementCollection([$element, $element]); + + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $option = new PluralOrSelectOption($elements, $location); + $clone = clone $option; + + $this->assertNotSame($elements, $clone->value); + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($element, $clone->value[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/PoundElementTest.php b/tests/Icu/MessageFormat/Parser/Type/PoundElementTest.php index 73fd365..73f5067 100644 --- a/tests/Icu/MessageFormat/Parser/Type/PoundElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/PoundElementTest.php @@ -12,7 +12,7 @@ class PoundElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); @@ -23,4 +23,18 @@ public function testType(): void $this->assertEquals(ElementType::Pound(), $element->type); $this->assertSame($location, $element->location); } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $element = new PoundElement($location); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); + } } diff --git a/tests/Icu/MessageFormat/Parser/Type/SelectElementTest.php b/tests/Icu/MessageFormat/Parser/Type/SelectElementTest.php index 0847a8f..b0a8e8a 100644 --- a/tests/Icu/MessageFormat/Parser/Type/SelectElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/SelectElementTest.php @@ -15,24 +15,42 @@ class SelectElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); $location = new Location($start, $end); $formatElement = $this->mockery(ElementInterface::class); - - $options = [ - 'one' => new PluralOrSelectOption(new ElementCollection([$formatElement]), $location), - 'two' => new PluralOrSelectOption(new ElementCollection([$formatElement]), $location), - ]; - + $option = new PluralOrSelectOption(new ElementCollection([$formatElement]), $location); + $options = ['one' => $option, 'two' => $option]; $element = new SelectElement('select value', $options, $location); $this->assertEquals(ElementType::Select(), $element->type); $this->assertSame('select value', $element->value); $this->assertSame($options, $element->options); $this->assertSame($location, $element->location); + $this->assertArrayHasKey('one', $element->options); + $this->assertSame($option, $element->options['one']); + $this->assertSame($formatElement, $element->options['one']->value[0]); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $formatElement = $this->mockery(ElementInterface::class); + $option = new PluralOrSelectOption(new ElementCollection([$formatElement]), $location); + $options = ['one' => $option, 'two' => $option]; + $element = new SelectElement('select value', $options, $location); + $clone = clone $element; + + $this->assertNotSame($options, $clone->options); + $this->assertNotSame($location, $clone->location); + $this->assertArrayHasKey('one', $clone->options); + $this->assertNotSame($option, $clone->options['one']); + $this->assertNotSame($formatElement, $clone->options['one']->value[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/TagElementTest.php b/tests/Icu/MessageFormat/Parser/Type/TagElementTest.php index 5281495..0781c75 100644 --- a/tests/Icu/MessageFormat/Parser/Type/TagElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/TagElementTest.php @@ -14,20 +14,40 @@ class TagElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); $location = new Location($start, $end); $formatElement = $this->mockery(ElementInterface::class); - $children = new ElementCollection([clone $formatElement, clone $formatElement]); + $children = new ElementCollection([$formatElement, $formatElement]); $element = new TagElement('tag name', $children, $location); $this->assertEquals(ElementType::Tag(), $element->type); $this->assertSame('tag name', $element->value); - $this->assertSame($children, $element->children); $this->assertSame($location, $element->location); + $this->assertSame($children, $element->children); + $this->assertCount(2, $element->children); + $this->assertSame($formatElement, $element->children[0]); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $formatElement = $this->mockery(ElementInterface::class); + $children = new ElementCollection([$formatElement, $formatElement]); + + $element = new TagElement('tag name', $children, $location); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($children, $clone->children); + $this->assertCount(2, $element->children); + $this->assertNotSame($formatElement, $clone->children[0]); } } diff --git a/tests/Icu/MessageFormat/Parser/Type/TimeElementTest.php b/tests/Icu/MessageFormat/Parser/Type/TimeElementTest.php index d1a2f8e..91e1018 100644 --- a/tests/Icu/MessageFormat/Parser/Type/TimeElementTest.php +++ b/tests/Icu/MessageFormat/Parser/Type/TimeElementTest.php @@ -4,6 +4,7 @@ namespace FormatPHP\Test\Icu\MessageFormat\Parser\Type; +use FormatPHP\Icu\MessageFormat\Parser\Type\DateTimeFormatOptions; use FormatPHP\Icu\MessageFormat\Parser\Type\DateTimeSkeleton; use FormatPHP\Icu\MessageFormat\Parser\Type\ElementType; use FormatPHP\Icu\MessageFormat\Parser\Type\Location; @@ -13,19 +14,45 @@ class TimeElementTest extends TestCase { - public function testType(): void + public function testConstructor(): void { $start = new LocationDetails(0, 1, 1); $end = new LocationDetails(2, 4, 6); $location = new Location($start, $end); - $skeleton = new DateTimeSkeleton('time pattern', $location); + $parsedOptions = new DateTimeFormatOptions(); + $skeleton = new DateTimeSkeleton('time pattern', $location, $parsedOptions); $element = new TimeElement('time element', $location, $skeleton); $this->assertEquals(ElementType::Time(), $element->type); $this->assertSame('time element', $element->value); $this->assertSame($location, $element->location); + $this->assertSame($start, $element->location->start); + $this->assertSame($end, $element->location->end); $this->assertSame($skeleton, $element->style); + $this->assertSame($parsedOptions, $element->style->parsedOptions); + } + + public function testDeepClone(): void + { + $start = new LocationDetails(0, 1, 1); + $end = new LocationDetails(2, 4, 6); + $location = new Location($start, $end); + + $parsedOptions = new DateTimeFormatOptions(); + $skeleton = new DateTimeSkeleton('time pattern', $location, $parsedOptions); + + $element = new TimeElement('time element', $location, $skeleton); + $clone = clone $element; + + $this->assertNotSame($location, $clone->location); + $this->assertNotSame($start, $clone->location->start); + $this->assertNotSame($end, $clone->location->end); + $this->assertNotSame($skeleton, $clone->style); + $this->assertNotNull($clone->style); + $this->assertInstanceOf(DateTimeSkeleton::class, $clone->style); + $this->assertNotSame($location, $clone->style->location); + $this->assertNotSame($parsedOptions, $clone->style->parsedOptions); } } diff --git a/tests/Icu/MessageFormat/PrinterTest.php b/tests/Icu/MessageFormat/PrinterTest.php new file mode 100644 index 0000000..00138aa --- /dev/null +++ b/tests/Icu/MessageFormat/PrinterTest.php @@ -0,0 +1,207 @@ +parse(); + + $printer = new Printer(); + + $this->assertNotNull($parsed->val); + $this->assertMatchesTextSnapshot($printer->printAst($parsed->val)); + } + + /** + * @return array + */ + public function messageProvider(): array + { + return [ + 'basic_argument_1' => ['message' => '{a}'], + 'basic_argument_2' => ['message' => "a {b} \nc"], + 'date_arg_skeleton_1' => ['message' => "{0, date, ::yyyy.MM.dd G 'at' HH:mm:ss vvvv}"], + 'date_arg_skeleton_2' => ['message' => "{0, date, ::EEE, MMM d, ''yy}"], + 'date_arg_skeleton_3' => ['message' => '{0, date, ::h:mm a}'], + 'escaped_pound_1' => [ + 'message' => "{numPhotos, plural, =0{no photos} =1{one photo} other{'#' photos}}", + ], + 'ignore_tag_number_arg_1' => [ + 'message' => 'I have {numCats, number} cats.', + 'options' => (function (): Options { + $options = new Options(); + $options->ignoreTag = true; + + return $options; + })(), + ], + 'ignore_tags_1' => [ + 'message' => '', + 'options' => (function (): Options { + $options = new Options(); + $options->ignoreTag = true; + + return $options; + })(), + ], + 'left_angle_bracket_1' => ['message' => 'I <3 cats.'], + 'less_than_sign_1' => [ + // See: https://github.com/formatjs/formatjs/issues/1845 + 'message' => '< {level, select, A {1} 4 {2} 3 {3} 2{6} 1{12}} hours', + ], + 'negative_offset_1' => [ + 'message' => '{c, plural, offset:-2 =-1 { {text} project} other { {text} projects}}', + ], + 'nested_1' => [ + 'message' => <<<'EOD' + + {gender_of_host, select, + female { + {num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to her party.} + =2 {{host} invites {guest} and one other person to her party.} + other {{host} invites {guest} and # other people to her party.}}} + male { + {num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to his party.} + =2 {{host} invites {guest} and one other person to his party.} + other {{host} invites {guest} and # other people to his party.}}} + other { + {num_guests, plural, offset:1 + =0 {{host} does not give a party.} + =1 {{host} invites {guest} to their party.} + =2 {{host} invites {guest} and one other person to their party.} + other {{host} invites {guest} and # other people to their party.}}}} + EOD, + ], + 'nested_tags_1' => ['message' => 'this is nested {placeholder}'], + 'not_escaped_pound_1' => ['message' => "'#'"], + 'not_quoted_string_2' => ['message' => "I don't know"], + 'not_self_closing_tag_1' => ['message' => '< test-tag />'], + 'number_arg_skeleton_2' => ['message' => '{0, number, :: currency/GBP}'], + 'number_arg_skeleton_3' => ['message' => '{0, number, ::currency/GBP compact-short}'], + 'number_arg_style_1' => ['message' => '{0, number, percent}'], + 'numeric_tag_1' => ['message' => 'foo'], + 'open_close_tag_1' => ['message' => ''], + 'open_close_tag_2' => ['message' => 'foo'], + 'open_close_tag_3' => ['message' => 'foo {0} bar'], + 'open_close_tag_with_args' => [ + 'message' => 'I have {numCats, number} some string {placeholder} cats.', + ], + 'open_close_tag_with_nested_arg' => [ + 'message' => <<<'EOD' + You have { + count, plural, + one {# apple} + other {# apples} + }. + EOD, + ], + 'plural_arg_1' => [ + 'message' => <<<'EOD' + + Cart: {itemCount} {itemCount, plural, + one {item} + other {items} + } + EOD, + ], + 'plural_arg_2' => [ + 'message' => <<<'EOD' + + You have {itemCount, plural, + =0 {no items} + one {1 item} + other {{itemCount} items} + }. + EOD, + ], + 'plural_arg_with_escaped_nested_message' => [ + 'message' => <<<'EOD' + + {itemCount, plural, + one {item'}'} + other {items'}'} + } + EOD, + ], + 'plural_arg_with_offset_1' => [ + 'message' => <<<'EOD' + You have {itemCount, plural, offset: 2 + =0 {no items} + one {1 item} + other {{itemCount} items} + }. + EOD, + ], + 'quoted_pound_sign_1' => [ + 'message' => "You {count, plural, one {worked for '#' hour} other {worked for '#' hours}} today.", + ], + 'quoted_string_3' => ['message' => "aaa'{'"], + 'quoted_string_4' => ['message' => "aaa'}'"], + 'select_arg_1' => [ + 'message' => <<<'EOD' + + {gender, select, + male {He} + female {She} + other {They} + } will respond shortly. + EOD, + ], + 'select_arg_with_nested_arguments' => [ + 'message' => <<<'EOD' + + {taxableArea, select, + yes {An additional {taxRate, number, percent} tax will be collected.} + other {No taxes apply.} + } + EOD, + ], + 'selectordinal_1' => [ + 'message' => '{floor, selectordinal, =0{ground} one{#st} two{#nd} few{#rd} other{#th}} floor', + ], + 'self_closing_tag_1' => ['message' => ''], + 'self_closing_tag_2' => ['message' => ''], + 'simple_argument_1' => ['message' => 'My name is {0}'], + 'simple_argument_2' => ['message' => 'My name is { name }'], + 'simple_date_and_time_arg_1' => [ + 'message' => 'Your meeting is scheduled for the {dateVal, date} at {timeVal, time}', + ], + 'simple_number_arg_1' => ['message' => 'I have {numCats, number} cats.'], + 'time_arg_skeleton_1' => ['message' => '{0, time, ::h:mm a}'], + 'treat_unicode_nbsp_as_whitespace' => [ + // phpcs:ignore SlevomatCodingStandard.PHP.RequireNowdoc.RequiredNowdoc + 'message' => << ['message' => 'a'], + 'trivial_2' => ['message' => '中文'], + 'unescaped_string_literal_1' => ['message' => '}'], + 'uppercase_tag_1' => ['message' => 'this is nested '], + ]; + } +} diff --git a/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set hoist some random case 1__1.txt b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set hoist some random case 1__1.txt new file mode 100644 index 0000000..1c43dc6 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set hoist some random case 1__1.txt @@ -0,0 +1 @@ +{p1, plural, one{{foo, select, bar{one two} baz{one three} other{one other}}} other{other}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist 1 plural__1.txt b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist 1 plural__1.txt new file mode 100644 index 0000000..01d6967 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist 1 plural__1.txt @@ -0,0 +1 @@ +{count, plural, one{I have a dog} other{I have many dogs}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist plural select and tag__1.txt b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist plural select and tag__1.txt new file mode 100644 index 0000000..83e6f1e --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/ManipulatorTest__testHoistSelectors with data set should hoist plural select and tag__1.txt @@ -0,0 +1,25 @@ +{count, plural, one{{gender, select, male{{count, plural, one{{gender, select, male{I have a male dog + and a male cat + } female{I have a male dog + and a female cat + } other{I have a male dog + and a male cat + }}} other{I have a male dog + and many cats}}} female{{count, plural, one{{gender, select, male{I have a female dog + and a male cat + } female{I have a female dog + and a female cat + } other{I have a female dog + and a male cat + }}} other{I have a female dog + and many cats}}} other{{count, plural, one{{gender, select, male{I have a male dog + and a male cat + } female{I have a male dog + and a female cat + } other{I have a male dog + and a male cat + }}} other{I have a male dog + and many cats}}}}} other{{count, plural, one{{gender, select, male{I have many dogs and a male cat + } female{I have many dogs and a female cat + } other{I have many dogs and a male cat + }}} other{I have many dogs and many cats}}}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_1__1.txt new file mode 100644 index 0000000..c772e5b --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_1__1.txt @@ -0,0 +1 @@ +{a} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_2__1.txt new file mode 100644 index 0000000..b9b4a9e --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set basic_argument_2__1.txt @@ -0,0 +1,2 @@ +a {b} +c \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_1__1.txt new file mode 100644 index 0000000..05c9632 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_1__1.txt @@ -0,0 +1 @@ +{0, date, ::yyyy.MM.dd G 'at' HH:mm:ss vvvv} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_2__1.txt new file mode 100644 index 0000000..11c79b5 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_2__1.txt @@ -0,0 +1 @@ +{0, date, ::EEE, MMM d, ''yy} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_3__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_3__1.txt new file mode 100644 index 0000000..fc8c1f1 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set date_arg_skeleton_3__1.txt @@ -0,0 +1 @@ +{0, date, ::h:mm a} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set escaped_pound_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set escaped_pound_1__1.txt new file mode 100644 index 0000000..bcf4c72 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set escaped_pound_1__1.txt @@ -0,0 +1 @@ +{numPhotos, plural, =0{no photos} =1{one photo} other{'#' photos}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tag_number_arg_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tag_number_arg_1__1.txt new file mode 100644 index 0000000..8bc235c --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tag_number_arg_1__1.txt @@ -0,0 +1 @@ +I have {numCats, number} cats. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tags_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tags_1__1.txt new file mode 100644 index 0000000..4ce649b --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set ignore_tags_1__1.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set left_angle_bracket_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set left_angle_bracket_1__1.txt new file mode 100644 index 0000000..2fddfb6 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set left_angle_bracket_1__1.txt @@ -0,0 +1 @@ +I <3 cats. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set less_than_sign_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set less_than_sign_1__1.txt new file mode 100644 index 0000000..8252615 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set less_than_sign_1__1.txt @@ -0,0 +1 @@ +< {level, select, A{1} 4{2} 3{3} 2{6} 1{12}} hours \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set negative_offset_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set negative_offset_1__1.txt new file mode 100644 index 0000000..8e6f023 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set negative_offset_1__1.txt @@ -0,0 +1 @@ +{c, plural, offset:-2 =-1{ {text} project} other{ {text} projects}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_1__1.txt new file mode 100644 index 0000000..87c38ff --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_1__1.txt @@ -0,0 +1,5 @@ + + {gender_of_host, select, female{ + {num_guests, plural, offset:1 =0{{host} does not give a party.} =1{{host} invites {guest} to her party.} =2{{host} invites {guest} and one other person to her party.} other{{host} invites {guest} and # other people to her party.}}} male{ + {num_guests, plural, offset:1 =0{{host} does not give a party.} =1{{host} invites {guest} to his party.} =2{{host} invites {guest} and one other person to his party.} other{{host} invites {guest} and # other people to his party.}}} other{ + {num_guests, plural, offset:1 =0{{host} does not give a party.} =1{{host} invites {guest} to their party.} =2{{host} invites {guest} and one other person to their party.} other{{host} invites {guest} and # other people to their party.}}}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_tags_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_tags_1__1.txt new file mode 100644 index 0000000..069c8ec --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set nested_tags_1__1.txt @@ -0,0 +1 @@ +this is nested {placeholder} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_escaped_pound_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_escaped_pound_1__1.txt new file mode 100644 index 0000000..187c0d5 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_escaped_pound_1__1.txt @@ -0,0 +1 @@ +'#' \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_quoted_string_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_quoted_string_2__1.txt new file mode 100644 index 0000000..bcbd8ce --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_quoted_string_2__1.txt @@ -0,0 +1 @@ +I don't know \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_self_closing_tag_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_self_closing_tag_1__1.txt new file mode 100644 index 0000000..951aa3e --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set not_self_closing_tag_1__1.txt @@ -0,0 +1 @@ +< test-tag /> \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_2__1.txt new file mode 100644 index 0000000..0d062cb --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_2__1.txt @@ -0,0 +1 @@ +{0, number, ::currency/GBP} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_3__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_3__1.txt new file mode 100644 index 0000000..ccced65 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_skeleton_3__1.txt @@ -0,0 +1 @@ +{0, number, ::currency/GBP compact-short} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_style_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_style_1__1.txt new file mode 100644 index 0000000..a611298 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set number_arg_style_1__1.txt @@ -0,0 +1 @@ +{0, number, percent} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set numeric_tag_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set numeric_tag_1__1.txt new file mode 100644 index 0000000..87dd140 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set numeric_tag_1__1.txt @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_1__1.txt new file mode 100644 index 0000000..4ce649b --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_1__1.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_2__1.txt new file mode 100644 index 0000000..ffe2cfc --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_2__1.txt @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_3__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_3__1.txt new file mode 100644 index 0000000..ea337b6 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_3__1.txt @@ -0,0 +1 @@ +foo {0} bar \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_args__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_args__1.txt new file mode 100644 index 0000000..af5c431 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_args__1.txt @@ -0,0 +1 @@ +I have {numCats, number} some string {placeholder} cats. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_nested_arg__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_nested_arg__1.txt new file mode 100644 index 0000000..f2ef637 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set open_close_tag_with_nested_arg__1.txt @@ -0,0 +1 @@ +You have {count, plural, one{# apple} other{# apples}}. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_1__1.txt new file mode 100644 index 0000000..4509c0b --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_1__1.txt @@ -0,0 +1,2 @@ + + Cart: {itemCount} {itemCount, plural, one{item} other{items}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_2__1.txt new file mode 100644 index 0000000..d8dbb0a --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_2__1.txt @@ -0,0 +1,2 @@ + + You have {itemCount, plural, =0{no items} one{1 item} other{{itemCount} items}}. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_escaped_nested_message__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_escaped_nested_message__1.txt new file mode 100644 index 0000000..a4275ec --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_escaped_nested_message__1.txt @@ -0,0 +1,2 @@ + + {itemCount, plural, one{item'}'} other{items'}'}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_offset_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_offset_1__1.txt new file mode 100644 index 0000000..d4d7a26 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set plural_arg_with_offset_1__1.txt @@ -0,0 +1 @@ +You have {itemCount, plural, offset:2 =0{no items} one{1 item} other{{itemCount} items}}. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_pound_sign_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_pound_sign_1__1.txt new file mode 100644 index 0000000..fc7bd79 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_pound_sign_1__1.txt @@ -0,0 +1 @@ +You {count, plural, one{worked for '#' hour} other{worked for '#' hours}} today. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_3__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_3__1.txt new file mode 100644 index 0000000..93e41df --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_3__1.txt @@ -0,0 +1 @@ +aaa'{' \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_4__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_4__1.txt new file mode 100644 index 0000000..5f993ab --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set quoted_string_4__1.txt @@ -0,0 +1 @@ +aaa'}' \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_1__1.txt new file mode 100644 index 0000000..4c243b2 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_1__1.txt @@ -0,0 +1,2 @@ + + {gender, select, male{He} female{She} other{They}} will respond shortly. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_with_nested_arguments__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_with_nested_arguments__1.txt new file mode 100644 index 0000000..15015d8 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set select_arg_with_nested_arguments__1.txt @@ -0,0 +1,2 @@ + + {taxableArea, select, yes{An additional {taxRate, number, percent} tax will be collected.} other{No taxes apply.}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set selectordinal_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set selectordinal_1__1.txt new file mode 100644 index 0000000..09ad349 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set selectordinal_1__1.txt @@ -0,0 +1 @@ +{floor, selectordinal, =0{ground} one{#st} two{#nd} few{#rd} other{#th}} floor \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_1__1.txt new file mode 100644 index 0000000..22a0d3f --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_1__1.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_2__1.txt new file mode 100644 index 0000000..22a0d3f --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set self_closing_tag_2__1.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_1__1.txt new file mode 100644 index 0000000..efab1a4 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_1__1.txt @@ -0,0 +1 @@ +My name is {0} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_2__1.txt new file mode 100644 index 0000000..3ec5059 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_argument_2__1.txt @@ -0,0 +1 @@ +My name is {name} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_date_and_time_arg_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_date_and_time_arg_1__1.txt new file mode 100644 index 0000000..8dcf758 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_date_and_time_arg_1__1.txt @@ -0,0 +1 @@ +Your meeting is scheduled for the {dateVal, date} at {timeVal, time} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_number_arg_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_number_arg_1__1.txt new file mode 100644 index 0000000..6d0154e --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set simple_number_arg_1__1.txt @@ -0,0 +1 @@ +I have {numCats, number} cats. \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set time_arg_skeleton_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set time_arg_skeleton_1__1.txt new file mode 100644 index 0000000..530cd32 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set time_arg_skeleton_1__1.txt @@ -0,0 +1 @@ +{0, time, ::h:mm a} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set treat_unicode_nbsp_as_whitespace__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set treat_unicode_nbsp_as_whitespace__1.txt new file mode 100644 index 0000000..fdf98b1 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set treat_unicode_nbsp_as_whitespace__1.txt @@ -0,0 +1,5 @@ + + {gender, select, male{ + {He}} female{ + {She}} other{ + {They}}} \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_1__1.txt new file mode 100644 index 0000000..2e65efe --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_1__1.txt @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_2__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_2__1.txt new file mode 100644 index 0000000..efbb133 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set trivial_2__1.txt @@ -0,0 +1 @@ +中文 \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set unescaped_string_literal_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set unescaped_string_literal_1__1.txt new file mode 100644 index 0000000..468a90a --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set unescaped_string_literal_1__1.txt @@ -0,0 +1 @@ +'}' \ No newline at end of file diff --git a/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set uppercase_tag_1__1.txt b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set uppercase_tag_1__1.txt new file mode 100644 index 0000000..6203c77 --- /dev/null +++ b/tests/Icu/MessageFormat/__snapshots__/PrinterTest__testPrintAst with data set uppercase_tag_1__1.txt @@ -0,0 +1 @@ +this is nested \ No newline at end of file