refactor(localize): update version of Babel (#44931)

The `@angular/localize` package depends on a version of Babel that is two years
old, so this commit updates to the latest version.

Some changes were made to the linker and compliance tests to account for slight
changes in source maps, along with a few code updates because of changes to
the typings of Babel.

PR Close #44931
This commit is contained in:
JoostK
2022-02-01 22:12:44 +01:00
committed by Jessica Janiuk
parent 8d634ac84d
commit 7bbddb5873
21 changed files with 518 additions and 332 deletions
@@ -10,6 +10,7 @@ ts_library(
deps = [
"//packages/compiler",
"//packages/compiler-cli/linker",
"//packages/compiler-cli/private",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/logging",
"//packages/compiler-cli/src/ngtsc/translator",
@@ -73,7 +73,7 @@ export class BabelAstHost implements AstHost<t.Expression> {
assert(property.value, t.isExpression, 'an expression');
assert(property.key, isPropertyName, 'a property name');
const key = t.isIdentifier(property.key) ? property.key.name : property.key.value;
result.set(key, property.value);
result.set(`${key}`, property.value);
}
return result;
}
@@ -99,7 +99,9 @@ export class BabelAstHost implements AstHost<t.Expression> {
}
const stmt = fn.body.body[0];
assert(stmt, t.isReturnStatement, 'a function body with a single return statement');
if (stmt.argument === null) {
// Babel declares `argument` as optional and nullable, so we account for both scenarios.
if (stmt.argument === null || stmt.argument === undefined) {
throw new FatalLinkerError(stmt, 'Unsupported syntax, expected function to return a value.');
}
@@ -48,6 +48,7 @@ if (_babelDefault !== undefined) {
}
export import types = _typesNamespace;
export type BabelFile = _babelNamespace.BabelFile;
export type PluginObj = _babelNamespace.PluginObj;
export type ConfigAPI = _babelNamespace.ConfigAPI;
export type NodePath<T = _babelNamespace.Node> = _babelNamespace.NodePath<T>;
@@ -11,7 +11,8 @@ import {DeclarationScope} from '../../../linker';
import {types as t} from './babel_core';
export type ConstantScopePath = NodePath<t.Function|t.Program>;
export type ConstantScopePath =
NodePath<t.FunctionDeclaration>|NodePath<t.FunctionExpression>|NodePath<t.Program>;
/**
* This class represents the lexical scope of a partial declaration in Babel source code.
@@ -56,10 +57,11 @@ export class BabelDeclarationScope implements DeclarationScope<ConstantScopePath
}
// We only support shared constant statements if the binding was in a UMD module (i.e. declared
// within a `t.Function`) or an ECMASCript module (i.e. declared at the top level of a
// within a function) or an ECMASCript module (i.e. declared at the top level of a
// `t.Program` that is marked as a module).
const path = binding.scope.path;
if (!path.isFunctionParent() && !(path.isProgram() && path.node.sourceType === 'module')) {
if (!path.isFunctionDeclaration() && !path.isFunctionExpression() &&
!(path.isProgram() && path.node.sourceType === 'module')) {
return null;
}
@@ -12,11 +12,10 @@ import {FileLinker, isFatalLinkerError, LinkerEnvironment} from '../../../linker
import {BabelAstFactory} from './ast/babel_ast_factory';
import {BabelAstHost} from './ast/babel_ast_host';
import {PluginObj, types as t} from './babel_core';
import {BabelFile, PluginObj, types as t} from './babel_core';
import {BabelDeclarationScope, ConstantScopePath} from './babel_declaration_scope';
import {LinkerPluginOptions} from './linker_plugin_options';
/**
* Create a Babel plugin that visits the program, identifying and linking partial declarations.
*
@@ -38,7 +37,7 @@ export function createEs2015LinkerPlugin({fileSystem, logger, ...options}: Linke
assertNull(fileLinker);
// Babel can be configured with a `filename` or `relativeFilename` (or both, or neither) -
// possibly relative to the optional `cwd` path.
const file: BabelFile = path.hub.file;
const file = path.hub.file;
const filename = file.opts.filename ?? file.opts.filenameRelative;
if (!filename) {
throw new Error(
@@ -105,17 +104,18 @@ export function createEs2015LinkerPlugin({fileSystem, logger, ...options}: Linke
* The actual insertion strategy depends upon the type of the `path`.
*/
function insertStatements(path: ConstantScopePath, statements: t.Statement[]): void {
if (path.isFunction()) {
insertIntoFunction(path, statements);
} else if (path.isProgram()) {
if (path.isProgram()) {
insertIntoProgram(path, statements);
} else {
insertIntoFunction(path, statements);
}
}
/**
* Insert the `statements` at the top of the body of the `fn` function.
*/
function insertIntoFunction(fn: NodePath<t.Function>, statements: t.Statement[]): void {
function insertIntoFunction(
fn: NodePath<t.FunctionExpression|t.FunctionDeclaration>, statements: t.Statement[]): void {
const body = fn.get('body');
body.unshiftContainer('body', statements);
}
@@ -179,18 +179,3 @@ function buildCodeFrameError(file: BabelFile, message: string, node: t.Node): st
const error = file.buildCodeFrameError(node, message);
return `${filename}: ${error.message}`;
}
/**
* This interface is making up for the fact that the Babel typings for `NodePath.hub.file` are
* lacking.
*/
interface BabelFile {
code: string;
opts: {
filename?: string,
filenameRelative?: string,
cwd?: string,
};
buildCodeFrameError(node: t.Node, message: string): Error;
}
@@ -13,6 +13,7 @@ ts_library(
"//packages/compiler",
"//packages/compiler-cli/linker",
"//packages/compiler-cli/linker/babel",
"//packages/compiler-cli/private",
"//packages/compiler-cli/src/ngtsc/file_system/testing",
"//packages/compiler-cli/src/ngtsc/logging/testing",
"//packages/compiler-cli/src/ngtsc/translator",
@@ -12,7 +12,7 @@ import {types as t} from '../../src/babel_core';
import {BabelAstFactory} from '../../src/ast/babel_ast_factory';
// Exposes shothands for the `expression` and `statement`
// Exposes shorthands for the `expression` and `statement`
// methods exposed by `@babel/template`.
const expression = template.expression;
const statement = template.statement;
@@ -327,7 +327,7 @@ describe('BabelAstHost', () => {
describe('getRange()', () => {
it('should extract the range from the expression', () => {
const file = parse('// preamble\nx = \'moo\';');
const stmt = file.program.body[0];
const stmt = file.program.body[0] as t.Statement;
assertExpressionStatement(stmt);
assertAssignmentExpression(stmt.expression);
expect(host.getRange(stmt.expression.right))
@@ -16,13 +16,13 @@ describe('BabelDeclarationScope', () => {
describe('getConstantScopeRef()', () => {
it('should return a path to the ES module where the expression was imported', () => {
const ast = parse(
[
'import * as core from \'@angular/core\';',
'function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'module'});
[
'import * as core from \'@angular/core\';',
'function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'module'}) as t.File;
const nodePath = findVarDeclaration(ast, 'TEST');
const scope = new BabelDeclarationScope(nodePath.scope);
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
@@ -32,13 +32,13 @@ describe('BabelDeclarationScope', () => {
it('should return a path to the ES Module where the expression is declared', () => {
const ast = parse(
[
'var core;',
'export function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'module'});
[
'var core;',
'export function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'module'}) as t.File;
const nodePath = findVarDeclaration(ast, 'TEST');
const scope = new BabelDeclarationScope(nodePath.scope);
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
@@ -48,13 +48,13 @@ describe('BabelDeclarationScope', () => {
it('should return null if the file is not an ES module', () => {
const ast = parse(
[
'var core;',
'function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'script'});
[
'var core;',
'function foo() {',
' var TEST = core;',
'}',
].join('\n'),
{sourceType: 'script'}) as t.File;
const nodePath = findVarDeclaration(ast, 'TEST');
const scope = new BabelDeclarationScope(nodePath.scope);
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
@@ -63,23 +63,23 @@ describe('BabelDeclarationScope', () => {
it('should return the IIFE factory function where the expression is a parameter', () => {
const ast = parse(
[
'var core;',
'(function(core) {',
' var BLOCK = \'block\';',
' function foo() {',
' var TEST = core;',
' }',
'})(core);',
].join('\n'),
{sourceType: 'script'});
[
'var core;',
'(function(core) {',
' var BLOCK = \'block\';',
' function foo() {',
' var TEST = core;',
' }',
'})(core);',
].join('\n'),
{sourceType: 'script'}) as t.File;
const nodePath = findVarDeclaration(ast, 'TEST');
const fnPath = findFirstFunction(ast);
const scope = new BabelDeclarationScope(nodePath.scope);
const constantScope = scope.getConstantScopeRef(nodePath.get('init').node);
expect(constantScope).not.toBe(null);
expect(constantScope!.isFunction()).toBe(true);
expect(constantScope!.node).toEqual(fnPath.node);
expect(constantScope!.node).toEqual(fnPath.node as t.FunctionDeclaration);
});
});
});
+1 -1
View File
@@ -53,7 +53,7 @@
}
},
"dependencies": {
"@babel/core": "^7.8.6",
"@babel/core": "^7.17.2",
"reflect-metadata": "^0.1.2",
"chokidar": "^3.0.0",
"convert-source-map": "^1.5.1",
+42
View File
@@ -0,0 +1,42 @@
/**
* @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 { BabelFile, types as t } from '@babel/core';
/**
* Augment some Babel types to add symbols that we rely on, but are not included in the Babel typings.
*/
declare module '@babel/traverse' {
interface Hub {
file: BabelFile;
}
}
declare module '@babel/core' {
interface BabelFile {
buildCodeFrameError(node: t.Node, message: string): Error;
}
}
// The following modules are declared to work around a limitation in tsc_wrapped's `strict_deps`
// check. Since Babel uses scoped packages, the corresponding lookup for declaration files in the
// `node_modules/@types/` directory follows a special strategy: the `@types` package has to be
// named `{scope}__{package}`, e.g. `@types/babel__generator`. When `tsc` performs module
// resolution for the `@babel/generator` module specifier, it will first try the `paths` mappings
// but resolution through path mappings does _not_ apply this special naming convention rule for
// `@types` packages, `tsc` only applies that rule in its `@types` resolution step. Consequently,
// the path mapping into Bazel's private `node_modules` directory fails to resolve, causing `tsc`
// to find the nearest `node_modules` directory in an ancestor directory of the origin source
// file. This finds the `node_modules` directory in the workspace, _not_ Bazel's private copy of
// `node_modules` and therefore the `@types` end up being resolved from a `node_modules` tree
// that is not governed by Bazel, and therefore not expected by the `strict_deps` rule.
// Declaring the modules here allows `strict_deps` to always find a declaration of the modules
// in an input file to the compilation, therefore accepting the module import.
declare module '@babel/generator' {}
declare module '@babel/template' {}
declare module '@babel/parser' {}
@@ -6,8 +6,8 @@ $localize` pre-p ${ // SOURCE: "/i18n_message_element_whitespace.ts" "\\n pre-p
…
"\uFFFD/#2\uFFFD" // SOURCE: "/i18n_message_element_whitespace.ts" "</p>"
…
}:CLOSE_PARAGRAPH: post-p\n` // SOURCE: "/i18n_message_element_whitespace.ts" "\\n post-p\\n"
…
}:CLOSE_PARAGRAPH: post-p\n // SOURCE: "/i18n_message_element_whitespace.ts" "\\n post-p\\n"
`…
.ɵɵelementStart(0, "div") // SOURCE: "/i18n_message_element_whitespace.ts" "<div i18n>"
…
@@ -2,7 +2,7 @@
…
// TODO - map the bindings better
// TODO: Work out how to fix the broken segment for the last item in a template
.ɵɵelementEnd();\n // SOURCE: "/ng_if_templated.ts" "</div>\n "
.ɵɵelementEnd();\n // SOURCE: "/ng_if_templated.ts" "</div>\n"
…
// TODO: the `ctx_r...` appears to be dependent upon previous tests!!!
// TODO: expectMapping(mappings, { source: '{{ name }}', generated: 'i0.ɵɵtextInterpolate(ctx_r0.name)', sourceUrl: '../ng_if_templated.ts'});
@@ -2,7 +2,7 @@
…
.ɵɵprojection(1) // SOURCE: "/projection.ts" "<ng-content select="title"></ng-content>"
…
.ɵɵelementEnd() // SOURCE: "/projection.ts" "</h3>\n "
.ɵɵelementEnd() // SOURCE: "/projection.ts" "</h3>\n"
…
.ɵɵelementStart(2, "div") // SOURCE: "/projection.ts" "<div>"
…
@@ -13,7 +13,7 @@ const COMMENT_END = /\*\//;
const OPERATOR =
/!|\?|%|\*|\/|\^|&&?|\|\|?|\(|\)|\{|\}|\[|\]|:|;|<=?|>=?|={1,3}|!==?|=>|\+\+?|--?|@|,|\.|\.\.\.|\\`|\\'/;
const STRING = /'(\\'|[^'])*'|"(\\"|[^"])*"/;
const INLINE_BACKTICK_STRING = /`(([\s\S]*?)(\$\{[^}]*?\})?)*?[^\\]`/;
const INLINE_BACKTICK_STRING = /`(?:[\s\S]|(?:\$\{[^}]*?\}))*?[^\\]`/;
const SINGLE_BACKTICK_STRING = new RegExp('^' + INLINE_BACKTICK_STRING.source);
const BACKTICK_INTERPOLATION = /(\$\{[^}]*\})/;
const NUMBER = /\d+/;
@@ -80,8 +80,11 @@ function extractMappings(
fs: ReadonlyFileSystem, expected: string): {expected: string, mappings: SegmentMapping[]} {
const mappings: SegmentMapping[] = [];
// capture and remove source mapping info
// Any newline at the end of an expectation line is removed, as a mapping expectation for a
// segment within a template literal would otherwise force a newline to be matched in the template
// literal.
expected = expected.replace(
/^(.*?) \/\/ SOURCE: "([^"]*?)" "(.*?)"$/gm,
/^(.*?) \/\/ SOURCE: "([^"]*?)" "(.*?)"(?:\n|$)/gm,
(_, rawGenerated: string, rawSourceUrl: string, rawSource: string) => {
// Since segments need to appear on a single line in the expected file, any newlines in the
// segment being checked must be escaped in the expected file and then unescaped here before
+1 -1
View File
@@ -34,7 +34,7 @@
"**/init.mjs"
],
"dependencies": {
"@babel/core": "7.8.6",
"@babel/core": "7.17.2",
"glob": "7.2.0",
"yargs": "^17.2.1"
},
@@ -215,7 +215,9 @@ export function unwrapMessagePartsFromTemplateLiteral(
export function unwrapExpressionsFromTemplateLiteral(
quasi: NodePath<t.TemplateLiteral>,
fs: PathManipulation = getFileSystem()): [t.Expression[], (ɵSourceLocation | undefined)[]] {
return [quasi.node.expressions, quasi.get('expressions').map(e => getLocation(fs, e))];
return [
quasi.node.expressions as t.Expression[], quasi.get('expressions').map(e => getLocation(fs, e))
];
}
/**
@@ -446,11 +448,11 @@ export function serializeLocationPosition(location: ɵSourceLocation): string {
function getFileFromPath(fs: PathManipulation, path: NodePath|undefined): AbsoluteFsPath|null {
const opts = path?.hub.file.opts;
const filename = opts?.filename;
if (!filename) {
if (!filename || !opts.cwd) {
return null;
}
const relativePath = fs.relative(opts.cwd, filename);
const root = opts.generatorOpts.sourceRoot ?? opts.cwd;
const root = opts.generatorOpts?.sourceRoot ?? opts.cwd;
const absPath = fs.resolve(root, relativePath);
return absPath;
}
+1
View File
@@ -10,6 +10,7 @@ ts_library(
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/private",
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/file_system/testing",
"//packages/compiler-cli/src/ngtsc/logging/testing",