feat(compiler): Support default parameters in static reflector (#10370)

Closes: #10369
This commit is contained in:
Chuck Jazdzewski 2016-07-29 09:10:45 -07:00 committed by GitHub
parent 0eca7abdd8
commit 763ca60f5b
5 changed files with 88 additions and 18 deletions

View File

@ -308,12 +308,17 @@ export class StaticReflector implements ReflectorReader {
} }
calling.set(functionSymbol, true); calling.set(functionSymbol, true);
try { try {
let value = targetFunction['value']; const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) { if (value && (depth != 0 || value.__symbolic != 'error')) {
// Determine the arguments // Determine the arguments
let args = (expression['arguments'] || []).map((arg: any) => simplify(arg)); const args: any[] =
let parameters: string[] = targetFunction['parameters']; (expression['arguments'] || []).map((arg: any) => simplify(arg));
let functionScope = BindingScope.build(); 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++) { for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]); functionScope.define(parameters[i], args[i]);
} }

View File

@ -421,6 +421,14 @@ describe('StaticReflector', () => {
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] [{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 { class MockReflectorHost implements StaticReflectorHost {
@ -973,6 +981,9 @@ class MockReflectorHost implements StaticReflectorHost {
static condMethod(cond: boolean) { static condMethod(cond: boolean) {
return [{ provider: 'a', useValue: cond ? '1' : '2'}]; return [{ provider: 'a', useValue: cond ? '1' : '2'}];
} }
static defaultsMethod(a, b = true, c = false) {
return [a, b, c];
}
} }
`, `,
'/tmp/src/static-method-call.ts': ` '/tmp/src/static-method-call.ts': `
@ -988,6 +999,11 @@ class MockReflectorHost implements StaticReflectorHost {
providers: [MyModule.condMethod(true), MyModule.condMethod(false)] providers: [MyModule.condMethod(true), MyModule.condMethod(false)]
}) })
export class MyCondComponent { } export class MyCondComponent { }
@Component({
providers: [MyModule.defaultsMethod('a')]
})
export class MyDefaultsComponent { }
`, `,
'/tmp/src/static-field.ts': ` '/tmp/src/static-field.ts': `
import {Injectable} from 'angular2/core'; import {Injectable} from 'angular2/core';

View File

@ -1,7 +1,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Evaluator, errorSymbol, isPrimitive} from './evaluator'; 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'; import {Symbols} from './symbols';
@ -19,7 +19,7 @@ export class MetadataCollector {
public getMetadata(sourceFile: ts.SourceFile): ModuleMetadata { public getMetadata(sourceFile: ts.SourceFile): ModuleMetadata {
const locals = new Symbols(sourceFile); const locals = new Symbols(sourceFile);
const evaluator = new Evaluator(locals); 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 { function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression); return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
@ -32,7 +32,7 @@ export class MetadataCollector {
function maybeGetSimpleFunction( function maybeGetSimpleFunction(
functionDeclaration: ts.FunctionDeclaration | functionDeclaration: ts.FunctionDeclaration |
ts.MethodDeclaration): {func: MetadataValue, name: string}|undefined { ts.MethodDeclaration): {func: FunctionMetadata, name: string}|undefined {
if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) { if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name; const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text; const functionName = nameNode.text;
@ -42,13 +42,17 @@ export class MetadataCollector {
if (statement.kind === ts.SyntaxKind.ReturnStatement) { if (statement.kind === ts.SyntaxKind.ReturnStatement) {
const returnStatement = <ts.ReturnStatement>statement; const returnStatement = <ts.ReturnStatement>statement;
if (returnStatement.expression) { if (returnStatement.expression) {
return { const func: FunctionMetadata = {
name: functionName, func: { __symbolic: 'function',
__symbolic: 'function', parameters: namesOf(functionDeclaration.parameters),
parameters: namesOf(functionDeclaration.parameters), value: evaluator.evaluateNode(returnStatement.expression)
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 // static member
let statics: MetadataObject = null; let statics: {[name: string]: MetadataValue | FunctionMetadata} = null;
function recordStaticMember(name: string, value: MetadataValue) { function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) {
if (!statics) statics = {}; if (!statics) statics = {};
statics[name] = value; statics[name] = value;
} }

View File

@ -12,7 +12,7 @@ export const VERSION = 1;
export interface ModuleMetadata { export interface ModuleMetadata {
__symbolic: 'module'; __symbolic: 'module';
version: number; version: number;
metadata: {[name: string]: (ClassMetadata | MetadataValue)}; metadata: {[name: string]: (ClassMetadata | FunctionMetadata | MetadataValue)};
} }
export function isModuleMetadata(value: any): value is ModuleMetadata { export function isModuleMetadata(value: any): value is ModuleMetadata {
return value && value.__symbolic === 'module'; return value && value.__symbolic === 'module';
@ -22,7 +22,7 @@ export interface ClassMetadata {
__symbolic: 'class'; __symbolic: 'class';
decorators?: (MetadataSymbolicExpression|MetadataError)[]; decorators?: (MetadataSymbolicExpression|MetadataError)[];
members?: MetadataMap; members?: MetadataMap;
statics?: MetadataObject; statics?: {[name: string]: MetadataValue | FunctionMetadata};
} }
export function isClassMetadata(value: any): value is ClassMetadata { export function isClassMetadata(value: any): value is ClassMetadata {
return value && value.__symbolic === 'class'; return value && value.__symbolic === 'class';
@ -65,7 +65,8 @@ export function isConstructorMetadata(value: any): value is ConstructorMetadata
export interface FunctionMetadata { export interface FunctionMetadata {
__symbolic: 'function'; __symbolic: 'function';
parameters: string[]; parameters: string[];
result: MetadataValue; defaults?: MetadataValue[];
value: MetadataValue;
} }
export function isFunctionMetadata(value: any): value is FunctionMetadata { export function isFunctionMetadata(value: any): value is FunctionMetadata {
return value && value.__symbolic === 'function'; return value && value.__symbolic === 'function';

View File

@ -28,6 +28,7 @@ describe('Collector', () => {
'static-method.ts', 'static-method.ts',
'static-method-call.ts', 'static-method-call.ts',
'static-method-with-if.ts', 'static-method-with-if.ts',
'static-method-with-default.ts',
]); ]);
service = ts.createLanguageService(host, documentRegistry); service = ts.createLanguageService(host, documentRegistry);
program = service.getProgram(); 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 = <ClassMetadata>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 // 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': ` 'static-method-call.ts': `
import {Component} from 'angular2/core'; import {Component} from 'angular2/core';
import {MyModule} from './static-method.ts'; import {MyModule} from './static-method.ts';