Skip to content

Commit bd23dd8

Browse files
committed
Require = after the id (but not really)
In Fluent Syntax 0.5, the identifier must be followed by an equals sign (=) even if the message doesn't have a value. key = .attribute = Attribute Value For some time fluent.js will allow both the new (0.5) and the old 0.4 syntax, which means that the following will still parse but the Serializer will insert the = after the key. key .attribute = Attribute Value This patch also fixes Bug 1406880 - Decide where multiline Pattern spans should start. The patterns now start when their first key = Value ^---- Pattern span start Lastly, this patch forbids null variant and attribute values in the tooling parser. Empty values should be written with the {""} idiom: key = { *[valid] {""} [invalid] } The runtime parser parses them as {val: null} for performance reasons.
1 parent 0b8ba5f commit bd23dd8

29 files changed

+695
-145
lines changed

fluent-react/examples/text-input/src/l10n.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ const MESSAGES_ALL = {
66
'pl': `
77
hello = Cześć { $username }!
88
hello-no-name = Witaj nieznajomy!
9-
type-name
9+
type-name =
1010
.placeholder = Twoje imię
1111
`,
1212
'en-US': `
1313
hello = Hello, { $username }!
1414
hello-no-name = Hello, stranger!
15-
type-name
15+
type-name =
1616
.placeholder = Your name
1717
`,
1818
};

fluent-syntax/src/ftlstream.js

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@ import { ParseError } from './errors';
55
import { includes } from './util';
66

77
const INLINE_WS = [' ', '\t'];
8+
const SPECIAL_LINE_START_CHARS = ['}', '.', '[', '*'];
89

