8000 fix(@angular-devkit/build-optimizer): fix error when `__decorate` has… · leon/angular-cli@ae36fbc · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit ae36fbc

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: ```js __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
1 parent c07c2a7 commit ae36fbc

File tree

2 files changed

+127
-36
lines changed

2 files changed

+127
-36
lines changed

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

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,9 @@ function scrubFileTransformer(checker: ts.TypeChecker, isAngularCoreFile: boolea
5858
nodes.push(node);
5959
} else if (isDecoratorAssignmentExpression(exprStmt)) {
6060
nodes.push(...pickDecorationNodesToRemove(exprStmt, ngMetadata, checker));
61-
} else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)) {
61+
} else if (isDecorateAssignmentExpression(exprStmt, tslibImports, checker)
62+
|| isAngularDecoratorExpression(exprStmt, ngMetadata, tslibImports, checker)) {
6263
nodes.push(...pickDecorateNodesToRemove(exprStmt, tslibImports, ngMetadata, checker));
63-
} else if (isAngularDecoratorMetadataExpression(exprStmt,
64-
ngMetadata, tslibImports, checker)) {
65-
nodes.push(node);
6664
} else if (isPropDecoratorAssignmentExpression(exprStmt)) {
6765
nodes.push(...pickPropDecorationNodesToRemove(exprStmt, ngMetadata, checker));
6866
}
@@ -218,7 +216,7 @@ function isDecorateAssignmentExpression(
218216
}
219217

220218
// Check if expression is `__decorate([smt, __metadata("design:type", Object)], ...)`.
221-
function isAngularDecoratorMetadataExpression(
219+
function isAngularDecoratorExpression(
222220
exprStmt: ts.ExpressionStatement,
223221
ngMetadata: ts.Node[],
224222
tslibImports: ts.NamespaceImport[],
@@ -240,27 +238,19 @@ function isAngularDecoratorMetadataExpression(
240238
}
241239
const decorateArray = callExpr.arguments[0] as ts.ArrayLiteralExpression;
242240
// Check first array entry for Angular decorators.
243-
if (decorateArray.elements[0].kind !== ts.SyntaxKind.CallExpression) {
244-
return false;
245-
}
246-
const decoratorCall = decorateArray.elements[0] as ts.CallExpression;
247-
if (decoratorCall.expression.kind !== ts.SyntaxKind.Identifier) {
248-
return false;
249-
}
250-
const decoratorId = decoratorCall.expression as ts.Identifier;
251-
if (!identifierIsMetadata(decoratorId, ngMetadata, checker)) {
252-
return false;
253-
}
254-
// Check second array entry for __metadata call.
255-
if (decorateArray.elements[1].kind !== ts.SyntaxKind.CallExpression) {
256-
return false;
257-
}
258-
const metadataCall = decorateArray.elements[1] as ts.CallExpression;
259-
if (!isTslibHelper(metadataCall, '__metadata', tslibImports, checker)) {
241+
if (decorateArray.elements.length === 0 || !ts.isCallExpression(decorateArray.elements[0])) {
260242
return false;
261243
}
262244

263-
return true;
245+
return decorateArray.elements.some(decoratorCall => {
246+
if (!ts.isCallExpression(decoratorCall) || !ts.isIdentifier(decoratorCall.expression)) {
247+
return false;
248+
}
249+
250+
const decoratorId = decoratorCall.expression;
251+
252+
return identifierIsMetadata(decoratorId, ngMetadata, checker);
253+
});
264254
}
265255

266256
// Check if assignment is `Clazz.propDecorators = [...];`.
@@ -359,16 +349,19 @@ function pickDecorateNodesToRemove(
359349
ngMetadata: ts.Node[],
360350
checker: ts.TypeChecker,
361351
): ts.Node[] {
352+
let callExpr: ts.CallExpression | undefined;
353+
if (ts.isCallExpression(exprStmt.expression)) {
354+
callExpr = exprStmt.expression;
355+
} else if (ts.isBinaryExpression(exprStmt.expression)) {
356+
const expr = exprStmt.expression;
357+
if (ts.isCallExpression(expr.right)) {
358+
callExpr = expr.right;
359+
} else if (ts.isBinaryExpression(expr.right) && ts.isCallExpression(expr.right.right)) {
360+
callExpr = expr.right.right;
361+
}
362+
}
362363

363-
const expr = expect<ts.BinaryExpression>(exprStmt.expression, ts.SyntaxKind.BinaryExpression);
364-
let callExpr: ts.CallExpression;
365-
366-
if (expr.right.kind === ts.SyntaxKind.CallExpression) {
367-
callExpr = expect<ts.CallExpression>(expr.right, ts.SyntaxKind.CallExpression);
368-
} else if (expr.right.kind === ts.SyntaxKind.BinaryExpression) {
369-
const innerExpr = expr.right as ts.BinaryExpression;
370-
callExpr = expect<ts.CallExpression>(innerExpr.right, ts.SyntaxKind.CallExpression);
371-
} else {
364+
if (!callExpr) {
372365
return [];
373366
}
374367

@@ -400,10 +393,6 @@ function pickDecorateNodesToRemove(
400393
if (el.arguments[0].kind !== ts.SyntaxKind.StringLiteral) {
401394
return false;
402395
}
403-
const metadataTypeId = el.arguments[0] as ts.StringLiteral;
404-
if (metadataTypeId.text !== 'design:paramtypes') {
405-
return false;
406-
}
407396

408397
return true;
409398
});
@@ -421,6 +410,7 @@ function pickDecorateNodesToRemove(
421410

422411
return true;
423412
});
413+
424414
ngDecoratorCalls.push(...metadataCalls, ...paramCalls);
425415

426416
// 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)
0