diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 64b2913fe1..f62a492330 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -17,7 +17,7 @@ import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {ResourceLoader} from './api'; import {extractDirectiveMetadata} from './directive'; import {SelectorScopeRegistry} from './selector_scope'; -import {isAngularCore} from './util'; +import {isAngularCore, unwrapExpression} from './util'; const EMPTY_MAP = new Map(); @@ -156,7 +156,8 @@ export class ComponentDecoratorHandler implements DecoratorHandler { constructor( @@ -35,7 +35,7 @@ export class PipeDecoratorHandler implements DecoratorHandler { if (decorator.args === null) { throw new Error(`@Pipe must be called`); } - const meta = decorator.args[0]; + const meta = unwrapExpression(decorator.args[0]); if (!ts.isObjectLiteralExpression(meta)) { throw new Error(`Decorator argument must be literal.`); } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index ec63dd357b..6322ddef86 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -87,3 +87,16 @@ export function referenceToExpression(ref: Reference, context: ts.SourceFile): E export function isAngularCore(decorator: Decorator): boolean { return decorator.import !== null && decorator.import.from === '@angular/core'; } + +/** + * Unwrap a `ts.Expression`, removing outer type-casts or parentheses until the expression is in its + * lowest level form. + * + * For example, the expression "(foo as Type)" unwraps to "foo". + */ +export function unwrapExpression(node: ts.Expression): ts.Expression { + while (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) { + node = node.expression; + } + return node; +} diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/util_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/util_spec.ts new file mode 100644 index 0000000000..95fc30ef1c --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/annotations/test/util_spec.ts @@ -0,0 +1,35 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as ts from 'typescript'; + +import {unwrapExpression} from '../src/util'; + +describe('ngtsc annotation utilities', () => { + describe('unwrapExpression', () => { + const obj = ts.createObjectLiteral(); + it('should pass through an ObjectLiteralExpression', + () => { expect(unwrapExpression(obj)).toBe(obj); }); + + it('should unwrap an ObjectLiteralExpression in parentheses', () => { + const wrapped = ts.createParen(obj); + expect(unwrapExpression(wrapped)).toBe(obj); + }); + + it('should unwrap an ObjectLiteralExpression with a type cast', () => { + const cast = ts.createAsExpression(obj, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); + expect(unwrapExpression(cast)).toBe(obj); + }); + + it('should unwrap an ObjectLiteralExpression with a type cast in parentheses', () => { + const cast = ts.createAsExpression(obj, ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)); + const wrapped = ts.createParen(cast); + expect(unwrapExpression(wrapped)).toBe(obj); + }); + }); +});