2016-03-22 20:11:42 -04:00
|
|
|
import * as ts from 'typescript';
|
2016-04-26 00:29:06 -04:00
|
|
|
import {Evaluator, ImportMetadata, ImportSpecifierMetadata} from './evaluator';
|
2016-03-22 20:11:42 -04:00
|
|
|
import {Symbols} from './symbols';
|
2016-04-12 12:40:37 -04:00
|
|
|
import {
|
|
|
|
ClassMetadata,
|
|
|
|
ConstructorMetadata,
|
|
|
|
ModuleMetadata,
|
|
|
|
MemberMetadata,
|
|
|
|
MetadataMap,
|
|
|
|
MetadataSymbolicExpression,
|
|
|
|
MetadataSymbolicReferenceExpression,
|
|
|
|
MetadataValue,
|
|
|
|
MethodMetadata
|
|
|
|
} from './schema';
|
2016-03-22 20:11:42 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Collect decorator metadata from a TypeScript module.
|
|
|
|
*/
|
|
|
|
export class MetadataCollector {
|
2016-04-26 00:29:06 -04:00
|
|
|
constructor() {}
|
|
|
|
|
|
|
|
collectImports(sourceFile: ts.SourceFile) {
|
|
|
|
let imports: ImportMetadata[] = [];
|
|
|
|
const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, '');
|
|
|
|
function visit(node: ts.Node) {
|
|
|
|
switch (node.kind) {
|
|
|
|
case ts.SyntaxKind.ImportDeclaration:
|
|
|
|
const importDecl = <ts.ImportDeclaration>node;
|
|
|
|
const from = stripQuotes(importDecl.moduleSpecifier.getText());
|
|
|
|
const newImport = {from};
|
|
|
|
if (!importDecl.importClause) {
|
|
|
|
// Bare imports do not bring symbols into scope, so we don't need to record them
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (importDecl.importClause.name) {
|
|
|
|
newImport['defaultName'] = importDecl.importClause.name.text;
|
|
|
|
}
|
|
|
|
const bindings = importDecl.importClause.namedBindings;
|
|
|
|
if (bindings) {
|
|
|
|
switch (bindings.kind) {
|
|
|
|
case ts.SyntaxKind.NamedImports:
|
|
|
|
const namedImports: ImportSpecifierMetadata[] = [];
|
|
|
|
(<ts.NamedImports>bindings)
|
|
|
|
.elements.forEach(i => {
|
|
|
|
const namedImport = {name: i.name.text};
|
|
|
|
if (i.propertyName) {
|
|
|
|
namedImport['propertyName'] = i.propertyName.text;
|
|
|
|
}
|
|
|
|
namedImports.push(namedImport);
|
|
|
|
});
|
|
|
|
newImport['namedImports'] = namedImports;
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.NamespaceImport:
|
|
|
|
newImport['namespace'] = (<ts.NamespaceImport>bindings).name.text;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
imports.push(newImport);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ts.forEachChild(node, visit);
|
|
|
|
}
|
|
|
|
ts.forEachChild(sourceFile, visit);
|
|
|
|
return imports;
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a JSON.stringify friendly form describing the decorators of the exported classes from
|
|
|
|
* the source file that is expected to correspond to a module.
|
|
|
|
*/
|
|
|
|
public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata {
|
|
|
|
const locals = new Symbols();
|
2016-04-26 00:29:06 -04:00
|
|
|
const evaluator = new Evaluator(typeChecker, locals, this.collectImports(sourceFile));
|
2016-03-22 20:11:42 -04:00
|
|
|
|
|
|
|
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
|
|
|
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
|
|
|
}
|
|
|
|
|
|
|
|
function referenceFromType(type: ts.Type): MetadataSymbolicReferenceExpression {
|
|
|
|
if (type) {
|
|
|
|
let symbol = type.getSymbol();
|
|
|
|
if (symbol) {
|
2016-04-26 00:29:06 -04:00
|
|
|
return evaluator.symbolReference(symbol);
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
2016-04-19 22:37:57 -04:00
|
|
|
let result: ClassMetadata = {__symbolic: "class"};
|
|
|
|
|
|
|
|
function getDecorators(decorators: ts.Decorator[]): MetadataSymbolicExpression[] {
|
|
|
|
if (decorators && decorators.length)
|
|
|
|
return decorators.map(decorator => objFromDecorator(decorator));
|
|
|
|
return undefined;
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
|
|
|
|
// Add class decorators
|
|
|
|
if (classDeclaration.decorators) {
|
|
|
|
result.decorators = getDecorators(classDeclaration.decorators);
|
|
|
|
}
|
|
|
|
|
|
|
|
// member decorators
|
|
|
|
let members: MetadataMap = null;
|
|
|
|
function recordMember(name: string, metadata: MemberMetadata) {
|
|
|
|
if (!members) members = {};
|
|
|
|
let data = members.hasOwnProperty(name) ? members[name] : [];
|
|
|
|
data.push(metadata);
|
|
|
|
members[name] = data;
|
|
|
|
}
|
|
|
|
for (const member of classDeclaration.members) {
|
|
|
|
let isConstructor = false;
|
|
|
|
switch (member.kind) {
|
|
|
|
case ts.SyntaxKind.Constructor:
|
|
|
|
isConstructor = true;
|
|
|
|
// fallthrough
|
|
|
|
case ts.SyntaxKind.MethodDeclaration:
|
2016-04-12 12:40:37 -04:00
|
|
|
const method = <ts.MethodDeclaration | ts.ConstructorDeclaration>member;
|
2016-03-22 20:11:42 -04:00
|
|
|
const methodDecorators = getDecorators(method.decorators);
|
|
|
|
const parameters = method.parameters;
|
|
|
|
const parameterDecoratorData: MetadataSymbolicExpression[][] = [];
|
|
|
|
const parametersData: MetadataSymbolicReferenceExpression[] = [];
|
|
|
|
let hasDecoratorData: boolean = false;
|
|
|
|
let hasParameterData: boolean = false;
|
|
|
|
for (const parameter of parameters) {
|
|
|
|
const parameterData = getDecorators(parameter.decorators);
|
|
|
|
parameterDecoratorData.push(parameterData);
|
|
|
|
hasDecoratorData = hasDecoratorData || !!parameterData;
|
|
|
|
if (isConstructor) {
|
|
|
|
const parameterType = typeChecker.getTypeAtLocation(parameter);
|
|
|
|
parametersData.push(referenceFromType(parameterType) || null);
|
|
|
|
hasParameterData = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (methodDecorators || hasDecoratorData || hasParameterData) {
|
2016-04-12 12:40:37 -04:00
|
|
|
const data: MethodMetadata = {__symbolic: isConstructor ? "constructor" : "method"};
|
|
|
|
const name = isConstructor ? "__ctor__" : evaluator.nameOf(member.name);
|
2016-03-22 20:11:42 -04:00
|
|
|
if (methodDecorators) {
|
|
|
|
data.decorators = methodDecorators;
|
|
|
|
}
|
|
|
|
if (hasDecoratorData) {
|
|
|
|
data.parameterDecorators = parameterDecoratorData;
|
|
|
|
}
|
|
|
|
if (hasParameterData) {
|
|
|
|
(<ConstructorMetadata>data).parameters = parametersData;
|
|
|
|
}
|
|
|
|
recordMember(name, data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.PropertyDeclaration:
|
2016-03-25 13:16:06 -04:00
|
|
|
case ts.SyntaxKind.GetAccessor:
|
|
|
|
case ts.SyntaxKind.SetAccessor:
|
2016-03-22 20:11:42 -04:00
|
|
|
const property = <ts.PropertyDeclaration>member;
|
|
|
|
const propertyDecorators = getDecorators(property.decorators);
|
|
|
|
if (propertyDecorators) {
|
2016-04-12 12:40:37 -04:00
|
|
|
recordMember(evaluator.nameOf(property.name),
|
|
|
|
{__symbolic: 'property', decorators: propertyDecorators});
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (members) {
|
|
|
|
result.members = members;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.decorators || members ? result : undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
let metadata: {[name: string]: (ClassMetadata | MetadataValue)};
|
|
|
|
const symbols = typeChecker.getSymbolsInScope(sourceFile, ts.SymbolFlags.ExportValue);
|
|
|
|
for (var symbol of symbols) {
|
|
|
|
for (var declaration of symbol.getDeclarations()) {
|
|
|
|
switch (declaration.kind) {
|
|
|
|
case ts.SyntaxKind.ClassDeclaration:
|
|
|
|
const classDeclaration = <ts.ClassDeclaration>declaration;
|
|
|
|
if (classDeclaration.decorators) {
|
|
|
|
if (!metadata) metadata = {};
|
2016-04-19 22:37:57 -04:00
|
|
|
metadata[classDeclaration.name.text] = classMetadataOf(classDeclaration);
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.VariableDeclaration:
|
|
|
|
const variableDeclaration = <ts.VariableDeclaration>declaration;
|
|
|
|
if (variableDeclaration.initializer) {
|
|
|
|
const value = evaluator.evaluateNode(variableDeclaration.initializer);
|
|
|
|
if (value !== undefined) {
|
|
|
|
if (evaluator.isFoldable(variableDeclaration.initializer)) {
|
|
|
|
// Record the value for use in other initializers
|
|
|
|
locals.set(symbol, value);
|
|
|
|
}
|
|
|
|
if (!metadata) metadata = {};
|
|
|
|
metadata[evaluator.nameOf(variableDeclaration.name)] =
|
|
|
|
evaluator.evaluateNode(variableDeclaration.initializer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-26 00:29:06 -04:00
|
|
|
|
|
|
|
return metadata && {__symbolic: "module", metadata};
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
}
|