Skip to content

Commit 20b03ba

Browse files
authored
feat(compiler): Added support for conditional expressions. (angular#10366)
Expression evaluated by the static reflector can now supports conditional expressions. Closes: angular#10365
1 parent 81d27da commit 20b03ba

File tree

5 files changed

+116
-23
lines changed

5 files changed

+116
-23
lines changed

modules/@angular/compiler-cli/src/static_reflector.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -307,26 +307,29 @@ export class StaticReflector implements ReflectorReader {
307307
throw new Error('Recursion not supported');
308308
}
309309
calling.set(functionSymbol, true);
310-
let value = targetFunction['value'];
311-
if (value && (depth != 0 || value.__symbolic != 'error')) {
312-
// Determine the arguments
313-
let args = (expression['arguments'] || []).map((arg: any) => simplify(arg));
314-
let parameters: string[] = targetFunction['parameters'];
315-
let functionScope = BindingScope.build();
316-
for (let i = 0; i < parameters.length; i++) {
317-
functionScope.define(parameters[i], args[i]);
318-
}
319-
let oldScope = scope;
320-
let result: any;
321-
try {
322-
scope = functionScope.done();
323-
result = simplifyInContext(functionSymbol, value, depth + 1);
324-
} finally {
325-
scope = oldScope;
310+
try {
311+
let value = targetFunction['value'];
312+
if (value && (depth != 0 || value.__symbolic != 'error')) {
313+
// Determine the arguments
314+
let args = (expression['arguments'] || []).map((arg: any) => simplify(arg));
315+
let parameters: string[] = targetFunction['parameters'];
316+
let functionScope = BindingScope.build();
317+
for (let i = 0; i < parameters.length; i++) {
318+
functionScope.define(parameters[i], args[i]);
319+
}
320+
let oldScope = scope;
321+
let result: any;
322+
try {
323+
scope = functionScope.done();
324+
result = simplifyInContext(functionSymbol, value, depth + 1);
325+
} finally {
326+
scope = oldScope;
327+
}
328+
return result;
326329
}
327-
return result;
330+
} finally {
331+
calling.delete(functionSymbol);
328332
}
329-
calling.delete(functionSymbol);
330333
}
331334
}
332335

@@ -417,6 +420,10 @@ export class StaticReflector implements ReflectorReader {
417420
return left % right;
418421
}
419422
return null;
423+
case 'if':
424+
let condition = simplify(expression['condition']);
425+
return condition ? simplify(expression['thenExpression']) :
426+
simplify(expression['elseExpression']);
420427
case 'pre':
421428
let operand = simplify(expression['operand']);
422429
if (shouldIgnore(operand)) return operand;

modules/@angular/compiler-cli/test/static_reflector_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ describe('StaticReflector', () => {
411411
expect(annotations.length).toBe(1);
412412
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
413413
});
414+
415+
it('should be able to get the metadata for a class calling a method with a conditional expression',
416+
() => {
417+
const annotations = reflector.annotations(
418+
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
419+
expect(annotations.length).toBe(1);
420+
expect(annotations[0].providers).toEqual([
421+
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
422+
]);
423+
});
414424
});
415425

416426
class MockReflectorHost implements StaticReflectorHost {
@@ -960,6 +970,9 @@ class MockReflectorHost implements StaticReflectorHost {
960970
static with(data: any) {
961971
return { provider: 'a', useValue: data }
962972
}
973+
static condMethod(cond: boolean) {
974+
return [{ provider: 'a', useValue: cond ? '1' : '2'}];
975+
}
963976
}
964977
`,
965978
'/tmp/src/static-method-call.ts': `
@@ -970,6 +983,11 @@ class MockReflectorHost implements StaticReflectorHost {
970983
providers: MyModule.with(100)
971984
})
972985
export class MyComponent { }
986+
987+
@Component({
988+
providers: [MyModule.condMethod(true), MyModule.condMethod(false)]
989+
})
990+
export class MyCondComponent { }
973991
`,
974992
'/tmp/src/static-field.ts': `
975993
import {Injectable} from 'angular2/core';

