Skip to content

Commit

Permalink
fix(@angular-devkit/build-optimizer): fix error when __decorate has…
Browse files Browse the repository at this point in the history
… 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: angular#15703
  • Loading branch information
alan-agius4 committed Oct 2, 2019
1 parent 600e009 commit 24cfe60
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,12 @@ function scrubFileTransformer(checker: ts.TypeChecker, isAngularCoreFile: boolea
const exprStmt = node as ts.ExpressionStatement;
if (isDecoratorAssignmentExpression(exprStmt)) {
nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
}
if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)) {
} else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)
|| isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker)) {
nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker));
}
if (isAngularDecoratorMetadataExpression(exprStmt, ngMetadata, tslibImports, checker)) {
nodes.push(node);
}
if (isPropDecoratorAssignmentExpression(exprStmt)) {
} else if (isPropDecoratorAssignmentExpression(exprStmt)) {
nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker));
}
if (isCtorParamsAssignmentExpression(exprStmt)) {
} else if (isCtorParamsAssignmentExpression(exprStmt)) {
nodes.push(node);
}
}
Expand Down Expand Up @@ -230,7 +225,7 @@ function isDecorateAssignmentExpression(
}

// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`.
function isAngularDecoratorMetadataExpression(
function isAngularDecoratorExpression(
exprStmt: ts.ExpressionStatement,
ngMetadata: ts.Node[],
tslibImports: ts.NamespaceImport[],
Expand All @@ -252,27 +247,19 @@ function isAngularDecoratorMetadataExpression(
}
const decorateArray = callExpr.arguments[0] as ts.ArrayLiteralExpression;
// Check first array entry for Angular decorators.
if (decorateArray.elements[0].kind !== ts.SyntaxKind.CallExpression) {
return false;
}
const decoratorCall = decorateArray.elements[0] as ts.CallExpression;
if (decoratorCall.expression.kind !== ts.SyntaxKind.Identifier) {
return false;
}
const decoratorId = decoratorCall.expression as ts.Identifier;
if (!identifierIsMetadata(decoratorId, ngMetadata, checker)) {
return false;
}
// Check second array entry for __metadata call.
if (decorateArray.elements[1].kind !== ts.SyntaxKind.CallExpression) {
return false;
}
const metadataCall = decorateArray.elements[1] as ts.CallExpression;
if (!isTslibHelper(metadataCall, '__metadata', tslibImports, checker)) {
if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) {
return false;
}

return true;
return decorateArray.elements.some(decoratorCall => {
if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) {
return false;
}

const decoratorId = decoratorCall.expression;

return identifierIsMetadata(decoratorId, ngMetadata, checker);
});
}

// Check if assignment is `Clazz.propDecorators = [...];`.
Expand Down Expand Up @@ -357,16 +344,19 @@ function pickDecorateNodesToRemove(
ngMetadata: ts.Node[],
checker: ts.TypeChecker,
): ts.Node[] {
let callExpr: ts.CallExpression | undefined;
if (ts.isCallExpression(exprStmt.expression)) {
callExpr = exprStmt.expression;
} else if (ts.isBinaryExpression(exprStmt.expression)) {
const expr = exprStmt.expression;
if (ts.isCallExpression(expr.right)) {
callExpr = expr.right;
} else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) {
callExpr = expr.right.right;
}
}

const expr = expect<ts.BinaryExpression>(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
let callExpr: ts.CallExpression;

if (expr.right.kind === ts.SyntaxKind.CallExpression) {
callExpr = expect<ts.CallExpression>(expr.right, ts.SyntaxKind.CallExpression);
} else if (expr.right.kind === ts.SyntaxKind.BinaryExpression) {
const innerExpr = expr.right as ts.BinaryExpression;
callExpr = expect<ts.CallExpression>(innerExpr.right, ts.SyntaxKind.CallExpression);
} else {
if (!callExpr) {
return [];
}

Expand Down Expand Up @@ -398,10 +388,6 @@ function pickDecorateNodesToRemove(
if (el.arguments[0].kind !== ts.SyntaxKind.StringLiteral) {
return false;
}
const metadataTypeId = el.arguments[0] as ts.StringLiteral;
if (metadataTypeId.text !== 'design:paramtypes') {
return false;
}

return true;
});
Expand All @@ -419,6 +405,7 @@ function pickDecorateNodesToRemove(

return true;
});

ngDecoratorCalls.push(...metadataCalls, ...paramCalls);

// If all decorators are metadata decorators then return the whole `Class = __decorate([...])'`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,107 @@ describe('scrub-file', () => {
expect(testScrubFile(input)).toBeTruthy();
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
});

it('removes Angular decorators calls in __decorate when no __metadata is present', () => {
const input = tags.stripIndent`
import { __decorate } from 'tslib';
import { Component, ElementRef, ContentChild} from '@angular/core';
var FooBarComponent = /** @class */ (function () {
function FooBarComponent(elementRef) {
this.elementRef = elementRef;
this.inlineButtons = [];
this.menuButtons = [];
}
FooBarComponent.ctorParameters = function () { return [
{ type: ElementRef }
]; };
__decorate([
ContentChild('heading', { read: ElementRef, static: true })
], FooBarComponent.prototype, "buttons", void 0);
FooBarComponent = __decorate([
Component({
selector: 'custom-foo-bar',
template: '',
styles: []
})
], FooBarComponent);
return FooBarComponent;
}());
`;

const output = tags.stripIndent`
import { __decorate } from 'tslib';
import { Component, ElementRef, ContentChild } from '@angular/core';
var FooBarComponent = /** @class */ (function () {
function FooBarComponent(elementRef) {
this.elementRef = elementRef;
this.inlineButtons = [];
this.menuButtons = [];
}
return FooBarComponent;
}());
`;

expect(testScrubFile(input)).toBeTruthy();
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
});

it('removes only Angular decorators calls in __decorate when no __metadata is present', () => {
const input = tags.stripIndent`
import { __decorate } from 'tslib';
import { Component, ElementRef, ContentChild} from '@angular/core';
import { NotComponent } from 'another-lib';
var FooBarComponent = /** @class */ (function () {
function FooBarComponent(elementRef) {
this.elementRef = elementRef;
this.inlineButtons = [];
this.menuButtons = [];
}
FooBarComponent.ctorParameters = function () { return [
{ type: ElementRef }
]; };
__decorate([
NotComponent(),
ContentChild('heading', { read: ElementRef, static: true })
], FooBarComponent.prototype, "buttons", void 0);
FooBarComponent = __decorate([
NotComponent(),
Component({
selector: 'custom-foo-bar',
template: '',
styles: []
})
], FooBarComponent);
return FooBarComponent;
}());
`;

const output = tags.stripIndent`
import { __decorate } from 'tslib';
import { Component, ElementRef, ContentChild } from '@angular/core';
import { NotComponent } from 'another-lib';
var FooBarComponent = /** @class */ (function () {
function FooBarComponent(elementRef) {
this.elementRef = elementRef;
this.inlineButtons = [];
this.menuButtons = [];
}
__decorate([
NotComponent()
], FooBarComponent.prototype, "buttons", void 0);
FooBarComponent = __decorate([ NotComponent() ], FooBarComponent); return FooBarComponent;
}());
`;

expect(testScrubFile(input)).toBeTruthy();
expect(tags.oneLine`${transformCore(input)}`).toEqual(tags.oneLine`${output}`);
});
});

describe('__metadata', () => {
Expand Down

0 comments on commit 24cfe60

Please sign in to comment.