fix(ivy): unwrap parenthesized or cast expressions for metadata (#24862)
Metadata in Ivy must be literal. For example, @NgModule({...}) is legal, whereas const meta = {...}; @NgModule(meta) is not. However, some code contains additional superfluous parentheses: @NgModule(({...})) It is desirable that ngtsc accept this form of literal object. PR Close #24862
This commit is contained in:
parent
8a5cd2200a
commit
1008bb6287
|
@ -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<string, Expression>();
|
||||
|
||||
|
@ -156,7 +156,8 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
|||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
throw new Error(`Incorrect number of arguments to @Component decorator`);
|
||||
}
|
||||
const meta = decorator.args[0];
|
||||
const meta = unwrapExpression(decorator.args[0]);
|
||||
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import {filterToMembersWithDecorator} from '../../metadata/src/reflector';
|
|||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies, isAngularCore} from './util';
|
||||
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
|
||||
|
@ -63,7 +63,7 @@ export function extractDirectiveMetadata(
|
|||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
throw new Error(`Incorrect number of arguments to @${decorator.name} decorator`);
|
||||
}
|
||||
const meta = decorator.args[0];
|
||||
const meta = unwrapExpression(decorator.args[0]);
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from
|
|||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies, isAngularCore, referenceToExpression} from './util';
|
||||
import {getConstructorDependencies, isAngularCore, referenceToExpression, unwrapExpression} from './util';
|
||||
|
||||
export interface NgModuleAnalysis {
|
||||
ngModuleDef: R3NgModuleMetadata;
|
||||
|
@ -43,7 +43,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
|||
|
||||
// @NgModule can be invoked without arguments. In case it is, pretend as if a blank object
|
||||
// literal was specified. This simplifies the code below.
|
||||
const meta = decorator.args.length === 1 ? decorator.args[0] : ts.createObjectLiteral([]);
|
||||
const meta = decorator.args.length === 1 ? unwrapExpression(decorator.args[0]) :
|
||||
ts.createObjectLiteral([]);
|
||||
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import {reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
|||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies, isAngularCore} from './util';
|
||||
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
|
||||
|
||||
export class PipeDecoratorHandler implements DecoratorHandler<R3PipeMetadata> {
|
||||
constructor(
|
||||
|
@ -35,7 +35,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<R3PipeMetadata> {
|
|||
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.`);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue