Skip to content

Commit 7ae0e10

Browse files
committed
feat(mf2): Allow number-literal as unquoted value
1 parent c4e8261 commit 7ae0e10

File tree

6 files changed

+52
-35
lines changed

6 files changed

+52
-35
lines changed

packages/mf2-messageformat/src/__fixtures/test-messages.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"exp": "hello world"
1010
},
1111
{
12-
"src": "hello {$place:-.}",
13-
"params": { "place:-.": "world" },
12+
"src": "hello {$place-.}",
13+
"params": { "place-.": "world" },
1414
"exp": "hello world"
1515
},
1616
{
@@ -29,7 +29,9 @@
2929
"params": { "one": 1.3, "two": 4.2 },
3030
"exp": "1,3 et 4,2"
3131
},
32-
{ "src": "hello {|4.2| :number}", "exp": "hello 4.2" },
32+
{ "src": "hello {4.2 :number}", "exp": "hello 4.2" },
33+
{ "src": "hello {-4.20 :number}", "exp": "hello -4.2" },
34+
{ "src": "hello {0.42e+1 :number}", "exp": "hello 4.2" },
3335
{
3436
"src": "hello {foo :number}",
3537
"exp": "hello {|foo|}",
@@ -41,15 +43,15 @@
4143
"errors": [{ "type": "bad-input" }]
4244
},
4345
{
44-
"src": "hello {|4.2| :number minimumFractionDigits=2}",
46+
"src": "hello {4.2 :number minimumFractionDigits=2}",
4547
"exp": "hello 4.20"
4648
},
4749
{
4850
"src": "hello {|4.2| :number minimumFractionDigits=|2|}",
4951
"exp": "hello 4.20"
5052
},
5153
{
52-
"src": "hello {|4.2| :number minimumFractionDigits=$foo}",
54+
"src": "hello {4.2 :number minimumFractionDigits=$foo}",
5355
"params": { "foo": 2 },
5456
"exp": "hello 4.20"
5557
},

packages/mf2-messageformat/src/cst-parser/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ import type * as CST from './cst-types.js';
22
export { CST };
33
export { asDataModel } from './as-data-model.js';
44
export { parseMessage } from './message.js';
5-
export { isValidUnquotedLiteral } from './names.js';

packages/mf2-messageformat/src/cst-parser/names.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,53 @@ const isNameStartCode = (cc: number) =>
2222
(cc >= 0xfdf0 && cc <= 0xfffd) ||
2323
(cc >= 0x10000 && cc <= 0xeffff);
2424

25-
// unquoted-start = name-start / DIGIT / "."
26-
// / %xB7 / %x300-36F / %x203F-2040
27-
const isUnquotedStartCharCode = (cc: number) =>
25+
// name-char = name-start / DIGIT / "-" / "."
26+
// / %xB7 / %x300-36F / %x203F-2040
27+
const isNameCharCode = (cc: number) =>
2828
isNameStartCode(cc) ||
29+
cc === 0x2d || // -
2930
cc === 0x2e || // .
3031
(cc >= 0x30 && cc <= 0x39) || // 0-9
3132
cc === 0xb7 || // ·
3233
(cc >= 0x300 && cc <= 0x36f) ||
3334
cc === 0x203f || // ‿
3435
cc === 0x2040; // ⁀
3536

36-
// name-char = name-start / DIGIT / "-" / "." / ":"
37-
// / %xB7 / %x300-36F / %x203F-2040
38-
const isNameCharCode = (cc: number) =>
39-
isUnquotedStartCharCode(cc) || cc === 0x2d || cc === 0x3a; // - :
37+
// This is sticky so that parsing doesn't need to substring the source
38+
const numberLiteral = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][-+]?\d+)?/y;
39+
40+
export function parseNameValue(src: string, start: number): string {
41+
if (!isNameStartCode(src.charCodeAt(start))) return '';
42+
let pos = start + 1;
43+
while (isNameCharCode(src.charCodeAt(pos))) pos += 1;
44+
return src.substring(start, pos);
45+
}
4046

4147
export function isValidUnquotedLiteral(str: string): boolean {
42-
if (!isUnquotedStartCharCode(str.charCodeAt(0))) return false;
48+
numberLiteral.lastIndex = 0;
49+
const num = numberLiteral.exec(str);
50+
if (num && num[0].length === str.length) return true;
51+
52+
if (!isNameStartCode(str.charCodeAt(0))) return false;
4353
for (let i = 1; i < str.length; ++i) {
4454
const cc = str.charCodeAt(i);
4555
if (!isNameCharCode(cc)) return false;
4656
}
4757
return str.length > 0;
4858
}
4959

50-
// name = name-start *name-char
51-
export function parseNameValue(src: string, start: number): string {
52-
if (!isNameStartCode(src.charCodeAt(start))) return '';
53-
let pos = start + 1;
54-
while (isNameCharCode(src.charCodeAt(pos))) pos += 1;
55-
return src.substring(start, pos);
56-
}
57-
58-
// unquoted = unquoted-start *name-char
5960
export function parseUnquotedLiteralValue(
60-
ctx: ParseContext,
61+
{ source }: ParseContext,
6162
start: number
6263
): string {
64+
numberLiteral.lastIndex = start;
65+
const num = numberLiteral.exec(source);
66+
if (num) return num[0];
67+
6368
let pos = start;
64-
if (isUnquotedStartCharCode(ctx.source.charCodeAt(pos))) {
69+
if (isNameStartCode(source.charCodeAt(pos))) {
6570
pos += 1;
66-
while (isNameCharCode(ctx.source.charCodeAt(pos))) pos += 1;
71+
while (isNameCharCode(source.charCodeAt(pos))) pos += 1;
6772
}
68-
return ctx.source.substring(start, pos);
73+
return source.substring(start, pos);
6974
}

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,6 @@ export function parseLiteral(
7070
return { type: 'literal', quoted: false, start, end, value };
7171
}
7272

73-
// quoted = "|" *(quoted-char / quoted-escape) "|"
74-
// quoted-char = %x0-5B ; omit \
75-
// / %x5D-7B ; omit |
76-
// / %x7D-D7FF ; omit surrogates
77-
// / %xE000-10FFFF
78-
// quoted-escape = backslash ( backslash / "|" )
79-
// backslash = %x5C ; U+005C REVERSE SOLIDUS "\"
8073
export function parseQuotedLiteral(
8174
ctx: ParseContext,
8275
start: number

packages/mf2-messageformat/src/expression/literal.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,21 @@ describe('quoted literals', () => {
3030
expect(() => new MessageFormat('{|quoted \\{iteral|}')).toThrow();
3131
});
3232
});
33+
34+
describe('unquoted numbers', () => {
35+
for (const value of [
36+
'0',
37+
'42',
38+
'2.5',
39+
'-1',
40+
'-0.999',
41+
'1e3',
42+
'0.4E+5',
43+
'11.1e-1'
44+
]) {
45+
test(value, () => {
46+
const res = resolve(`{${value}}`);
47+
expect(res).toMatchObject([{ type: 'string', value }]);
48+
});
49+
}
50+
});

packages/mf2-messageformat/src/stringifier/message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isValidUnquotedLiteral } from '../cst-parser/index.js';
1+
import { isValidUnquotedLiteral } from '../cst-parser/names.js';
22
import {
33
Declaration,
44
Expression,

0 commit comments

Comments
 (0)