diff --git a/tools/metadata/src/evaluator.ts b/tools/metadata/src/evaluator.ts index 19fbca0158..7dd3deea05 100644 --- a/tools/metadata/src/evaluator.ts +++ b/tools/metadata/src/evaluator.ts @@ -180,6 +180,7 @@ export class Evaluator { } } } + // We can fold a call to CONST_EXPR if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1) return this.isFoldableWorker(callExpression.arguments[0], folding); @@ -286,18 +287,35 @@ export class Evaluator { if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1) { return args[0]; } + if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) { + const firstArgument = callExpression.arguments[0]; + if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) { + const arrowFunction = firstArgument; + return this.evaluateNode(arrowFunction.body); + } + } const expression = this.evaluateNode(callExpression.expression); if (isDefined(expression) && args.every(isDefined)) { - const result: MetadataSymbolicCallExpression = { - __symbolic: "call", - expression: this.evaluateNode(callExpression.expression) - }; + const result: + MetadataSymbolicCallExpression = {__symbolic: "call", expression: expression}; if (args && args.length) { result.arguments = args; } return result; } break; + case ts.SyntaxKind.NewExpression: + const newExpression = node; + const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg)); + const newTarget = this.evaluateNode(newExpression.expression); + if (isDefined(newTarget) && newArgs.every(isDefined)) { + const result: MetadataSymbolicCallExpression = {__symbolic: "new", expression: newTarget}; + if (newArgs.length) { + result.arguments = newArgs; + } + return result; + } + break; case ts.SyntaxKind.PropertyAccessExpression: { const propertyAccessExpression = node; const expression = this.evaluateNode(propertyAccessExpression.expression); diff --git a/tools/metadata/src/schema.ts b/tools/metadata/src/schema.ts index f6bd27f113..b024207064 100644 --- a/tools/metadata/src/schema.ts +++ b/tools/metadata/src/schema.ts @@ -57,7 +57,7 @@ export interface MetadataObject { [name: string]: MetadataValue; } export interface MetadataArray { [name: number]: MetadataValue; } export interface MetadataSymbolicExpression { - __symbolic: "binary" | "call" | "index" | "pre" | "reference" | "select" + __symbolic: "binary" | "call" | "index" | "new" | "pre" | "reference" | "select" } export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression { if (value) { @@ -65,6 +65,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo case "binary": case "call": case "index": + case "new": case "pre": case "reference": case "select": @@ -97,13 +98,13 @@ export function isMetadataSymbolicIndexExpression( } export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression { - __symbolic: "call"; + __symbolic: "call" | "new"; expression: MetadataValue; arguments?: MetadataValue[]; } export function isMetadataSymbolicCallExpression( value: any): value is MetadataSymbolicCallExpression { - return value && value.__symbolic === "call"; + return value && (value.__symbolic === "call" || value.__symbolic === "new"); } export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression { diff --git a/tools/metadata/test/evaluator.spec.ts b/tools/metadata/test/evaluator.spec.ts index 5a5e6be090..272645e7e0 100644 --- a/tools/metadata/test/evaluator.spec.ts +++ b/tools/metadata/test/evaluator.spec.ts @@ -13,7 +13,9 @@ describe('Evaluator', () => { let evaluator: Evaluator; beforeEach(() => { - host = new Host(FILES, ['expressions.ts']); + host = new Host( + FILES, + ['expressions.ts', 'const_expr.ts', 'forwardRef.ts', 'classes.ts', 'newExpression.ts']); service = ts.createLanguageService(host); program = service.getProgram(); typeChecker = program.getTypeChecker(); @@ -25,6 +27,7 @@ describe('Evaluator', () => { expectNoDiagnostics(service.getCompilerOptionsDiagnostics()); for (const sourceFile of program.getSourceFiles()) { expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName)); + expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName)); } }); @@ -101,6 +104,34 @@ describe('Evaluator', () => { expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer)) .toEqual({__symbolic: "reference", name: "recursiveA", module: undefined}); }); + + it('should correctly handle special cases for CONST_EXPR', () => { + var const_expr = program.getSourceFile('const_expr.ts'); + expect(evaluator.evaluateNode(findVar(const_expr, 'bTrue').initializer)).toEqual(true); + expect(evaluator.evaluateNode(findVar(const_expr, 'bFalse').initializer)).toEqual(false); + }); + + it('should resolve a forwardRef', () => { + var forwardRef = program.getSourceFile('forwardRef.ts'); + expect(evaluator.evaluateNode(findVar(forwardRef, 'bTrue').initializer)).toEqual(true); + expect(evaluator.evaluateNode(findVar(forwardRef, 'bFalse').initializer)).toEqual(false); + }); + + it('should return new expressions', () => { + var newExpression = program.getSourceFile('newExpression.ts'); + expect(evaluator.evaluateNode(findVar(newExpression, 'someValue').initializer)) + .toEqual({ + __symbolic: "new", + expression: {__symbolic: "reference", name: "Value", module: "classes.ts"}, + arguments: ["name", 12] + }); + expect(evaluator.evaluateNode(findVar(newExpression, 'complex').initializer)) + .toEqual({ + __symbolic: "new", + expression: {__symbolic: "reference", name: "Value", module: "classes.ts"}, + arguments: ["name", 12] + }); + }); }); const FILES: Directory = { @@ -109,6 +140,11 @@ const FILES: Directory = { return function(fn: Function) { } } `, + 'classes.ts': ` + export class Value { + constructor(public name: string, public value: any) {} + } + `, 'consts.ts': ` export var someName = 'some-name'; export var someBool = true; @@ -159,5 +195,22 @@ const FILES: Directory = { import {someName, someBool} from './consts'; @Pipe({name: someName, pure: someBool}) - export class B {}` + export class B {}`, + 'const_expr.ts': ` + function CONST_EXPR(value: any) { return value; } + export var bTrue = CONST_EXPR(true); + export var bFalse = CONST_EXPR(false); + `, + 'forwardRef.ts': ` + function forwardRef(value: any) { return value; } + export var bTrue = forwardRef(() => true); + export var bFalse = forwardRef(() => false); + `, + 'newExpression.ts': ` + import {Value} from './classes'; + function CONST_EXPR(value: any) { return value; } + function forwardRef(value: any) { return value; } + export const someValue = new Value("name", 12); + export const complex = CONST_EXPR(new Value("name", forwardRef(() => 12))); + ` };