diff --git a/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts b/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts index 732a102636..27ae97333a 100644 --- a/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts +++ b/packages/localize/src/tools/src/extract/source_files/es5_extract_plugin.ts @@ -10,24 +10,35 @@ import {ɵParsedMessage, ɵparseMessage} from '@angular/localize'; import {NodePath, PluginObj} from '@babel/core'; import {CallExpression} from '@babel/types'; -import {getLocation, isGlobalIdentifier, isNamedIdentifier, unwrapMessagePartsFromLocalizeCall, unwrapSubstitutionsFromLocalizeCall} from '../../source_file_utils'; +import {buildCodeFrameError, getLocation, isBabelParseError, isGlobalIdentifier, isNamedIdentifier, unwrapMessagePartsFromLocalizeCall, unwrapSubstitutionsFromLocalizeCall} from '../../source_file_utils'; export function makeEs5ExtractPlugin( fs: PathManipulation, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj { return { visitor: { CallExpression(callPath: NodePath) { - const calleePath = callPath.get('callee'); - if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) { - const [messageParts, messagePartLocations] = - unwrapMessagePartsFromLocalizeCall(callPath, fs); - const [expressions, expressionLocations] = - unwrapSubstitutionsFromLocalizeCall(callPath, fs); - const [messagePartsArg, expressionsArg] = callPath.get('arguments'); - const location = getLocation(fs, messagePartsArg, expressionsArg); - const message = ɵparseMessage( - messageParts, expressions, location, messagePartLocations, expressionLocations); - messages.push(message); + try { + const calleePath = callPath.get('callee'); + if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) { + const [messageParts, messagePartLocations] = + unwrapMessagePartsFromLocalizeCall(callPath, fs); + const [expressions, expressionLocations] = + unwrapSubstitutionsFromLocalizeCall(callPath, fs); + const [messagePartsArg, expressionsArg] = callPath.get('arguments'); + const location = getLocation(fs, messagePartsArg, expressionsArg); + const message = ɵparseMessage( + messageParts, expressions, location, messagePartLocations, expressionLocations); + messages.push(message); + } + } catch (e) { + if (isBabelParseError(e)) { + // If we get a BabelParseError here then something went wrong with Babel itself + // since there must be something wrong with the structure of the AST generated + // by Babel parsing a TaggedTemplateExpression. + throw buildCodeFrameError(callPath, e); + } else { + throw e; + } } } } diff --git a/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts b/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts new file mode 100644 index 0000000000..db587f0413 --- /dev/null +++ b/packages/localize/src/tools/test/extract/source_files/es5_extract_plugin_spec.ts @@ -0,0 +1,37 @@ + +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system'; +import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {ɵParsedMessage} from '@angular/localize/private'; +import {transformSync} from '@babel/core'; + +import {makeEs5ExtractPlugin} from '../../../src/extract/source_files/es5_extract_plugin'; + +runInEachFileSystem(() => { + describe('makeEs5ExtractPlugin()', () => { + it('should error with code-frame information if the first argument to `$localize` is not an array', + () => { + const input = '$localize(null, [])'; + expect(() => transformCode(input)) + .toThrowError( + 'Cannot create property \'message\' on string \'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' + + '> 1 | $localize(null, [])\n' + + ' | ^^^^\''); + }); + + function transformCode(input: string): ɵParsedMessage[] { + const messages: ɵParsedMessage[] = []; + transformSync(input, { + plugins: [makeEs5ExtractPlugin(getFileSystem(), messages)], + filename: '/app/dist/test.js' + })!.code!; + return messages; + } + }); +});