Skip to content

Commit 9a8294d

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 values. Empty values should be written with the {""} idiom: key = { *[valid] {""} [invalid] }
1 parent 0b8ba5f commit 9a8294d

19 files changed

+482
-117
lines changed

fluent-syntax/src/ftlstream.js

Lines changed: 35 additions & 11 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,12 +56,11 @@ 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();
59+
maybeExpectIndent() {
60+
this.skipInlineWS();
61+
62+
if (this.currentIs('\n')) {
63+
this.expectIndent();
5564
}
5665
}
5766

@@ -125,6 +134,24 @@ export class FTLParserStream extends ParserStream {
125134
return isDigit;
126135
}
127136

137+
isCharPatternStart(ch) {
138+
return !includes(SPECIAL_LINE_START_CHARS, ch);
139+
}
140+
141+
isPeekPatternStart() {
142+
this.peekInlineWS();
143+
144+
const ch = this.currentPeek();
145+
146+
if (ch === '\n') {
147+
return this.isPeekNextLinePatternStart();
148+
}
149+
150+
const isPattern = this.isCharPatternStart(this.currentPeek());
151+
this.resetPeek();
152+
return isPattern;
153+
}
154+
128155
isPeekNextLineZeroFourStyleComment() {
129156
if (!this.currentPeekIs('\n')) {
130157
return false;
@@ -234,7 +261,7 @@ export class FTLParserStream extends ParserStream {
234261
return false;
235262
}
236263

237-
isPeekNextNonBlankLinePattern() {
264+
isPeekNextLinePatternStart() {
238265
if (!this.currentPeekIs('\n')) {
239266
return false;
240267
}
@@ -252,10 +279,7 @@ export class FTLParserStream extends ParserStream {
252279
return false;
253280
}
254281

255-
if (this.currentPeekIs('}') ||
256-
this.currentPeekIs('.') ||
257-
this.currentPeekIs('[') ||
258-
this.currentPeekIs('*')) {
282+
if (!this.isCharPatternStart(this.currentPeek())) {
259283
this.resetPeek();
260284
return false;
261285
}

fluent-syntax/src/parser.js

Lines changed: 13 additions & 19 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.maybeExpectIndent();
267+
pattern = this.getPattern(ps);
268+
}
267269
}
268270

269271
if (ps.isPeekNextLineAttributeStart()) {
@@ -278,13 +280,14 @@ 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();
290+
ps.maybeExpectIndent();
288291

289292
const value = this.getPattern(ps);
290293

@@ -299,8 +302,6 @@ export default class FluentParser {
299302
const attrs = [];
300303

301304
while (true) {
302-
ps.expectIndent();
303-
304305
const attr = this.getAttribute(ps);
305306
attrs.push(attr);
306307

@@ -349,6 +350,8 @@ export default class FluentParser {
349350
}
350351

351352
getVariant(ps, hasDefault) {
353+
ps.expectIndent();
354+
352355
let defaultIndex = false;
353356

354357
if (ps.currentIs('*')) {
@@ -366,7 +369,7 @@ export default class FluentParser {
366369

367370
ps.expectChar(']');
368371

369-
ps.skipInlineWS();
372+
ps.maybeExpectIndent();
370373

371374
const value = this.getPattern(ps);
372375

@@ -382,8 +385,6 @@ export default class FluentParser {
382385
let hasDefault = false;
383386

384387
while (true) {
385-
ps.expectIndent();
386-
387388
const variant = this.getVariant(ps, hasDefault);
388389

389390
if (variant.default) {
@@ -459,18 +460,12 @@ export default class FluentParser {
459460
const elements = [];
460461
ps.skipInlineWS();
461462

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

471466
// The end condition for getPattern's while loop is a newline
472467
// which is not followed by a valid pattern continuation.
473-
if (ch === '\n' && !ps.isPeekNextNonBlankLinePattern()) {
468+
if (ch === '\n' && !ps.isPeekNextLinePatternStart()) {
474469
break;
475470
}
476471

@@ -491,13 +486,12 @@ export default class FluentParser {
491486

492487
let ch;
493488
while ((ch = ps.current())) {
494-
495489
if (ch === '{') {
496490
return new AST.TextElement(buffer);
497491
}
498492

499493
if (ch === '\n') {
500-
if (!ps.isPeekNextNonBlankLinePattern()) {
494+
if (!ps.isPeekNextLinePatternStart()) {
501495
return new AST.TextElement(buffer);
502496
}
503497

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 E0003, pos 25, args " "
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 E0003, pos 58, args " "

fluent-syntax/test/fixtures_structure/elements_indent.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
},
127127
"span": {
128128
"type": "Span",
129-
"start": 42,
129+
"start": 37,
130130
"end": 61
131131
}
132132
}

fluent-syntax/test/fixtures_structure/message_with_empty_pattern.json

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,23 @@
22
"type": "Resource",
33
"body": [
44
{
5-
"type": "Message",
6-
"annotations": [],
7-
"id": {
8-
"type": "Identifier",
9-
"name": "foo",
10-
"span": {
11-
"type": "Span",
12-
"start": 0,
13-
"end": 3
5+
"type": "Junk",
6+
"annotations": [
7+
{
8+
"type": "Annotation",
9+
"code": "E0005",
10+
"args": [
11+
"foo"
12+
],
13+
"message": "Expected entry \"foo\" to have a value or attributes",
14+
"span": {
15+
"type": "Span",
16+
"start": 5,
17+
"end": 5
18+
}
1419
}
15-
},
16-
"value": {
17-
"type": "Pattern",
18-
"elements": [],
19-
"span": {
20-
"type": "Span",
21-
"start": 7,
22-
"end": 7
23-
}
24-
},
25-
"attributes": [],
26-
"comment": null,
20+
],
21+
"content": "foo = \n",
2722
"span": {
2823
"type": "Span",
2924
"start": 0,

fluent-syntax/test/fixtures_structure/placeable_at_eol.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
],
6363
"span": {
6464
"type": "Span",
65-
"start": 7,
65+
"start": 11,
6666
"end": 131
6767
}
6868
},
@@ -126,7 +126,7 @@
126126
],
127127
"span": {
128128
"type": "Span",
129-
"start": 140,
129+
"start": 144,
130130
"end": 184
131131
}
132132
},

fluent-syntax/test/fixtures_structure/private_message.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"default": true,
5656
"span": {
5757
"type": "Span",
58-
"start": 27,
58+
"start": 19,
5959
"end": 48
6060
}
6161
},
@@ -92,7 +92,7 @@
9292
"default": false,
9393
"span": {
9494
"type": "Span",
95-
"start": 57,
95+
"start": 48,
9696
"end": 78
9797
}
9898
}
@@ -112,7 +112,7 @@
112112
],
113113
"span": {
114114
"type": "Span",
115-
"start": 14,
115+
"start": 18,
116116
"end": 84
117117
}
118118
},
@@ -149,7 +149,7 @@
149149
},
150150
"span": {
151151
"type": "Span",
152-
"start": 89,
152+
"start": 84,
153153
"end": 108
154154
}
155155
}
@@ -231,7 +231,7 @@
231231
],
232232
"span": {
233233
"type": "Span",
234-
"start": 127,
234+
"start": 131,
235235
"end": 171
236236
}
237237
},
@@ -347,7 +347,7 @@
347347
"default": false,
348348
"span": {
349349
"type": "Span",
350-
"start": 229,
350+
"start": 220,
351351
"end": 289
352352
}
353353
},
@@ -409,7 +409,7 @@
409409
"default": false,
410410
"span": {
411411
"type": "Span",
412-
"start": 298,
412+
"start": 289,
413413
"end": 358
414414
}
415415
},
@@ -480,7 +480,7 @@
480480
"default": true,
481481
"span": {
482482
"type": "Span",
483-
"start": 366,
483+
"start": 358,
484484
"end": 431
485485
}
486486
}
@@ -500,7 +500,7 @@
500500
],
501501
"span": {
502502
"type": "Span",
503-
"start": 193,
503+
"start": 197,
504504
"end": 437
505505
}
506506
},

0 commit comments

Comments
 (0)