Skip to content

Commit 3d30b67

Browse files
alan-agius4vikerman
authored andcommitted
fix(@angular-devkit/build-optimizer): fix error when __decorate has no __metadata
When a __decorator expression has no __metadata call, example: ``` __decorate([ ContentChild('heading', { read: ElementRef, static: true }) ], FooBarComponent.prototype, "buttons", void 0); ``` A Cannot read property 'kind' of undefined error will be thrown. Closes: #15703
1 parent b3aaae0 commit 3d30b67

File tree

2 files changed

+129
-41
lines changed

2 files changed

+129
-41
lines changed

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

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,12 @@ function scrubFileTransformer(checker: ts.TypeChecker, isAngularCoreFile: boolea
5252
const exprStmt = node as ts.ExpressionStatement;
5353
if (isDecoratorAssignmentExpression(exprStmt)) {
5454
nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
55-
}
56-
if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)) {
55+
} else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)
56+
|| isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker)) {
5757
nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker));
58-
}
59-
if (isAngularDecoratorMetadataExpression(exprStmt, ngMetadata, tslibImports, checker)) {
60-
nodes.push(node);
61-
}
62-
if (isPropDecoratorAssignmentExpression(exprStmt)) {
58+
} else if (isPropDecoratorAssignmentExpression(exprStmt)) {
6359
nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker));
64-
}
65-
if (isCtorParamsAssignmentExpression(exprStmt)) {
60+
} else if (isCtorParamsAssignmentExpression(exprStmt)) {
6661
nodes.push(node);
6762
}
6863
}
@@ -230,7 +225,7 @@ function isDecorateAssignmentExpression(
230225
}
231226

232227
// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`.
233-
function isAngularDecoratorMetadataExpression(
228+
function isAngularDecoratorExpression(
234229
exprStmt: ts.ExpressionStatement,
235230
ngMetadata: ts.Node[],
236231
tslibImports: ts.NamespaceImport[],
@@ -252,27 +247,19 @@ function isAngularDecoratorMetadataExpression(
252247
}
253248
const decorateArray = callExpr.arguments[0] as ts.ArrayLiteralExpression;
254249
// Check first array entry for Angular decorators.
255-
if (decorateArray.elements[0].kind !== ts.SyntaxKind.CallExpression) {
256-
return false;
257-
}
258-
const decoratorCall = decorateArray.elements[0] as ts.CallExpression;
259-
if (decoratorCall.expression.kind !== ts.SyntaxKind.Identifier) {
260-
return false;
261-
}
262-
const decoratorId = decoratorCall.expression as ts.Identifier;
263-
if (!identifierIsMetadata(decoratorId, ngMetadata, checker)) {
264-
return false;
265-
}
266-
// Check second array entry for __metadata call.
267-
if (decorateArray.elements[1].kind !== ts.SyntaxKind.CallExpression) {
268-
return false;
269-
}
270-
const metadataCall = decorateArray.elements[1] as ts.CallExpression;
271-
if (!isTslibHelper(metadataCall, '__metadata', tslibImports, checker)) {
250+
if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) {
272251
return false;
273252
}
274253

275-
return true;
254+
return decorateArray.elements.some(decoratorCall => {
255+
if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) {
256+
return false;
257+
}
258+
259+
const decoratorId = decoratorCall.expression;
260+
261+
return identifierIsMetadata(decoratorId, ngMetadata, checker);
262+
});
276263
}
277264

278265
// Check if assignment is `Clazz.propDecorators = [...];`.
@@ -357,16 +344,19 @@ function pickDecorateNodesToRemove(
357344
ngMetadata: ts.Node[],
358345
checker: ts.TypeChecker,
359346
): ts.Node[] {
347+
let callExpr: ts.CallExpression | undefined;
348+
if (ts.isCallExpression(exprStmt.expression)) {
349+
callExpr = exprStmt.expression;
350+
} else if (ts.isBinaryExpression(exprStmt.expression)) {
351+
const expr = exprStmt.expression;
352+
if (ts.isCallExpression(expr.right)) {
353+
callExpr = expr.right;
354+
} else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) {
355+
callExpr = expr.right.right;
356+
}
357+
}
360358

