feat(compiler): Added support for conditional expressions. (#10366)
Expression evaluated by the static reflector can now supports conditional expressions. Closes: #10365
This commit is contained in:
parent
81d27daf0d
commit
20b03bad11
|
@ -307,26 +307,29 @@ export class StaticReflector implements ReflectorReader {
|
||||||
throw new Error('Recursion not supported');
|
throw new Error('Recursion not supported');
|
||||||
}
|
}
|
||||||
calling.set(functionSymbol, true);
|
calling.set(functionSymbol, true);
|
||||||
let value = targetFunction['value'];
|
try {
|
||||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
let value = targetFunction['value'];
|
||||||
// Determine the arguments
|
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||||
let args = (expression['arguments'] || []).map((arg: any) => simplify(arg));
|
// Determine the arguments
|
||||||
let parameters: string[] = targetFunction['parameters'];
|
let args = (expression['arguments'] || []).map((arg: any) => simplify(arg));
|
||||||
let functionScope = BindingScope.build();
|
let parameters: string[] = targetFunction['parameters'];
|
||||||
for (let i = 0; i < parameters.length; i++) {
|
let functionScope = BindingScope.build();
|
||||||
functionScope.define(parameters[i], args[i]);
|
for (let i = 0; i < parameters.length; i++) {
|
||||||
|
functionScope.define(parameters[i], args[i]);
|
||||||
|
}
|
||||||
|
let oldScope = scope;
|
||||||
|
let result: any;
|
||||||
|
try {
|
||||||
|
scope = functionScope.done();
|
||||||
|
result = simplifyInContext(functionSymbol, value, depth + 1);
|
||||||
|
} finally {
|
||||||
|
scope = oldScope;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
let oldScope = scope;
|
} finally {
|
||||||
let result: any;
|
calling.delete(functionSymbol);
|
||||||
try {
|
|
||||||
scope = functionScope.done();
|
|
||||||
result = simplifyInContext(functionSymbol, value, depth + 1);
|
|
||||||
} finally {
|
|
||||||
scope = oldScope;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
calling.delete(functionSymbol);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,6 +420,10 @@ export class StaticReflector implements ReflectorReader {
|
||||||
return left % right;
|
return left % right;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
case 'if':
|
||||||
|
let condition = simplify(expression['condition']);
|
||||||
|
return condition ? simplify(expression['thenExpression']) :
|
||||||
|
simplify(expression['elseExpression']);
|
||||||
case 'pre':
|
case 'pre':
|
||||||
let operand = simplify(expression['operand']);
|
let operand = simplify(expression['operand']);
|
||||||
if (shouldIgnore(operand)) return operand;
|
if (shouldIgnore(operand)) return operand;
|
||||||
|
|
|
@ -411,6 +411,16 @@ describe('StaticReflector', () => {
|
||||||
expect(annotations.length).toBe(1);
|
expect(annotations.length).toBe(1);
|
||||||
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
|
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to get the metadata for a class calling a method with a conditional expression',
|
||||||
|
() => {
|
||||||
|
const annotations = reflector.annotations(
|
||||||
|
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
|
||||||
|
expect(annotations.length).toBe(1);
|
||||||
|
expect(annotations[0].providers).toEqual([
|
||||||
|
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class MockReflectorHost implements StaticReflectorHost {
|
class MockReflectorHost implements StaticReflectorHost {
|
||||||
|
@ -960,6 +970,9 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||||
static with(data: any) {
|
static with(data: any) {
|
||||||
return { provider: 'a', useValue: data }
|
return { provider: 'a', useValue: data }
|
||||||
}
|
}
|
||||||
|
static condMethod(cond: boolean) {
|
||||||
|
return [{ provider: 'a', useValue: cond ? '1' : '2'}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'/tmp/src/static-method-call.ts': `
|
'/tmp/src/static-method-call.ts': `
|
||||||
|
@ -970,6 +983,11 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||||
providers: MyModule.with(100)
|
providers: MyModule.with(100)
|
||||||
})
|
})
|
||||||
export class MyComponent { }
|
export class MyComponent { }
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [MyModule.condMethod(true), MyModule.condMethod(false)]
|
||||||
|
})
|
||||||
|
export class MyCondComponent { }
|
||||||
`,
|
`,
|
||||||
'/tmp/src/static-field.ts': `
|
'/tmp/src/static-field.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
import {Injectable} from 'angular2/core';
|
||||||
|
|
|
@ -511,6 +511,15 @@ export class Evaluator {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ts.SyntaxKind.ConditionalExpression:
|
||||||
|
const conditionalExpression = <ts.ConditionalExpression>node;
|
||||||
|
const condition = this.evaluateNode(conditionalExpression.condition);
|
||||||
|
const thenExpression = this.evaluateNode(conditionalExpression.whenTrue);
|
||||||
|
const elseExpression = this.evaluateNode(conditionalExpression.whenFalse);
|
||||||
|
if (isPrimitive(condition)) {
|
||||||
|
return condition ? thenExpression : elseExpression;
|
||||||
|
}
|
||||||
|
return {__symbolic: 'if', condition, thenExpression, elseExpression};
|
||||||
case ts.SyntaxKind.FunctionExpression:
|
case ts.SyntaxKind.FunctionExpression:
|
||||||
case ts.SyntaxKind.ArrowFunction:
|
case ts.SyntaxKind.ArrowFunction:
|
||||||
return errorSymbol('Function call not supported', node);
|
return errorSymbol('Function call not supported', node);
|
||||||
|
|
|
@ -79,7 +79,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'|'new'|'pre'|'reference'|'select'|'spread'
|
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'|'if'
|
||||||
}
|
}
|
||||||
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
|
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -92,6 +92,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
|
||||||
case 'reference':
|
case 'reference':
|
||||||
case 'select':
|
case 'select':
|
||||||
case 'spread':
|
case 'spread':
|
||||||
|
case 'if':
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,6 +141,16 @@ export function isMetadataSymbolicPrefixExpression(value: any):
|
||||||
return value && value.__symbolic === 'pre';
|
return value && value.__symbolic === 'pre';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetadataSymbolicIfExpression extends MetadataSymbolicExpression {
|
||||||
|
__symbolic: 'if';
|
||||||
|
condition: MetadataValue;
|
||||||
|
thenExpression: MetadataValue;
|
||||||
|
elseExpression: MetadataValue;
|
||||||
|
}
|
||||||
|
export function isMetadataSymbolicIfExpression(value: any): value is MetadataSymbolicIfExpression {
|
||||||
|
return value && value.__symbolic === 'if';
|
||||||
|
}
|
||||||
|
|
||||||
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
|
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
|
||||||
__symbolic: 'reference';
|
__symbolic: 'reference';
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -14,10 +14,20 @@ describe('Collector', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
host = new Host(FILES, [
|
host = new Host(FILES, [
|
||||||
'/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts',
|
'/app/app.component.ts',
|
||||||
'/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts', 'exported-functions.ts',
|
'/app/cases-data.ts',
|
||||||
'exported-enum.ts', 'exported-consts.ts', 'static-method.ts', 'static-method-call.ts',
|
'/app/error-cases.ts',
|
||||||
'static-field-reference.ts'
|
'/promise.ts',
|
||||||
|
'/unsupported-1.ts',
|
||||||
|
'/unsupported-2.ts',
|
||||||
|
'import-star.ts',
|
||||||
|
'exported-functions.ts',
|
||||||
|
'exported-enum.ts',
|
||||||
|
'exported-consts.ts',
|
||||||
|
'static-field-reference.ts',
|
||||||
|
'static-method.ts',
|
||||||
|
'static-method-call.ts',
|
||||||
|
'static-method-with-if.ts',
|
||||||
]);
|
]);
|
||||||
service = ts.createLanguageService(host, documentRegistry);
|
service = ts.createLanguageService(host, documentRegistry);
|
||||||
program = service.getProgram();
|
program = service.getProgram();
|
||||||
|
@ -410,6 +420,31 @@ describe('Collector', () => {
|
||||||
}]
|
}]
|
||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to collect a method with a conditional expression', () => {
|
||||||
|
let source = program.getSourceFile('/static-method-with-if.ts');
|
||||||
|
let metadata = collector.getMetadata(source);
|
||||||
|
expect(metadata).toBeDefined();
|
||||||
|
let classData = <ClassMetadata>metadata.metadata['MyModule'];
|
||||||
|
expect(classData).toBeDefined();
|
||||||
|
expect(classData.statics).toEqual({
|
||||||
|
with: {
|
||||||
|
__symbolic: 'function',
|
||||||
|
parameters: ['cond'],
|
||||||
|
value: [
|
||||||
|
{__symbolic: 'reference', name: 'MyModule'}, {
|
||||||
|
provider: 'a',
|
||||||
|
useValue: {
|
||||||
|
__symbolic: 'if',
|
||||||
|
condition: {__symbolic: 'reference', name: 'cond'},
|
||||||
|
thenExpression: '1',
|
||||||
|
elseExpression: '2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Do not use \` in a template literal as it confuses clang-format
|
// TODO: Do not use \` in a template literal as it confuses clang-format
|
||||||
|
@ -691,6 +726,19 @@ const FILES: Directory = {
|
||||||
})
|
})
|
||||||
export class Foo { }
|
export class Foo { }
|
||||||
`,
|
`,
|
||||||
|
'static-method-with-if.ts': `
|
||||||
|
import {Injectable} from 'angular2/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MyModule {
|
||||||
|
static with(cond: boolean): any[] {
|
||||||
|
return [
|
||||||
|
MyModule,
|
||||||
|
{ provider: 'a', useValue: cond ? '1' : '2' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
'node_modules': {
|
'node_modules': {
|
||||||
'angular2': {
|
'angular2': {
|
||||||
'core.d.ts': `
|
'core.d.ts': `
|
||||||
|
|
Loading…
Reference in New Issue