fix(localize): support downleveled inlined helper localize calls (#40754)

When the downleveling helper function has been  inlined into the
`$localize` call, it is a bit more tricky to parse out the cooked and
raw strings. There was already code to do this but it assumed that
the `cooked` and `raw` items were both arrays.

Sometimes the `raw` array is just a copy of the `cooked` array
via an expression similar to `raw || (raw=tcookedslice(0))`. This
commit changes the `unwrapMessagePartsFromLocalizeCall()`
function to be able to handle such a situation.

Fixes #40702

PR Close #40754
This commit is contained in:
Pete Bacon Darwin 2021-02-08 21:29:41 +00:00 committed by Alex Rickabaugh
parent 4e4fcacb6d
commit ce44a3d1dc
2 changed files with 110 additions and 5 deletions

View File

@ -89,7 +89,7 @@ export function unwrapMessagePartsFromLocalizeCall(
// If there is no call to `__makeTemplateObject(...)`, then `raw` must be the same as `cooked`.
let raw = cooked;
// Check for cached call of the form `x || x = __makeTemplateObject(...)`
// Check for a memoized form: `x || x = ...`
if (cooked.isLogicalExpression() && cooked.node.operator === '||' &&
cooked.get('left').isIdentifier()) {
const right = cooked.get('right');
@ -105,15 +105,22 @@ export function unwrapMessagePartsFromLocalizeCall(
// This is a minified sequence expression, where the first two expressions in the sequence
// are assignments of the cooked and raw arrays respectively.
const [first, second] = expressions;
if (first.isAssignmentExpression() && second.isAssignmentExpression()) {
if (first.isAssignmentExpression()) {
cooked = first.get('right');
if (!cooked.isExpression()) {
throw new BabelParseError(
first.node, 'Unexpected cooked value, expected an expression.');
}
raw = second.get('right');
if (!raw.isExpression()) {
throw new BabelParseError(second.node, 'Unexpected raw value, expected an expression.');
if (second.isAssignmentExpression()) {
raw = second.get('right');
if (!raw.isExpression()) {
throw new BabelParseError(
second.node, 'Unexpected raw value, expected an expression.');
}
} else {
// If the second expression is not an assignment then it is probably code to take a copy
// of the cooked array. For example: `raw || (raw=cooked.slice(0))`.
raw = cooked;
}
}
}

View File

@ -132,6 +132,104 @@ runInEachFileSystem(() => {
]);
});
it('should return an array of string literals and locations from a (Babel helper) downleveled tagged template',
() => {
let localizeCall = getLocalizeCall(
`$localize(babelHelpers.taggedTemplateLiteral(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c']), 1, 2)`);
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
expect(parts).toEqual(['a', 'b\t', 'c']);
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
expect(locations).toEqual([
{
start: {line: 0, column: 65},
end: {line: 0, column: 68},
file: absoluteFrom('/test/file.js'),
text: `'a'`,
},
{
start: {line: 0, column: 70},
end: {line: 0, column: 76},
file: absoluteFrom('/test/file.js'),
text: `'b\\\\t'`,
},
{
start: {line: 0, column: 78},
end: {line: 0, column: 81},
file: absoluteFrom('/test/file.js'),
text: `'c'`,
},
]);
});
it('should return an array of string literals and locations from a memoized downleveled tagged template',
() => {
let localizeCall = getLocalizeCall(`
var _templateObject;
$localize(_templateObject || (_templateObject = __makeTemplateObject(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c'])), 1, 2)`);
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
expect(parts).toEqual(['a', 'b\t', 'c']);
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
expect(locations).toEqual([
{
start: {line: 2, column: 105},
end: {line: 2, column: 108},
file: absoluteFrom('/test/file.js'),
text: `'a'`,
},
{
start: {line: 2, column: 110},
end: {line: 2, column: 116},
file: absoluteFrom('/test/file.js'),
text: `'b\\\\t'`,
},
{
start: {line: 2, column: 118},
end: {line: 2, column: 121},
file: absoluteFrom('/test/file.js'),
text: `'c'`,
},
]);
});
it('should return an array of string literals and locations from a memoized (inlined Babel helper) downleveled tagged template',
() => {
let localizeCall = getLocalizeCall(`
var e,t,n;
$localize(e ||
(
t=["a","b\t","c"],
n || (n=t.slice(0)),
e = Object.freeze(
Object.defineProperties(t, { raw: { value: Object.freeze(n) } })
)
),
1,2
)`);
const [parts, locations] = unwrapMessagePartsFromLocalizeCall(localizeCall, fs);
expect(parts).toEqual(['a', 'b\t', 'c']);
expect(parts.raw).toEqual(['a', 'b\t', 'c']);
expect(locations).toEqual([
{
start: {line: 4, column: 21},
end: {line: 4, column: 24},
file: absoluteFrom('/test/file.js'),
text: `"a"`,
},
{
start: {line: 4, column: 25},
end: {line: 4, column: 29},
file: absoluteFrom('/test/file.js'),
text: `"b\t"`,
},
{
start: {line: 4, column: 30},
end: {line: 4, column: 33},
file: absoluteFrom('/test/file.js'),
text: `"c"`,
},
]);
});
it('should return an array of string literals and locations from a lazy load template helper',
() => {
let localizeCall = getLocalizeCall(`