Skip to content

Commit 672b464

Browse files
committed
refactor(compiler): tighten the i18n placeholder name annotation regex
The regex that reads the name out of a `//i18n(ph="NAME")` annotation on an interpolation expression chained five greedy `[\s\S]*` segments between the literals `i18n`, `(`, `ph`, `=` and the opening quote. The separators in that annotation are only ever whitespace, so bind them to `\s*`, and stop the name at its own delimiter quote instead of letting it scan to the end of the input. The loose pattern also had to try every way a run of `i18n(ph=` text could be split across those wildcards, so rejecting an unterminated annotation took exponential time: `'//' + 'i18n(ph='.repeat(200)` (~1.6 KB) took ~96s to extract, and now resolves immediately. Well-formed annotations resolve to the same name as before. The stricter pattern does differ on malformed or repeated annotations, where the greedy wildcards used to reach across them: - `// i18n(ph="A") // i18n(ph="B")` resolves to `A` rather than `B`. - `// i18n(ph="A"] )` is no longer recognized, since `]` is not whitespace. - `// i18n(ph='say "hi"')` resolves to `say "hi"` rather than `hi`.
1 parent 3bf2430 commit 672b464

2 files changed

Lines changed: 12 additions & 2 deletions

File tree

‎packages/compiler/src/i18n/i18n_parser.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,7 @@ ${nodes.map((node) => `"${node.sourceSpan.toString()}"`).join('\n')}
453453
}
454454
}
455455

456-
const _CUSTOM_PH_EXP =
457-
/\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*("|')([\s\S]*?)\1[\s\S]*\)/g;
456+
const _CUSTOM_PH_EXP = /\/\/\s*i18n\s*\(\s*ph\s*=\s*("|')((?:(?!\1)[\s\S])*)\1\s*\)/g;
458457

459458
function extractPlaceholderName(input: string): string {
460459
return input.split(_CUSTOM_PH_EXP)[2];

‎packages/compiler/test/i18n/i18n_parser_spec.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ describe('I18nParser', () => {
147147
[[`[before, <ph name="TEST"> exp //i18n(ph='teSt') </ph>, after]`], 'm', 'd', ''],
148148
]);
149149
});
150+
151+
it('should not backtrack on an unterminated placeholder annotation', () => {
152+
// A `//i18n(ph=...` annotation that never closes must fall back to the default
153+
// INTERPOLATION placeholder quickly instead of hanging while extracting the name.
154+
const expression = '//' + 'i18n(ph='.repeat(200);
155+
const start = Date.now();
156+
expect(_humanizeMessages(`<div i18n="m|d">before{{ ${expression} }}after</div>`)).toEqual([
157+
[[`[before, <ph name="INTERPOLATION"> ${expression} </ph>, after]`], 'm', 'd', ''],
158+
]);
159+
expect(Date.now() - start).toBeLessThan(2000);
160+
});
150161
});
151162

152163
describe('blocks', () => {

0 commit comments

Comments
 (0)