xref: /aosp_15_r20/external/icu/icu4c/source/test/intltest/messageformat2test.cpp (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2024 and later: Unicode, Inc. and others.
2 
3 #include "unicode/utypes.h"
4 
5 #if !UCONFIG_NO_FORMATTING
6 
7 #if !UCONFIG_NO_MF2
8 
9 #include "unicode/calendar.h"
10 #include "messageformat2test.h"
11 
12 using namespace icu::message2;
13 
14 /*
15   TODO: Tests need to be unified in a single format that
16   both ICU4C and ICU4J can use, rather than being embedded in code.
17 
18   Tests are included in their current state to give a sense of
19   how much test coverage has been achieved. Most of the testing is
20   of the parser/serializer; the formatter needs to be tested more
21   thoroughly.
22 */
23 
24 /*
25 Tests reflect the syntax specified in
26 
27   https://github.com/unicode-org/message-format-wg/commits/main/spec/message.abnf
28 
29 as of the following commit from 2023-05-09:
30   https://github.com/unicode-org/message-format-wg/commit/194f6efcec5bf396df36a19bd6fa78d1fa2e0867
31 
32 */
33 
34 static const int32_t numValidTestCases = 55;
35 TestResult validTestCases[] = {
36     {"hello {|4.2| :number}", "hello 4.2"},
37     {"hello {|4.2| :number minimumFractionDigits=2}", "hello 4.20"},
38     {"hello {|4.2| :number minimumFractionDigits = 2}", "hello 4.20"},
39     {"hello {|4.2| :number minimumFractionDigits= 2}", "hello 4.20"},
40     {"hello {|4.2| :number minimumFractionDigits =2}", "hello 4.20"},
41     {"hello {|4.2| :number minimumFractionDigits=2  }", "hello 4.20"},
42     {"hello {|4.2| :number minimumFractionDigits=2 bar=3}", "hello 4.20"},
43     {"hello {|4.2| :number minimumFractionDigits=2 bar=3  }", "hello 4.20"},
44     {"hello {|4.2| :number minimumFractionDigits=|2|}", "hello 4.20"},
45     {"content -tag", "content -tag"},
46     {"", ""},
47     // tests for escape sequences in literals
48     {"{|hel\\\\lo|}", "hel\\lo"},
49     {"{|hel\\|lo|}", "hel|lo"},
50     {"{|hel\\|\\\\lo|}", "hel|\\lo"},
51     // tests for text escape sequences
52     {"hel\\{lo", "hel{lo"},
53     {"hel\\}lo", "hel}lo"},
54     {"hel\\\\lo", "hel\\lo"},
55     {"hel\\{\\\\lo", "hel{\\lo"},
56     {"hel\\{\\}lo", "hel{}lo"},
57     // tests for newlines in literals and text
58     {"hello {|wo\nrld|}", "hello wo\nrld"},
59     {"hello wo\nrld", "hello wo\nrld"},
60     // Markup is ignored when formatting to string
61     {"{#tag/} content", " content"},
62     {"{#tag} content", " content"},
63     {"{#tag/} {|content|}", " content"},
64     {"{#tag} {|content|}", " content"},
65     {"{|content|} {#tag/}", "content "},
66     {"{|content|} {#tag}", "content "},
67     {"{/tag} {|content|}", " content"},
68     {"{|content|} {/tag}", "content "},
69     {"{#tag} {|content|} {/tag}", " content "},
70     {"{/tag} {|content|} {#tag}", " content "},
71     {"{#tag/} {|content|} {#tag}", " content "},
72     {"{#tag/} {|content|} {/tag}", " content "},
73     {"{#tag foo=bar/} {|content|}", " content"},
74     {"{#tag foo=bar} {|content|}", " content"},
75     {"{/tag foo=bar} {|content|}", " content"},
76     {"{#tag foo=bar} {|content|} {/tag foo=bar}", " content "},
77     {"{/tag foo=bar} {|content|} {#tag foo=bar}", " content "},
78     {"{#tag foo=bar /} {|content|} {#tag foo=bar}", " content "},
79     {"{#tag foo=bar/} {|content|} {/tag foo=bar}", " content "},
80     // Attributes are ignored
81     {"The value is {horse @horse}.", "The value is horse."},
82     {"hello {|4.2| @number}", "hello 4.2"},
83     {"The value is {horse @horse=cool}.", "The value is horse."},
84     {"hello {|4.2| @number=5}", "hello 4.2"},
85     // Number literals
86     {"{-1}", "-1"},
87     {"{0}", "0"},
88     {"{0.0123}", "0.0123"},
89     {"{1.234e5}", "1.234e5"},
90     {"{1.234E5}", "1.234E5"},
91     {"{1.234E+5}", "1.234E+5"},
92     {"{1.234e-5}", "1.234e-5"},
93     {"{42e5}", "42e5"},
94     {"{42e0}", "42e0"},
95     {"{42e000}", "42e000"},
96     {"{42e369}", "42e369"},
97 };
98 
99 
100 static const int32_t numResolutionErrors = 3;
101 TestResultError jsonTestCasesResolutionError[] = {
102     {".local $foo = {$bar} .match {$foo :number}  one {{one}}  * {{other}}", "other", U_MF_UNRESOLVED_VARIABLE_ERROR},
103     {".local $foo = {$bar} .match {$foo :number}  one {{one}}  * {{other}}", "other", U_MF_UNRESOLVED_VARIABLE_ERROR},
104     {".local $bar = {$none :number} .match {$foo :string}  one {{one}}  * {{{$bar}}}", "{$none}", U_MF_UNRESOLVED_VARIABLE_ERROR}
105 };
106 
107 static const int32_t numReservedErrors = 34;
108 UnicodeString reservedErrors[] = {
109     // tests for reserved syntax
110     "hello {|4.2| %number}",
111     "hello {|4.2| %n|um|ber}",
112     "{+42}",
113     // Private use -- n.b. this implementation doesn't support
114     // any private-use annotations, so it's treated like reserved
115     "hello {|4.2| &num|be|r}",
116     "hello {|4.2| ^num|be|r}",
117     "hello {|4.2| +num|be|r}",
118     "hello {|4.2| ?num|be||r|s}",
119     "hello {|foo| !number}",
120     "hello {|foo| *number}",
121     "hello {#number}",
122     "{<tag}",
123     ".local $bar = {$none ~plural} .match {$foo :string}  * {{{$bar}}}",
124     // tests for reserved syntax with escaped chars
125     "hello {|4.2| %num\\\\ber}",
126     "hello {|4.2| %num\\{be\\|r}",
127     "hello {|4.2| %num\\\\\\}ber}",
128     // tests for reserved syntax
129     "hello {|4.2| !}",
130     "hello {|4.2| %}",
131     "hello {|4.2| *}",
132     "hello {|4.2| ^abc|123||5|\\\\}",
133     "hello {|4.2| ^ abc|123||5|\\\\}",
134     "hello {|4.2| ^ abc|123||5|\\\\ \\|def |3.14||2|}",
135     // tests for reserved syntax with trailing whitespace
136     "hello {|4.2| ? }",
137     "hello {|4.2| %xyzz }",
138     "hello {|4.2| >xyzz   }",
139     "hello {$foo ~xyzz }",
140     "hello {$x   <xyzz   }",
141     "{>xyzz }",
142     "{  !xyzz   }",
143     "{~xyzz }",
144     "{ <xyzz   }",
145     // tests for reserved syntax with space-separated sequences
146     "hello {|4.2| !xy z z }",
147     "hello {|4.2| *num \\\\ b er}",
148     "hello {|4.2| %num \\\\ b |3.14| r    }",
149     "hello {|4.2|    +num xx \\\\ b |3.14| r  }",
150     "hello {$foo    +num x \\\\ abcde |3.14| r  }",
151     "hello {$foo    >num x \\\\ abcde |aaa||3.14||42| r  }",
152     "hello {$foo    >num x \\\\ abcde |aaa||3.14| |42| r  }",
153     0
154 };
155 
156 static const int32_t numMatches = 15;
157 UnicodeString matches[] = {
158     // multiple scrutinees, with or without whitespace
159     "match {$foo :string} {$bar :string} when one * {one} when * * {other}",
160     "match {$foo :string} {$bar :string}when one * {one} when * * {other}",
161     "match {$foo :string}{$bar :string} when one * {one} when * * {other}",
162     "match {$foo :string}{$bar :string}when one * {one} when * * {other}",
163     "match{$foo :string} {$bar :string} when one * {one} when * * {other}",
164     "match{$foo :string} {$bar :string}when one * {one} when * * {other}",
165     "match{$foo :string}{$bar :string} when one * {one} when * * {other}",
166     "match{$foo :string}{$bar :string}when one * {one} when * * {other}",
167     // multiple variants, with or without whitespace
168     "match {$foo :string} {$bar :string} when one * {one} when * * {other}",
169     "match {$foo :string} {$bar :string} when one * {one}when * * {other}",
170     "match {$foo :string} {$bar :string}when one * {one} when * * {other}",
171     "match {$foo :string} {$bar :string}when one * {one}when * * {other}",
172     // one or multiple keys, with or without whitespace before pattern
173     "match {$foo :string} {$bar :string} when one *{one} when * * {foo}",
174     "match {$foo :string} {$bar :string} when one * {one} when * * {foo}",
175     "match {$foo :string} {$bar :string} when one *  {one} when * * {foo}",
176     0
177 };
178 
179 static const int32_t numSyntaxTests = 19;
180 // These patterns are tested to ensure they parse without a syntax error
181 UnicodeString syntaxTests[] = {
182     "hello {|foo| :number   }",
183     // zero, one or multiple options, with or without whitespace before '}'
184     "{:foo}",
185     "{:foo }",
186     "{:foo   }",
187     "{:foo k=v}",
188     "{:foo k=v   }",
189     "{:foo k1=v1   k2=v2}",
190     "{:foo k1=v1   k2=v2   }",
191     // literals or variables followed by space, with or without an annotation following
192     "{|3.14| }",
193     "{|3.14|    }",
194     "{|3.14|    :foo}",
195     "{|3.14|    :foo   }",
196     "{$bar }",
197     "{$bar    }",
198     "{$bar    :foo}",
199     "{$bar    :foo   }",
200     // Variable names can contain '-'
201     "{$bar-foo}",
202     // Not a syntax error (is a semantic error)
203     ".local $foo = {|hello|} .local $foo = {$foo} {{{$foo}}}",
204     // Unquoted literal -- should work
205     "good {placeholder}",
206     0
207 };
208 
209 void
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)210 TestMessageFormat2::runIndexedTest(int32_t index, UBool exec,
211                                   const char* &name, char* /*par*/) {
212     TESTCASE_AUTO_BEGIN;
213     TESTCASE_AUTO(testAPICustomFunctions);
214     TESTCASE_AUTO(messageFormat1Tests);
215     TESTCASE_AUTO(featureTests);
216     TESTCASE_AUTO(testCustomFunctions);
217     TESTCASE_AUTO(testBuiltInFunctions);
218     TESTCASE_AUTO(testDataModelErrors);
219     TESTCASE_AUTO(testResolutionErrors);
220     TESTCASE_AUTO(testAPI);
221     TESTCASE_AUTO(testAPISimple);
222     TESTCASE_AUTO(testDataModelAPI);
223     TESTCASE_AUTO(testVariousPatterns);
224     TESTCASE_AUTO(testInvalidPatterns);
225     TESTCASE_AUTO(specTests);
226     TESTCASE_AUTO_END;
227 }
228 
229 // Needs more tests
testDataModelAPI()230 void TestMessageFormat2::testDataModelAPI() {
231     IcuTestErrorCode errorCode1(*this, "testAPI");
232     UErrorCode errorCode = (UErrorCode) errorCode1;
233 
234     using Pattern = data_model::Pattern;
235 
236     Pattern::Builder builder(errorCode);
237 
238     builder.add("a", errorCode);
239     builder.add("b", errorCode);
240     builder.add("c", errorCode);
241 
242     Pattern p = builder.build(errorCode);
243     int32_t i = 0;
244     for (auto iter = p.begin(); iter != p.end(); ++iter) {
245         std::variant<UnicodeString, Expression, Markup> part = *iter;
246         UnicodeString val = *std::get_if<UnicodeString>(&part);
247         if (i == 0) {
248             assertEquals("testDataModelAPI", val, "a");
249         } else if (i == 1) {
250             assertEquals("testDataModelAPI", val, "b");
251         } else if (i == 2) {
252             assertEquals("testDataModelAPI", val, "c");
253         }
254         i++;
255     }
256     assertEquals("testDataModelAPI", i, 3);
257 }
258 
259 // Example for design doc -- version without null and error checks
testAPISimple()260 void TestMessageFormat2::testAPISimple() {
261     IcuTestErrorCode errorCode1(*this, "testAPI");
262     UErrorCode errorCode = (UErrorCode) errorCode1;
263     UParseError parseError;
264     Locale locale = "en_US";
265 
266     // Since this is the example used in the
267     // design doc, it elides null checks and error checks.
268     // To be used in the test suite, it should include those checks
269     // Null checks and error checks elided
270     MessageFormatter::Builder builder(errorCode);
271     MessageFormatter mf = builder.setPattern(u"Hello, {$userName}!", parseError, errorCode)
272         .build(errorCode);
273 
274     std::map<UnicodeString, message2::Formattable> argsBuilder;
275     argsBuilder["userName"] = message2::Formattable("John");
276     MessageArguments args(argsBuilder, errorCode);
277 
278     UnicodeString result;
279     result = mf.formatToString(args, errorCode);
280     assertEquals("testAPI", result, "Hello, John!");
281 
282     mf = builder.setPattern("Today is {$today :date style=full}.", parseError, errorCode)
283         .setLocale(locale)
284         .build(errorCode);
285 
286     Calendar* cal(Calendar::createInstance(errorCode));
287     // Sunday, October 28, 2136 8:39:12 AM PST
288     cal->set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
289     UDate date = cal->getTime(errorCode);
290 
291     argsBuilder.clear();
292     argsBuilder["today"] = message2::Formattable::forDate(date);
293     args = MessageArguments(argsBuilder, errorCode);
294     result = mf.formatToString(args, errorCode);
295     assertEquals("testAPI", "Today is Sunday, October 28, 2136.", result);
296 
297     argsBuilder.clear();
298     argsBuilder["photoCount"] = message2::Formattable((int64_t) 12);
299     argsBuilder["userGender"] = message2::Formattable("feminine");
300     argsBuilder["userName"] = message2::Formattable("Maria");
301     args = MessageArguments(argsBuilder, errorCode);
302 
303     mf = builder.setPattern(".match {$photoCount :number} {$userGender :string}\n\
304                       1 masculine {{{$userName} added a new photo to his album.}}\n \
305                       1 feminine {{{$userName} added a new photo to her album.}}\n \
306                       1 * {{{$userName} added a new photo to their album.}}\n \
307                       * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
308                       * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
309                       * * {{{$userName} added {$photoCount} photos to their album.}}", parseError, errorCode)
310         .setLocale(locale)
311         .build(errorCode);
312     result = mf.formatToString(args, errorCode);
313     assertEquals("testAPI", "Maria added 12 photos to her album.", result);
314 
315     delete cal;
316 }
317 
318 // Design doc example, with more details
testAPI()319 void TestMessageFormat2::testAPI() {
320     IcuTestErrorCode errorCode(*this, "testAPI");
321     TestCase::Builder testBuilder;
322 
323     // Pattern: "Hello, {$userName}!"
324     TestCase test(testBuilder.setName("testAPI")
325                   .setPattern("Hello, {$userName}!")
326                   .setArgument("userName", "John")
327                   .setExpected("Hello, John!")
328                   .setLocale("en_US")
329                   .build());
330     TestUtils::runTestCase(*this, test, errorCode);
331 
332     // Pattern: "{Today is {$today ..."
333     LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
334     // Sunday, October 28, 2136 8:39:12 AM PST
335     cal->set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
336     UDate date = cal->getTime(errorCode);
337 
338     test = testBuilder.setName("testAPI")
339         .setPattern("Today is {$today :date style=full}.")
340         .setDateArgument("today", date)
341         .setExpected("Today is Sunday, October 28, 2136.")
342         .setLocale("en_US")
343         .build();
344     TestUtils::runTestCase(*this, test, errorCode);
345 
346     // Pattern matching - plural
347     UnicodeString pattern = ".match {$photoCount :string} {$userGender :string}\n\
348                       1 masculine {{{$userName} added a new photo to his album.}}\n \
349                       1 feminine {{{$userName} added a new photo to her album.}}\n \
350                       1 * {{{$userName} added a new photo to their album.}}\n \
351                       * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
352                       * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
353                       * * {{{$userName} added {$photoCount} photos to their album.}}";
354 
355 
356     int64_t photoCount = 12;
357     test = testBuilder.setName("testAPI")
358         .setPattern(pattern)
359         .setArgument("photoCount", photoCount)
360         .setArgument("userGender", "feminine")
361         .setArgument("userName", "Maria")
362         .setExpected("Maria added 12 photos to her album.")
363         .setLocale("en_US")
364         .build();
365     TestUtils::runTestCase(*this, test, errorCode);
366 
367     // Built-in functions
368     pattern = ".match {$photoCount :number} {$userGender :string}\n\
369                       1 masculine {{{$userName} added a new photo to his album.}}\n \
370                       1 feminine {{{$userName} added a new photo to her album.}}\n \
371                       1 * {{{$userName} added a new photo to their album.}}\n \
372                       * masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
373                       * feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
374                       * * {{{$userName} added {$photoCount} photos to their album.}}";
375 
376     photoCount = 1;
377     test = testBuilder.setName("testAPI")
378         .setPattern(pattern)
379         .setArgument("photoCount", photoCount)
380         .setArgument("userGender", "feminine")
381         .setArgument("userName", "Maria")
382         .setExpected("Maria added a new photo to her album.")
383         .setLocale("en_US")
384         .build();
385     TestUtils::runTestCase(*this, test, errorCode);
386 }
387 
388 // Custom functions example from the ICU4C API design doc
389 // Note: error/null checks are omitted
testAPICustomFunctions()390 void TestMessageFormat2::testAPICustomFunctions() {
391     IcuTestErrorCode errorCode1(*this, "testAPICustomFunctions");
392     UErrorCode errorCode = (UErrorCode) errorCode1;
393     UParseError parseError;
394     Locale locale = "en_US";
395 
396     // Set up custom function registry
397     MFFunctionRegistry::Builder builder(errorCode);
398     MFFunctionRegistry functionRegistry =
399         builder.adoptFormatter(data_model::FunctionName("person"), new PersonNameFormatterFactory(), errorCode)
400                .build();
401 
402     Person* person = new Person(UnicodeString("Mr."), UnicodeString("John"), UnicodeString("Doe"));
403 
404     std::map<UnicodeString, message2::Formattable> argsBuilder;
405     argsBuilder["name"] = message2::Formattable(person);
406     MessageArguments arguments(argsBuilder, errorCode);
407 
408     MessageFormatter::Builder mfBuilder(errorCode);
409     UnicodeString result;
410     // This fails, because we did not provide a function registry:
411     MessageFormatter mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
412                                     .setLocale(locale)
413                                     .build(errorCode);
414     result = mf.formatToString(arguments, errorCode);
415     assertEquals("testAPICustomFunctions", U_MF_UNKNOWN_FUNCTION_ERROR, errorCode);
416 
417     errorCode = U_ZERO_ERROR;
418     mfBuilder.setFunctionRegistry(functionRegistry).setLocale(locale);
419 
420     mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
421                     .build(errorCode);
422     result = mf.formatToString(arguments, errorCode);
423     assertEquals("testAPICustomFunctions", "Hello John", result);
424 
425     mf = mfBuilder.setPattern("Hello {$name :person formality=formal}", parseError, errorCode)
426                     .build(errorCode);
427     result = mf.formatToString(arguments, errorCode);
428     assertEquals("testAPICustomFunctions", "Hello Mr. Doe", result);
429 
430     mf = mfBuilder.setPattern("Hello {$name :person formality=formal length=long}", parseError, errorCode)
431                     .build(errorCode);
432     result = mf.formatToString(arguments, errorCode);
433     assertEquals("testAPICustomFunctions", "Hello Mr. John Doe", result);
434 
435     // By type
436     MFFunctionRegistry::Builder builderByType(errorCode);
437     FunctionName personFormatterName("person");
438     MFFunctionRegistry functionRegistryByType =
439         builderByType.adoptFormatter(personFormatterName,
440                                    new PersonNameFormatterFactory(),
441                                    errorCode)
442                      .setDefaultFormatterNameByType("person",
443                                                     personFormatterName,
444                                                     errorCode)
445                      .build();
446     mfBuilder.setFunctionRegistry(functionRegistryByType);
447     mf = mfBuilder.setPattern("Hello {$name}", parseError, errorCode)
448         .setLocale(locale)
449         .build(errorCode);
450     result = mf.formatToString(arguments, errorCode);
451     assertEquals("testAPICustomFunctions", U_ZERO_ERROR, errorCode);
452     // Expect "Hello John" because in the custom function we registered,
453     // "informal" is the default formality and "length" is the default length
454     assertEquals("testAPICustomFunctions", "Hello John", result);
455     delete person;
456 }
457 
testValidPatterns(const TestResult * patterns,int32_t len,IcuTestErrorCode & errorCode)458 void TestMessageFormat2::testValidPatterns(const TestResult* patterns, int32_t len, IcuTestErrorCode& errorCode) {
459     CHECK_ERROR(errorCode);
460 
461     TestCase::Builder testBuilder;
462     testBuilder.setName("testOtherJsonPatterns");
463 
464     for (int32_t i = 0; i < len - 1; i++) {
465         TestUtils::runTestCase(*this, testBuilder.setPattern(patterns[i].pattern)
466                                .setExpected(patterns[i].output)
467                                .setExpectSuccess()
468                                .build(), errorCode);
469     }
470 }
471 
testResolutionErrors(IcuTestErrorCode & errorCode)472 void TestMessageFormat2::testResolutionErrors(IcuTestErrorCode& errorCode) {
473     CHECK_ERROR(errorCode);
474 
475     TestCase::Builder testBuilder;
476     testBuilder.setName("testResolutionErrorPattern");
477 
478     for (int32_t i = 0; i < numResolutionErrors - 1; i++) {
479         TestUtils::runTestCase(*this, testBuilder.setPattern(jsonTestCasesResolutionError[i].pattern)
480                           .setExpected(jsonTestCasesResolutionError[i].output)
481                           .setExpectedError(jsonTestCasesResolutionError[i].expected)
482                           .build(), errorCode);
483     }
484 }
485 
testNoSyntaxErrors(const UnicodeString * patterns,int32_t len,IcuTestErrorCode & errorCode)486 void TestMessageFormat2::testNoSyntaxErrors(const UnicodeString* patterns, int32_t len, IcuTestErrorCode& errorCode) {
487     CHECK_ERROR(errorCode);
488 
489     TestCase::Builder testBuilder;
490     testBuilder.setName("testNoSyntaxErrors");
491 
492     for (int32_t i = 0; i < len - 1; i++) {
493         TestUtils::runTestCase(*this, testBuilder.setPattern(patterns[i])
494                           .setNoSyntaxError()
495                           .build(), errorCode);
496     }
497 }
498 
testVariousPatterns()499 void TestMessageFormat2::testVariousPatterns() {
500     IcuTestErrorCode errorCode(*this, "jsonTests");
501 
502     jsonTests(errorCode);
503     testValidPatterns(validTestCases, numValidTestCases, errorCode);
504     testResolutionErrors(errorCode);
505     testNoSyntaxErrors(reservedErrors, numReservedErrors, errorCode);
506     testNoSyntaxErrors(matches, numMatches, errorCode);
507     testNoSyntaxErrors(syntaxTests, numSyntaxTests, errorCode);
508 }
509 
specTests()510 void TestMessageFormat2::specTests() {
511     IcuTestErrorCode errorCode(*this, "specTests");
512 
513     runSpecTests(errorCode);
514 }
515 
516 /*
517  Tests a single pattern, which is expected to be invalid.
518 
519  `testNum`: Test number (only used for diagnostic output)
520  `s`: The pattern string.
521 
522  The error is assumed to be on line 0, offset `s.length()`.
523 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s)524 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s) {
525     testInvalidPattern(testNum, s, s.length(), 0);
526 }
527 
528 /*
529  Tests a single pattern, which is expected to be invalid.
530 
531  `testNum`: Test number (only used for diagnostic output)
532  `s`: The pattern string.
533 
534  The error is assumed to be on line 0, offset `expectedErrorOffset`.
535 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s,uint32_t expectedErrorOffset)536 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s, uint32_t expectedErrorOffset) {
537     testInvalidPattern(testNum, s, expectedErrorOffset, 0);
538 }
539 
540 /*
541  Tests a single pattern, which is expected to be invalid.
542 
543  `testNum`: Test number (only used for diagnostic output)
544  `s`: The pattern string.
545  `expectedErrorOffset`: The expected character offset for the parse error.
546 
547  The error is assumed to be on line `expectedErrorLine`, offset `expectedErrorOffset`.
548 */
testInvalidPattern(uint32_t testNum,const UnicodeString & s,uint32_t expectedErrorOffset,uint32_t expectedErrorLine)549 void TestMessageFormat2::testInvalidPattern(uint32_t testNum, const UnicodeString& s, uint32_t expectedErrorOffset, uint32_t expectedErrorLine) {
550     IcuTestErrorCode errorCode(*this, "testInvalidPattern");
551     char testName[50];
552     snprintf(testName, sizeof(testName), "testInvalidPattern: %d", testNum);
553 
554     TestCase::Builder testBuilder;
555     testBuilder.setName("testName");
556 
557     TestUtils::runTestCase(*this, testBuilder.setPattern(s)
558                            .setExpectedError(U_MF_SYNTAX_ERROR)
559                            .setExpectedLineNumberAndOffset(expectedErrorLine, expectedErrorOffset)
560                            .build(), errorCode);
561 }
562 
563 /*
564  Tests a single pattern, which is expected to cause the parser to
565  emit a data model error
566 
567  `testNum`: Test number (only used for diagnostic output)
568  `s`: The pattern string.
569  `expectedErrorCode`: the error code expected to be returned by the formatter
570 
571   For now, the line and character numbers are not checked
572 */
testSemanticallyInvalidPattern(uint32_t testNum,const UnicodeString & s,UErrorCode expectedErrorCode)573 void TestMessageFormat2::testSemanticallyInvalidPattern(uint32_t testNum, const UnicodeString& s, UErrorCode expectedErrorCode) {
574     IcuTestErrorCode errorCode(*this, "testInvalidPattern");
575 
576     char testName[50];
577     snprintf(testName, sizeof(testName), "testSemanticallyInvalidPattern: %d", testNum);
578 
579     TestCase::Builder testBuilder;
580     testBuilder.setName("testName").setPattern(s);
581     testBuilder.setExpectedError(expectedErrorCode);
582 
583     TestUtils::runTestCase(*this, testBuilder.build(), errorCode);
584 }
585 
586 /*
587  Tests a single pattern, which is expected to cause the formatter
588  to emit a resolution error, selection error, or
589  formatting error
590 
591  `testNum`: Test number (only used for diagnostic output)
592  `s`: The pattern string.
593  `expectedErrorCode`: the error code expected to be returned by the formatter
594 
595  For now, the line and character numbers are not checked
596 */
testRuntimeErrorPattern(uint32_t testNum,const UnicodeString & s,UErrorCode expectedErrorCode)597 void TestMessageFormat2::testRuntimeErrorPattern(uint32_t testNum, const UnicodeString& s, UErrorCode expectedErrorCode) {
598     IcuTestErrorCode errorCode(*this, "testInvalidPattern");
599     char testName[50];
600     snprintf(testName, sizeof(testName), "testInvalidPattern (errors): %u", testNum);
601 
602     TestCase::Builder testBuilder;
603     TestUtils::runTestCase(*this, testBuilder.setName(testName)
604                            .setPattern(s)
605                            .setExpectedError(expectedErrorCode)
606                            .build(), errorCode);
607 }
608 
609 /*
610  Tests a single pattern, which is expected to cause the formatter
611  to emit a resolution error, selection error, or
612  formatting error
613 
614  `testNum`: Test number (only used for diagnostic output)
615  `s`: The pattern string.
616  `expectedErrorCode`: the error code expected to be returned by the formatter
617 
618  For now, the line and character numbers are not checked
619 */
testRuntimeWarningPattern(uint32_t testNum,const UnicodeString & s,const UnicodeString & expectedResult,UErrorCode expectedErrorCode)620 void TestMessageFormat2::testRuntimeWarningPattern(uint32_t testNum, const UnicodeString& s, const UnicodeString& expectedResult, UErrorCode expectedErrorCode) {
621     IcuTestErrorCode errorCode(*this, "testInvalidPattern");
622     char testName[50];
623     snprintf(testName, sizeof(testName), "testInvalidPattern (warnings): %u", testNum);
624 
625     TestCase::Builder testBuilder;
626     TestUtils::runTestCase(*this, testBuilder.setName(testName)
627                                 .setPattern(s)
628                                 .setExpected(expectedResult)
629                                 .setExpectedError(expectedErrorCode)
630                                 .build(), errorCode);
631 }
632 
testDataModelErrors()633 void TestMessageFormat2::testDataModelErrors() {
634     uint32_t i = 0;
635     IcuTestErrorCode errorCode(*this, "testDataModelErrors");
636 
637     // The following tests are syntactically valid but should trigger a data model error
638 
639     // Examples taken from https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md
640 
641     // Variant key mismatch
642     testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number}  one{{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
643     testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number}  one {{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
644     testSemanticallyInvalidPattern(++i, ".match {$foo :number} {$bar :number}  one  {{one}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
645 
646     testSemanticallyInvalidPattern(++i, ".match {$foo :number}  * * {{foo}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
647     testSemanticallyInvalidPattern(++i, ".match {$one :number}\n\
648                               1 2 {{Too many}}\n\
649                               * {{Otherwise}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
650     testSemanticallyInvalidPattern(++i, ".match {$one :number} {$two :number}\n\
651                               1 2 {{Two keys}}\n\
652                               * {{Missing a key}}\n\
653                               * * {{Otherwise}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
654     testSemanticallyInvalidPattern(++i, ".match {$foo :x} {$bar :x} * {{foo}}", U_MF_VARIANT_KEY_MISMATCH_ERROR);
655 
656     // Non-exhaustive patterns
657     testSemanticallyInvalidPattern(++i, ".match {$one :number}\n\
658                                           1 {{Value is one}}\n\
659                                           2 {{Value is two}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
660     testSemanticallyInvalidPattern(++i, ".match {$one :number} {$two :number}\n\
661                                           1 * {{First is one}}\n\
662                                           * 1 {{Second is one}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
663     testSemanticallyInvalidPattern(++i, ".match {:foo} 1 {{_}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
664     testSemanticallyInvalidPattern(++i, ".match {:foo} other {{_}}", U_MF_NONEXHAUSTIVE_PATTERN_ERROR);
665 
666     // Duplicate option names
667     testSemanticallyInvalidPattern(++i, "{:foo a=1 b=2 a=1}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
668     testSemanticallyInvalidPattern(++i, "{:foo a=1 a=1}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
669     testSemanticallyInvalidPattern(++i, "{:foo a=1 a=2}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
670     testSemanticallyInvalidPattern(++i, "{|x| :foo a=1 a=2}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
671     testSemanticallyInvalidPattern(++i, "bad {:placeholder option=x option=x}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
672     testSemanticallyInvalidPattern(++i, "bad {:placeholder ns:option=x ns:option=y}", U_MF_DUPLICATE_OPTION_NAME_ERROR);
673 
674     // Missing selector annotation
675     testSemanticallyInvalidPattern(++i, ".match {$one}\n\
676                                           1 {{Value is one}}\n\
677                                           * {{Value is not one}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
678     testSemanticallyInvalidPattern(++i, ".local $one = {|The one|}\n\
679                                          .match {$one}\n\
680                                           1 {{Value is one}}\n\
681                                           * {{Value is not one}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
682     testSemanticallyInvalidPattern(++i, ".match {|horse| ^private}\n\
683                                           1 {{The value is one.}}\n          \
684                                           * {{The value is not one.}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
685     testSemanticallyInvalidPattern(++i, ".match {$foo !select}  |1| {{one}}  * {{other}}",
686                                    U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
687     testSemanticallyInvalidPattern(++i, ".match {$foo ^select}  |1| {{one}}  * {{other}}",
688                                    U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
689     testSemanticallyInvalidPattern(++i, ".input {$foo} .match {$foo} one {{one}} * {{other}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
690     testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .match {$foo} one {{one}} * {{other}}", U_MF_MISSING_SELECTOR_ANNOTATION_ERROR);
691 
692     // Duplicate declaration errors
693     testSemanticallyInvalidPattern(++i, ".local $x = {|1|} .input {$x :number} {{{$x}}}",
694                                    U_MF_DUPLICATE_DECLARATION_ERROR);
695     testSemanticallyInvalidPattern(++i, ".input {$x :number} .input {$x :string} {{{$x}}}",
696                                    U_MF_DUPLICATE_DECLARATION_ERROR);
697     testSemanticallyInvalidPattern(++i, ".input {$foo} .input {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
698     testSemanticallyInvalidPattern(++i, ".input {$foo} .local $foo = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
699     testSemanticallyInvalidPattern(++i, ".local $foo = {42} .input {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
700     testSemanticallyInvalidPattern(++i, ".local $foo = {:unknown} .local $foo = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
701     testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .local $bar = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
702     testSemanticallyInvalidPattern(++i, ".local $foo = {$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
703     testSemanticallyInvalidPattern(++i, ".local $foo = {$bar} .local $bar = {$baz} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
704     testSemanticallyInvalidPattern(++i, ".local $foo = {$bar :func} .local $bar = {$baz} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
705     testSemanticallyInvalidPattern(++i, ".local $foo = {42 :func opt=$foo} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
706     testSemanticallyInvalidPattern(++i, ".local $foo = {42 :func opt=$bar} .local $bar = {42} {{_}}", U_MF_DUPLICATE_DECLARATION_ERROR);
707 
708     // Disambiguating unsupported statements from match
709     testSemanticallyInvalidPattern(++i, ".matc {-1} {{hello}}", U_MF_UNSUPPORTED_STATEMENT_ERROR);
710     testSemanticallyInvalidPattern(++i, ".m {-1} {{hello}}", U_MF_UNSUPPORTED_STATEMENT_ERROR);
711 
712     TestCase::Builder testBuilder;
713     testBuilder.setName("testDataModelErrors");
714 
715     // This should *not* trigger a "missing selector annotation" error
716     TestCase test = testBuilder.setPattern(".local $one = {|The one| :string}\n\
717                  .match {$one}\n\
718                   1 {{Value is one}}\n\
719                   * {{Value is not one}}")
720                           .setExpected("Value is not one")
721                           .setExpectSuccess()
722                           .build();
723     TestUtils::runTestCase(*this, test, errorCode);
724 
725     test = testBuilder.setPattern(".local $one = {|The one| :string}\n\
726                  .local $two = {$one}\n\
727                  .match {$two}\n\
728                   1 {{Value is one}}\n\
729                   * {{Value is not one}}")
730                           .setExpected("Value is not one")
731                           .setExpectSuccess()
732                           .build();
733     TestUtils::runTestCase(*this, test, errorCode);
734 }
735 
testResolutionErrors()736 void TestMessageFormat2::testResolutionErrors() {
737     uint32_t i = 0;
738 
739     // The following tests are syntactically valid and free of data model errors,
740     // but should trigger a resolution error
741 
742     // Unresolved variable
743     testRuntimeWarningPattern(++i, "{$oops}", "{$oops}", U_MF_UNRESOLVED_VARIABLE_ERROR);
744     // .input of $x but $x is not supplied as an argument -- also unresolved variable
745     testRuntimeWarningPattern(++i, ".input {$x :number} {{{$x}}}", "{$x}", U_MF_UNRESOLVED_VARIABLE_ERROR);
746 
747     // Unknown function
748     testRuntimeWarningPattern(++i, "The value is {horse :func}.", "The value is {|horse|}.", U_MF_UNKNOWN_FUNCTION_ERROR);
749     testRuntimeWarningPattern(++i, ".match {|horse| :func}\n\
750                                           1 {{The value is one.}}\n\
751                                           * {{The value is not one.}}",
752                               "The value is not one.", U_MF_UNKNOWN_FUNCTION_ERROR);
753     // Using formatter as selector
754     // The fallback string will match the '*' variant
755     testRuntimeWarningPattern(++i, ".match {|horse| :number}\n\
756                                           1 {{The value is one.}}\n\
757                                           * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
758 
759     // Using selector as formatter
760     testRuntimeWarningPattern(++i, ".match {|horse| :string}\n\
761                                           1 {{The value is one.}}\n   \
762                                           * {{{|horse| :string}}}",
763                               "{|horse|}", U_MF_FORMATTING_ERROR);
764 
765     // Unsupported expressions
766     testRuntimeErrorPattern(++i, "hello {|4.2| !number}", U_MF_UNSUPPORTED_EXPRESSION_ERROR);
767     testRuntimeErrorPattern(++i, "{<tag}", U_MF_UNSUPPORTED_EXPRESSION_ERROR);
768     testRuntimeErrorPattern(++i, ".local $bar = {|42| ~plural} .match {|horse| :string}  * {{{$bar}}}",
769                             U_MF_UNSUPPORTED_EXPRESSION_ERROR);
770 
771     // Selector error
772     // Here, the plural selector returns "no match" so the * variant matches
773     testRuntimeWarningPattern(++i, ".match {|horse| :number}\n\
774                                    1 {{The value is one.}}\n\
775                                    * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
776     testRuntimeWarningPattern(++i, ".local $sel = {|horse| :number}\n\
777                                   .match {$sel}\n\
778                                    1 {{The value is one.}}\n\
779                                    * {{The value is not one.}}", "The value is not one.", U_MF_SELECTOR_ERROR);
780 }
781 
testInvalidPatterns()782 void TestMessageFormat2::testInvalidPatterns() {
783 /*
784   These tests are mostly from the test suite created for the JavaScript implementation of MessageFormat v2:
785   <p>Original JSON file
786   <a href="https://github.com/messageformat/messageformat/blob/master/packages/mf2-messageformat/src/__fixtures/test-messages.json">here</a>.</p>
787   Some have been modified or added to reflect syntax changes that post-date the JSON file.
788 
789  */
790     uint32_t i = 0;
791 
792     // Unexpected end of input
793     testInvalidPattern(++i, ".local    ");
794     testInvalidPattern(++i, ".lo");
795     testInvalidPattern(++i, ".local $foo");
796     testInvalidPattern(++i, ".local $foo =    ");
797     testInvalidPattern(++i, "{:fszzz");
798     testInvalidPattern(++i, "{:fszzz   ");
799     testInvalidPattern(++i, ".match {$foo}  |xyz");
800     testInvalidPattern(++i, "{:f aaa");
801     testInvalidPattern(++i, "{{missing end brace");
802     testInvalidPattern(++i, "{{missing end brace}");
803     testInvalidPattern(++i, "{{missing end {$brace");
804     testInvalidPattern(++i, "{{missing end {$brace}");
805     testInvalidPattern(++i, "{{missing end {$brace}}");
806 
807     // Error should be reported at character 0, not end of input
808     testInvalidPattern(++i, "}{|xyz|", 0);
809     testInvalidPattern(++i, "}", 0);
810 
811     // %xyz is a valid annotation (`reserved`) so the error should be at the end of input
812     testInvalidPattern(++i, "{{%xyz");
813     // Backslash followed by non-backslash followed by a '{' -- this should be an error
814     // immediately after the first backslash
815     testInvalidPattern(++i, "{{{%\\y{}}", 5);
816 
817     // Reserved chars followed by a '|' that doesn't begin a valid literal -- this should be
818     // an error at the first invalid char in the literal
819     testInvalidPattern(++i, "{%abc|\\z}}", 7);
820 
821     // Same pattern, but with a valid reserved-char following the erroneous reserved-escape
822     // -- the offset should be the same as with the previous one
823     testInvalidPattern(++i, "{%\\y{p}}", 3);
824     // Erroneous literal inside a reserved string -- the error should be at the first
825     // erroneous literal char
826     testInvalidPattern(++i, "{{{%ab|\\z|cd}}", 8);
827 
828     // tests for reserved syntax with bad escaped chars
829     // Single backslash - not allowed
830     testInvalidPattern(++i, "hello {|4.2| %num\\ber}}", 18);
831     // Unescaped '{' -- not allowed
832     testInvalidPattern(++i, "hello {|4.2| %num{be\\|r}}", 17);
833     // Unescaped '}' -- will be interpreted as the end of the reserved
834     // string, and the error will be reported at the index of '|', which is
835     // when the parser determines that "\|" isn't a valid text-escape
836     testInvalidPattern(++i, "hello {|4.2| %num}be\\|r}}", 21);
837     // Unescaped '|' -- will be interpreted as the beginning of a literal
838     // Error at end of input
839     testInvalidPattern(++i, "hello {|4.2| %num\\{be|r}}", 25);
840 
841     // Invalid escape sequence in a `text` -- the error should be at the character
842     // following the backslash
843     testInvalidPattern(++i, "a\\qbc", 2);
844 
845     // No spaces are required here. The error should be
846     // in the pattern, not before
847     testInvalidPattern(++i, ".match{|y|}|y|{{{|||}}}", 19);
848 
849     // Missing spaces betwen keys
850     testInvalidPattern(++i, ".match {|y|}|foo|bar {{{a}}}", 17);
851     testInvalidPattern(++i, ".match {|y|} |quux| |foo|bar {{{a}}}", 25);
852     testInvalidPattern(++i, ".match {|y|}  |quux| |foo||bar| {{{a}}}", 26);
853 
854     // Error parsing the first key -- the error should be there, not in the
855     // also-erroneous third key
856     testInvalidPattern(++i, ".match {|y|}  |\\q| * %{! {z}", 16);
857 
858     // Error parsing the second key -- the error should be there, not in the
859     // also-erroneous third key
860     testInvalidPattern(++i, ".match {|y|}  * %{! {z} |\\q|", 16);
861 
862     // Error parsing the last key -- the error should be there, not in the erroneous
863     // pattern
864     testInvalidPattern(++i, ".match {|y|}  * |\\q| {\\z}", 18);
865 
866     // Non-expression as scrutinee in pattern -- error should be at the first
867     // non-expression, not the later non-expression
868     testInvalidPattern(++i, ".match {|y|} {\\|} {@}  * * * {{a}}", 14);
869 
870     // Non-key in variant -- error should be there, not in the next erroneous
871     // variant
872     testInvalidPattern(++i, ".match {|y|}  $foo * {{a}} when * :bar {{b}}", 14);
873 
874 
875     // Error should be within the first erroneous `text` or expression
876     testInvalidPattern(++i, "{{ foo {|bar|} \\q baz  ", 16);
877 
878     // ':' has to be followed by a function name -- the error should be at the first
879     // whitespace character
880     testInvalidPattern(++i, "{{{:    }}}", 4);
881 
882     // Expression not starting with a '{'
883     testInvalidPattern(++i, ".local $x = }|foo|}", 12);
884 
885     // Error should be at the first declaration not starting with a `.local`
886     testInvalidPattern(++i, ".local $x = {|foo|} .l $y = {|bar|} .local $z {|quux|}", 22);
887 
888     // Missing '=' in `.local` declaration
889     testInvalidPattern(++i, ".local $bar {|foo|} {{$bar}}", 12);
890 
891     // LHS of declaration doesn't start with a '$'
892     testInvalidPattern(++i, ".local bar = {|foo|} {{$bar}}", 7);
893 
894     // `.local` RHS isn't an expression
895     testInvalidPattern(++i, ".local $bar = |foo| {{$bar}}", 14);
896 
897     // Trailing characters that are not whitespace
898     testInvalidPattern(++i, "{{extra}}content", 9);
899     testInvalidPattern(++i, ".match {|x|}  * {{foo}}extra", 28);
900 
901     // Trailing whitespace at end of message should not be accepted either
902     UnicodeString longMsg(".match {$foo :string} {$bar :string}  one * {{one}}  * * {{other}}   ");
903     testInvalidPattern(++i, longMsg, longMsg.length() - 3);
904     testInvalidPattern(++i, "{{hi}} ", 6);
905 
906     // Empty expression
907     testInvalidPattern(++i, "empty { }", 8);
908     testInvalidPattern(++i, ".match {}  * {{foo}}", 8);
909 
910     // ':' not preceding a function name
911     testInvalidPattern(++i, "bad {:}", 6);
912 
913     // Missing '=' after option name
914     testInvalidPattern(++i, "{{no-equal {|42| :number m }}}", 27);
915     testInvalidPattern(++i, "{{no-equal {|42| :number minimumFractionDigits 2}}}", 47);
916     testInvalidPattern(++i, "bad {:placeholder option value}", 25);
917 
918     // Extra '=' after option value
919     testInvalidPattern(++i, "hello {|4.2| :number min=2=3}", 26),
920     testInvalidPattern(++i, "hello {|4.2| :number min=2max=3}", 26),
921     // Missing whitespace between valid options
922     testInvalidPattern(++i, "hello {|4.2| :number min=|a|max=3}", 28),
923     // Ill-formed RHS of option -- the error should be within the RHS,
924     // not after parsing options
925     testInvalidPattern(++i, "hello {|4.2| :number min=|\\a|}", 27),
926 
927 
928     // Junk after annotation
929     testInvalidPattern(++i, "no-equal {|42| :number   {}", 25);
930 
931     // Missing RHS of option
932     testInvalidPattern(++i, "bad {:placeholder option=}", 25);
933     testInvalidPattern(++i, "bad {:placeholder option}", 24);
934 
935     // Annotation is not a function or reserved text
936     testInvalidPattern(++i, "bad {$placeholder option}", 18);
937     testInvalidPattern(++i, "no {$placeholder end", 17);
938 
939     // Missing expression in selectors
940     testInvalidPattern(++i, ".match  * {{foo}}", 8);
941     // Non-expression in selectors
942     testInvalidPattern(++i, ".match |x|  * {{foo}}", 7);
943 
944     // Missing RHS in variant
945     testInvalidPattern(++i, ".match {|x|}  * foo");
946 
947     // Text may include newlines; check that the missing closing '}' is
948     // reported on the correct line
949     testInvalidPattern(++i, "{{hello wo\nrld", 3, 1);
950     testInvalidPattern(++i, "{{hello wo\nr\nl\ndddd", 4, 3);
951     // Offset for end-of-input should be 0 here because the line begins
952     // after the '\n', but there is no character after the '\n'
953     testInvalidPattern(++i, "{{hello wo\nr\nl\n", 0, 3);
954 
955     // Quoted literals may include newlines; check that the missing closing '|' is
956     // reported on the correct line
957     testInvalidPattern(++i, "hello {|wo\nrld", 3, 1);
958     testInvalidPattern(++i, "hello {|wo\nr\nl\ndddd", 4, 3);
959     // Offset for end-of-input should be 0 here because the line begins
960     // after the '\n', but there is no character after the '\n'
961     testInvalidPattern(++i, "hello {|wo\nr\nl\n", 0, 3);
962 
963     // Variable names can't start with a : or -
964     testInvalidPattern(++i, "{$:abc}", 2);
965     testInvalidPattern(++i, "{$-abc}", 2);
966 
967     // Missing space before annotation
968     // Note that {{$bar:foo}} and {{$bar-foo}} are valid,
969     // because variable names can contain a ':' or a '-'
970     testInvalidPattern(++i, "{$bar+foo}", 5);
971     testInvalidPattern(++i, "{|3.14|:foo}", 7);
972     testInvalidPattern(++i, "{|3.14|-foo}", 7);
973     testInvalidPattern(++i, "{|3.14|+foo}", 7);
974 
975     // Unquoted literals can't begin with a ':'
976     testInvalidPattern(++i, ".local $foo = {$bar} .match {$foo}  :one {one} * {other}", 36);
977     testInvalidPattern(++i, ".local $foo = {$bar :fun option=:a} {{bar {$foo}}}", 32);
978 
979     // Markup in wrong place
980     testInvalidPattern(++i, "{|foo| {#markup}}", 7);
981     testInvalidPattern(++i, "{|foo| #markup}", 7);
982     testInvalidPattern(++i, "{|foo| {#markup/}}", 7);
983     testInvalidPattern(++i, "{|foo| {/markup}}", 7);
984 
985     // .input with non-variable-expression
986     testInvalidPattern(++i, ".input $x = {|1|} {{{$x}}}", 7);
987     testInvalidPattern(++i, ".input $x = {:number} {{{$x}}}", 7);
988     testInvalidPattern(++i, ".input {|1| :number} {{{$x}}}", 7);
989     testInvalidPattern(++i, ".input {:number} {{{$x}}}", 7);
990     testInvalidPattern(++i, ".input {|1|} {{{$x}}}", 7);
991 
992     // invalid number literals
993     testInvalidPattern(++i, "{00}", 2);
994     testInvalidPattern(++i, "{042}", 2);
995     testInvalidPattern(++i, "{1.}", 3);
996     testInvalidPattern(++i, "{1e}", 3);
997     testInvalidPattern(++i, "{1E}", 3);
998     testInvalidPattern(++i, "{1.e}", 3);
999     testInvalidPattern(++i, "{1.2e}", 5);
1000     testInvalidPattern(++i, "{1.e3}", 3);
1001     testInvalidPattern(++i, "{1e+}", 4);
1002     testInvalidPattern(++i, "{1e-}", 4);
1003     testInvalidPattern(++i, "{1.0e2.0}", 6);
1004 
1005     // The following are from https://github.com/unicode-org/message-format-wg/blob/main/test/syntax-errors.json
1006     testInvalidPattern(++i,".", 1);
1007     testInvalidPattern(++i, "{", 1);
1008     testInvalidPattern(++i, "}", 0);
1009     testInvalidPattern(++i, "{}", 1);
1010     testInvalidPattern(++i, "{{", 2);
1011     testInvalidPattern(++i, "{{}", 3);
1012     testInvalidPattern(++i, "{{}}}", 4);
1013     testInvalidPattern(++i, "{|foo| #markup}", 7);
1014     testInvalidPattern(++i, "{{missing end brace}", 20);
1015     testInvalidPattern(++i, "{{missing end braces", 20);
1016     testInvalidPattern(++i, "{{missing end {$braces", 22);
1017     testInvalidPattern(++i, "{{extra}} content", 9);
1018     testInvalidPattern(++i, "empty { } placeholder", 8);
1019     testInvalidPattern(++i, "missing space {42:func}", 17);
1020     testInvalidPattern(++i, "missing space {|foo|:func}", 20);
1021     testInvalidPattern(++i, "missing space {|foo|@bar}", 20);
1022     testInvalidPattern(++i, "missing space {:func@bar}", 20);
1023     testInvalidPattern(++i, "{:func @bar@baz}", 11);
1024     testInvalidPattern(++i, "{:func @bar=42@baz}", 14);
1025     testInvalidPattern(++i, "{+reserved@bar}", 10);
1026     testInvalidPattern(++i, "{&private@bar}", 9);
1027     testInvalidPattern(++i, "bad {:} placeholder", 6);
1028     testInvalidPattern(++i, "bad {\\u0000placeholder}", 5);
1029     testInvalidPattern(++i, "no-equal {|42| :number minimumFractionDigits 2}", 45);
1030     testInvalidPattern(++i, "bad {:placeholder option=}", 25);
1031     testInvalidPattern(++i, "bad {:placeholder option value}", 25);
1032     testInvalidPattern(++i, "bad {:placeholder option:value}", 30);
1033     testInvalidPattern(++i, "bad {:placeholder option}", 24);
1034     testInvalidPattern(++i, "bad {:placeholder:}", 18);
1035     testInvalidPattern(++i, "bad {::placeholder}", 6);
1036     testInvalidPattern(++i, "bad {:placeholder::foo}", 18);
1037     testInvalidPattern(++i, "bad {:placeholder option:=x}", 25);
1038     testInvalidPattern(++i, "bad {:placeholder :option=x}", 18);
1039     testInvalidPattern(++i, "bad {:placeholder option::x=y}", 25);
1040     testInvalidPattern(++i, "bad {$placeholder option}", 18);
1041     testInvalidPattern(++i, "bad {:placeholder @attribute=}", 29);
1042     testInvalidPattern(++i, "bad {:placeholder @attribute=@foo}", 29);
1043     testInvalidPattern(++i, "no {placeholder end", 16);
1044     testInvalidPattern(++i, "no {$placeholder end", 17);
1045     testInvalidPattern(++i, "no {:placeholder end", 20);
1046     testInvalidPattern(++i, "no {|placeholder| end", 18);
1047     testInvalidPattern(++i, "no {|literal} end", 17);
1048     testInvalidPattern(++i, "no {|literal or placeholder end", 31);
1049     testInvalidPattern(++i, ".local bar = {|foo|} {{_}}", 7);
1050     testInvalidPattern(++i, ".local #bar = {|foo|} {{_}}", 7);
1051     testInvalidPattern(++i, ".local $bar {|foo|} {{_}}", 12);
1052     testInvalidPattern(++i, ".local $bar = |foo| {{_}}", 14);
1053     testInvalidPattern(++i, ".match {#foo} * {{foo}}", 8);
1054     testInvalidPattern(++i, ".match {} * {{foo}}", 8);
1055     testInvalidPattern(++i, ".match {|foo| :x} {|bar| :x} ** {{foo}}", 30);
1056     testInvalidPattern(++i, ".match * {{foo}}", 7);
1057     testInvalidPattern(++i, ".match {|x| :x} * foo", 21);
1058     testInvalidPattern(++i, ".match {|x| :x} * {{foo}} extra", 31);
1059     testInvalidPattern(++i, ".match |x| * {{foo}}", 7);
1060 
1061     // tests for ':' in unquoted literals (not allowed)
1062     testInvalidPattern(++i, ".match {|foo| :string} o:ne {{one}}  * {{other}}", 24);
1063     testInvalidPattern(++i, ".match {|foo| :string} one: {{one}}  * {{other}}", 26);
1064     testInvalidPattern(++i, ".local $foo = {|42| :number option=a:b} {{bar {$foo}}}", 36);
1065     testInvalidPattern(++i, ".local $foo = {|42| :number option=a:b:c} {{bar {$foo}}}", 36);
1066     testInvalidPattern(++i, "{$bar:foo}", 5);
1067 
1068     // Disambiguating a wrong .match from an unsupported statement
1069     testInvalidPattern(++i, ".match {1} {{_}}", 12);
1070 }
1071 
1072 #endif /* #if !UCONFIG_NO_MF2 */
1073 
1074 #endif /* #if !UCONFIG_NO_FORMATTING */
1075 
1076