Skip to content

Commit dce474a

Browse files
committed
Forbid uppercase Message identifiers
1 parent 741be04 commit dce474a

File tree

7 files changed

+93
-62
lines changed

7 files changed

+93
-62
lines changed

spec/fluent.ebnf

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,31 +55,33 @@ block_placeable ::= blank_block blank_inline? inline_placeable
5555
* syntax/abstract.mjs. */
5656
InlineExpression ::= StringLiteral
5757
| NumberLiteral
58-
| VariableReference
5958
| CallExpression
6059
| AttributeExpression
6160
| VariantExpression
62-
| MessageReference
63-
| TermReference
61+
| ReferenceExpression
6462
| inline_placeable
6563

6664
/* Literals */
6765
StringLiteral ::= "\"" quoted_char* "\""
6866
NumberLiteral ::= "-"? digit+ ("." digit+)?
6967

70-
/* Inline Expressions */
68+
/* Reference Expressions */
69+
ReferenceExpression ::= FunctionReference
70+
| MessageReference
71+
| TermReference
72+
| VariableReference
7173
MessageReference ::= Identifier
7274
TermReference ::= "-" Identifier
7375
VariableReference ::= "$" Identifier
7476
FunctionReference ::= Identifier
75-
CallExpression ::= Callee blank? "(" blank? argument_list blank? ")"
76-
Callee ::= FunctionReference
77-
| TermReference
77+
78+
/* Complex Expressions */
79+
CallExpression ::= ReferenceExpression blank? "(" blank? argument_list blank? ")"
7880
argument_list ::= (Argument blank? "," blank?)* Argument?
7981
Argument ::= NamedArgument
8082
| InlineExpression
8183
NamedArgument ::= Identifier blank? ":" blank? (StringLiteral | NumberLiteral)
82-
AttributeExpression ::= (MessageReference | TermReference) "." Identifier
84+
AttributeExpression ::= ReferenceExpression "." Identifier
8385
/* DEPRECATION NOTICE VariantExpressions have been deprecated in Syntax 0.8. */
8486
VariantExpression ::= TermReference VariantKey
8587

syntax/abstract.mjs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,20 @@
88
import * as FTL from "./ast.mjs";
99
import {always, never} from "../lib/combinators.mjs";
1010

