fix(localize): improve invalid syntax in extraction error message (#40724)
Previously if the code is invalid the error message might look like: ``` Unexpected messageParts for `$localize` (expected an array of strings). ``` This is not very helpful for debugging where the problem occurs. Now we build a "code-frame" description to give more useful information: ``` TypeError: Cannot create property 'message' on string '.../src/app/app.component.js: Unexpected messageParts for `$localize` (expected an array of strings). 4 | export class AppComponent { 5 | constructor() { > 6 | this.title = $localize(a = ['myapp'], []); | ^^^^^^^^^^^^^ 7 | } 8 | } ``` PR Close #40724
This commit is contained in:
parent
4ce44eac33
commit
9dada8a2dd
|
@ -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<CallExpression>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue