Skip to content

Commit 7dd0db6

Browse files
petebacondarwinjosephperrott
authored andcommitted
refactor(compiler-cli): implement BabelAstFactory and AstHosts (angular#38866)
This commit adds the `AstHost` interface, along with implementations for both Babel and TS. It also implements the Babel vesion of the `AstFactory` interface, along with a linker specific implementation of the `ImportGenerator` interface. These classes will be used by the new "ng-linker" to transform prelinked library code using a Babel plugin. PR Close angular#38866
1 parent 1f5d7dc commit 7dd0db6

20 files changed

+1746
-4
lines changed

.github/angular-robot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ merge:
4747
- "packages/bazel/src/ng_package/**"
4848
- "packages/bazel/src/protractor/**"
4949
- "packages/bazel/src/schematics/**"
50+
- "packages/compiler-cli/linker/**"
5051
- "packages/compiler-cli/ngcc/**"
5152
- "packages/compiler-cli/src/ngtsc/sourcemaps/**"
5253
- "packages/docs/**"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "linker",
7+
srcs = ["index.ts"] + glob([
8+
"src/**/*.ts",
9+
]),
10+
deps = [
11+
"//packages/compiler-cli/src/ngtsc/translator",
12+
"@npm//@babel/core",
13+
"@npm//@babel/types",
14+
"@npm//@types/babel__core",
15+
"@npm//@types/babel__traverse",
16+
"@npm//typescript",
17+
],
18+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* An abstraction for getting information from an AST while being agnostic to the underlying AST
11+
* implementation.
12+
*/
13+
export interface AstHost<TExpression> {
14+
/**
15+
* Get the name of the symbol represented by the given expression node, or `null` if it is not a
16+
* symbol.
17+
*/
18+
getSymbolName(node: TExpression): string|null;
19+
20+
/**
21+
* Return `true` if the given expression is a string literal, or false otherwise.
22+
*/
23+
isStringLiteral(node: TExpression): boolean;
24+
/**
25+
* Parse the string value from the given expression, or throw if it is not a string literal.
26+
*/
27+
parseStringLiteral(str: TExpression): string;
28+
29+
/**
30+
* Return `true` if the given expression is a numeric literal, or false otherwise.
31+
*/
32+
isNumericLiteral(node: TExpression): boolean;
33+
/**
34+
* Parse the numeric value from the given expression, or throw if it is not a numeric literal.
35+
*/
36+
parseNumericLiteral(num: TExpression): number;
37+
38+
/**
39+
* Return `true` if the given expression is a boolean literal, or false otherwise.
40+
*/
41+
isBooleanLiteral(node: TExpression): boolean;
42+
/**
43+
* Parse the boolean value from the given expression, or throw if it is not a boolean literal.
44+
*/
45+
parseBooleanLiteral(bool: TExpression): boolean;
46+
47+
/**
48+
* Return `true` if the given expression is an array literal, or false otherwise.
49+
*/
50+
isArrayLiteral(node: TExpression): boolean;
51+
/**
52+
* Parse an array of expressions from the given expression, or throw if it is not an array
53+
* literal.
54+
*/
55+
parseArrayLiteral(array: TExpression): TExpression[];
56+
57+
/**
58+
* Return `true` if the given expression is an object literal, or false otherwise.
59+
*/
60+
isObjectLiteral(node: TExpression): boolean;
61+
/**
62+
* Parse the given expression into a map of object property names to property expressions, or
63+
* throw if it is not an object literal.
64+
*/
65+
parseObjectLiteral(obj: TExpression): Map<string, TExpression>;
66+
67+
/**
68+
* Return `true` if the given expression is a function, or false otherwise.
69+
*/
70+
isFunctionExpression(node: TExpression): boolean;
71+
/**
72+
* Compute the "value" of a function expression by parsing its body for a single `return`
73+
* statement, extracting the returned expression, or throw if it is not possible.
74+
*/
75+
parseReturnValue(fn: TExpression): TExpression;
76+
77+
/**
78+
* Compute the location range of the expression in the source file, to be used for source-mapping.
79+
*/
80+
getRange(node: TExpression): Range;
81+
}
82+
83+
/**
84+
* The location of the start and end of an expression in the original source file.
85+
*/
86+
export interface Range {
87+
/** 0-based character position of the range start in the source file text. */
88+
startPos: number;
89+
/** 0-based line index of the range start in the source file text. */
90+
startLine: number;
91+
/** 0-based column position of the range start in the source file text. */
92+
startCol: number;
93+
/** 0-based character position of the range end in the source file text. */
94+
endPos: number;
95+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import * as t from '@babel/types';
9+
10+
import {AstFactory, BinaryOperator, LeadingComment, ObjectLiteralProperty, SourceMapRange, TemplateLiteral, VariableDeclarationType} from '../../../../src/ngtsc/translator';
11+
import {assert} from '../utils';
12+
13+
export class BabelAstFactory implements AstFactory<t.Statement, t.Expression> {
14+
attachComments(statement: t.Statement, leadingComments: LeadingComment[]|undefined): t.Statement {
15+
if (leadingComments === undefined) {
16+
return statement;
17+
}
18+
// We must process the comments in reverse because `t.addComment()` will add new ones in front.
19+
for (let i = leadingComments.length - 1; i >= 0; i--) {
20+
const comment = leadingComments[i];
21+
t.addComment(statement, 'leading', comment.toString(), !comment.multiline);
22+
}
23+
return statement;
24+
}
25+
26+
createArrayLiteral = t.arrayExpression;
27+
28+
createAssignment(target: t.Expression, value: t.Expression): t.Expression {
29+
assert(target, isLExpression, 'must be a left hand side expression');
30+
return t.assignmentExpression('=', target, value);
31+
}
32+
33+
createBinaryExpression(
34+
leftOperand: t.Expression, operator: BinaryOperator,
35+
rightOperand: t.Expression): t.Expression {
36+
switch (operator) {
37+
case '&&':
38+
case '||':
39+
return t.logicalExpression(operator, leftOperand, rightOperand);
40+
default:
41+
return t.binaryExpression(operator, leftOperand, rightOperand);
42+
}
43+
}
44+
45+
createBlock = t.blockStatement;
46+
47+
createCallExpression(callee: t.Expression, args: t.Expression[], pure: boolean): t.Expression {
48+
const call = t.callExpression(callee, args);
49+
if (pure) {
50+
t.addComment(call, 'leading', ' @__PURE__ ', /* line */ false);
51+
}
52+
return call;
53+
}
54+
55+
createConditional = t.conditionalExpression;
56+
57+
createElementAccess(expression: t.Expression, element: t.Expression): t.Expression {
58+
return t.memberExpression(expression, element, /* computed */ true);
59+
}
60+
61+
createExpressionStatement = t.expressionStatement;
62+
63+
createFunctionDeclaration(functionName: string, parameters: string[], body: t.Statement):
64+
t.Statement {
65+
assert(body, t.isBlockStatement, 'a block');
66+
return t.functionDeclaration(
67+
t.identifier(functionName), parameters.map(param => t.identifier(param)), body);
68+
}
69+
70+
createFunctionExpression(functionName: string|null, parameters: string[], body: t.Statement):
71+
t.Expression {
72+
assert(body, t.isBlockStatement, 'a block');
73+
const name = functionName !== null ? t.identifier(functionName) : null;
74+
return t.functionExpression(name, parameters.map(param => t.identifier(param)), body);
75+
}
76+
77+
createIdentifier = t.identifier;
78+
79+
createIfStatement = t.ifStatement;
80+
81+
createLiteral(value: string|number|boolean|null|undefined): t.Expression {
82+
if (typeof value === 'string') {
83+
return t.stringLiteral(value);
84+
} else if (typeof value === 'number') {
85+
return t.numericLiteral(value);
86+
} else if (typeof value === 'boolean') {
87+
return t.booleanLiteral(value);
88+
} else if (value === undefined) {
89+
return t.identifier('undefined');
90+
} else if (value === null) {
91+
return t.nullLiteral();
92+
} else {
93+
throw new Error(`Invalid literal: ${value} (${typeof value})`);
94+
}
95+
}
96+
97+
createNewExpression = t.newExpression;
98+
99+
createObjectLiteral(properties: ObjectLiteralProperty<t.Expression>[]): t.Expression {
100+
return t.objectExpression(properties.map(prop => {
101+
const key =
102+
prop.quoted ? t.stringLiteral(prop.propertyName) : t.identifier(prop.propertyName);
103+
return t.objectProperty(key, prop.value);
104+
}));
105+
}
106+
107+
createParenthesizedExpression = t.parenthesizedExpression;
108+
109+
createPropertyAccess(expression: t.Expression, propertyName: string): t.Expression {
110+
return t.memberExpression(expression, t.identifier(propertyName), /* computed */ false);
111+
}
112+
113+
createReturnStatement = t.returnStatement;
114+
115+
createTaggedTemplate(tag: t.Expression, template: TemplateLiteral<t.Expression>): t.Expression {
116+
const elements = template.elements.map(
117+
(element, i) => this.setSourceMapRange(
118+
t.templateElement(element, i === template.elements.length - 1), element.range));
119+
return t.taggedTemplateExpression(tag, t.templateLiteral(elements, template.expressions));
120+
}
121+
122+
createThrowStatement = t.throwStatement;
123+
124+
createTypeOfExpression(expression: t.Expression): t.Expression {
125+
return t.unaryExpression('typeof', expression);
126+
}
127+
128+
createUnaryExpression = t.unaryExpression;
129+
130+
createVariableDeclaration(
131+
variableName: string, initializer: t.Expression|null,
132+
type: VariableDeclarationType): t.Statement {
133+
return t.variableDeclaration(
134+
type, [t.variableDeclarator(t.identifier(variableName), initializer)]);
135+
}
136+
137+
setSourceMapRange<T extends t.Statement|t.Expression|t.TemplateElement>(
138+
node: T, sourceMapRange: SourceMapRange|null): T {
139+
if (sourceMapRange === null) {
140+
return node;
141+
}
142+
// Note that the linker only works on a single file at a time, so there is no need to track the
143+
// filename. Babel will just use the current filename in the source-map.
144+
node.loc = {
145+
start: {
146+
line: sourceMapRange.start.line + 1, // lines are 1-based in Babel.
147+
column: sourceMapRange.start.column,
148+
},
149+
end: {
150+
line: sourceMapRange.end.line + 1, // lines are 1-based in Babel.
151+
column: sourceMapRange.end.column,
152+
},
153+
};
154+
node.start = sourceMapRange.start.offset;
155+
node.end = sourceMapRange.end.offset;
156+
157+
return node;
158+
}
159+
}
160+
161+
function isLExpression(expr: t.Expression): expr is Extract<t.LVal, t.Expression> {
162+
// Some LVal types are not expressions, which prevents us from using `t.isLVal()`
163+
// directly with `assert()`.
164+
return t.isLVal(expr);
165+
}

0 commit comments

Comments
 (0)