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:
parent
30de2db349
commit
7c0d4976b1
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)));
|
||||||
|
`
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue