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:
Chuck Jazdzewski 2016-07-28 17:32:29 -07:00 committed by GitHub
parent 81d27daf0d
commit 20b03bad11
5 changed files with 116 additions and 23 deletions

View File

@ -307,6 +307,7 @@ 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);
try {
let value = targetFunction['value']; let value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) { if (value && (depth != 0 || value.__symbolic != 'error')) {
// Determine the arguments // Determine the arguments
@ -326,9 +327,11 @@ export class StaticReflector implements ReflectorReader {
} }
return result; return result;
} }
} finally {
calling.delete(functionSymbol); calling.delete(functionSymbol);
} }
} }
}
if (depth === 0) { if (depth === 0) {
// If depth is 0 we are evaluating the top level expression that is describing element // If depth is 0 we are evaluating the top level expression that is describing element
@ -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;

View File

@ -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';

View File

@ -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);

View File

@ -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;

View File

@ -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': `