feat(compiler): Allow calls to simple static methods (#10289)
Closes: #10266
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Evaluator, errorSymbol, isPrimitive} from './evaluator';
|
||||
import {ClassMetadata, ConstructorMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema';
|
||||
import {ClassMetadata, ConstructorMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
|
||||
@ -30,6 +30,31 @@ export class MetadataCollector {
|
||||
return errorSymbol(message, node, context, sourceFile);
|
||||
}
|
||||
|
||||
function maybeGetSimpleFunction(
|
||||
functionDeclaration: ts.FunctionDeclaration |
|
||||
ts.MethodDeclaration): {func: MetadataValue, name: string}|undefined {
|
||||
if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
|
||||
const nameNode = <ts.Identifier>functionDeclaration.name;
|
||||
const functionName = nameNode.text;
|
||||
const functionBody = functionDeclaration.body;
|
||||
if (functionBody && functionBody.statements.length == 1) {
|
||||
const statement = functionBody.statements[0];
|
||||
if (statement.kind === ts.SyntaxKind.ReturnStatement) {
|
||||
const returnStatement = <ts.ReturnStatement>statement;
|
||||
if (returnStatement.expression) {
|
||||
return {
|
||||
name: functionName, func: {
|
||||
__symbolic: 'function',
|
||||
parameters: namesOf(functionDeclaration.parameters),
|
||||
value: evaluator.evaluateNode(returnStatement.expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
||||
let result: ClassMetadata = {__symbolic: 'class'};
|
||||
|
||||
@ -63,6 +88,14 @@ export class MetadataCollector {
|
||||
data.push(metadata);
|
||||
members[name] = data;
|
||||
}
|
||||
|
||||
// static member
|
||||
let statics: MetadataObject = null;
|
||||
function recordStaticMember(name: string, value: MetadataValue) {
|
||||
if (!statics) statics = {};
|
||||
statics[name] = value;
|
||||
}
|
||||
|
||||
for (const member of classDeclaration.members) {
|
||||
let isConstructor = false;
|
||||
switch (member.kind) {
|
||||
@ -70,6 +103,13 @@ export class MetadataCollector {
|
||||
case ts.SyntaxKind.MethodDeclaration:
|
||||
isConstructor = member.kind === ts.SyntaxKind.Constructor;
|
||||
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
|
||||
if (method.flags & ts.NodeFlags.Static) {
|
||||
const maybeFunc = maybeGetSimpleFunction(<ts.MethodDeclaration>method);
|
||||
if (maybeFunc) {
|
||||
recordStaticMember(maybeFunc.name, maybeFunc.func);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const methodDecorators = getDecorators(method.decorators);
|
||||
const parameters = method.parameters;
|
||||
const parameterDecoratorData: (MetadataSymbolicExpression | MetadataError)[][] = [];
|
||||
@ -123,8 +163,11 @@ export class MetadataCollector {
|
||||
if (members) {
|
||||
result.members = members;
|
||||
}
|
||||
if (statics) {
|
||||
result.statics = statics;
|
||||
}
|
||||
|
||||
return result.decorators || members ? result : undefined;
|
||||
return result.decorators || members || statics ? result : undefined;
|
||||
}
|
||||
|
||||
// Predeclare classes
|
||||
@ -160,21 +203,10 @@ export class MetadataCollector {
|
||||
// names substitution will be performed by the StaticReflector.
|
||||
if (node.flags & ts.NodeFlags.Export) {
|
||||
const functionDeclaration = <ts.FunctionDeclaration>node;
|
||||
const functionName = functionDeclaration.name.text;
|
||||
const functionBody = functionDeclaration.body;
|
||||
if (functionBody && functionBody.statements.length == 1) {
|
||||
const statement = functionBody.statements[0];
|
||||
if (statement.kind === ts.SyntaxKind.ReturnStatement) {
|
||||
const returnStatement = <ts.ReturnStatement>statement;
|
||||
if (returnStatement.expression) {
|
||||
if (!metadata) metadata = {};
|
||||
metadata[functionName] = {
|
||||
__symbolic: 'function',
|
||||
parameters: namesOf(functionDeclaration.parameters),
|
||||
value: evaluator.evaluateNode(returnStatement.expression)
|
||||
};
|
||||
}
|
||||
}
|
||||
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
|
||||
if (maybeFunc) {
|
||||
if (!metadata) metadata = {};
|
||||
metadata[maybeFunc.name] = maybeFunc.func;
|
||||
}
|
||||
}
|
||||
// Otherwise don't record the function.
|
||||
|
@ -22,6 +22,7 @@ export interface ClassMetadata {
|
||||
__symbolic: 'class';
|
||||
decorators?: (MetadataSymbolicExpression|MetadataError)[];
|
||||
members?: MetadataMap;
|
||||
statics?: MetadataObject;
|
||||
}
|
||||
export function isClassMetadata(value: any): value is ClassMetadata {
|
||||
return value && value.__symbolic === 'class';
|
||||
|
@ -16,7 +16,7 @@ describe('Collector', () => {
|
||||
host = new Host(FILES, [
|
||||
'/app/app.component.ts', '/app/cases-data.ts', '/app/error-cases.ts', '/promise.ts',
|
||||
'/unsupported-1.ts', '/unsupported-2.ts', 'import-star.ts', 'exported-functions.ts',
|
||||
'exported-enum.ts', 'exported-consts.ts'
|
||||
'exported-enum.ts', 'exported-consts.ts', 'static-method.ts', 'static-method-call.ts'
|
||||
]);
|
||||
service = ts.createLanguageService(host, documentRegistry);
|
||||
program = service.getProgram();
|
||||
@ -337,6 +337,47 @@ describe('Collector', () => {
|
||||
E: {__symbolic: 'reference', module: './exported-consts', name: 'constValue'}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to collect a simple static method', () => {
|
||||
let staticSource = program.getSourceFile('/static-method.ts');
|
||||
let metadata = collector.getMetadata(staticSource);
|
||||
expect(metadata).toBeDefined();
|
||||
let classData = <ClassMetadata>metadata.metadata['MyModule'];
|
||||
expect(classData).toBeDefined();
|
||||
expect(classData.statics).toEqual({
|
||||
with: {
|
||||
__symbolic: 'function',
|
||||
parameters: ['comp'],
|
||||
value: [
|
||||
{__symbolic: 'reference', name: 'MyModule'},
|
||||
{provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to collect a call to a static method', () => {
|
||||
let staticSource = program.getSourceFile('/static-method-call.ts');
|
||||
let metadata = collector.getMetadata(staticSource);
|
||||
expect(metadata).toBeDefined();
|
||||
let classData = <ClassMetadata>metadata.metadata['Foo'];
|
||||
expect(classData).toBeDefined();
|
||||
expect(classData.decorators).toEqual([{
|
||||
__symbolic: 'call',
|
||||
expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
|
||||
arguments: [{
|
||||
providers: {
|
||||
__symbolic: 'call',
|
||||
expression: {
|
||||
__symbolic: 'select',
|
||||
expression: {__symbolic: 'reference', module: './static-method.ts', name: 'MyModule'},
|
||||
member: 'with'
|
||||
},
|
||||
arguments: ['a']
|
||||
}
|
||||
}]
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Do not use \` in a template literal as it confuses clang-format
|
||||
@ -579,6 +620,28 @@ const FILES: Directory = {
|
||||
'exported-consts.ts': `
|
||||
export const constValue = 100;
|
||||
`,
|
||||
'static-method.ts': `
|
||||
import {Injectable} from 'angular2/core';
|
||||
|
||||
@Injectable()
|
||||
export class MyModule {
|
||||
static with(comp: any): any[] {
|
||||
return [
|
||||
MyModule,
|
||||
{ provider: 'a', useValue: comp }
|
||||
];
|
||||
}
|
||||
}
|
||||
`,
|
||||
'static-method-call.ts': `
|
||||
import {Component} from 'angular2/core';
|
||||
import {MyModule} from './static-method.ts';
|
||||
|
||||
@Component({
|
||||
providers: MyModule.with('a')
|
||||
})
|
||||
export class Foo { }
|
||||
`,
|
||||
'node_modules': {
|
||||
'angular2': {
|
||||
'core.d.ts': `
|
||||
|
Reference in New Issue
Block a user