refactor(ivy): i18n - change `unwrapMessagePartsFromLocalizeCall` to accept a `NodePath` (#33097)
In Babel `NodePath` objects have more useful information available than simple AST nodes. But they are more difficult to create, especially for testing. This commit prepares the way for parsing more complex code downlevelling scenarios. PR Close #33097
This commit is contained in:
parent
127cec0214
commit
d617373a76
|
@ -21,7 +21,7 @@ export function makeEs5TranslatePlugin(
|
||||||
try {
|
try {
|
||||||
const calleePath = callPath.get('callee');
|
const calleePath = callPath.get('callee');
|
||||||
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
if (isNamedIdentifier(calleePath, localizeName) && isGlobalIdentifier(calleePath)) {
|
||||||
const messageParts = unwrapMessagePartsFromLocalizeCall(callPath.node);
|
const messageParts = unwrapMessagePartsFromLocalizeCall(callPath);
|
||||||
const expressions = unwrapSubstitutionsFromLocalizeCall(callPath.node);
|
const expressions = unwrapSubstitutionsFromLocalizeCall(callPath.node);
|
||||||
const translated =
|
const translated =
|
||||||
translate(diagnostics, translations, messageParts, expressions, missingTranslation);
|
translate(diagnostics, translations, messageParts, expressions, missingTranslation);
|
||||||
|
|
|
@ -51,41 +51,53 @@ export function buildLocalizeReplacement(
|
||||||
*
|
*
|
||||||
* @param call The AST node of the call to process.
|
* @param call The AST node of the call to process.
|
||||||
*/
|
*/
|
||||||
export function unwrapMessagePartsFromLocalizeCall(call: t.CallExpression): TemplateStringsArray {
|
export function unwrapMessagePartsFromLocalizeCall(call: NodePath<t.CallExpression>):
|
||||||
let cooked = call.arguments[0];
|
TemplateStringsArray {
|
||||||
if (!t.isExpression(cooked)) {
|
let cooked = call.get('arguments')[0];
|
||||||
throw new BabelParseError(call, 'Unexpected argument to `$localize`: ' + cooked);
|
|
||||||
|
if (cooked === undefined) {
|
||||||
|
throw new BabelParseError(call.node, '`$localize` called without any arguments.');
|
||||||
|
}
|
||||||
|
if (!cooked.isExpression()) {
|
||||||
|
throw new BabelParseError(
|
||||||
|
cooked.node, 'Unexpected argument to `$localize` (expected an array).');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no call to `__makeTemplateObject(...)`, then `raw` must be the same as `cooked`.
|
// If there is no call to `__makeTemplateObject(...)`, then `raw` must be the same as `cooked`.
|
||||||
let raw = cooked;
|
let raw = cooked;
|
||||||
|
|
||||||
// Check for cached call of the form `x || x = __makeTemplateObject(...)`
|
// Check for cached call of the form `x || x = __makeTemplateObject(...)`
|
||||||
if (t.isLogicalExpression(cooked) && cooked.operator === '||' && t.isIdentifier(cooked.left) &&
|
if (cooked.isLogicalExpression() && cooked.node.operator === '||' &&
|
||||||
t.isExpression(cooked.right)) {
|
cooked.get('left').isIdentifier()) {
|
||||||
if (t.isAssignmentExpression(cooked.right)) {
|
const right = cooked.get('right');
|
||||||
cooked = cooked.right.right;
|
if (right.isAssignmentExpression()) {
|
||||||
|
cooked = right.get('right');
|
||||||
|
if (!cooked.isExpression()) {
|
||||||
|
throw new BabelParseError(
|
||||||
|
cooked.node, 'Unexpected "makeTemplateObject()" function (expected an expression).');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for `__makeTemplateObject(cooked, raw)` call
|
// Check for `__makeTemplateObject(cooked, raw)` call
|
||||||
if (t.isCallExpression(cooked)) {
|
if (cooked.isCallExpression()) {
|
||||||
raw = cooked.arguments[1] as t.Expression;
|
const arg2 = cooked.get('arguments')[1];
|
||||||
if (!t.isExpression(raw)) {
|
if (!arg2.isExpression()) {
|
||||||
throw new BabelParseError(
|
throw new BabelParseError(
|
||||||
raw,
|
arg2.node,
|
||||||
'Unexpected `raw` argument to the "makeTemplateObject()" function (expected an expression).');
|
'Unexpected `raw` argument to the "makeTemplateObject()" function (expected an expression).');
|
||||||
}
|
}
|
||||||
cooked = cooked.arguments[0];
|
raw = arg2;
|
||||||
if (!t.isExpression(cooked)) {
|
cooked = cooked.get('arguments')[0];
|
||||||
|
if (!cooked.isExpression()) {
|
||||||
throw new BabelParseError(
|
throw new BabelParseError(
|
||||||
cooked,
|
cooked.node,
|
||||||
'Unexpected `cooked` argument to the "makeTemplateObject()" function (expected an expression).');
|
'Unexpected `cooked` argument to the "makeTemplateObject()" function (expected an expression).');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cookedStrings = unwrapStringLiteralArray(cooked);
|
const cookedStrings = unwrapStringLiteralArray(cooked.node);
|
||||||
const rawStrings = unwrapStringLiteralArray(raw);
|
const rawStrings = unwrapStringLiteralArray(raw.node);
|
||||||
return ɵmakeTemplateObject(cookedStrings, rawStrings);
|
return ɵmakeTemplateObject(cookedStrings, rawStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,12 +97,28 @@ describe('makeEs5Plugin', () => {
|
||||||
expect(diagnostics.hasErrors).toBe(true);
|
expect(diagnostics.hasErrors).toBe(true);
|
||||||
expect(diagnostics.messages[0]).toEqual({
|
expect(diagnostics.messages[0]).toEqual({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: '/app/dist/test.js: Unexpected argument to `$localize`: undefined\n' +
|
message: '/app/dist/test.js: `$localize` called without any arguments.\n' +
|
||||||
'> 1 | $localize()\n' +
|
'> 1 | $localize()\n' +
|
||||||
' | ^',
|
' | ^',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add diagnostic error with code-frame information if the arguments to `$localize` are invalid',
|
||||||
|
() => {
|
||||||
|
const diagnostics = new Diagnostics();
|
||||||
|
const input = '$localize(...x)';
|
||||||
|
transformSync(
|
||||||
|
input,
|
||||||
|
{plugins: [makeEs5TranslatePlugin(diagnostics, {})], filename: '/app/dist/test.js'});
|
||||||
|
expect(diagnostics.hasErrors).toBe(true);
|
||||||
|
expect(diagnostics.messages[0]).toEqual({
|
||||||
|
type: 'error',
|
||||||
|
message: '/app/dist/test.js: Unexpected argument to `$localize` (expected an array).\n' +
|
||||||
|
'> 1 | $localize(...x)\n' +
|
||||||
|
' | ^',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should add diagnostic error with code-frame information if the first argument to `$localize` is not an array',
|
it('should add diagnostic error with code-frame information if the first argument to `$localize` is not an array',
|
||||||
() => {
|
() => {
|
||||||
const diagnostics = new Diagnostics();
|
const diagnostics = new Diagnostics();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {ɵmakeTemplateObject} from '@angular/localize';
|
||||||
import {NodePath, transformSync} from '@babel/core';
|
import {NodePath, transformSync} from '@babel/core';
|
||||||
import generate from '@babel/generator';
|
import generate from '@babel/generator';
|
||||||
import template from '@babel/template';
|
import template from '@babel/template';
|
||||||
import {Expression, Identifier, TaggedTemplateExpression, ExpressionStatement, FunctionDeclaration, CallExpression, isParenthesizedExpression, numericLiteral, binaryExpression, NumericLiteral} from '@babel/types';
|
import {Expression, Identifier, TaggedTemplateExpression, ExpressionStatement, FunctionDeclaration, CallExpression, isParenthesizedExpression, numericLiteral, binaryExpression, NumericLiteral, traverse} from '@babel/types';
|
||||||
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral} from '../../../src/translate/source_files/source_file_utils';
|
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral} from '../../../src/translate/source_files/source_file_utils';
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
|
@ -71,17 +71,14 @@ describe('utils', () => {
|
||||||
|
|
||||||
describe('unwrapMessagePartsFromLocalizeCall', () => {
|
describe('unwrapMessagePartsFromLocalizeCall', () => {
|
||||||
it('should return an array of string literals from a direct call to a tag function', () => {
|
it('should return an array of string literals from a direct call to a tag function', () => {
|
||||||
const ast = template.ast `$localize(['a', 'b\\t', 'c'], 1, 2)` as ExpressionStatement;
|
const call = getFirstCallExpression(`$localize(['a', 'b\\t', 'c'], 1, 2)`);
|
||||||
const call = ast.expression as CallExpression;
|
|
||||||
const parts = unwrapMessagePartsFromLocalizeCall(call);
|
const parts = unwrapMessagePartsFromLocalizeCall(call);
|
||||||
expect(parts).toEqual(['a', 'b\t', 'c']);
|
expect(parts).toEqual(['a', 'b\t', 'c']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an array of string literals from a downleveled tagged template', () => {
|
it('should return an array of string literals from a downleveled tagged template', () => {
|
||||||
const ast = template.ast
|
let call = getFirstCallExpression(
|
||||||
`$localize(__makeTemplateObject(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c']), 1, 2)` as
|
`$localize(__makeTemplateObject(['a', 'b\\t', 'c'], ['a', 'b\\\\t', 'c']), 1, 2)`);
|
||||||
ExpressionStatement;
|
|
||||||
const call = ast.expression as CallExpression;
|
|
||||||
const parts = unwrapMessagePartsFromLocalizeCall(call);
|
const parts = unwrapMessagePartsFromLocalizeCall(call);
|
||||||
expect(parts).toEqual(['a', 'b\t', 'c']);
|
expect(parts).toEqual(['a', 'b\t', 'c']);
|
||||||
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
expect(parts.raw).toEqual(['a', 'b\\t', 'c']);
|
||||||
|
@ -182,3 +179,21 @@ function collectExpressionsPlugin() {
|
||||||
const visitor = {Expression: (path: NodePath<Expression>) => { expressions.push(path); }};
|
const visitor = {Expression: (path: NodePath<Expression>) => { expressions.push(path); }};
|
||||||
return {expressions, plugin: {visitor}};
|
return {expressions, plugin: {visitor}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFirstCallExpression(code: string): NodePath<CallExpression> {
|
||||||
|
let callPath: NodePath<CallExpression>|undefined = undefined;
|
||||||
|
transformSync(code, {
|
||||||
|
plugins: [{
|
||||||
|
visitor: {
|
||||||
|
CallExpression(path) {
|
||||||
|
callPath = path;
|
||||||
|
path.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
if (callPath === undefined) {
|
||||||
|
throw new Error('CallExpression not found in code:' + code);
|
||||||
|
}
|
||||||
|
return callPath;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue