Skip to content

Commit fdc9541

Browse files
authored
Emit INVALID_TOKEN_IN_PLACEHOLDER instead of UNTERMINATED_CLOSING_BRACE when invalid token is in placeholder and update docs (#2252)
1 parent f809bb3 commit fdc9541

File tree

3 files changed

+103
-31
lines changed

3 files changed

+103
-31
lines changed

docs/guide/essentials/syntax.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ The locale messages is the resource specified by the `messages` option of `creat
2626

2727
Named interpolation allows you to specify variables defined in JavaScript. In the locale message above, you can localize it by giving the JavaScript defined `msg` as a parameter to the translation function.
2828

29+
The variable name inside `{}` must starts with a letter (a-z, A-Z) or an underscore (`_`), followed by any combination of letters, digits, underscores (`_`), hyphens (`-`), or dollar signs (`$`).
30+
31+
Examples: `{msg}`, `{_userName}`, `{user-id}`, `{total$}`
32+
2933
The following is an example of the use of `$t` in a template:
3034

3135
```html
@@ -267,7 +271,7 @@ In `message.greeting`, we use a named interpolation for `{count}` and link to `m
267271

268272
The key `message.name` contains `{name}`, which will be interpolated with the passed `name` param.
269273

270-
The `message.greeting` is linked to the locale message key `message.name`.
274+
The `message.greeting` is linked to the locale message key `message.name`.
271275

272276
```html
273277
<p>{{ $t('message.greeting', { name: 'Alice', count: 5 }) }}</p>

packages/message-compiler/src/tokenizer.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,26 @@ export function createTokenizer(
484484
name += ch
485485
}
486486

487+
// Check if takeNamedIdentifierChar stoped because of invalid characters
488+
const currentChar = scnr.currentChar()
489+
if (
490+
currentChar &&
491+
currentChar !== '}' &&
492+
currentChar !== EOF &&
493+
currentChar !== SPACE &&
494+
currentChar !== NEW_LINE &&
495+
currentChar !== '\u3000'
496+
) {
497+
const invalidPart = readInvalidIdentifier(scnr)
498+
emitError(
499+
CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER,
500+
currentPosition(),
501+
0,
502+
name + invalidPart
503+
)
504+
return name + invalidPart
505+
}
506+
487507
if (scnr.currentChar() === EOF) {
488508
emitError(
489509
CompileErrorCodes.UNTERMINATED_CLOSING_BRACE,

packages/message-compiler/test/tokenizer/named.test.ts

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { format } from '@intlify/shared'
22
import { CompileErrorCodes, errorMessages } from '../../src/errors'
33
import {
44
createTokenizer,
5-
TokenTypes,
65
ERROR_DOMAIN,
7-
parse
6+
parse,
7+
TokenTypes
88
} from '../../src/tokenizer'
99

10-
import type { TokenizeOptions } from '../../src/options'
1110
import type { CompileError } from '../../src/errors'
11+
import type { TokenizeOptions } from '../../src/options'
1212

1313
test('basic', () => {
1414
const tokenizer = createTokenizer('hi {name} !')
@@ -645,32 +645,80 @@ describe('errors', () => {
645645
}
646646
] as CompileError[])
647647
})
648-
const items = [`$`, `-`]
649-
for (const ch of items) {
650-
test(`invalid '${ch}' in placeholder`, () => {
651-
parse(`hi {${ch}} !`, options)
652-
expect(errors).toEqual([
653-
{
654-
code: CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER,
655-
domain: ERROR_DOMAIN,
656-
message: format(
657-
errorMessages[CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER],
658-
ch
659-
),
660-
location: {
661-
start: {
662-
line: 1,
663-
offset: 4,
664-
column: 5
665-
},
666-
end: {
667-
line: 1,
668-
offset: 5,
669-
column: 6
670-
}
671-
}
648+
649+
test.each([
650+
[
651+
'$',
652+
{
653+
start: {
654+
line: 1,
655+
offset: 4,
656+
column: 5
657+
},
658+
end: {
659+
line: 1,
660+
offset: 5,
661+
column: 6
662+
}
663+
}
664+
],
665+
[
666+
'-',
667+
{
668+
start: {
669+
line: 1,
670+
offset: 4,
671+
column: 5
672+
},
673+
end: {
674+
line: 1,
675+
offset: 5,
676+
column: 6
677+
}
678+
}
679+
],
680+
[
681+
'àaa',
682+
{
683+
start: {
684+
line: 1,
685+
offset: 4,
686+
column: 5
687+
},
688+
end: {
689+
line: 1,
690+
offset: 7,
691+
column: 8
672692
}
673-
] as CompileError[])
674-
})
675-
}
693+
}
694+
],
695+
[
696+
'aàa',
697+
{
698+
start: {
699+
line: 1,
700+
offset: 4,
701+
column: 5
702+
},
703+
end: {
704+
line: 1,
705+
offset: 7,
706+
column: 8
707+
}
708+
}
709+
]
710+
])(`invalid '%s' in placeholder`, (ch, location) => {
711+
parse(`hi {${ch}} !`, options)
712+
expect(errors).toEqual([
713+
{
714+
code: CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER,
715+
domain: ERROR_DOMAIN,
716+
message: format(
717+
errorMessages[CompileErrorCodes.INVALID_TOKEN_IN_PLACEHOLDER],
718+
ch
719+
),
720+
location
721+
}
722+
] as CompileError[])
723+
})
676724
})

0 commit comments

Comments
 (0)