diff --git a/packages/localize/src/tools/src/source_file_utils.ts b/packages/localize/src/tools/src/source_file_utils.ts index e0814955e8..55cd10c5fa 100644 --- a/packages/localize/src/tools/src/source_file_utils.ts +++ b/packages/localize/src/tools/src/source_file_utils.ts @@ -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; } } } diff --git a/packages/localize/src/tools/test/source_file_utils_spec.ts b/packages/localize/src/tools/test/source_file_utils_spec.ts index 1f8421fcd7..249c4beef8 100644 --- a/packages/localize/src/tools/test/source_file_utils_spec.ts +++ b/packages/localize/src/tools/test/source_file_utils_spec.ts @@ -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(`