Skip to content

Commit 32fb7c3

Browse files
filipesilvahansl
authored andcommitted
refactor(@angular-devkit/build-optimizer): use symbol lookup instead of regex for tslib check
Followup to #217
1 parent a00bff0 commit 32fb7c3

File tree

2 files changed

+98
-25
lines changed

2 files changed

+98
-25
lines changed

packages/angular_devkit/build_optimizer/src/transforms/scrub-file.ts

+94-25
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
import * as ts from 'typescript';
99
import { collectDeepNodes } from '../helpers/ast-utils';
1010

11-
const tslibDecorateRe = /\btslib(?:_\d+)?\.__decorate\b/;
12-
const tslibRe = /\btslib(?:_\d+)?\b/;
13-
1411

1512
export function testScrubFile(content: string) {
1613
const markers = [
@@ -20,8 +17,7 @@ export function testScrubFile(content: string) {
2017
'ctorParameters',
2118
];
2219

23-
return markers.some((marker) => content.indexOf(marker) !== -1)
24-
|| tslibDecorateRe.test(content);
20+
return markers.some((marker) => content.indexOf(marker) !== -1);
2521
}
2622

2723
// Don't remove `ctorParameters` from these.
@@ -59,6 +55,7 @@ export function getScrubFileTransformer(program: ts.Program): ts.TransformerFact
5955
const transformer: ts.Transformer<ts.SourceFile> = (sf: ts.SourceFile) => {
6056

6157
const ngMetadata = findAngularMetadata(sf);
58+
const tslibImports = findTslibImports(sf);
6259

6360
const nodes: ts.Node[] = [];
6461
ts.forEachChild(sf, checkNodeForDecorators);
@@ -73,7 +70,7 @@ export function getScrubFileTransformer(program: ts.Program): ts.TransformerFact
7370
if (isDecoratorAssignmentExpression(exprStmt)) {
7471
nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
7572
}
76-
if (isDecorateAssignmentExpression(exprStmt)) {
73+
if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)) {
7774
nodes.push(...pickDecorateNodesToRemove(exprStmt, ngMetadata, checker));
7875
}
7976
if (isPropDecoratorAssignmentExpression(exprStmt)) {
@@ -193,7 +190,12 @@ function isDecoratorAssignmentExpression(exprStmt: ts.ExpressionStatement): bool
193190
}
194191

195192
// Check if assignment is `Clazz = __decorate([...], Clazz)`.
196-
function isDecorateAssignmentExpression(exprStmt: ts.ExpressionStatement): boolean {
193+
function isDecorateAssignmentExpression(
194+
exprStmt: ts.ExpressionStatement,
195+
tslibIdentifiers: ts.NamespaceImport[],
196+
checker: ts.TypeChecker,
197+
): boolean {
198+
197199
if (exprStmt.expression.kind !== ts.SyntaxKind.BinaryExpression) {
198200
return false;
199201
}
@@ -206,27 +208,11 @@ function isDecorateAssignmentExpression(exprStmt: ts.ExpressionStatement): boole
206208
}
207209
const classIdent = expr.left as ts.Identifier;
208210
const callExpr = expr.right as ts.CallExpression;
209-
let callExprIdent = callExpr.expression as ts.Identifier;
210-
211-
if (callExpr.expression.kind !== ts.SyntaxKind.Identifier) {
212-
if (callExpr.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
213-
const propAccess = callExpr.expression as ts.PropertyAccessExpression;
214-
const left = propAccess.expression;
215-
callExprIdent = propAccess.name;
216-
217-
if (!(left.kind === ts.SyntaxKind.Identifier && tslibRe.test((left as ts.Identifier).text))) {
218-
return false;
219-
}
220-
} else {
221-
return false;
222-
}
223-
}
224211

225-
// node.text on a name that starts with two underscores will return three instead.
226-
// Unless it's an expression like tslib.__decorate, in which case it's only 2.
227-
if (callExprIdent.text !== '___decorate' && callExprIdent.text !== '__decorate') {
212+
if (!isTslibHelper(callExpr, '__decorate', tslibIdentifiers, checker)) {
228213
return false;
229214
}
215+
230216
if (callExpr.arguments.length !== 2) {
231217
return false;
232218
}
@@ -454,3 +440,86 @@ function identifierIsMetadata(
454440
.declarations
455441
.some((spec) => metadata.indexOf(spec) !== -1);
456442
}
443+
444+
// Check if an import is a tslib helper import (`import * as tslib from "tslib";`)
445+
function isTslibImport(node: ts.ImportDeclaration): boolean {
446+
return !!(node.moduleSpecifier &&
447+
node.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral &&
448+
(node.moduleSpecifier as ts.StringLiteral).text === 'tslib' &&
449+
node.importClause &&
450+
node.importClause.namedBindings &&
451+
node.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport);
452+
}
453+
454+
// Find all namespace imports for `tslib`.
455+
function findTslibImports(node: ts.Node): ts.NamespaceImport[] {
456+
const imports: ts.NamespaceImport[] = [];
457+
ts.forEachChild(node, (child) => {
458+
if (child.kind === ts.SyntaxKind.ImportDeclaration) {
459+
const importDecl = child as ts.ImportDeclaration;
460+
if (isTslibImport(importDecl)) {
461+
const importClause = importDecl.importClause as ts.ImportClause;
462+
const namespaceImport = importClause.namedBindings as ts.NamespaceImport;
463+
imports.push(namespaceImport);
464+
}
465+
}
466+
});
467+
468+
return imports;
469+
}
470+
471+
// Check if an identifier is part of the known tslib identifiers.
472+
function identifierIsTslib(
473+
id: ts.Identifier,
474+
tslibImports: ts.NamespaceImport[],
475+
checker: ts.TypeChecker,
476+
): boolean {
477+
const symbol = checker.getSymbolAtLocation(id);
478+
if (!symbol || !symbol.declarations || !symbol.declarations.length) {
479+
return false;
480+
}
481+
482+
return symbol
483+
.declarations
484+
.some((spec) => tslibImports.indexOf(spec as ts.NamespaceImport) !== -1);
485+
}
486+
487+
// Check if a function call is a tslib helper.
488+
function isTslibHelper(
489+
callExpr: ts.CallExpression,
490+
helper: string,
491+
tslibImports: ts.NamespaceImport[],
492+
checker: ts.TypeChecker,
493+
) {
494+
495+
let callExprIdent = callExpr.expression as ts.Identifier;
496+
497+
if (callExpr.expression.kind !== ts.SyntaxKind.Identifier) {
498+
if (callExpr.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
499+
const propAccess = callExpr.expression as ts.PropertyAccessExpression;
500+
const left = propAccess.expression;
501+
callExprIdent = propAccess.name;
502+
503+
if (left.kind !== ts.SyntaxKind.Identifier) {
504+
return false;
505+
}
506+
507+
const id = left as ts.Identifier;
508+
509+
if (!identifierIsTslib(id, tslibImports, checker)) {
510+
return false;
511+
}
512+
513+
} else {
514+
return false;
515+
}
516+
}
517+
518+
// node.text on a name that starts with two underscores will return three instead.
519+
// Unless it's an expression like tslib.__decorate, in which case it's only 2.
520+
if (callExprIdent.text !== `_${helper}` && callExprIdent.text !== helper) {
521+
return false;
522+
}
523+
524+
return true;
525+
}

packages/angular_devkit/build_optimizer/src/transforms/scrub-file_spec.ts

+4
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ describe('scrub-file', () => {
143143

144144
it('recognizes tslib as well', () => {
145145
const input = tags.stripIndent`
146+
import * as tslib from "tslib";
147+
import * as tslib_2 from "tslib";
146148
import { Component } from '@angular/core';
147149
import { NotComponent } from 'another-lib';
148150
var Clazz = (function () {
@@ -172,6 +174,8 @@ describe('scrub-file', () => {
172174
}());
173175
`;
174176
const output = tags.stripIndent`
177+
import * as tslib from "tslib";
178+
import * as tslib_2 from "tslib";
175179
import { Component } from '@angular/core';
176180
import { NotComponent } from 'another-lib';
177181
var Clazz = (function () {

0 commit comments

Comments
 (0)