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:
Pete Bacon Darwin 2021-02-05 12:05:49 +00:00 committed by Alex Rickabaugh
parent 4ce44eac33
commit 9dada8a2dd
2 changed files with 60 additions and 12 deletions

View File

@ -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;
}
}
}
}

View File

@ -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;
}
});
});