diff --git a/fluent-syntax/src/ftlstream.js b/fluent-syntax/src/ftlstream.js index 9f269f595..857f921de 100644 --- a/fluent-syntax/src/ftlstream.js +++ b/fluent-syntax/src/ftlstream.js @@ -4,33 +4,34 @@ import { ParserStream } from "./stream"; import { ParseError } from "./errors"; import { includes } from "./util"; -const INLINE_WS = [" ", "\t"]; +const INLINE_WS = " "; +const ANY_WS = [INLINE_WS, "\n"]; const SPECIAL_LINE_START_CHARS = ["}", ".", "[", "*"]; export class FTLParserStream extends ParserStream { - skipInlineWS() { + skipBlankInline() { while (this.ch) { - if (!includes(INLINE_WS, this.ch)) { + if (this.ch !== INLINE_WS) { break; } this.next(); } } - peekInlineWS() { + peekBlankInline() { let ch = this.currentPeek(); while (ch) { - if (!includes(INLINE_WS, ch)) { + if (ch !== INLINE_WS) { break; } ch = this.peek(); } } - skipBlankLines() { + skipBlankBlock() { let lineCount = 0; while (true) { - this.peekInlineWS(); + this.peekBlankInline(); if (this.currentPeekIs("\n")) { this.skipToPeek(); @@ -43,11 +44,11 @@ export class FTLParserStream extends ParserStream { } } - peekBlankLines() { + peekBlankBlock() { while (true) { const lineStart = this.getPeekIndex(); - this.peekInlineWS(); + this.peekBlankInline(); if (this.currentPeekIs("\n")) { this.peek(); @@ -58,9 +59,16 @@ export class FTLParserStream extends ParserStream { } } - skipIndent() { - this.skipBlankLines(); - this.skipInlineWS(); + skipBlank() { + while (includes(ANY_WS, this.ch)) { + this.next(); + } + } + + peekBlank() { + while (includes(ANY_WS, this.currentPeek())) { + this.peek(); + } } expectChar(ch) { @@ -79,9 +87,9 @@ export class FTLParserStream extends ParserStream { expectIndent() { this.expectChar("\n"); - this.skipBlankLines(); + this.skipBlankBlock(); this.expectChar(" "); - this.skipInlineWS(); + this.skipBlankInline(); } expectLineEnd() { @@ -143,16 +151,19 @@ export class FTLParserStream extends ParserStream { return !includes(SPECIAL_LINE_START_CHARS, ch); } - isPeekValueStart() { - this.peekInlineWS(); + isValueStart({skip = true} = {}) { + if (skip === false) throw new Error("Unimplemented"); + + this.peekBlankInline(); const ch = this.currentPeek(); // Inline Patterns may start with any char. if (ch !== undefined && ch !== "\n") { + this.skipToPeek(); return true; } - return this.isPeekNextLineValue(); + return this.isNextLineValue(); } // -1 - any @@ -193,24 +204,13 @@ export class FTLParserStream extends ParserStream { return false; } - this.peek(); - - this.peekBlankLines(); - - const ptr = this.getPeekIndex(); - - this.peekInlineWS(); - - if (this.getPeekIndex() - ptr === 0) { - this.resetPeek(); - return false; - } + this.peekBlank(); if (this.currentPeekIs("*")) { this.peek(); } - if (this.currentPeekIs("[") && !this.peekCharIs("[")) { + if (this.currentPeekIs("[")) { this.resetPeek(); return true; } @@ -218,26 +218,13 @@ export class FTLParserStream extends ParserStream { return false; } - isPeekNextLineAttributeStart() { - if (!this.currentPeekIs("\n")) { - return false; - } - - this.peek(); + isNextLineAttributeStart({skip = true} = {}) { + if (skip === false) throw new Error("Unimplemented"); - this.peekBlankLines(); - - const ptr = this.getPeekIndex(); - - this.peekInlineWS(); - - if (this.getPeekIndex() - ptr === 0) { - this.resetPeek(); - return false; - } + this.peekBlank(); if (this.currentPeekIs(".")) { - this.resetPeek(); + this.skipToPeek(); return true; } @@ -245,30 +232,34 @@ export class FTLParserStream extends ParserStream { return false; } - isPeekNextLineValue() { + isNextLineValue({skip = true} = {}) { if (!this.currentPeekIs("\n")) { return false; } - this.peek(); - - this.peekBlankLines(); + this.peekBlankBlock(); const ptr = this.getPeekIndex(); - this.peekInlineWS(); + this.peekBlankInline(); - if (this.getPeekIndex() - ptr === 0) { - this.resetPeek(); - return false; + if (!this.currentPeekIs("{")) { + if (this.getPeekIndex() - ptr === 0) { + this.resetPeek(); + return false; + } + + if (!this.isCharPatternContinuation(this.currentPeek())) { + this.resetPeek(); + return false; + } } - if (!this.isCharPatternContinuation(this.currentPeek())) { + if (skip) { + this.skipToPeek(); + } else { this.resetPeek(); - return false; } - - this.resetPeek(); return true; } diff --git a/fluent-syntax/src/parser.js b/fluent-syntax/src/parser.js index c3bb59db5..f3b4242c7 100644 --- a/fluent-syntax/src/parser.js +++ b/fluent-syntax/src/parser.js @@ -50,15 +50,15 @@ export default class FluentParser { } parse(source) { - const ps = new FTLParserStream(source); - ps.skipBlankLines(); + const ps = new FTLParserStream(source.replace(/\r\n/g, "\n")); + ps.skipBlankBlock(); const entries = []; let lastComment = null; while (ps.current()) { const entry = this.getEntryOrJunk(ps); - const blankLines = ps.skipBlankLines(); + const blankLines = ps.skipBlankBlock(); // Regular Comments require special logic. Comments may be attached to // Messages or Terms if they are followed immediately by them. However @@ -107,8 +107,8 @@ export default class FluentParser { * themselves, in which case Junk for the invalid comment is returned. */ parseEntry(source) { - const ps = new FTLParserStream(source); - ps.skipBlankLines(); + const ps = new FTLParserStream(source.replace(/\r\n/g, "\n")); + ps.skipBlankBlock(); while (ps.currentIs("#")) { const skipped = this.getEntryOrJunk(ps); @@ -116,7 +116,7 @@ export default class FluentParser { // Don't skip Junk comments. return skipped; } - ps.skipBlankLines(); + ps.skipBlankBlock(); } return this.getEntryOrJunk(ps); @@ -219,17 +219,14 @@ export default class FluentParser { getMessage(ps) { const id = this.getIdentifier(ps); - ps.skipInlineWS(); + ps.skipBlankInline(); ps.expectChar("="); - if (ps.isPeekValueStart()) { - ps.skipIndent(); + if (ps.isValueStart()) { var pattern = this.getPattern(ps); - } else { - ps.skipInlineWS(); } - if (ps.isPeekNextLineAttributeStart()) { + if (ps.isNextLineAttributeStart()) { var attrs = this.getAttributes(ps); } @@ -243,17 +240,17 @@ export default class FluentParser { getTerm(ps) { const id = this.getTermIdentifier(ps); - ps.skipInlineWS(); + ps.skipBlankInline(); ps.expectChar("="); - if (ps.isPeekValueStart()) { - ps.skipIndent(); + if (ps.isValueStart()) { + ps.skipBlankInline(); var value = this.getValue(ps); } else { throw new ParseError("E0006", id.name); } - if (ps.isPeekNextLineAttributeStart()) { + if (ps.isNextLineAttributeStart()) { var attrs = this.getAttributes(ps); } @@ -265,11 +262,11 @@ export default class FluentParser { const key = this.getIdentifier(ps); - ps.skipInlineWS(); + ps.skipBlankInline(); ps.expectChar("="); - if (ps.isPeekValueStart()) { - ps.skipIndent(); + if (ps.isValueStart()) { + ps.skipBlankInline(); const value = this.getPattern(ps); return new AST.Attribute(key, value); } @@ -281,11 +278,10 @@ export default class FluentParser { const attrs = []; while (true) { - ps.expectIndent(); const attr = this.getAttribute(ps); attrs.push(attr); - if (!ps.isPeekNextLineAttributeStart()) { + if (!ps.isNextLineAttributeStart()) { break; } } @@ -340,12 +336,16 @@ export default class FluentParser { ps.expectChar("["); + ps.skipBlank(); + const key = this.getVariantKey(ps); + ps.skipBlank(); + ps.expectChar("]"); - if (ps.isPeekValueStart()) { - ps.skipIndent(); + if (ps.isValueStart()) { + ps.skipBlankInline(); const value = this.getValue(ps); return new AST.Variant(key, value, defaultIndex); } @@ -358,7 +358,6 @@ export default class FluentParser { let hasDefault = false; while (true) { - ps.expectIndent(); const variant = this.getVariant(ps, hasDefault); if (variant.default) { @@ -370,6 +369,7 @@ export default class FluentParser { if (!ps.isPeekNextLineVariantStart()) { break; } + ps.skipBlank(); } if (!hasDefault) { @@ -416,10 +416,12 @@ export default class FluentParser { getValue(ps) { if (ps.currentIs("{")) { ps.peek(); - ps.peekInlineWS(); + ps.peekBlankInline(); if (ps.isPeekNextLineVariantStart()) { return this.getVariantList(ps); } + + ps.resetPeek(); } return this.getPattern(ps); @@ -427,23 +429,26 @@ export default class FluentParser { getVariantList(ps) { ps.expectChar("{"); - ps.skipInlineWS(); + ps.skipBlankInline(); + ps.expectChar("\n"); + ps.skipBlank(); const variants = this.getVariants(ps); - ps.expectIndent(); + ps.expectChar("\n"); + ps.skipBlank(); ps.expectChar("}"); return new AST.VariantList(variants); } getPattern(ps) { const elements = []; - ps.skipInlineWS(); + ps.skipBlankInline(); let ch; while ((ch = ps.current())) { // The end condition for getPattern's while loop is a newline // which is not followed by a valid pattern continuation. - if (ch === "\n" && !ps.isPeekNextLineValue()) { + if (ch === "\n" && !ps.isNextLineValue({skip: false})) { break; } @@ -458,8 +463,11 @@ export default class FluentParser { // Trim trailing whitespace. const lastElement = elements[elements.length - 1]; - if (lastElement.type === "TextElement") { + if (lastElement && lastElement.type === "TextElement") { lastElement.value = lastElement.value.replace(trailingWSRe, ""); + if (lastElement.value.length === 0) { + elements.pop(); + } } return new AST.Pattern(elements); @@ -475,12 +483,12 @@ export default class FluentParser { } if (ch === "\n") { - if (!ps.isPeekNextLineValue()) { + if (!ps.isNextLineValue({skip: false})) { return new AST.TextElement(buffer); } ps.next(); - ps.skipInlineWS(); + ps.skipBlankInline(); // Add the new line to the buffer buffer += ch; @@ -535,11 +543,11 @@ export default class FluentParser { } getExpression(ps) { - ps.skipInlineWS(); + ps.skipBlank(); const selector = this.getSelectorExpression(ps); - ps.skipInlineWS(); + ps.skipBlank(); if (ps.currentIs("-")) { ps.peek(); @@ -565,9 +573,12 @@ export default class FluentParser { ps.next(); ps.next(); - ps.skipInlineWS(); + ps.skipBlankInline(); + ps.expectChar("\n"); + ps.skipBlank(); const variants = this.getVariants(ps); + ps.skipBlank(); if (variants.length === 0) { throw new ParseError("E0011"); @@ -578,14 +589,14 @@ export default class FluentParser { throw new ParseError("E0023"); } - ps.expectIndent(); - return new AST.SelectExpression(selector, variants); } else if (selector.type === "AttributeExpression" && selector.ref.type === "TermReference") { throw new ParseError("E0019"); } + ps.skipBlank(); + return selector; } @@ -652,7 +663,7 @@ export default class FluentParser { getCallArg(ps) { const exp = this.getSelectorExpression(ps); - ps.skipInlineWS(); + ps.skipBlank(); if (ps.current() !== ":") { return exp; @@ -663,7 +674,7 @@ export default class FluentParser { } ps.next(); - ps.skipInlineWS(); + ps.skipBlank(); const val = this.getArgVal(ps); @@ -675,8 +686,7 @@ export default class FluentParser { const named = []; const argumentNames = new Set(); - ps.skipInlineWS(); - ps.skipIndent(); + ps.skipBlank(); while (true) { if (ps.current() === ")") { @@ -696,13 +706,11 @@ export default class FluentParser { positional.push(arg); } - ps.skipInlineWS(); - ps.skipIndent(); + ps.skipBlank(); if (ps.current() === ",") { ps.next(); - ps.skipInlineWS(); - ps.skipIndent(); + ps.skipBlank(); continue; } else { break; diff --git a/fluent-syntax/test/fixtures_behavior/attribute_starts_from_nl.ftl b/fluent-syntax/test/fixtures_behavior/attribute_starts_from_nl.ftl index 5c8191e58..e996c6b51 100644 --- a/fluent-syntax/test/fixtures_behavior/attribute_starts_from_nl.ftl +++ b/fluent-syntax/test/fixtures_behavior/attribute_starts_from_nl.ftl @@ -1,3 +1,2 @@ foo = Value .attr = Value 2 -# ~ERROR E0002, pos 12 diff --git a/fluent-syntax/test/fixtures_behavior/indent.ftl b/fluent-syntax/test/fixtures_behavior/indent.ftl index 053443b79..5c386613c 100644 --- a/fluent-syntax/test/fixtures_behavior/indent.ftl +++ b/fluent-syntax/test/fixtures_behavior/indent.ftl @@ -3,13 +3,9 @@ key2 = { a } -# ~ERROR E0014, pos 20 -# ~ERROR E0003, pos 23, args "=" key3 = { a } -# ~ERROR E0003, pos 36, args "}" key4 = { { a }} -# ~ERROR E0014, pos 48 diff --git a/fluent-syntax/test/fixtures_behavior/placeable_in_placeable.ftl b/fluent-syntax/test/fixtures_behavior/placeable_in_placeable.ftl index 75d8bcbcf..aacdc70fe 100644 --- a/fluent-syntax/test/fixtures_behavior/placeable_in_placeable.ftl +++ b/fluent-syntax/test/fixtures_behavior/placeable_in_placeable.ftl @@ -8,7 +8,7 @@ key2 = { { foo } } # } key4 = { { foo } -# ~ERROR E0003, pos 93, args "}" +# ~ERROR E0003, pos 96, args "}" # key5 = { foo } } diff --git a/fluent-syntax/test/fixtures_behavior/placeable_without_close_bracket.ftl b/fluent-syntax/test/fixtures_behavior/placeable_without_close_bracket.ftl index 517e8ca18..c78e9aa49 100644 --- a/fluent-syntax/test/fixtures_behavior/placeable_without_close_bracket.ftl +++ b/fluent-syntax/test/fixtures_behavior/placeable_without_close_bracket.ftl @@ -1,3 +1,3 @@ key = { $num -# ~ERROR E0003, pos 12, args "}" +# ~ERROR E0003, pos 14, args "}" diff --git a/fluent-syntax/test/fixtures_behavior/second_attribute_starts_from_nl.ftl b/fluent-syntax/test/fixtures_behavior/second_attribute_starts_from_nl.ftl index 0caaa5396..9a0d7f1c3 100644 --- a/fluent-syntax/test/fixtures_behavior/second_attribute_starts_from_nl.ftl +++ b/fluent-syntax/test/fixtures_behavior/second_attribute_starts_from_nl.ftl @@ -1,4 +1,3 @@ key = Value .label = Value .accesskey = K -# ~ERROR E0002, pos 31 diff --git a/fluent-syntax/test/fixtures_behavior/selector_expression_ends_abruptly.ftl b/fluent-syntax/test/fixtures_behavior/selector_expression_ends_abruptly.ftl index 00dc742f3..a11a18144 100644 --- a/fluent-syntax/test/fixtures_behavior/selector_expression_ends_abruptly.ftl +++ b/fluent-syntax/test/fixtures_behavior/selector_expression_ends_abruptly.ftl @@ -1,2 +1,2 @@ key = { $foo -> -# ~ERROR E0003, pos 16, args " " +# ~ERROR E0003, pos 16, args "[" diff --git a/fluent-syntax/test/fixtures_behavior/unclosed_empty_placeable_error.ftl b/fluent-syntax/test/fixtures_behavior/unclosed_empty_placeable_error.ftl index 093903c7e..350b6b0d1 100644 --- a/fluent-syntax/test/fixtures_behavior/unclosed_empty_placeable_error.ftl +++ b/fluent-syntax/test/fixtures_behavior/unclosed_empty_placeable_error.ftl @@ -1,2 +1,2 @@ bar = Bar { -# ~ERROR E0014, pos 11 +# ~ERROR E0014, pos 12 diff --git a/fluent-syntax/test/fixtures_behavior/variant_ends_abruptly.ftl b/fluent-syntax/test/fixtures_behavior/variant_ends_abruptly.ftl index 04fd2ef7a..aaead3d06 100644 --- a/fluent-syntax/test/fixtures_behavior/variant_ends_abruptly.ftl +++ b/fluent-syntax/test/fixtures_behavior/variant_ends_abruptly.ftl @@ -1,3 +1,3 @@ key = { $foo -> *[ -# ~ERROR E0004, pos 22, args "a-zA-Z" +# ~ERROR E0013, pos 23 diff --git a/fluent-syntax/test/fixtures_behavior/variant_lists.ftl b/fluent-syntax/test/fixtures_behavior/variant_lists.ftl index b63088f2b..10f5cab3d 100644 --- a/fluent-syntax/test/fixtures_behavior/variant_lists.ftl +++ b/fluent-syntax/test/fixtures_behavior/variant_lists.ftl @@ -1,10 +1,10 @@ -# ~ERROR E0014, pos 16 +# ~ERROR E0014, pos 25 message1 = { *[one] One } -# ~ERROR E0023, pos 118 +# ~ERROR E0023, pos 123 message2 = { $sel -> *[one] { @@ -24,7 +24,7 @@ message2 = } } -# ~ERROR E0023, pos 313 +# ~ERROR E0023, pos 318 -term3 = { $sel -> *[one] { diff --git a/fluent-syntax/test/fixtures_behavior/variant_starts_from_nl.ftl b/fluent-syntax/test/fixtures_behavior/variant_starts_from_nl.ftl index 927f7d57f..269988645 100644 --- a/fluent-syntax/test/fixtures_behavior/variant_starts_from_nl.ftl +++ b/fluent-syntax/test/fixtures_behavior/variant_starts_from_nl.ftl @@ -1,4 +1,3 @@ -term = { *[one] Value } -# ~ERROR E0014, pos 9 diff --git a/fluent-syntax/test/fixtures_behavior/variant_with_leading_space_in_name.ftl b/fluent-syntax/test/fixtures_behavior/variant_with_leading_space_in_name.ftl index 21a14a087..004f482ed 100644 --- a/fluent-syntax/test/fixtures_behavior/variant_with_leading_space_in_name.ftl +++ b/fluent-syntax/test/fixtures_behavior/variant_with_leading_space_in_name.ftl @@ -1,4 +1,3 @@ -term = { *[ one] Foo } -# ~ERROR E0004, pos 20, args "a-zA-Z" diff --git a/fluent-syntax/test/fixtures_behavior/variant_with_symbol_with_space.ftl b/fluent-syntax/test/fixtures_behavior/variant_with_symbol_with_space.ftl index d11c54ac2..d84611022 100644 --- a/fluent-syntax/test/fixtures_behavior/variant_with_symbol_with_space.ftl +++ b/fluent-syntax/test/fixtures_behavior/variant_with_symbol_with_space.ftl @@ -1,4 +1,4 @@ -# ~ERROR E0003, pos 23, args "]" +# ~ERROR E0003, pos 24, args "]" -term = { *[New York] Nowy Jork } diff --git a/fluent-syntax/test/fixtures_reference/call_expressions.ftl b/fluent-syntax/test/fixtures_reference/call_expressions.ftl index 8df58b2f6..065626422 100644 --- a/fluent-syntax/test/fixtures_reference/call_expressions.ftl +++ b/fluent-syntax/test/fixtures_reference/call_expressions.ftl @@ -30,8 +30,6 @@ empty-multiline-call = {FUN( )} -## Syntax errors for multiline call expressions - unindented-arg-number = {FUN( 1)} @@ -95,8 +93,6 @@ sparse-named-arg = {FUN( )} -## Syntax errors for named arguments - unindented-colon = {FUN( x :1)} diff --git a/fluent-syntax/test/fixtures_reference/call_expressions.json b/fluent-syntax/test/fixtures_reference/call_expressions.json index 7792719e3..ae9029a05 100644 --- a/fluent-syntax/test/fixtures_reference/call_expressions.json +++ b/fluent-syntax/test/fixtures_reference/call_expressions.json @@ -431,48 +431,276 @@ "comment": null }, { - "type": "GroupComment", - "content": "Syntax errors for multiline call expressions" - }, - { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-number = {FUN(\n1)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-number" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "NumberLiteral", + "value": "1" + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-string = {FUN(\n\"a\")}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-string" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "StringLiteral", + "value": "a" + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-msg-ref = {FUN(\nmsg)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-msg-ref" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "msg" + } + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-term-ref = {FUN(\n-msg)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-term-ref" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "TermReference", + "id": { + "type": "Identifier", + "name": "-msg" + } + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-var-ref = {FUN(\n$var)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-var-ref" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "var" + } + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-arg-call = {FUN(\nOTHER())}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-arg-call" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "OTHER" + }, + "positional": [], + "named": [] + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-named-arg = {FUN(\nx:1)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-named-arg" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x" + }, + "value": { + "type": "NumberLiteral", + "value": "1" + } + } + ] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-closing-paren = {FUN(\n x\n)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-closing-paren" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [ + { + "type": "MessageReference", + "id": { + "type": "Identifier", + "name": "x" + } + } + ], + "named": [] + } + } + ] + }, + "attributes": [], + "comment": null }, { "type": "GroupComment", @@ -731,18 +959,80 @@ "comment": null }, { - "type": "GroupComment", - "content": "Syntax errors for named arguments" - }, - { - "type": "Junk", - "annotations": [], - "content": "unindented-colon = {FUN(\n x\n:1)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-colon" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x" + }, + "value": { + "type": "NumberLiteral", + "value": "1" + } + } + ] + } + } + ] + }, + "attributes": [], + "comment": null }, { - "type": "Junk", - "annotations": [], - "content": "unindented-value = {FUN(\n x:\n1)}\n" + "type": "Message", + "id": { + "type": "Identifier", + "name": "unindented-value" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "CallExpression", + "callee": { + "type": "Function", + "name": "FUN" + }, + "positional": [], + "named": [ + { + "type": "NamedArgument", + "name": { + "type": "Identifier", + "name": "x" + }, + "value": { + "type": "NumberLiteral", + "value": "1" + } + } + ] + } + } + ] + }, + "attributes": [], + "comment": null } ] } diff --git a/fluent-syntax/test/fixtures_reference/crlf.ftl b/fluent-syntax/test/fixtures_reference/crlf.ftl new file mode 100644 index 000000000..538d7adc1 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/crlf.ftl @@ -0,0 +1,7 @@ +key01 = Value 01 +key02 = + Value 02 + Continued + +# ERROR (Missing value or attributes) +key03 diff --git a/fluent-syntax/test/fixtures_reference/crlf.json b/fluent-syntax/test/fixtures_reference/crlf.json new file mode 100644 index 000000000..a36838a0b --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/crlf.json @@ -0,0 +1,50 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 01" + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key02" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 02\nContinued" + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR (Missing value or attributes)" + }, + { + "type": "Junk", + "annotations": [], + "content": "key03\n" + } + ] +} diff --git a/fluent-syntax/test/fixtures_reference/leading_dots.ftl b/fluent-syntax/test/fixtures_reference/leading_dots.ftl index 0ce3b4835..0b9d6693a 100644 --- a/fluent-syntax/test/fixtures_reference/leading_dots.ftl +++ b/fluent-syntax/test/fixtures_reference/leading_dots.ftl @@ -35,7 +35,7 @@ key11 = key12 = .accesskey = A - + key13 = .attribute = .Value @@ -63,3 +63,14 @@ key17 = *[one] Value .Continued } + +# JUNK (attr .Value must have a value) +key18 = +.Value + +key19 = +.attribute = Value + Continued + +key20 = +{"."}Value diff --git a/fluent-syntax/test/fixtures_reference/leading_dots.json b/fluent-syntax/test/fixtures_reference/leading_dots.json index 15f579c16..6459cb57c 100644 --- a/fluent-syntax/test/fixtures_reference/leading_dots.json +++ b/fluent-syntax/test/fixtures_reference/leading_dots.json @@ -350,7 +350,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "one" }, "value": { @@ -367,7 +367,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "other" }, "value": { @@ -413,6 +413,67 @@ "type": "Junk", "annotations": [], "content": "key17 =\n { 1 ->\n *[one] Value\n .Continued\n }\n" + }, + { + "type": "Comment", + "content": "JUNK (attr .Value must have a value)" + }, + { + "type": "Junk", + "annotations": [], + "content": "key18 =\n.Value\n" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key19" + }, + "value": null, + "attributes": [ + { + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attribute" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued" + } + ] + } + } + ], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key20" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "StringLiteral", + "value": "." + } + }, + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "attributes": [], + "comment": null } ] } diff --git a/fluent-syntax/test/fixtures_reference/mixed_entries.ftl b/fluent-syntax/test/fixtures_reference/mixed_entries.ftl index 00c628003..99cc023da 100644 --- a/fluent-syntax/test/fixtures_reference/mixed_entries.ftl +++ b/fluent-syntax/test/fixtures_reference/mixed_entries.ftl @@ -17,3 +17,8 @@ key02 = Value # Standalone Comment .attr = Dangling attribute + +# There are 5 spaces on the line between key03 and key04. +key03 = Value 03 + +key04 = Value 04 diff --git a/fluent-syntax/test/fixtures_reference/mixed_entries.json b/fluent-syntax/test/fixtures_reference/mixed_entries.json index 26b7588ca..4e349a5ab 100644 --- a/fluent-syntax/test/fixtures_reference/mixed_entries.json +++ b/fluent-syntax/test/fixtures_reference/mixed_entries.json @@ -92,6 +92,45 @@ "type": "Junk", "annotations": [], "content": " .attr = Dangling attribute\n" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key03" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 03" + } + ] + }, + "attributes": [], + "comment": { + "type": "Comment", + "content": "There are 5 spaces on the line between key03 and key04." + } + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value 04" + } + ] + }, + "attributes": [], + "comment": null } ] } diff --git a/fluent-syntax/test/fixtures_reference/select_expressions.ftl b/fluent-syntax/test/fixtures_reference/select_expressions.ftl index e1c420a18..3c54f57c2 100644 --- a/fluent-syntax/test/fixtures_reference/select_expressions.ftl +++ b/fluent-syntax/test/fixtures_reference/select_expressions.ftl @@ -6,7 +6,7 @@ new-messages = valid-selector = { -term.case -> - *[ many words ] value + *[key] value } # ERROR diff --git a/fluent-syntax/test/fixtures_reference/select_expressions.json b/fluent-syntax/test/fixtures_reference/select_expressions.json index d13fba602..3dac7c79d 100644 --- a/fluent-syntax/test/fixtures_reference/select_expressions.json +++ b/fluent-syntax/test/fixtures_reference/select_expressions.json @@ -44,7 +44,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "other" }, "value": { @@ -104,8 +104,8 @@ { "type": "Variant", "key": { - "type": "VariantName", - "name": "many words" + "type": "Identifier", + "name": "key" }, "value": { "type": "Pattern", @@ -156,7 +156,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "one" }, "value": { @@ -202,7 +202,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "one" }, "value": { @@ -220,7 +220,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "two" }, "value": { diff --git a/fluent-syntax/test/fixtures_reference/select_indent.ftl b/fluent-syntax/test/fixtures_reference/select_indent.ftl new file mode 100644 index 000000000..6c13076b2 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/select_indent.ftl @@ -0,0 +1,95 @@ +select-1tbs-inline = { $selector -> + *[key] Value +} + +select-1tbs-newline = { +$selector -> + *[key] Value +} + +select-1tbs-indent = { + $selector -> + *[key] Value +} + +select-allman-inline = +{ $selector -> + *[key] Value +} + +select-allman-newline = +{ +$selector -> + *[key] Value +} + +select-allman-indent = +{ + $selector -> + *[key] Value +} + +select-gnu-inline = + { $selector -> + *[key] Value + } + +select-gnu-newline = + { +$selector -> + *[key] Value + } + +select-gnu-indent = + { + $selector -> + *[key] Value + } + +select-no-indent = +{ +$selector -> +*[key] Value +[other] Other +} + +select-no-indent-multiline = +{ +$selector -> +*[key] Value + Continued +[other] + Other + Multiline +} + +# ERROR (Multiline text must be indented) +select-no-indent-multiline = { $selector -> + *[key] Value +Continued without indent. +} + +select-flat = +{ +$selector +-> +*[ +key +] Value +[ +other +] Other +} + +# Each line ends with 5 spaces. +select-flat-with-trailing-spaces = +{ +$selector +-> +*[ +key +] Value +[ +other +] Other +} diff --git a/fluent-syntax/test/fixtures_reference/select_indent.json b/fluent-syntax/test/fixtures_reference/select_indent.json new file mode 100644 index 000000000..03109cb0f --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/select_indent.json @@ -0,0 +1,683 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-inline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-newline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-1tbs-indent" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-inline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-newline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-allman-indent" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-inline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-newline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-gnu-indent" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-no-indent" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other" + } + ] + }, + "default": false + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-no-indent-multiline" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value\nContinued" + } + ] + }, + "default": true + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other\nMultiline" + } + ] + }, + "default": false + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR (Multiline text must be indented)" + }, + { + "type": "Junk", + "annotations": [], + "content": "select-no-indent-multiline = { $selector ->\n *[key] Value\nContinued without indent.\n}\n" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-flat" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other" + } + ] + }, + "default": false + } + ] + } + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "select-flat-with-trailing-spaces" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "Placeable", + "expression": { + "type": "SelectExpression", + "selector": { + "type": "VariableReference", + "id": { + "type": "Identifier", + "name": "selector" + } + }, + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other" + } + ] + }, + "default": false + } + ] + } + } + ] + }, + "attributes": [], + "comment": { + "type": "Comment", + "content": "Each line ends with 5 spaces." + } + } + ] +} diff --git a/fluent-syntax/test/fixtures_reference/tab.ftl b/fluent-syntax/test/fixtures_reference/tab.ftl new file mode 100644 index 000000000..4b23ad87c --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/tab.ftl @@ -0,0 +1,14 @@ +# OK (tab after = is part of the value) +key01 = Value 01 + +# Error (tab before =) +key02 = Value 02 + +# Error (tab is not a valid indent) +key03 = + This line isn't properly indented. + +# Partial Error (tab is not a valid indent) +key04 = + This line is indented by 4 spaces, + whereas this line by 1 tab. diff --git a/fluent-syntax/test/fixtures_reference/tab.json b/fluent-syntax/test/fixtures_reference/tab.json new file mode 100644 index 000000000..fe438ab43 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/tab.json @@ -0,0 +1,70 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key01" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "\tValue 01" + } + ] + }, + "attributes": [], + "comment": { + "type": "Comment", + "content": "OK (tab after = is part of the value)" + } + }, + { + "type": "Comment", + "content": "Error (tab before =)" + }, + { + "type": "Junk", + "annotations": [], + "content": "key02\t= Value 02\n" + }, + { + "type": "Comment", + "content": "Error (tab is not a valid indent)" + }, + { + "type": "Junk", + "annotations": [], + "content": "key03 =\n\tThis line isn't properly indented.\n" + }, + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key04" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "This line is indented by 4 spaces," + } + ] + }, + "attributes": [], + "comment": { + "type": "Comment", + "content": "Partial Error (tab is not a valid indent)" + } + }, + { + "type": "Junk", + "annotations": [], + "content": "\twhereas this line by 1 tab.\n" + } + ] +} diff --git a/fluent-syntax/test/fixtures_reference/variant_keys.ftl b/fluent-syntax/test/fixtures_reference/variant_keys.ftl new file mode 100644 index 000000000..7586d524c --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/variant_keys.ftl @@ -0,0 +1,37 @@ +-simple-identifier = + { + *[key] value + } + +-identifier-surrounded-by-whitespace = + { + *[ key ] value + } + +-int-number = + { + *[1] value + } + +-float-number = + { + *[3.14] value + } + +# ERROR +-invalid-identifier = + { + *[two words] value + } + +# ERROR +-invalid-int = + { + *[1 apple] value + } + +# ERROR +-invalid-int = + { + *[3.14 apples] value + } diff --git a/fluent-syntax/test/fixtures_reference/variant_keys.json b/fluent-syntax/test/fixtures_reference/variant_keys.json new file mode 100644 index 000000000..f85abe669 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/variant_keys.json @@ -0,0 +1,156 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-simple-identifier" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-identifier-surrounded-by-whitespace" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-int-number" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "NumberLiteral", + "value": "1" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-float-number" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "NumberLiteral", + "value": "3.14" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Comment", + "content": "ERROR" + }, + { + "type": "Junk", + "annotations": [], + "content": "-invalid-identifier =\n {\n *[two words] value\n }\n" + }, + { + "type": "Comment", + "content": "ERROR" + }, + { + "type": "Junk", + "annotations": [], + "content": "-invalid-int =\n {\n *[1 apple] value\n }\n" + }, + { + "type": "Comment", + "content": "ERROR" + }, + { + "type": "Junk", + "annotations": [], + "content": "-invalid-int =\n {\n *[3.14 apples] value\n }\n" + } + ] +} diff --git a/fluent-syntax/test/fixtures_reference/variant_lists.json b/fluent-syntax/test/fixtures_reference/variant_lists.json index 38f08db19..9713e098e 100644 --- a/fluent-syntax/test/fixtures_reference/variant_lists.json +++ b/fluent-syntax/test/fixtures_reference/variant_lists.json @@ -13,7 +13,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "key" }, "value": { @@ -105,7 +105,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "one" }, "value": { @@ -114,7 +114,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "two" }, "value": { @@ -149,7 +149,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "one" }, "value": { @@ -167,7 +167,7 @@ { "type": "Variant", "key": { - "type": "VariantName", + "type": "Identifier", "name": "two" }, "value": { diff --git a/fluent-syntax/test/fixtures_reference/variants_indent.ftl b/fluent-syntax/test/fixtures_reference/variants_indent.ftl new file mode 100644 index 000000000..38f5a62ed --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/variants_indent.ftl @@ -0,0 +1,19 @@ +-variants-1tbs = { + *[key] Value +} + +-variants-allman = +{ + *[key] Value +} + +-variants-gnu = + { + *[key] Value + } + +-variants-no-indent = +{ +*[key] Value +[other] Other +} diff --git a/fluent-syntax/test/fixtures_reference/variants_indent.json b/fluent-syntax/test/fixtures_reference/variants_indent.json new file mode 100644 index 000000000..ad5c1723a --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/variants_indent.json @@ -0,0 +1,146 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-variants-1tbs" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-variants-allman" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-variants-gnu" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + } + ] + }, + "attributes": [], + "comment": null + }, + { + "type": "Term", + "id": { + "type": "Identifier", + "name": "-variants-no-indent" + }, + "value": { + "type": "VariantList", + "variants": [ + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Value" + } + ] + }, + "default": true + }, + { + "type": "Variant", + "key": { + "type": "Identifier", + "name": "other" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Other" + } + ] + }, + "default": false + } + ] + }, + "attributes": [], + "comment": null + } + ] +} diff --git a/fluent-syntax/test/fixtures_reference/whitespace_in_value.ftl b/fluent-syntax/test/fixtures_reference/whitespace_in_value.ftl new file mode 100644 index 000000000..2fba55350 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/whitespace_in_value.ftl @@ -0,0 +1,10 @@ +# Caution, lines 6 and 7 contain white-space-only lines +key = + first line + + + + + + + last line diff --git a/fluent-syntax/test/fixtures_reference/whitespace_in_value.json b/fluent-syntax/test/fixtures_reference/whitespace_in_value.json new file mode 100644 index 000000000..077e68676 --- /dev/null +++ b/fluent-syntax/test/fixtures_reference/whitespace_in_value.json @@ -0,0 +1,26 @@ +{ + "type": "Resource", + "body": [ + { + "type": "Message", + "id": { + "type": "Identifier", + "name": "key" + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "first line\n\n\n\n\n\n\nlast line" + } + ] + }, + "attributes": [], + "comment": { + "type": "Comment", + "content": "Caution, lines 6 and 7 contain white-space-only lines" + } + } + ] +} diff --git a/fluent-syntax/test/fixtures_structure/elements_indent.json b/fluent-syntax/test/fixtures_structure/elements_indent.json index 469903fca..6a23cc27f 100644 --- a/fluent-syntax/test/fixtures_structure/elements_indent.json +++ b/fluent-syntax/test/fixtures_structure/elements_indent.json @@ -31,34 +31,49 @@ "end": 9 } }, - "attributes": [], - "comment": null, - "span": { - "type": "Span", - "start": 0, - "end": 9 - } - }, - { - "type": "Junk", - "annotations": [ + "attributes": [ { - "type": "Annotation", - "code": "E0002", - "args": [], - "message": "Expected an entry start", + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr", + "span": { + "type": "Span", + "start": 11, + "end": 15 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Foo Attr", + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + } + ], + "span": { + "type": "Span", + "start": 18, + "end": 26 + } + }, "span": { "type": "Span", "start": 10, - "end": 10 + "end": 26 } } ], - "content": ".attr = Foo Attr\n\n", + "comment": null, "span": { "type": "Span", - "start": 10, - "end": 28 + "start": 0, + "end": 26 } }, { @@ -127,35 +142,49 @@ "start": 42, "end": 61 } - } - ], - "comment": null, - "span": { - "type": "Span", - "start": 28, - "end": 61 - } - }, - { - "type": "Junk", - "annotations": [ + }, { - "type": "Annotation", - "code": "E0002", - "args": [], - "message": "Expected an entry start", + "type": "Attribute", + "id": { + "type": "Identifier", + "name": "attr2", + "span": { + "type": "Span", + "start": 63, + "end": 68 + } + }, + "value": { + "type": "Pattern", + "elements": [ + { + "type": "TextElement", + "value": "Bar Attr 2", + "span": { + "type": "Span", + "start": 71, + "end": 81 + } + } + ], + "span": { + "type": "Span", + "start": 71, + "end": 81 + } + }, "span": { "type": "Span", "start": 62, - "end": 62 + "end": 81 } } ], - "content": ".attr2 = Bar Attr 2\n", + "comment": null, "span": { "type": "Span", - "start": 62, - "end": 82 + "start": 28, + "end": 81 } } ], 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 b92463781..eeeb0fa69 100644 --- a/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json +++ b/fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json @@ -46,8 +46,8 @@ "message": "Expected message \"key2\" to have a value or attributes", "span": { "type": "Span", - "start": 344, - "end": 344 + "start": 343, + "end": 343 } } ], diff --git a/fluent-syntax/test/reference_test.js b/fluent-syntax/test/reference_test.js index b846b1717..a6cd8404e 100644 --- a/fluent-syntax/test/reference_test.js +++ b/fluent-syntax/test/reference_test.js @@ -22,14 +22,6 @@ readdir(fixtures, function(err, filenames) { // The following fixtures produce different ASTs in the tooling parser than // in the reference parser. Skip them for now. const skips = [ - // Call arguments edge-cases. - "call_expressions.ftl", - - // The tooling parser rejects variant keys which contain leading whitespace. - // There's even a behavior fixture for this; it must have been a - // deliberate decision. - "select_expressions.ftl", - // Broken Attributes break the entire Entry right now. // https://github.com/projectfluent/fluent.js/issues/237 "leading_dots.ftl",