From 763ca60f5ba8f165109a5898f40f8bae25bfd9ac Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Fri, 29 Jul 2016 09:10:45 -0700 Subject: [PATCH] feat(compiler): Support default parameters in static reflector (#10370) Closes: #10369 --- .../compiler-cli/src/static_reflector.ts | 13 ++++-- .../test/static_reflector_spec.ts | 16 +++++++ tools/@angular/tsc-wrapped/src/collector.ts | 26 ++++++----- tools/@angular/tsc-wrapped/src/schema.ts | 7 +-- .../tsc-wrapped/test/collector.spec.ts | 44 +++++++++++++++++++ 5 files changed, 88 insertions(+), 18 deletions(-) diff --git a/modules/@angular/compiler-cli/src/static_reflector.ts b/modules/@angular/compiler-cli/src/static_reflector.ts index 59979f0f55..b4862ef723 100644 --- a/modules/@angular/compiler-cli/src/static_reflector.ts +++ b/modules/@angular/compiler-cli/src/static_reflector.ts @@ -308,12 +308,17 @@ export class StaticReflector implements ReflectorReader { } calling.set(functionSymbol, true); try { - let value = targetFunction['value']; + const value = targetFunction['value']; if (value && (depth != 0 || value.__symbolic != 'error')) { // Determine the arguments - let args = (expression['arguments'] || []).map((arg: any) => simplify(arg)); - let parameters: string[] = targetFunction['parameters']; - let functionScope = BindingScope.build(); + const args: any[] = + (expression['arguments'] || []).map((arg: any) => simplify(arg)); + const parameters: string[] = targetFunction['parameters']; + const defaults: any[] = targetFunction.defaults; + if (defaults && defaults.length > args.length) { + args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); + } + const functionScope = BindingScope.build(); for (let i = 0; i < parameters.length; i++) { functionScope.define(parameters[i], args[i]); } diff --git a/modules/@angular/compiler-cli/test/static_reflector_spec.ts b/modules/@angular/compiler-cli/test/static_reflector_spec.ts index 54620120cd..d1dcc11af8 100644 --- a/modules/@angular/compiler-cli/test/static_reflector_spec.ts +++ b/modules/@angular/compiler-cli/test/static_reflector_spec.ts @@ -421,6 +421,14 @@ describe('StaticReflector', () => { [{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] ]); }); + + it('should be able to get the metadata for a class calling a method with default parameters', + () => { + const annotations = reflector.annotations( + host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent')); + expect(annotations.length).toBe(1); + expect(annotations[0].providers).toEqual([['a', true, false]]); + }); }); class MockReflectorHost implements StaticReflectorHost { @@ -973,6 +981,9 @@ class MockReflectorHost implements StaticReflectorHost { static condMethod(cond: boolean) { return [{ provider: 'a', useValue: cond ? '1' : '2'}]; } + static defaultsMethod(a, b = true, c = false) { + return [a, b, c]; + } } `, '/tmp/src/static-method-call.ts': ` @@ -988,6 +999,11 @@ class MockReflectorHost implements StaticReflectorHost { providers: [MyModule.condMethod(true), MyModule.condMethod(false)] }) export class MyCondComponent { } + + @Component({ + providers: [MyModule.defaultsMethod('a')] + }) + export class MyDefaultsComponent { } `, '/tmp/src/static-field.ts': ` import {Injectable} from 'angular2/core'; diff --git a/tools/@angular/tsc-wrapped/src/collector.ts b/tools/@angular/tsc-wrapped/src/collector.ts index c138da4095..4e6a822bee 100644 --- a/tools/@angular/tsc-wrapped/src/collector.ts +++ b/tools/@angular/tsc-wrapped/src/collector.ts @@ -1,7 +1,7 @@ import * as ts from 'typescript'; import {Evaluator, errorSymbol, isPrimitive} from './evaluator'; -import {ClassMetadata, ConstructorMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema'; +import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema'; import {Symbols} from './symbols'; @@ -19,7 +19,7 @@ export class MetadataCollector { public getMetadata(sourceFile: ts.SourceFile): ModuleMetadata { const locals = new Symbols(sourceFile); const evaluator = new Evaluator(locals); - let metadata: {[name: string]: MetadataValue | ClassMetadata}|undefined; + let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined; function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression { return evaluator.evaluateNode(decoratorNode.expression); @@ -32,7 +32,7 @@ export class MetadataCollector { function maybeGetSimpleFunction( functionDeclaration: ts.FunctionDeclaration | - ts.MethodDeclaration): {func: MetadataValue, name: string}|undefined { + ts.MethodDeclaration): {func: FunctionMetadata, name: string}|undefined { if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) { const nameNode = functionDeclaration.name; const functionName = nameNode.text; @@ -42,13 +42,17 @@ export class MetadataCollector { if (statement.kind === ts.SyntaxKind.ReturnStatement) { const returnStatement = statement; if (returnStatement.expression) { - return { - name: functionName, func: { - __symbolic: 'function', - parameters: namesOf(functionDeclaration.parameters), - value: evaluator.evaluateNode(returnStatement.expression) - } + const func: FunctionMetadata = { + __symbolic: 'function', + parameters: namesOf(functionDeclaration.parameters), + value: evaluator.evaluateNode(returnStatement.expression) + }; + if (functionDeclaration.parameters.some(p => p.initializer != null)) { + const defaults: MetadataValue[] = []; + func.defaults = functionDeclaration.parameters.map( + p => p.initializer && evaluator.evaluateNode(p.initializer)); } + return { func, name: functionName } } } } @@ -90,8 +94,8 @@ export class MetadataCollector { } // static member - let statics: MetadataObject = null; - function recordStaticMember(name: string, value: MetadataValue) { + let statics: {[name: string]: MetadataValue | FunctionMetadata} = null; + function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) { if (!statics) statics = {}; statics[name] = value; } diff --git a/tools/@angular/tsc-wrapped/src/schema.ts b/tools/@angular/tsc-wrapped/src/schema.ts index 5dd176bff3..ee255db1d4 100644 --- a/tools/@angular/tsc-wrapped/src/schema.ts +++ b/tools/@angular/tsc-wrapped/src/schema.ts @@ -12,7 +12,7 @@ export const VERSION = 1; export interface ModuleMetadata { __symbolic: 'module'; version: number; - metadata: {[name: string]: (ClassMetadata | MetadataValue)}; + metadata: {[name: string]: (ClassMetadata | FunctionMetadata | MetadataValue)}; } export function isModuleMetadata(value: any): value is ModuleMetadata { return value && value.__symbolic === 'module'; @@ -22,7 +22,7 @@ export interface ClassMetadata { __symbolic: 'class'; decorators?: (MetadataSymbolicExpression|MetadataError)[]; members?: MetadataMap; - statics?: MetadataObject; + statics?: {[name: string]: MetadataValue | FunctionMetadata}; } export function isClassMetadata(value: any): value is ClassMetadata { return value && value.__symbolic === 'class'; @@ -65,7 +65,8 @@ export function isConstructorMetadata(value: any): value is ConstructorMetadata export interface FunctionMetadata { __symbolic: 'function'; parameters: string[]; - result: MetadataValue; + defaults?: MetadataValue[]; + value: MetadataValue; } export function isFunctionMetadata(value: any): value is FunctionMetadata { return value && value.__symbolic === 'function'; diff --git a/tools/@angular/tsc-wrapped/test/collector.spec.ts b/tools/@angular/tsc-wrapped/test/collector.spec.ts index 01a51b768a..3a9bd2b932 100644 --- a/tools/@angular/tsc-wrapped/test/collector.spec.ts +++ b/tools/@angular/tsc-wrapped/test/collector.spec.ts @@ -28,6 +28,7 @@ describe('Collector', () => { 'static-method.ts', 'static-method-call.ts', 'static-method-with-if.ts', + 'static-method-with-default.ts', ]); service = ts.createLanguageService(host, documentRegistry); program = service.getProgram(); @@ -445,6 +446,35 @@ describe('Collector', () => { } }); }); + + it('should be able to collect a method with a default parameter', () => { + let source = program.getSourceFile('/static-method-with-default.ts'); + let metadata = collector.getMetadata(source); + expect(metadata).toBeDefined(); + let classData = metadata.metadata['MyModule']; + expect(classData).toBeDefined(); + expect(classData.statics).toEqual({ + with: { + __symbolic: 'function', + parameters: ['comp', 'foo', 'bar'], + defaults: [undefined, true, false], + value: [ + {__symbolic: 'reference', name: 'MyModule'}, { + __symbolic: 'if', + condition: {__symbolic: 'reference', name: 'foo'}, + thenExpression: {provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}}, + elseExpression: {provider: 'b', useValue: {__symbolic: 'reference', name: 'comp'}} + }, + { + __symbolic: 'if', + condition: {__symbolic: 'reference', name: 'bar'}, + thenExpression: {provider: 'c', useValue: {__symbolic: 'reference', name: 'comp'}}, + elseExpression: {provider: 'd', useValue: {__symbolic: 'reference', name: 'comp'}} + } + ] + } + }); + }); }); // TODO: Do not use \` in a template literal as it confuses clang-format @@ -700,6 +730,20 @@ const FILES: Directory = { } } `, + 'static-method-with-default.ts': ` + import {Injectable} from 'angular2/core'; + + @Injectable() + export class MyModule { + static with(comp: any, foo: boolean = true, bar: boolean = false): any[] { + return [ + MyModule, + foo ? { provider: 'a', useValue: comp } : {provider: 'b', useValue: comp}, + bar ? { provider: 'c', useValue: comp } : {provider: 'd', useValue: comp} + ]; + } + } + `, 'static-method-call.ts': ` import {Component} from 'angular2/core'; import {MyModule} from './static-method.ts';