910
export class FTLParserStream extends ParserStream {
11+
skipInlineWS() {
12+
while (this.ch) {
13+
if (!includes(INLINE_WS, this.ch)) {
14+
break;
15+
}
16+
this.next();
17+
}
18+
}
19+
1020
peekInlineWS() {
1121
let ch = this.currentPeek();
1222
while (ch) {
@@ -46,13 +56,9 @@ export class FTLParserStream extends ParserStream {
4656
}
4757
}
4858

49-
skipInlineWS() {
50-
while (this.ch) {
51-
if (!includes(INLINE_WS, this.ch)) {
52-
break;
53-
}
54-
this.next();
55-
}
59+
skipIndent() {
60+
this.skipBlankLines();
61+
this.skipInlineWS();
5662
}
5763

5864
expectChar(ch) {
@@ -125,6 +131,24 @@ export class FTLParserStream extends ParserStream {
125131
return isDigit;
126132
}
127133

134+
isCharPatternStart(ch) {
135+
return !includes(SPECIAL_LINE_START_CHARS, ch);
136+
}
137+
138+
isPeekPatternStart() {
139+
this.peekInlineWS();
140+
141+
const ch = this.currentPeek();
142+
143+
if (ch === '\n') {
144+
return this.isPeekNextLinePatternStart();
145+
}
146+
147+
const isPattern = this.isCharPatternStart(this.currentPeek());
148+
this.resetPeek();
149+
return isPattern;
150+
}
151+
128152
isPeekNextLineZeroFourStyleComment() {
129153
if (!this.currentPeekIs('\n')) {
130154
return false;
@@ -234,7 +258,7 @@ export class FTLParserStream extends ParserStream {
234258
return false;
235259
}
236260

237-
isPeekNextNonBlankLinePattern() {
261+
isPeekNextLinePatternStart() {
238262
if (!this.currentPeekIs('\n')) {
239263
return false;
240264
}
@@ -252,10 +276,7 @@ export class FTLParserStream extends ParserStream {
252276
return false;
253277
}
254278

255-
if (this.currentPeekIs('}') ||
256-
this.currentPeekIs('.') ||
257-
this.currentPeekIs('[') ||
258-
this.currentPeekIs('*')) {
279+
if (!this.isCharPatternStart(this.currentPeek())) {
259280
this.resetPeek();
260281
return false;
261282
}

fluent-syntax/src/parser.js

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export default class FluentParser {
7474
entries.push(entry);
7575
}
7676

77-
ps.skipInlineWS();
7877
ps.skipBlankLines();
7978
}
8079

@@ -258,12 +257,15 @@ export default class FluentParser {
258257
let pattern;
259258
let attrs;
260259

260+
// XXX Syntax 0.4 compatibility.
261+
// XXX Replace with ps.expectChar('=').
261262
if (ps.currentIs('=')) {
262263
ps.next();
263-
ps.skipInlineWS();
264-
ps.skipBlankLines();
265264

266-
pattern = this.getPattern(ps);
265+
if (ps.isPeekPatternStart()) {
266+
ps.skipIndent();
267+
pattern = this.getPattern(ps);
268+
}
267269
}
268270

269271
if (ps.isPeekNextLineAttributeStart()) {
@@ -278,29 +280,27 @@ export default class FluentParser {
278280
}
279281

280282
getAttribute(ps) {
283+
ps.expectIndent();
281284
ps.expectChar('.');
282285

283286
const key = this.getPublicIdentifier(ps);
284287

285288
ps.skipInlineWS();
286289
ps.expectChar('=');
287-
ps.skipInlineWS();
288-
289-
const value = this.getPattern(ps);
290290

291-
if (value === undefined) {
292-
throw new ParseError('E0006', 'value');
291+
if (ps.isPeekPatternStart()) {
292+
ps.skipIndent();
293+
const value = this.getPattern(ps);
294+
return new AST.Attribute(key, value);
293295
}
294296

295-
return new AST.Attribute(key, value);
297+
throw new ParseError('E0006', 'value');
296298
}
297299

298300
getAttributes(ps) {
299301
const attrs = [];
300302

301303
while (true) {
302-
ps.expectIndent();
303-
304304
const attr = this.getAttribute(ps);
305305
attrs.push(attr);
306306

@@ -349,6 +349,8 @@ export default class FluentParser {
349349
}
350350

351351
getVariant(ps, hasDefault) {
352+
ps.expectIndent();
353+
352354
let defaultIndex = false;
353355

354356
if (ps.currentIs('*')) {
@@ -366,24 +368,20 @@ export default class FluentParser {
366368

367369
ps.expectChar(']');
368370

369-
ps.skipInlineWS();
370-
371-
const value = this.getPattern(ps);
372-
373-
if (!value) {
374-
throw new ParseError('E0006', 'value');
371+
if (ps.isPeekPatternStart()) {
372+
ps.skipIndent();
373+
const value = this.getPattern(ps);
374+
return new AST.Variant(key, value, defaultIndex);
375375
}
376376

377-
return new AST.Variant(key, value, defaultIndex);
377+
throw new ParseError('E0006', 'value');
378378
}
379379

380380
getVariants(ps) {
381381
const variants = [];
382382
let hasDefault = false;
383383

384384
while (true) {
385-
ps.expectIndent();
386-
387385
const variant = this.getVariant(ps, hasDefault);
388386

389387
if (variant.default) {
@@ -459,18 +457,12 @@ export default class FluentParser {
459457
const elements = [];
460458
ps.skipInlineWS();
461459

462-
// Special-case: trim leading whitespace and newlines.
463-
if (ps.isPeekNextNonBlankLinePattern()) {
464-
ps.skipBlankLines();
465-
ps.skipInlineWS();
466-
}
467-
468460
let ch;
469461
while ((ch = ps.current())) {
470462

471463
// The end condition for getPattern's while loop is a newline
472464
// which is not followed by a valid pattern continuation.
473-
if (ch === '\n' && !ps.isPeekNextNonBlankLinePattern()) {
465+
if (ch === '\n' && !ps.isPeekNextLinePatternStart()) {
474466
break;
475467
}
476468

@@ -491,13 +483,12 @@ export default class FluentParser {
491483

492484
let ch;
493485
while ((ch = ps.current())) {
494-
495486
if (ch === '{') {
496487
return new AST.TextElement(buffer);
497488
}
498489

499490
if (ch === '\n') {
500-
if (!ps.isPeekNextNonBlankLinePattern()) {
491+
if (!ps.isPeekNextLinePatternStart()) {
501492
return new AST.TextElement(buffer);
502493
}
503494

fluent-syntax/src/serializer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ function serializeMessage(message) {
113113
}
114114

115115
parts.push(serializeIdentifier(message.id));
116+
parts.push(' =');
116117

117118
if (message.value) {
118-
parts.push(' =');
119119
parts.push(serializeValue(message.value));
120120
}
121121

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
key = Value
22
.label =
3+
//~ ERROR E0006, pos 24, args "value"
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
key = {
1+
key1 = {
2+
*[one] {""}
3+
}
4+
5+
err1 = {
26
*[one]
37
}
8+
//~ ERROR E0006, pos 51, args "value"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
key1 = Value 1
2+
.attr =
3+
4+
key2 =
5+
.attr =
6+
7+
key3 =
8+
.attr1 = Attr 1
9+
.attr2 =
10+
11+
key4 =
12+
.attr1 =
13+
.attr2 = Attr 2
14+
15+
key5 =
16+
.attr1 =
17+
.attr2 =

0 commit comments

Comments
 (0)