feat(compiler): Support default parameters in static reflector (#10370)
Closes: #10369
This commit is contained in:
parent
0eca7abdd8
commit
763ca60f5b
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in New Issue