diff --git a/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.ftl b/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.ftl new file mode 100644 index 000000000..aba7c4267 --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.ftl @@ -0,0 +1,17 @@ +key1 = Value 1 + .attr = + +key2 = + .attr = + +key3 = + .attr1 = Attr 1 + .attr2 = + +key4 = + .attr1 = + .attr2 = Attr 2 + +key5 = + .attr1 = + .attr2 = diff --git a/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.json b/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.json new file mode 100644 index 000000000..7cfd02cd0 --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/attribute_with_empty_pattern.json @@ -0,0 +1,324 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 1", + "span": { + "type": "Span", + "start": 7, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 7, + "end": 14 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 20, + "end": 24 + } + }, + "value": { + "type": "Pattern", + "elements": [], + "span": { + "type": "Span", + "start": 26, + "end": 26 + } + }, + "span": { + "type": "Span", + "start": 19, + "end": 26 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 26 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 28, + "end": 32 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attr =", + "span": { + "type": "Span", + "start": 39, + "end": 46 + } + } + ], + "span": { + "type": "Span", + "start": 35, + "end": 46 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 28, + "end": 46 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key3", + "span": { + "type": "Span", + "start": 48, + "end": 52 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attr1 = Attr 1", + "span": { + "type": "Span", + "start": 59, + "end": 74 + } + } + ], + "span": { + "type": "Span", + "start": 55, + "end": 74 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 80, + "end": 85 + } + }, + "value": { + "type": "Pattern", + "elements": [], + "span": { + "type": "Span", + "start": 87, + "end": 87 + } + }, + "span": { + "type": "Span", + "start": 79, + "end": 87 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 48, + "end": 87 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key4", + "span": { + "type": "Span", + "start": 89, + "end": 93 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attr1 =", + "span": { + "type": "Span", + "start": 100, + "end": 108 + } + } + ], + "span": { + "type": "Span", + "start": 96, + "end": 108 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 114, + "end": 119 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr 2", + "span": { + "type": "Span", + "start": 122, + "end": 128 + } + } + ], + "span": { + "type": "Span", + "start": 122, + "end": 128 + } + }, + "span": { + "type": "Span", + "start": 113, + "end": 128 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 89, + "end": 128 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key5", + "span": { + "type": "Span", + "start": 130, + "end": 134 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attr1 =", + "span": { + "type": "Span", + "start": 141, + "end": 149 + } + } + ], + "span": { + "type": "Span", + "start": 137, + "end": 149 + } + }, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 155, + "end": 160 + } + }, + "value": { + "type": "Pattern", + "elements": [], + "span": { + "type": "Span", + "start": 162, + "end": 162 + } + }, + "span": { + "type": "Span", + "start": 154, + "end": 162 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 130, + "end": 162 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 163 + } +} diff --git a/fluent-syntax/test/fixtures_structure/leading_dots.ftl b/fluent-syntax/test/fixtures_structure/leading_dots.ftl new file mode 100644 index 000000000..500021e9b --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/leading_dots.ftl @@ -0,0 +1,64 @@ +key01 = .Value +key02 = …Value +key03 = {"."}Value +key04 = + {"."}Value + +key05 = Value + {"."}Continued + +key06 = .Value + {"."}Continued + +# ERROR (attr .Continued must have a value) +key07 = Value + .Continued + +# ERROR (attr .Value must have a value) +key08 = + .Value + +# ERROR (attr .Value must have a value) +key09 = + .Value + Continued + +key10 = + .Value = which looks like an attribute + Continued + +key11 = + {"."}Value = which looks like an attribute + Continued + +key12 = + .accesskey = + A + +key13 = + .attribute = .Value + +key14 = + .attribute = + {"."}Value + +key15 = + { 1 -> + [one] .Value + *[other] + {"."}Value + } + +# ERROR (variant must have a value) +key16 = + { 1 -> + *[one] + .Value + } + +# ERROR (unclosed placeable) +key17 = + { 1 -> + *[one] Value + .Continued + } diff --git a/fluent-syntax/test/fixtures_structure/leading_dots.json b/fluent-syntax/test/fixtures_structure/leading_dots.json new file mode 100644 index 000000000..b63151396 --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/leading_dots.json @@ -0,0 +1,933 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key01", + "span": { + "type": "Span", + "start": 0, + "end": 5 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + } + ], + "span": { + "type": "Span", + "start": 8, + "end": 14 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 14 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key02", + "span": { + "type": "Span", + "start": 15, + "end": 20 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "…Value", + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + } + ], + "span": { + "type": "Span", + "start": 23, + "end": 29 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 15, + "end": 29 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key03", + "span": { + "type": "Span", + "start": 30, + "end": 35 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 39, + "end": 42 + } + }, + "span": { + "type": "Span", + "start": 38, + "end": 43 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 43, + "end": 48 + } + } + ], + "span": { + "type": "Span", + "start": 38, + "end": 48 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 30, + "end": 48 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key04", + "span": { + "type": "Span", + "start": 49, + "end": 54 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 62, + "end": 65 + } + }, + "span": { + "type": "Span", + "start": 61, + "end": 66 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 66, + "end": 71 + } + } + ], + "span": { + "type": "Span", + "start": 57, + "end": 71 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 49, + "end": 71 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key05", + "span": { + "type": "Span", + "start": 73, + "end": 78 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\n", + "span": { + "type": "Span", + "start": 81, + "end": 91 + } + }, + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 92, + "end": 95 + } + }, + "span": { + "type": "Span", + "start": 91, + "end": 96 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 96, + "end": 105 + } + } + ], + "span": { + "type": "Span", + "start": 81, + "end": 105 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 73, + "end": 105 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key06", + "span": { + "type": "Span", + "start": 107, + "end": 112 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value\n", + "span": { + "type": "Span", + "start": 115, + "end": 126 + } + }, + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 127, + "end": 130 + } + }, + "span": { + "type": "Span", + "start": 126, + "end": 131 + } + }, + { + "type": "TextElement", + "value": "Continued", + "span": { + "type": "Span", + "start": 131, + "end": 140 + } + } + ], + "span": { + "type": "Span", + "start": 115, + "end": 140 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 107, + "end": 140 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "args": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 142, + "end": 142 + } + } + ], + "content": "# ERROR (attr .Continued must have a value)\n", + "span": { + "type": "Span", + "start": 142, + "end": 186 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "args": [ + "=" + ], + "message": "Expected token: \"=\"", + "span": { + "type": "Span", + "start": 214, + "end": 214 + } + } + ], + "content": "key07 = Value\n .Continued\n\n# ERROR (attr .Value must have a value)\n", + "span": { + "type": "Span", + "start": 186, + "end": 256 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key08", + "span": { + "type": "Span", + "start": 256, + "end": 261 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 268, + "end": 274 + } + } + ], + "span": { + "type": "Span", + "start": 264, + "end": 274 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 256, + "end": 274 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "args": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 276, + "end": 276 + } + } + ], + "content": "# ERROR (attr .Value must have a value)\n", + "span": { + "type": "Span", + "start": 276, + "end": 316 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key09", + "span": { + "type": "Span", + "start": 316, + "end": 321 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value\nContinued", + "span": { + "type": "Span", + "start": 328, + "end": 348 + } + } + ], + "span": { + "type": "Span", + "start": 324, + "end": 348 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 316, + "end": 348 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key10", + "span": { + "type": "Span", + "start": 350, + "end": 355 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value = which looks like an attribute\nContinued", + "span": { + "type": "Span", + "start": 362, + "end": 414 + } + } + ], + "span": { + "type": "Span", + "start": 358, + "end": 414 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 350, + "end": 414 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key11", + "span": { + "type": "Span", + "start": 416, + "end": 421 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 429, + "end": 432 + } + }, + "span": { + "type": "Span", + "start": 428, + "end": 433 + } + }, + { + "type": "TextElement", + "value": "Value = which looks like an attribute\nContinued", + "span": { + "type": "Span", + "start": 433, + "end": 484 + } + } + ], + "span": { + "type": "Span", + "start": 424, + "end": 484 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 416, + "end": 484 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key12", + "span": { + "type": "Span", + "start": 486, + "end": 491 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".accesskey =\nA", + "span": { + "type": "Span", + "start": 498, + "end": 516 + } + } + ], + "span": { + "type": "Span", + "start": 494, + "end": 516 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 486, + "end": 516 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key13", + "span": { + "type": "Span", + "start": 518, + "end": 523 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attribute = .Value", + "span": { + "type": "Span", + "start": 530, + "end": 549 + } + } + ], + "span": { + "type": "Span", + "start": 526, + "end": 549 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 518, + "end": 549 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key14", + "span": { + "type": "Span", + "start": 551, + "end": 556 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".attribute =\n", + "span": { + "type": "Span", + "start": 563, + "end": 585 + } + }, + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 586, + "end": 589 + } + }, + "span": { + "type": "Span", + "start": 585, + "end": 590 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 590, + "end": 595 + } + } + ], + "span": { + "type": "Span", + "start": 559, + "end": 595 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 551, + "end": 595 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key15", + "span": { + "type": "Span", + "start": 597, + "end": 602 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "expression": { + "type": "NumberExpression", + "value": "1", + "span": { + "type": "Span", + "start": 611, + "end": 612 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Symbol", + "name": "one", + "span": { + "type": "Span", + "start": 625, + "end": 628 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": ".Value", + "span": { + "type": "Span", + "start": 630, + "end": 636 + } + } + ], + "span": { + "type": "Span", + "start": 630, + "end": 636 + } + }, + "default": false, + "span": { + "type": "Span", + "start": 624, + "end": 636 + } + }, + { + "type": "Variant", + "key": { + "type": "Symbol", + "name": "other", + "span": { + "type": "Span", + "start": 646, + "end": 651 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": ".", + "span": { + "type": "Span", + "start": 666, + "end": 669 + } + }, + "span": { + "type": "Span", + "start": 665, + "end": 670 + } + }, + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 670, + "end": 675 + } + } + ], + "span": { + "type": "Span", + "start": 652, + "end": 675 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 644, + "end": 675 + } + } + ], + "span": { + "type": "Span", + "start": 610, + "end": 680 + } + }, + "span": { + "type": "Span", + "start": 609, + "end": 681 + } + } + ], + "span": { + "type": "Span", + "start": 605, + "end": 681 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 597, + "end": 681 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "args": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 683, + "end": 683 + } + } + ], + "content": "# ERROR (variant must have a value)\n", + "span": { + "type": "Span", + "start": 683, + "end": 719 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "args": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 763, + "end": 763 + } + } + ], + "content": "key16 =\n { 1 ->\n *[one]\n .Value\n }\n\n# ERROR (unclosed placeable)\n", + "span": { + "type": "Span", + "start": 719, + "end": 806 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0003", + "args": [ + "}" + ], + "message": "Expected token: \"}\"", + "span": { + "type": "Span", + "start": 856, + "end": 856 + } + } + ], + "content": "key17 =\n { 1 ->\n *[one] Value\n .Continued\n }\n", + "span": { + "type": "Span", + "start": 806, + "end": 873 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 873 + } +} diff --git a/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.ftl b/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.ftl index 179dbacc6..c305ab2d8 100644 --- a/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.ftl +++ b/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.ftl @@ -1 +1,10 @@ +### BE CAREFUL WHEN EDITING THIS FILE +### +### The last character in this file is the equals sign in `bar =` in line 10. +### We want to test a message with no value and with no EOL after its +### identifier. Most editors automatically add a trailing newline at EOF. +### If you edit this file make sure to turn this behavior off. + foo = + +bar = \ No newline at end of file diff --git a/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json b/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json index eab2feeae..0312810d5 100644 --- a/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json +++ b/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json @@ -1,6 +1,28 @@ { "type": "Resource", "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "args": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 0, + "end": 0 + } + } + ], + "content": "### BE CAREFUL WHEN EDITING THIS FILE\n###\n### The last character in this file is the equals sign in `bar =` in line 10.\n### We want to test a message with no value and with no EOL after its\n### identifier. Most editors automatically add a trailing newline at EOF.\n### If you edit this file make sure to turn this behavior off.\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 328 + } + }, { "type": "Message", "annotations": [], @@ -9,25 +31,35 @@ "name": "foo", "span": { "type": "Span", - "start": 0, - "end": 3 + "start": 328, + "end": 331 } }, "value": { "type": "Pattern", - "elements": [], + "elements": [ + { + "type": "TextElement", + "value": "bar =", + "span": { + "type": "Span", + "start": 336, + "end": 341 + } + } + ], "span": { "type": "Span", - "start": 7, - "end": 7 + "start": 336, + "end": 341 } }, "attributes": [], "comment": null, "span": { "type": "Span", - "start": 0, - "end": 7 + "start": 328, + "end": 341 } } ], @@ -35,6 +67,6 @@ "span": { "type": "Span", "start": 0, - "end": 7 + "end": 341 } } diff --git a/fluent-syntax/test/fixtures_structure/multiline-comment.ftl b/fluent-syntax/test/fixtures_structure/multiline-comment.ftl new file mode 100644 index 000000000..2fbc819df --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/multiline-comment.ftl @@ -0,0 +1,6 @@ + +# This is +# +# An example of a multiline comment + +key = Value diff --git a/fluent-syntax/test/fixtures_structure/multiline-comment.json b/fluent-syntax/test/fixtures_structure/multiline-comment.json new file mode 100644 index 000000000..211d6a0d5 --- /dev/null +++ b/fluent-syntax/test/fixtures_structure/multiline-comment.json @@ -0,0 +1,72 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0002", + "args": [], + "message": "Expected an entry start", + "span": { + "type": "Span", + "start": 1, + "end": 1 + } + } + ], + "content": "# This is\n#\n# An example of a multiline comment\n\n", + "span": { + "type": "Span", + "start": 1, + "end": 50 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key", + "span": { + "type": "Span", + "start": 50, + "end": 53 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value", + "span": { + "type": "Span", + "start": 56, + "end": 61 + } + } + ], + "span": { + "type": "Span", + "start": 56, + "end": 61 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 50, + "end": 61 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 62 + } +} diff --git a/fluent/src/parser.js b/fluent/src/parser.js index 660c9b4e6..9b96b3f4d 100644 --- a/fluent/src/parser.js +++ b/fluent/src/parser.js @@ -75,7 +75,8 @@ class RuntimeParser { // We don't care about comments or sections at runtime if (ch === '/' || - (ch === '#' && [' ', '#'].includes(this._source[this._index + 1]))) { + (ch === '#' && + [' ', '#', '\n'].includes(this._source[this._index + 1]))) { this.skipComment(); return; } @@ -340,7 +341,9 @@ class RuntimeParser { this.skipBlankLines(); if (this._source[this._index] !== ' ') { - // No indentation means we're done with this message. + // No indentation means we're done with this message. Callers should check + // if the return value here is null. It may be OK for messages, but not OK + // for terms, attributes and variants. return firstLineContent; } @@ -759,6 +762,10 @@ class RuntimeParser { const val = this.getPattern(); + if (val === null) { + throw this.error('Expected attribute to have a value'); + } + if (typeof val === 'string') { attrs[key] = val; } else { @@ -806,11 +813,13 @@ class RuntimeParser { this.skipInlineWS(); - const variant = { - key, - val: this.getPattern() - }; - variants[index++] = variant; + const val = this.getPattern(); + + if (val === null) { + throw this.error('Expected variant to have a value'); + } + + variants[index++] = {key, val}; this.skipWS(); } diff --git a/fluent/test/fixtures_structure/attribute_with_empty_pattern.json b/fluent/test/fixtures_structure/attribute_with_empty_pattern.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/fluent/test/fixtures_structure/attribute_with_empty_pattern.json @@ -0,0 +1 @@ +{} diff --git a/fluent/test/fixtures_structure/leading_dots.json b/fluent/test/fixtures_structure/leading_dots.json new file mode 100644 index 000000000..4c80220cd --- /dev/null +++ b/fluent/test/fixtures_structure/leading_dots.json @@ -0,0 +1,114 @@ +{ + "key01": ".Value", + "key02": "…Value", + "key03": { + "val": [ + ".", + "Value" + ] + }, + "key04": { + "val": [ + ".", + "Value" + ] + }, + "key05": { + "val": [ + "Value\n", + ".", + "Continued" + ] + }, + "key06": { + "val": [ + ".Value\n", + ".", + "Continued" + ] + }, + "key10": { + "attrs": { + "Value": "which looks like an attribute\nContinued" + } + }, + "key11": { + "val": [ + ".", + "Value = which looks like an attribute\nContinued" + ] + }, + "key12": { + "attrs": { + "accesskey": "A" + } + }, + "key13": { + "attrs": { + "attribute": ".Value" + } + }, + "key14": { + "attrs": { + "attribute": { + "val": [ + ".", + "Value" + ] + } + } + }, + "key15": { + "val": [ + { + "type": "sel", + "exp": { + "type": "num", + "val": "1" + }, + "vars": [ + { + "key": { + "type": "varname", + "name": "one" + }, + "val": ".Value" + }, + { + "key": { + "type": "varname", + "name": "other" + }, + "val": [ + ".", + "Value" + ] + } + ], + "def": 1 + } + ] + }, + "key17": { + "val": [ + { + "type": "sel", + "exp": { + "type": "num", + "val": "1" + }, + "vars": [ + { + "key": { + "type": "varname", + "name": "one" + }, + "val": "Value" + } + ], + "def": 0 + }, + "Continued" + ] + } +} diff --git a/fluent/test/fixtures_structure/multiline-comment.json b/fluent/test/fixtures_structure/multiline-comment.json new file mode 100644 index 000000000..7d54589d5 --- /dev/null +++ b/fluent/test/fixtures_structure/multiline-comment.json @@ -0,0 +1,3 @@ +{ + "key": "Value" +}