fix(8223): Preserve Provider expressions

Preserves constructor calls in addition to function calls.
Introduced a special case for forwardRef() similar to CONST_EXPR.
This commit is contained in:
Chuck Jazdzewski 2016-04-26 16:00:48 -07:00 committed by Alex Eagle
parent 30de2db349
commit 7c0d4976b1
3 changed files with 81 additions and 9 deletions

View File

@ -180,6 +180,7 @@ export class Evaluator {
} }
} }
} }
// We can fold a call to CONST_EXPR // We can fold a call to CONST_EXPR
if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1) if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1)
return this.isFoldableWorker(callExpression.arguments[0], folding); return this.isFoldableWorker(callExpression.arguments[0], folding);
@ -286,18 +287,35 @@ export class Evaluator {
if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1) { if (isCallOf(callExpression, "CONST_EXPR") && callExpression.arguments.length === 1) {
return args[0]; return args[0];
} }
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
const firstArgument = callExpression.arguments[0];
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
const arrowFunction = <ts.ArrowFunction>firstArgument;
return this.evaluateNode(arrowFunction.body);
}
}
const expression = this.evaluateNode(callExpression.expression); const expression = this.evaluateNode(callExpression.expression);
if (isDefined(expression) && args.every(isDefined)) { if (isDefined(expression) && args.every(isDefined)) {
const result: MetadataSymbolicCallExpression = { const result:
__symbolic: "call", MetadataSymbolicCallExpression = {__symbolic: "call", expression: expression};
expression: this.evaluateNode(callExpression.expression)
};
if (args && args.length) { if (args && args.length) {
result.arguments = args; result.arguments = args;
} }
return result; return result;
} }
break; break;
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.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: { case ts.SyntaxKind.PropertyAccessExpression: {
const propertyAccessExpression = <ts.PropertyAccessExpression>node; const propertyAccessExpression = <ts.PropertyAccessExpression>node;
const expression = this.evaluateNode(propertyAccessExpression.expression); const expression = this.evaluateNode(propertyAccessExpression.expression);

View File

@ -57,7 +57,7 @@ export interface MetadataObject { [name: string]: MetadataValue; }
export interface MetadataArray { [name: number]: MetadataValue; } export interface MetadataArray { [name: number]: MetadataValue; }
export interface MetadataSymbolicExpression { 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 { export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
if (value) { if (value) {
@ -65,6 +65,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
case "binary": case "binary":
case "call": case "call":
case "index": case "index":
case "new":
case "pre": case "pre":
case "reference": case "reference":
case "select": case "select":
@ -97,13 +98,13 @@ export function isMetadataSymbolicIndexExpression(
} }
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression { export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
__symbolic: "call"; __symbolic: "call" | "new";
expression: MetadataValue; expression: MetadataValue;
arguments?: MetadataValue[]; arguments?: MetadataValue[];
} }
export function isMetadataSymbolicCallExpression( export function isMetadataSymbolicCallExpression(
value: any): value is MetadataSymbolicCallExpression { value: any): value is MetadataSymbolicCallExpression {
return value && value.__symbolic === "call"; return value && (value.__symbolic === "call" || value.__symbolic === "new");
} }
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression { export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {

View File

@ -13,7 +13,9 @@ describe('Evaluator', () => {
let evaluator: Evaluator; let evaluator: Evaluator;
beforeEach(() => { 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); service = ts.createLanguageService(host);
program = service.getProgram(); program = service.getProgram();
typeChecker = program.getTypeChecker(); typeChecker = program.getTypeChecker();
@ -25,6 +27,7 @@ describe('Evaluator', () => {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics()); expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) { for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName)); expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName));
} }
}); });
@ -101,6 +104,34 @@ describe('Evaluator', () => {
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer)) expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer))
.toEqual({__symbolic: "reference", name: "recursiveA", module: undefined}); .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 = { const FILES: Directory = {
@ -109,6 +140,11 @@ const FILES: Directory = {
return function(fn: Function) { } return function(fn: Function) { }
} }
`, `,
'classes.ts': `
export class Value {
constructor(public name: string, public value: any) {}
}
`,
'consts.ts': ` 'consts.ts': `
export var someName = 'some-name'; export var someName = 'some-name';
export var someBool = true; export var someBool = true;
@ -159,5 +195,22 @@ const FILES: Directory = {
import {someName, someBool} from './consts'; import {someName, someBool} from './consts';
@Pipe({name: someName, pure: someBool}) @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)));
`
}; };