361-
const expr = expect<ts.BinaryExpression>(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
362-
let callExpr: ts.CallExpression;
363-
364-
if (expr.right.kind === ts.SyntaxKind.CallExpression) {
365-
callExpr = expect<ts.CallExpression>(expr.right, ts.SyntaxKind.CallExpression);
366-
} else if (expr.right.kind === ts.SyntaxKind.BinaryExpression) {
367-
const innerExpr = expr.right as ts.BinaryExpression;
368-
callExpr = expect<ts.CallExpression>(innerExpr.right, ts.SyntaxKind.CallExpression);
369-
} else {
359+
if (!callExpr) {
370360
return [];
371361
}
372362

@@ -398,10 +388,6 @@ function pickDecorateNodesToRemove(
398388
if (el.arguments[0].kind !== ts.SyntaxKind.StringLiteral) {
399389
return false;
400390
}
401-
const metadataTypeId = el.arguments[0] as ts.StringLiteral;
402-
if (metadataTypeId.text !== 'design:paramtypes') {
403-
return false;
404-
}
405391

406392
return true;
407393
});
@@ -419,6 +405,7 @@ function pickDecorateNodesToRemove(
419405

420406
return true;
421407
});
408+
422409
ngDecoratorCalls.push(...metadataCalls, ...paramCalls);
423410

424411
// If all decorators are metadata decorators then return the whole `Class = __decorate([...])'`

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

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,107 @@ describe('scrub-file', () => {
319319
expect(testScrubFile(input)).toBeTruthy();
320320
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
321321
});
322+
323+
it('removes Angular decorators calls in __decorate when no __metadata is present', () => {
324+
const input = tags.stripIndent`
325+
import { __decorate } from 'tslib';
326+
import { Component, ElementRef, ContentChild} from '@angular/core';
327+
328+
var FooBarComponent = /** @class */ (function () {
329+
function FooBarComponent(elementRef) {
330+
this.elementRef = elementRef;
331+
this.inlineButtons = [];
332+
this.menuButtons = [];
333+
}
334+
FooBarComponent.ctorParameters = function () { return [
335+
{ type: ElementRef }
336+
]; };
337+
__decorate([
338+
ContentChild('heading', { read: ElementRef, static: true })
339+
], FooBarComponent.prototype, "buttons", void 0);
340+
FooBarComponent = __decorate([
341+
Component({
342+
selector: 'custom-foo-bar',
343+
template: '',
344+
styles: []
345+
})
346+
], FooBarComponent);
347+
return FooBarComponent;
348+
}());
349+
`;
350+
351+
const output = tags.stripIndent`
352+
import { __decorate } from 'tslib';
353+
import { Component, ElementRef, ContentChild } from '@angular/core';
354+
355+
var FooBarComponent = /** @class */ (function () {
356+
function FooBarComponent(elementRef) {
357+
this.elementRef = elementRef;
358+
this.inlineButtons = [];
359+
this.menuButtons = [];
360+
}
361+
362+
return FooBarComponent;
363+
}());
364+
`;
365+
366+
expect(testScrubFile(input)).toBeTruthy();
367+
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
368+
});
369+
370+
it('removes only Angular decorators calls in __decorate when no __metadata is present', () => {
371+
const input = tags.stripIndent`
372+
import { __decorate } from 'tslib';
373+
import { Component, ElementRef, ContentChild} from '@angular/core';
374+
import { NotComponent } from 'another-lib';
375+
376+
var FooBarComponent = /** @class */ (function () {
377+
function FooBarComponent(elementRef) {
378+
this.elementRef = elementRef;
379+
this.inlineButtons = [];
380+
this.menuButtons = [];
381+
}
382+
FooBarComponent.ctorParameters = function () { return [
383+
{ type: ElementRef }
384+
]; };
385+
__decorate([
386+
NotComponent(),
387+
ContentChild('heading', { read: ElementRef, static: true })
388+
], FooBarComponent.prototype, "buttons", void 0);
389+
FooBarComponent = __decorate([
390+
NotComponent(),
391+
Component({
392+
selector: 'custom-foo-bar',
393+
template: '',
394+
styles: []
395+
})
396+
], FooBarComponent);
397+
return FooBarComponent;
398+
}());
399+
`;
400+
401+
const output = tags.stripIndent`
402+
import { __decorate } from 'tslib';
403+
import { Component, ElementRef, ContentChild } from '@angular/core';
404+
import { NotComponent } from 'another-lib';
405+
406+
var FooBarComponent = /** @class */ (function () {
407+
function FooBarComponent(elementRef) {
408+
this.elementRef = elementRef;
409+
this.inlineButtons = [];
410+
this.menuButtons = [];
411+
}
412+
__decorate([
413+
NotComponent()
414+
], FooBarComponent.prototype, "buttons", void 0);
415+
416+
FooBarComponent = __decorate([ NotComponent() ], FooBarComponent); return FooBarComponent;
417+
}());
418+
`;
419+
420+
expect(testScrubFile(input)).toBeTruthy();
421+
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
422+
});
322423
});
323424

324425
describe('__metadata', () => {

0 commit comments

Comments
 (0)