11+
const VALID_FUNCTION_NAME = /^[A-Z][A-Z0-9_?-]*$/;
12+
1113
export function list_into(Type) {
1214
switch (Type) {
15+
case FTL.AttributeExpression:
16+
return ([ref, name]) => {
17+
let ref_is_valid =
18+
ref instanceof FTL.MessageReference
19+
|| ref instanceof FTL.TermReference;
20+
if (ref_is_valid) {
21+
return always(new Type(ref, name));
22+
}
23+
return never(`Invalid ref type: ${ref.type}.`);
24+
};
1325
case FTL.Comment:
1426
return ([sigil, content = ""]) => {
1527
switch (sigil) {
@@ -20,11 +32,17 @@ export function list_into(Type) {
2032
case "###":
2133
return always(new FTL.ResourceComment(content));
2234
default:
23-
return never(`Unknown comment sigil: ${sigil}`);
35+
return never(`Unknown comment sigil: ${sigil}.`);
2436
}
2537
};
2638
case FTL.CallExpression:
2739
return ([callee, args]) => {
40+
let callee_is_valid =
41+
callee instanceof FTL.FunctionReference
42+
|| callee instanceof FTL.TermReference;
43+
if (!callee_is_valid) {
44+
return never(`Invalid callee type: ${callee.type}.`);
45+
}
2846
let positional_args = [];
2947
let named_map = new Map();
3048
for (let arg of args) {
@@ -45,6 +63,16 @@ export function list_into(Type) {
4563
let named_args = Array.from(named_map.values());
4664
return always(new Type(callee, positional_args, named_args));
4765
};
66+
case FTL.Message:
67+
return ([identifier, value, attributes]) => {
68+
if (VALID_FUNCTION_NAME.test(identifier.name)) {
69+
return never(
70+
`Invalid message identifier: ${identifier.name}. `
71+
+ "Message identifiers must not be all uppercase "
72+
+ "ASCII letters.");
73+
}
74+
return always(new Type(identifier, value, attributes));
75+
};
4876
case FTL.Pattern:
4977
return elements =>
5078
always(new FTL.Pattern(
@@ -64,24 +92,27 @@ export function list_into(Type) {
6492
.filter(remove_blank_lines)));
6593
case FTL.SelectExpression:
6694
return ([selector, variants]) => {
67-
let invalid_selector_found =
68-
selector instanceof FTL.MessageReference
69-
|| selector instanceof FTL.TermReference
95+
let selector_is_valid =
96+
selector instanceof FTL.StringLiteral
97+
|| selector instanceof FTL.NumberLiteral
98+
|| selector instanceof FTL.VariableReference
7099
|| (selector instanceof FTL.CallExpression
71-
&& selector.callee instanceof FTL.TermReference)
72-
|| selector instanceof FTL.VariantExpression
100+
&& selector.callee instanceof FTL.FunctionReference)
73101
|| (selector instanceof FTL.AttributeExpression
74-
&& selector.ref instanceof FTL.MessageReference);
75-
if (invalid_selector_found) {
102+
&& selector.ref instanceof FTL.TermReference);
103+
if (!selector_is_valid) {
76104
return never(`Invalid selector type: ${selector.type}.`);
77105
}
106+
107+
// DEPRECATED
78108
let invalid_variants_found = variants.some(
79109
variant => variant.value instanceof FTL.VariantList);
80110
if (invalid_variants_found) {
81111
return never(
82112
"VariantLists are only allowed inside of " +
83113
"other VariantLists.");
84114
}
115+
85116
return always(new Type(selector, variants));
86117
};
87118
case FTL.VariantList:
@@ -96,20 +127,20 @@ export function list_into(Type) {
96127
export function into(Type) {
97128
switch (Type) {
98129
case FTL.FunctionReference:
99-
const VALID_FUNCTION_NAME = /^[A-Z][A-Z0-9_?-]*$/;
100130
return identifier => {
101-
if (!VALID_FUNCTION_NAME.test(identifier.name)) {
102-
return never(
103-
`Invalid function name: ${identifier.name}. ` +
104-
"Function names must be upper-case.");
131+
if (VALID_FUNCTION_NAME.test(identifier.name)) {
132+
return always(new Type(identifier));
105133
}
106-
return always(new Type(identifier));
134+
return never(
135+
`Invalid function name: ${identifier.name}. `
136+
+ "Function names must be all upper-case ASCII letters.");
107137
};
108138
case FTL.Placeable:
109139
return expression => {
110140
let invalid_expression_found =
111-
expression instanceof FTL.AttributeExpression
112-
&& expression.ref instanceof FTL.TermReference;
141+
expression instanceof FTL.FunctionReference
142+
|| (expression instanceof FTL.AttributeExpression
143+
&& expression.ref instanceof FTL.TermReference);
113144
if (invalid_expression_found) {
114145
return never(
115146
`Invalid expression type: ${expression.type}.`);

syntax/grammar.mjs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,10 @@ let InlineExpression = defer(() =>
206206
either(
207207
StringLiteral,
208208
NumberLiteral,
209-
VariableReference,
210-
CallExpression, // Must be before MessageReference
209+
CallExpression,
211210
AttributeExpression,
212211
VariantExpression,
213-
MessageReference,
214-
TermReference,
212+
ReferenceExpression,
215213
inline_placeable));
216214

217215
/* -------- */
@@ -237,8 +235,15 @@ let NumberLiteral = defer(() =>
237235
.map(join)
238236
.chain(into(FTL.NumberLiteral)));
239237

240-
/* ------------------ */
241-
/* Inline Expressions */
238+
/* --------------------- */
239+
/* Reference Expressions */
240+
let ReferenceExpression = defer(() =>
241+
either(
242+
FunctionReference,
243+
MessageReference,
244+
TermReference,
245+
VariableReference));
246+
242247
let MessageReference = defer(() =>
243248
Identifier.chain(into(FTL.MessageReference)));
244249

@@ -259,9 +264,11 @@ let VariableReference = defer(() =>
259264
let FunctionReference = defer(() =>
260265
Identifier.chain(into(FTL.FunctionReference)));
261266

267+
/* ------------------- */
268+
/* Complex Expressions */
262269
let CallExpression = defer(() =>
263270
sequence(
264-
Callee.abstract,
271+
ReferenceExpression.abstract,
265272
maybe(blank),
266273
string("("),
267274
maybe(blank),
@@ -271,11 +278,6 @@ let CallExpression = defer(() =>
271278
.map(keep_abstract)
272279
.chain(list_into(FTL.CallExpression)));
273280

274-
let Callee =
275-
either(
276-
FunctionReference,
277-
TermReference);
278-
279281
let argument_list = defer(() =>
280282
sequence(
281283
repeat(
@@ -307,9 +309,7 @@ let NamedArgument = defer(() =>
307309

308310
let AttributeExpression = defer(() =>
309311
sequence(
310-
either(
311-
MessageReference,
312-
TermReference).abstract,
312+
ReferenceExpression.abstract,
313313
string("."),
314314
Identifier.abstract)
315315
.map(keep_abstract)

test/fixtures/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ key07 =
2525
2626
# JUNK Missing =
2727
key08
28+
29+
# JUNK Message names must not be all uppercase ASCII letters.
30+
KEY09 = Value 09

test/fixtures/messages.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,16 @@
243243
{
244244
"type": "Junk",
245245
"annotations": [],
246-
"content": "key08\n"
246+
"content": "key08\n\n"
247+
},
248+
{
249+
"type": "Comment",
250+
"content": "JUNK Message names must not be all uppercase ASCII letters."
251+
},
252+
{
253+
"type": "Junk",
254+
"annotations": [],
255+
"content": "KEY09 = Value 09"
247256
}
248257
]
249258
}

test/fixtures/reference_expressions.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ message-reference = {msg}
22
term-reference = {-term}
33
variable-reference = {$var}
44
5+
# ERROR Function references are invalid outside of call expressions.
56
not-call-expression = {FUN}

test/fixtures/reference_expressions.json

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -74,28 +74,13 @@
7474
"comment": null
7575
},
7676
{
77-
"type": "Message",
78-
"id": {
79-
"type": "Identifier",
80-
"name": "not-call-expression"
81-
},
82-
"value": {
83-
"type": "Pattern",
84-
"elements": [
85-
{
86-
"type": "Placeable",
87-
"expression": {
88-
"type": "MessageReference",
89-
"id": {
90-
"type": "Identifier",
91-
"name": "FUN"
92-
}
93-
}
94-
}
95-
]
96-
},
97-
"attributes": [],
98-
"comment": null
77+
"type": "Comment",
78+
"content": "ERROR Function references are invalid outside of call expressions."
79+
},
80+
{
81+
"type": "Junk",
82+
"annotations": [],
83+
"content": "not-call-expression = {FUN}\n"
9984
}
10085
]
10186
}

0 commit comments

Comments
 (0)