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 {NodePath, PluginObj} from '@babel/core';
|
||||||
import {CallExpression} from '@babel/types';
|
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(
|
export function makeEs5ExtractPlugin(
|
||||||
fs: PathManipulation, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
fs: PathManipulation, messages: ɵParsedMessage[], localizeName = '$localize'): PluginObj {
|
||||||
return {
|
return {
|
||||||
visitor: {
|
visitor: {
|
||||||
CallExpression(callPath: NodePath<CallExpression>) {
|
CallExpression(callPath: NodePath<CallExpression>) {
|
||||||
const calleePath = callPath.get('callee');
|
try {
|
||||||
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
const calleePath = callPath.get('callee');
|
||||||
const [messageParts, messagePartLocations] =
|
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
||||||
unwrapMessagePartsFromLocalizeCall(callPath, fs);
|
const [messageParts, messagePartLocations] =
|
||||||
const [expressions, expressionLocations] =
|
unwrapMessagePartsFromLocalizeCall(callPath, fs);
|
||||||
unwrapSubstitutionsFromLocalizeCall(callPath, fs);
|
const [expressions, expressionLocations] =
|
||||||
const [messagePartsArg, expressionsArg] = callPath.get('arguments');
|
unwrapSubstitutionsFromLocalizeCall(callPath, fs);
|
||||||
const location = getLocation(fs, messagePartsArg, expressionsArg);
|
const [messagePartsArg, expressionsArg] = callPath.get('arguments');
|
||||||
const message = ɵparseMessage(
|
const location = getLocation(fs, messagePartsArg, expressionsArg);
|
||||||
messageParts, expressions, location, messagePartLocations, expressionLocations);
|
const message = ɵparseMessage(
|
||||||
messages.push(message);
|
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