diff --git a/fluent/syntax/ast.py b/fluent/syntax/ast.py index b99b5ebb..1cc85526 100644 --- a/fluent/syntax/ast.py +++ b/fluent/syntax/ast.py @@ -8,6 +8,8 @@ def to_json(value): return value.to_json() if isinstance(value, list): return list(map(to_json, value)) + if isinstance(value, tuple): + return list(map(to_json, value)) else: return value diff --git a/fluent/syntax/ftlstream.py b/fluent/syntax/ftlstream.py index 806beddf..8140e380 100644 --- a/fluent/syntax/ftlstream.py +++ b/fluent/syntax/ftlstream.py @@ -4,11 +4,18 @@ INLINE_WS = (' ', '\t') +SPECIAL_LINE_START_CHARS = ('}', '.', '[', '*') class FTLParserStream(ParserStream): last_comment_zero_four_syntax = False + def skip_inline_ws(self): + while self.ch: + if self.ch not in INLINE_WS: + break + self.next() + def peek_inline_ws(self): ch = self.current_peek() while ch: @@ -39,11 +46,9 @@ def peek_blank_lines(self): self.reset_peek(line_start) break - def skip_inline_ws(self): - while self.ch: - if self.ch not in INLINE_WS: - break - self.next() + def skip_indent(self): + self.skip_blank_lines() + self.skip_inline_ws() def expect_char(self, ch): if self.ch == ch: @@ -101,6 +106,19 @@ def is_number_start(self): self.reset_peek() return is_digit + def is_char_pattern_start(self, ch): + return ch not in SPECIAL_LINE_START_CHARS + + def is_peek_pattern_start(self): + self.peek_inline_ws() + + if self.current_peek_is('\n'): + return self.is_peek_next_line_pattern_start() + + is_pattern = self.is_char_pattern_start(self.current_peek()) + self.reset_peek() + return is_pattern + def is_peek_next_line_zero_four_style_comment(self): if not self.current_peek_is('\n'): return False @@ -193,7 +211,7 @@ def is_peek_next_line_attribute_start(self): self.reset_peek() return False - def is_peek_next_non_blank_line_pattern(self): + def is_peek_next_line_pattern_start(self): if not self.current_peek_is('\n'): return False @@ -209,10 +227,7 @@ def is_peek_next_non_blank_line_pattern(self): self.reset_peek() return False - if (self.current_peek_is('}') or - self.current_peek_is('.') or - self.current_peek_is('[') or - self.current_peek_is('*')): + if not self.is_char_pattern_start(self.current_peek()): self.reset_peek() return False diff --git a/fluent/syntax/parser.py b/fluent/syntax/parser.py index b4db8d34..e3731534 100644 --- a/fluent/syntax/parser.py +++ b/fluent/syntax/parser.py @@ -56,8 +56,6 @@ def parse(self, source): entries.append(entry) ps.last_comment_zero_four_syntax = False - - ps.skip_inline_ws() ps.skip_blank_lines() res = ast.Resource(entries) @@ -209,12 +207,13 @@ def get_message(self, ps, comment): pattern = None attrs = None + # XXX Syntax 0.4 compat if ps.current_is('='): ps.next() - ps.skip_inline_ws() - ps.skip_blank_lines() - pattern = self.get_pattern(ps) + if ps.is_peek_pattern_start(): + ps.skip_indent() + pattern = self.get_pattern(ps) if ps.is_peek_next_line_attribute_start(): attrs = self.get_attributes(ps) @@ -226,27 +225,25 @@ def get_message(self, ps, comment): @with_span def get_attribute(self, ps): + ps.expect_indent() ps.expect_char('.') key = self.get_public_identifier(ps) ps.skip_inline_ws() ps.expect_char('=') - ps.skip_inline_ws() - value = self.get_pattern(ps) + if ps.is_peek_pattern_start(): + ps.skip_indent() + value = self.get_pattern(ps) + return ast.Attribute(key, value) - if value is None: - raise ParseError('E0006', 'value') - - return ast.Attribute(key, value) + raise ParseError('E0006', 'value') def get_attributes(self, ps): attrs = [] while True: - ps.expect_indent() - attr = self.get_attribute(ps) attrs.append(attr) @@ -288,6 +285,8 @@ def get_variant_key(self, ps): @with_span def get_variant(self, ps, has_default): + ps.expect_indent() + default_index = False if ps.current_is('*'): @@ -302,22 +301,18 @@ def get_variant(self, ps, has_default): ps.expect_char(']') - ps.skip_inline_ws() - - value = self.get_pattern(ps) - - if value is None: - raise ParseError('E0006', 'value') + if ps.is_peek_pattern_start(): + ps.skip_indent() + value = self.get_pattern(ps) + return ast.Variant(key, value, default_index) - return ast.Variant(key, value, default_index) + raise ParseError('E0006', 'value') def get_variants(self, ps): variants = [] has_default = False while True: - ps.expect_indent() - variant = self.get_variant(ps, has_default) if variant.default: @@ -383,17 +378,12 @@ def get_pattern(self, ps): elements = [] ps.skip_inline_ws() - # Special-case: trim leading whitespace and newlines. - if ps.is_peek_next_non_blank_line_pattern(): - ps.skip_blank_lines() - ps.skip_inline_ws() - while ps.current(): ch = ps.current() # The end condition for get_pattern's while loop is a newline # which is not followed by a valid pattern continuation. - if ch == '\n' and not ps.is_peek_next_non_blank_line_pattern(): + if ch == '\n' and not ps.is_peek_next_line_pattern_start(): break if ch == '{': @@ -416,7 +406,7 @@ def get_text_element(self, ps): return ast.TextElement(buf) if ch == '\n': - if not ps.is_peek_next_non_blank_line_pattern(): + if not ps.is_peek_next_line_pattern_start(): return ast.TextElement(buf) ps.next() diff --git a/fluent/syntax/serializer.py b/fluent/syntax/serializer.py index b8501d09..2de93361 100644 --- a/fluent/syntax/serializer.py +++ b/fluent/syntax/serializer.py @@ -98,9 +98,9 @@ def serialize_message(message): parts.append("\n") parts.append(serialize_identifier(message.id)) + parts.append(" =") if message.value: - parts.append(" =") parts.append(serialize_value(message.value)) if message.attributes: diff --git a/tests/migrate/fixtures/en-US/aboutDownloads.ftl b/tests/migrate/fixtures/en-US/aboutDownloads.ftl index 26728e3c..c759e082 100644 --- a/tests/migrate/fixtures/en-US/aboutDownloads.ftl +++ b/tests/migrate/fixtures/en-US/aboutDownloads.ftl @@ -7,19 +7,19 @@ header = Your Downloads empty = No Downloads about = About Downloads -open-menuitem +open-menuitem = .label = Open -retry-menuitem +retry-menuitem = .label = Retry -remove-menuitem +remove-menuitem = .label = Delete -pause-menuitem +pause-menuitem = .label = Pause -resume-menuitem +resume-menuitem = .label = Resume -cancel-menuitem +cancel-menuitem = .label = Cancel -remove-all-menuitem +remove-all-menuitem = .label = Delete All delete-all-title = Delete All diff --git a/tests/migrate/fixtures/en-US/toolbar.ftl b/tests/migrate/fixtures/en-US/toolbar.ftl index b7463912..c102d7a9 100644 --- a/tests/migrate/fixtures/en-US/toolbar.ftl +++ b/tests/migrate/fixtures/en-US/toolbar.ftl @@ -2,23 +2,23 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -urlbar-textbox +urlbar-textbox = .placeholder = Search or enter address .accesskey = d ## Toolbar items -view-bookmarks-broadcaster +view-bookmarks-broadcaster = .label = Bookmarks -view-bookmarks-command +view-bookmarks-command = .key = b -view-bookmarks-command-win +view-bookmarks-command-win = .key = i -view-history-broadcaster +view-history-broadcaster = .label = History -view-history-command +view-history-command = .key = h -view-tabs-broadcaster +view-tabs-broadcaster = .label = Synced Tabs diff --git a/tests/migrate/test_context_real_examples.py b/tests/migrate/test_context_real_examples.py index b50c6f75..482dbc83 100644 --- a/tests/migrate/test_context_real_examples.py +++ b/tests/migrate/test_context_real_examples.py @@ -215,19 +215,19 @@ def test_merge_context_all_messages(self): header = Twoje pobrane pliki empty = Brak pobranych plików - open-menuitem + open-menuitem = .label = Otwórz - retry-menuitem + retry-menuitem = .label = Spróbuj ponownie - remove-menuitem + remove-menuitem = .label = Usuń - pause-menuitem + pause-menuitem = .label = Wstrzymaj - resume-menuitem + resume-menuitem = .label = Wznów - cancel-menuitem + cancel-menuitem = .label = Anuluj - remove-all-menuitem + remove-all-menuitem = .label = Usuń wszystko delete-all-title = Usuń wszystko diff --git a/tests/migrate/test_merge.py b/tests/migrate/test_merge.py index c950b5d6..a85fead0 100644 --- a/tests/migrate/test_merge.py +++ b/tests/migrate/test_merge.py @@ -32,7 +32,7 @@ def setUp(self): empty = No Downloads about = About Downloads - open-menuitem + open-menuitem = .label = Open download-state-downloading = Downloading… @@ -96,7 +96,7 @@ def test_merge_two_way(self): title = Pobrane pliki about = Hardcoded Value - open-menuitem + open-menuitem = .label = Otwórz download-state-downloading = Pobieranie… @@ -116,7 +116,7 @@ def test_merge_three_way(self): empty = Brak pobranych plików about = Previously Hardcoded Value - open-menuitem + open-menuitem = .label = Otwórz download-state-downloading = Pobieranie… @@ -140,7 +140,7 @@ def setUp(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Open download-state-downloading = Downloading… @@ -205,7 +205,7 @@ def test_merge_two_way(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz download-state-downloading = Pobieranie… @@ -231,7 +231,7 @@ def test_merge_three_way(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz download-state-downloading = Pobieranie… @@ -256,7 +256,7 @@ def setUp(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Open download-state-downloading = Downloading… @@ -395,7 +395,7 @@ def test_three_way_one_entity_existing_section(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz ''')) @@ -418,7 +418,7 @@ def test_three_way_one_entity_existing_section(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz ''') ) @@ -432,7 +432,7 @@ def test_three_way_two_entities_existing_section(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz ''')) @@ -455,7 +455,7 @@ def test_three_way_two_entities_existing_section(self): ## Menu items # A message comment. - open-menuitem + open-menuitem = .label = Otwórz download-state-downloading = Pobieranie… ''') diff --git a/tests/syntax/fixtures_behavior/attribute_with_empty_pattern.ftl b/tests/syntax/fixtures_behavior/attribute_with_empty_pattern.ftl index 66e4df3e..c3bd163a 100644 --- a/tests/syntax/fixtures_behavior/attribute_with_empty_pattern.ftl +++ b/tests/syntax/fixtures_behavior/attribute_with_empty_pattern.ftl @@ -1,2 +1,3 @@ key = Value .label = +//~ ERROR E0006, pos 24, args "value" diff --git a/tests/syntax/fixtures_behavior/variant_with_empty_pattern.ftl b/tests/syntax/fixtures_behavior/variant_with_empty_pattern.ftl index 8dfb5bc1..7db7def1 100644 --- a/tests/syntax/fixtures_behavior/variant_with_empty_pattern.ftl +++ b/tests/syntax/fixtures_behavior/variant_with_empty_pattern.ftl @@ -1,3 +1,8 @@ -key = { +key1 = { + *[one] {""} + } + +err1 = { *[one] } +//~ ERROR E0006, pos 51, args "value" diff --git a/tests/syntax/fixtures_structure/attribute_with_empty_pattern.ftl b/tests/syntax/fixtures_structure/attribute_with_empty_pattern.ftl new file mode 100644 index 00000000..aba7c426 --- /dev/null +++ b/tests/syntax/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/tests/syntax/fixtures_structure/attribute_with_empty_pattern.json b/tests/syntax/fixtures_structure/attribute_with_empty_pattern.json new file mode 100644 index 00000000..7e1fde35 --- /dev/null +++ b/tests/syntax/fixtures_structure/attribute_with_empty_pattern.json @@ -0,0 +1,130 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "args": [ + "value" + ], + "message": "Expected field: \"value\"", + "span": { + "type": "Span", + "start": 26, + "end": 26 + } + } + ], + "content": "key1 = Value 1\n .attr =\n\n", + "span": { + "type": "Span", + "start": 0, + "end": 28 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "args": [ + "value" + ], + "message": "Expected field: \"value\"", + "span": { + "type": "Span", + "start": 46, + "end": 46 + } + } + ], + "content": "key2 =\n .attr =\n\n", + "span": { + "type": "Span", + "start": 28, + "end": 48 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "args": [ + "value" + ], + "message": "Expected field: \"value\"", + "span": { + "type": "Span", + "start": 87, + "end": 87 + } + } + ], + "content": "key3 =\n .attr1 = Attr 1\n .attr2 =\n\n", + "span": { + "type": "Span", + "start": 48, + "end": 89 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "args": [ + "value" + ], + "message": "Expected field: \"value\"", + "span": { + "type": "Span", + "start": 108, + "end": 108 + } + } + ], + "content": "key4 =\n .attr1 =\n .attr2 = Attr 2\n\n", + "span": { + "type": "Span", + "start": 89, + "end": 130 + } + }, + { + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0006", + "args": [ + "value" + ], + "message": "Expected field: \"value\"", + "span": { + "type": "Span", + "start": 149, + "end": 149 + } + } + ], + "content": "key5 =\n .attr1 =\n .attr2 =\n", + "span": { + "type": "Span", + "start": 130, + "end": 163 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 163 + } +} diff --git a/tests/syntax/fixtures_structure/elements_indent.json b/tests/syntax/fixtures_structure/elements_indent.json index 131459a3..584cccf3 100644 --- a/tests/syntax/fixtures_structure/elements_indent.json +++ b/tests/syntax/fixtures_structure/elements_indent.json @@ -126,7 +126,7 @@ }, "span": { "type": "Span", - "start": 42, + "start": 37, "end": 61 } } diff --git a/tests/syntax/fixtures_structure/message_with_empty_pattern.json b/tests/syntax/fixtures_structure/message_with_empty_pattern.json index 072d6168..65b2e1f6 100644 --- a/tests/syntax/fixtures_structure/message_with_empty_pattern.json +++ b/tests/syntax/fixtures_structure/message_with_empty_pattern.json @@ -2,28 +2,23 @@ "type": "Resource", "body": [ { - "type": "Message", - "annotations": [], - "id": { - "type": "Identifier", - "name": "foo", - "span": { - "type": "Span", - "start": 0, - "end": 3 + "type": "Junk", + "annotations": [ + { + "type": "Annotation", + "code": "E0005", + "args": [ + "foo" + ], + "message": "Expected entry \"foo\" to have a value or attributes", + "span": { + "type": "Span", + "start": 5, + "end": 5 + } } - }, - "value": { - "type": "Pattern", - "elements": [], - "span": { - "type": "Span", - "start": 7, - "end": 7 - } - }, - "attributes": [], - "comment": null, + ], + "content": "foo = \n", "span": { "type": "Span", "start": 0, diff --git a/tests/syntax/fixtures_structure/placeable_at_eol.json b/tests/syntax/fixtures_structure/placeable_at_eol.json index 6b441ea4..15c820f6 100644 --- a/tests/syntax/fixtures_structure/placeable_at_eol.json +++ b/tests/syntax/fixtures_structure/placeable_at_eol.json @@ -62,7 +62,7 @@ ], "span": { "type": "Span", - "start": 7, + "start": 11, "end": 131 } }, @@ -126,7 +126,7 @@ ], "span": { "type": "Span", - "start": 140, + "start": 144, "end": 184 } }, diff --git a/tests/syntax/fixtures_structure/private_message.json b/tests/syntax/fixtures_structure/private_message.json index dfac5c8b..8c72dba7 100644 --- a/tests/syntax/fixtures_structure/private_message.json +++ b/tests/syntax/fixtures_structure/private_message.json @@ -55,7 +55,7 @@ "default": true, "span": { "type": "Span", - "start": 27, + "start": 19, "end": 48 } }, @@ -92,7 +92,7 @@ "default": false, "span": { "type": "Span", - "start": 57, + "start": 48, "end": 78 } } @@ -112,7 +112,7 @@ ], "span": { "type": "Span", - "start": 14, + "start": 18, "end": 84 } }, @@ -149,7 +149,7 @@ }, "span": { "type": "Span", - "start": 89, + "start": 84, "end": 108 } } @@ -231,7 +231,7 @@ ], "span": { "type": "Span", - "start": 127, + "start": 131, "end": 171 } }, @@ -347,7 +347,7 @@ "default": false, "span": { "type": "Span", - "start": 229, + "start": 220, "end": 289 } }, @@ -409,7 +409,7 @@ "default": false, "span": { "type": "Span", - "start": 298, + "start": 289, "end": 358 } }, @@ -480,7 +480,7 @@ "default": true, "span": { "type": "Span", - "start": 366, + "start": 358, "end": 431 } } @@ -500,7 +500,7 @@ ], "span": { "type": "Span", - "start": 193, + "start": 197, "end": 437 } }, diff --git a/tests/syntax/fixtures_structure/sparse-messages.json b/tests/syntax/fixtures_structure/sparse-messages.json index fe42d6c1..eda44118 100644 --- a/tests/syntax/fixtures_structure/sparse-messages.json +++ b/tests/syntax/fixtures_structure/sparse-messages.json @@ -28,7 +28,7 @@ ], "span": { "type": "Span", - "start": 8, + "start": 12, "end": 17 } }, @@ -86,7 +86,7 @@ }, "span": { "type": "Span", - "start": 30, + "start": 23, "end": 47 } } @@ -125,7 +125,7 @@ ], "span": { "type": "Span", - "start": 57, + "start": 61, "end": 102 } }, @@ -162,7 +162,7 @@ }, "span": { "type": "Span", - "start": 110, + "start": 102, "end": 125 } } @@ -267,7 +267,7 @@ "default": false, "span": { "type": "Span", - "start": 164, + "start": 152, "end": 173 } }, @@ -304,7 +304,7 @@ "default": true, "span": { "type": "Span", - "start": 186, + "start": 173, "end": 196 } } diff --git a/tests/syntax/fixtures_structure/syntax_zero_four.ftl b/tests/syntax/fixtures_structure/syntax_zero_four.ftl new file mode 100644 index 00000000..2bf51ae7 --- /dev/null +++ b/tests/syntax/fixtures_structure/syntax_zero_four.ftl @@ -0,0 +1,6 @@ +key1 + .attr1 = Attr 1 + +key2 + .attr1 = Attr 1 + .attr2 = Attr 2 diff --git a/tests/syntax/fixtures_structure/syntax_zero_four.json b/tests/syntax/fixtures_structure/syntax_zero_four.json new file mode 100644 index 00000000..9fef17ae --- /dev/null +++ b/tests/syntax/fixtures_structure/syntax_zero_four.json @@ -0,0 +1,162 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 10, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr 1", + "span": { + "type": "Span", + "start": 18, + "end": 24 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 24 + } + }, + "span": { + "type": "Span", + "start": 4, + "end": 24 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 24 + } + }, + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key2", + "span": { + "type": "Span", + "start": 26, + "end": 30 + } + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr1", + "span": { + "type": "Span", + "start": 36, + "end": 41 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr 1", + "span": { + "type": "Span", + "start": 44, + "end": 50 + } + } + ], + "span": { + "type": "Span", + "start": 44, + "end": 50 + } + }, + "span": { + "type": "Span", + "start": 30, + "end": 50 + } + }, + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 56, + "end": 61 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Attr 2", + "span": { + "type": "Span", + "start": 64, + "end": 70 + } + } + ], + "span": { + "type": "Span", + "start": 64, + "end": 70 + } + }, + "span": { + "type": "Span", + "start": 50, + "end": 70 + } + } + ], + "comment": null, + "span": { + "type": "Span", + "start": 26, + "end": 70 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 71 + } +} diff --git a/tests/syntax/fixtures_structure/variant_with_empty_pattern.ftl b/tests/syntax/fixtures_structure/variant_with_empty_pattern.ftl new file mode 100644 index 00000000..75a54b20 --- /dev/null +++ b/tests/syntax/fixtures_structure/variant_with_empty_pattern.ftl @@ -0,0 +1,4 @@ +key1 = + { 1 -> + *[one] {""} + } diff --git a/tests/syntax/fixtures_structure/variant_with_empty_pattern.json b/tests/syntax/fixtures_structure/variant_with_empty_pattern.json new file mode 100644 index 00000000..799cf0ef --- /dev/null +++ b/tests/syntax/fixtures_structure/variant_with_empty_pattern.json @@ -0,0 +1,112 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "annotations": [], + "id": { + "type": "Identifier", + "name": "key1", + "span": { + "type": "Span", + "start": 0, + "end": 4 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "expression": { + "type": "NumberExpression", + "value": "1", + "span": { + "type": "Span", + "start": 13, + "end": 14 + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "VariantName", + "name": "one", + "span": { + "type": "Span", + "start": 27, + "end": 30 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringExpression", + "value": "", + "span": { + "type": "Span", + "start": 33, + "end": 35 + } + }, + "span": { + "type": "Span", + "start": 32, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 32, + "end": 36 + } + }, + "default": true, + "span": { + "type": "Span", + "start": 17, + "end": 36 + } + } + ], + "span": { + "type": "Span", + "start": 12, + "end": 41 + } + }, + "span": { + "type": "Span", + "start": 11, + "end": 42 + } + } + ], + "span": { + "type": "Span", + "start": 11, + "end": 42 + } + }, + "attributes": [], + "comment": null, + "span": { + "type": "Span", + "start": 0, + "end": 42 + } + } + ], + "span": { + "type": "Span", + "start": 0, + "end": 43 + } +} diff --git a/tests/syntax/test_serializer.py b/tests/syntax/test_serializer.py index 7cb9f052..5114dc26 100644 --- a/tests/syntax/test_serializer.py +++ b/tests/syntax/test_serializer.py @@ -119,28 +119,52 @@ def test_multiline_with_placeable(self): """ self.assertEqual(pretty_ftl(input), dedent_ftl(input)) - def test_attribute(self): + def test_attribute_syntax_zero_four(self): input = """\ foo .attr = Foo Attr """ + output = """\ + foo = + .attr = Foo Attr + """ + self.assertEqual(pretty_ftl(input), dedent_ftl(output)) + + def test_attribute(self): + input = """\ + foo = + .attr = Foo Attr + """ self.assertEqual(pretty_ftl(input), dedent_ftl(input)) def test_attribute_multiline(self): input = """\ - foo + foo = .attr = Foo Attr Continued """ self.assertEqual(pretty_ftl(input), dedent_ftl(input)) - def test_two_attributes(self): + def test_two_attributes_syntax_zero_four(self): input = """\ foo .attr-a = Foo Attr A .attr-b = Foo Attr B """ + output = """\ + foo = + .attr-a = Foo Attr A + .attr-b = Foo Attr B + """ + self.assertEqual(pretty_ftl(input), dedent_ftl(output)) + + def test_two_attributes(self): + input = """\ + foo = + .attr-a = Foo Attr A + .attr-b = Foo Attr B + """ self.assertEqual(pretty_ftl(input), dedent_ftl(input)) def test_value_and_attributes(self):