tools/@angular/tsc-wrapped/src/evaluator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,15 @@ export class Evaluator {
511511
};
512512
}
513513
break;
514+
case ts.SyntaxKind.ConditionalExpression:
515+
const conditionalExpression = <ts.ConditionalExpression>node;
516+
const condition = this.evaluateNode(conditionalExpression.condition);
517+
const thenExpression = this.evaluateNode(conditionalExpression.whenTrue);
518+
const elseExpression = this.evaluateNode(conditionalExpression.whenFalse);
519+
if (isPrimitive(condition)) {
520+
return condition ? thenExpression : elseExpression;
521+
}
522+
return {__symbolic: 'if', condition, thenExpression, elseExpression};
514523
case ts.SyntaxKind.FunctionExpression:
515524
case ts.SyntaxKind.ArrowFunction:
516525
return errorSymbol('Function call not supported', node);

tools/@angular/tsc-wrapped/src/schema.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export interface MetadataObject { [name: string]: MetadataValue; }
7979
export interface MetadataArray { [name: number]: MetadataValue; }
8080

8181
export interface MetadataSymbolicExpression {
82-
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'
82+
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'|'if'
8383
}
8484
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
8585
if (value) {
@@ -92,6 +92,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
9292
case 'reference':
9393
case 'select':
9494
case 'spread':
95+
case 'if':
9596
return true;
9697
}
9798
}
@@ -140,6 +141,16 @@ export function isMetadataSymbolicPrefixExpression(value: any):
140141
return value && value.__symbolic === 'pre';
141142
}
142143

144+
export interface MetadataSymbolicIfExpression extends MetadataSymbolicExpression {
145+
__symbolic: 'if';
146+
condition: MetadataValue;
147+
thenExpression: MetadataValue;
148+
elseExpression: MetadataValue;
149+
}
150+
export function isMetadataSymbolicIfExpression(value: any): value is MetadataSymbolicIfExpression {
151+
return value && value.__symbolic === 'if';
152+
}
153+
143154
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
144155
__symbolic: 'reference';
145156
name: string;

tools/@angular/tsc-wrapped/test/collector.spec.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@ describe('Collector', () => {
1414

1515
beforeEach(() => {
1616
host = new Host(FILES, [
17-
'/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts',
18-
'/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts', 'exported-functions.ts',
19-
'exported-enum.ts', 'exported-consts.ts', 'static-method.ts', 'static-method-call.ts',
20-
'static-field-reference.ts'
17+
'/app/app.component.ts',
18+
'/app/cases-data.ts',
19+
'/app/error-cases.ts',
20+
'/promise.ts',
21+
'/unsupported-1.ts',
22+
'/unsupported-2.ts',
23+
'import-star.ts',
24+
'exported-functions.ts',
25+
'exported-enum.ts',
26+
'exported-consts.ts',
27+
'static-field-reference.ts',
28+
'static-method.ts',
29+
'static-method-call.ts',
30+
'static-method-with-if.ts',
2131
]);
2232
service = ts.createLanguageService(host, documentRegistry);
2333
program = service.getProgram();
@@ -410,6 +420,31 @@ describe('Collector', () => {
410420
}]
411421
}]);
412422
});
423+
424+
it('should be able to collect a method with a conditional expression', () => {
425+
let source = program.getSourceFile('/static-method-with-if.ts');
426+
let metadata = collector.getMetadata(source);
427+
expect(metadata).toBeDefined();
428+
let classData = <ClassMetadata>metadata.metadata['MyModule'];
429+
expect(classData).toBeDefined();
430+
expect(classData.statics).toEqual({
431+
with: {
432+
__symbolic: 'function',
433+
parameters: ['cond'],
434+
value: [
435+
{__symbolic: 'reference', name: 'MyModule'}, {
436+
provider: 'a',
437+
useValue: {
438+
__symbolic: 'if',
439+
condition: {__symbolic: 'reference', name: 'cond'},
440+
thenExpression: '1',
441+
elseExpression: '2'
442+
}
443+
}
444+
]
445+
}
446+
});
447+
});
413448
});
414449

415450
// TODO: Do not use \` in a template literal as it confuses clang-format
@@ -691,6 +726,19 @@ const FILES: Directory = {
691726
})
692727
export class Foo { }
693728
`,
729+
'static-method-with-if.ts': `
730+
import {Injectable} from 'angular2/core';
731+
732+
@Injectable()
733+
export class MyModule {
734+
static with(cond: boolean): any[] {
735+
return [
736+
MyModule,
737+
{ provider: 'a', useValue: cond ? '1' : '2' }
738+
];
739+
}
740+
}
741+
`,
694742
'node_modules': {
695743
'angular2': {
696744
'core.d.ts': `

0 commit comments

Comments
 (0)