Skip to content

Commit 030ecf3

Browse files
feat(mf2): Support escapes for all of {|} in text and literals (unicode-org/message-format-wg#743)
1 parent 3498741 commit 030ecf3

File tree

3 files changed

+38
-63
lines changed

3 files changed

+38
-63
lines changed

packages/mf2-messageformat/src/cst/values.ts

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function parseText(ctx: ParseContext, start: number): CST.Text {
1414
loop: for (; i < ctx.source.length; ++i) {
1515
switch (ctx.source[i]) {
1616
case '\\': {
17-
const esc = parseEscape(ctx, 'text', i);
17+
const esc = parseEscape(ctx, i);
1818
if (esc) {
1919
value += ctx.source.substring(pos, i) + esc.value;
2020
i += esc.length;
@@ -80,7 +80,7 @@ export function parseQuotedLiteral(
8080
for (let i = pos; i < ctx.source.length; ++i) {
8181
switch (ctx.source[i]) {
8282
case '\\': {
83-
const esc = parseEscape(ctx, 'literal', i);
83+
const esc = parseEscape(ctx, i);
8484
if (esc) {
8585
value += ctx.source.substring(pos, i) + esc.value;
8686
i += esc.length;
@@ -142,54 +142,42 @@ export function parseVariable(
142142

143143
function parseEscape(
144144
ctx: ParseContext,
145-
scope: 'text' | 'literal',
146145
start: number
147146
): { value: string; length: number } | null {
148147
const raw = ctx.source[start + 1];
149-
switch (raw) {
150-
case '\\':
151-
return { value: raw, length: 1 };
152-
case '{':
153-
case '}':
154-
if (scope === 'text') return { value: raw, length: 1 };
155-
break;
156-
case '|':
157-
if (scope === 'literal') return { value: raw, length: 1 };
158-
break;
159-
default:
160-
if (ctx.resource) {
161-
let hexLen = 0;
162-
switch (raw) {
163-
case '\t':
164-
case ' ':
165-
return { value: raw, length: 1 };
166-
case 'n':
167-
return { value: '\n', length: 1 };
168-
case 'r':
169-
return { value: '\r', length: 1 };
170-
case 't':
171-
return { value: '\t', length: 1 };
172-
case 'u':
173-
hexLen = 4;
174-
break;
175-
case 'U':
176-
hexLen = 6;
177-
break;
178-
case 'x':
179-
hexLen = 2;
180-
break;
181-
}
182-
if (hexLen > 0) {
183-
const h0 = start + 2;
184-
const raw = ctx.source.substring(h0, h0 + hexLen);
185-
if (raw.length === hexLen && /^[0-9A-Fa-f]+$/.test(raw)) {
186-
return {
187-
value: String.fromCharCode(parseInt(raw, 16)),
188-
length: 1 + hexLen
189-
};
190-
}
191-
}
148+
if ('\\{|}'.includes(raw)) return { value: raw, length: 1 };
149+
if (ctx.resource) {
150+
let hexLen = 0;
151+
switch (raw) {
152+
case '\t':
153+
case ' ':
154+
return { value: raw, length: 1 };
155+
case 'n':
156+
return { value: '\n', length: 1 };
157+
case 'r':
158+
return { value: '\r', length: 1 };
159+
case 't':
160+
return { value: '\t', length: 1 };
161+
case 'u':
162+
hexLen = 4;
163+
break;
164+
case 'U':
165+
hexLen = 6;
166+
break;
167+
case 'x':
168+
hexLen = 2;
169+
break;
170+
}
171+
if (hexLen > 0) {
172+
const h0 = start + 2;
173+
const raw = ctx.source.substring(h0, h0 + hexLen);
174+
if (raw.length === hexLen && /^[0-9A-Fa-f]+$/.test(raw)) {
175+
return {
176+
value: String.fromCharCode(parseInt(raw, 16)),
177+
length: 1 + hexLen
178+
};
192179
}
180+
}
193181
}
194182
ctx.onError('bad-escape', start, start + 2);
195183
return null;

packages/mf2-messageformat/src/data-model/literal.test.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,11 @@ describe('quoted literals', () => {
1919
});
2020

2121
test('spaces, newlines and escapes', () => {
22-
const res = resolve('{| quoted \n \\\\\\|literal\\\\\\||}');
22+
const res = resolve('{| quoted \n \\\\\\|literal\\\\\\|\\{\\}|}');
2323
expect(res).toMatchObject([
24-
{ type: 'string', value: ' quoted \n \\|literal\\|' }
24+
{ type: 'string', value: ' quoted \n \\|literal\\|{}' }
2525
]);
2626
});
27-
28-
test('invalid escapes', () => {
29-
expect(
30-
() => new MessageFormat(undefined, '{|quoted \\}iteral|}')
31-
).toThrow();
32-
expect(
33-
() => new MessageFormat(undefined, '{|quoted \\{iteral|}')
34-
).toThrow();
35-
});
3627
});
3728

3829
describe('unquoted numbers', () => {

packages/mf2-messageformat/src/data-model/parse.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,7 @@ function text(): string {
377377
switch (source[i]) {
378378
case '\\': {
379379
const esc = source[i + 1];
380-
if (esc !== '\\' && esc !== '{' && esc !== '}') {
381-
throw SyntaxError('bad-escape', i, i + 2);
382-
}
380+
if (!'\\{|}'.includes(esc)) throw SyntaxError('bad-escape', i, i + 2);
383381
value += source.substring(pos, i) + esc;
384382
i += 1;
385383
pos = i + 1;
@@ -430,9 +428,7 @@ function quotedLiteral(): Model.Literal {
430428
switch (source[i]) {
431429
case '\\': {
432430
const esc = source[i + 1];
433-
if (esc !== '\\' && esc !== '|') {
434-
throw SyntaxError('bad-escape', i, i + 2);
435-
}
431+
if (!'\\{|}'.includes(esc)) throw SyntaxError('bad-escape', i, i + 2);
436432
value += source.substring(pos, i) + esc;
437433
i += 1;
438434
pos = i + 1;

0 commit comments

Comments
 (0)