2016-03-22 20:11:42 -04:00
|
|
|
import * as ts from 'typescript';
|
2016-05-26 13:45:37 -04:00
|
|
|
|
2016-06-15 17:56:56 -04:00
|
|
|
import {Evaluator, errorSymbol, isPrimitive} from './evaluator';
|
2016-08-02 14:45:14 -04:00
|
|
|
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema';
|
2016-03-22 20:11:42 -04:00
|
|
|
import {Symbols} from './symbols';
|
2016-05-26 13:45:37 -04:00
|
|
|
|
2016-06-08 14:14:43 -04:00
|
|
|
|
2016-07-11 20:26:35 -04:00
|
|
|
|
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() {}
|
|
|
|
|
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.
|
|
|
|
*/
|
2016-05-31 14:00:39 -04:00
|
|
|
public getMetadata(sourceFile: ts.SourceFile): ModuleMetadata {
|
|
|
|
const locals = new Symbols(sourceFile);
|
|
|
|
const evaluator = new Evaluator(locals);
|
2016-07-29 12:10:45 -04:00
|
|
|
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
|
2016-08-02 14:45:14 -04:00
|
|
|
let exports: ModuleExportMetadata[];
|
2016-03-22 20:11:42 -04:00
|
|
|
|
|
|
|
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
|
|
|
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
|
|
|
}
|
|
|
|
|
2016-06-03 18:43:09 -04:00
|
|
|
function errorSym(
|
|
|
|
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
|
|
|
|
return errorSymbol(message, node, context, sourceFile);
|
|
|
|
}
|
|
|
|
|
2016-07-26 13:18:35 -04:00
|
|
|
function maybeGetSimpleFunction(
|
|
|
|
functionDeclaration: ts.FunctionDeclaration |
|
2016-07-29 12:10:45 -04:00
|
|
|
ts.MethodDeclaration): {func: FunctionMetadata, name: string}|undefined {
|
2016-07-26 13:18:35 -04:00
|
|
|
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) {
|
2016-07-29 12:10:45 -04:00
|
|
|
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));
|
2016-07-26 13:18:35 -04:00
|
|
|
}
|
2016-07-29 12:10:45 -04:00
|
|
|
return { func, name: functionName }
|
2016-07-26 13:18:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-22 20:11:42 -04:00
|
|
|
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
2016-05-26 13:45:37 -04:00
|
|
|
let result: ClassMetadata = {__symbolic: 'class'};
|
2016-04-19 22:37:57 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
2016-07-11 20:26:35 -04:00
|
|
|
function referenceFrom(node: ts.Node): MetadataSymbolicReferenceExpression|MetadataError|
|
|
|
|
MetadataSymbolicSelectExpression {
|
2016-05-31 14:00:39 -04:00
|
|
|
const result = evaluator.evaluateNode(node);
|
2016-07-11 20:26:35 -04:00
|
|
|
if (isMetadataError(result) || isMetadataSymbolicReferenceExpression(result) ||
|
|
|
|
isMetadataSymbolicSelectExpression(result)) {
|
2016-05-31 14:00:39 -04:00
|
|
|
return result;
|
|
|
|
} else {
|
2016-06-03 18:43:09 -04:00
|
|
|
return errorSym('Symbol reference expected', node);
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
2016-07-26 13:18:35 -04:00
|
|
|
|
|
|
|
// static member
|
2016-07-29 12:10:45 -04:00
|
|
|
let statics: {[name: string]: MetadataValue | FunctionMetadata} = null;
|
|
|
|
function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) {
|
2016-07-26 13:18:35 -04:00
|
|
|
if (!statics) statics = {};
|
|
|
|
statics[name] = value;
|
|
|
|
}
|
|
|
|
|
2016-03-22 20:11:42 -04:00
|
|
|
for (const member of classDeclaration.members) {
|
|
|
|
let isConstructor = false;
|
|
|
|
switch (member.kind) {
|
|
|
|
case ts.SyntaxKind.Constructor:
|
|
|
|
case ts.SyntaxKind.MethodDeclaration:
|
2016-06-22 18:57:24 -04:00
|
|
|
isConstructor = member.kind === ts.SyntaxKind.Constructor;
|
2016-05-26 13:45:37 -04:00
|
|
|
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
|
2016-07-26 13:18:35 -04:00
|
|
|
if (method.flags & ts.NodeFlags.Static) {
|
|
|
|
const maybeFunc = maybeGetSimpleFunction(<ts.MethodDeclaration>method);
|
|
|
|
if (maybeFunc) {
|
|
|
|
recordStaticMember(maybeFunc.name, maybeFunc.func);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
const methodDecorators = getDecorators(method.decorators);
|
|
|
|
const parameters = method.parameters;
|
2016-05-31 14:00:39 -04:00
|
|
|
const parameterDecoratorData: (MetadataSymbolicExpression | MetadataError)[][] = [];
|
2016-07-11 20:26:35 -04:00
|
|
|
const parametersData:
|
|
|
|
(MetadataSymbolicReferenceExpression | MetadataError |
|
|
|
|
MetadataSymbolicSelectExpression | null)[] = [];
|
2016-03-22 20:11:42 -04:00
|
|
|
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) {
|
2016-05-31 14:00:39 -04:00
|
|
|
if (parameter.type) {
|
|
|
|
parametersData.push(referenceFrom(parameter.type));
|
|
|
|
} else {
|
|
|
|
parametersData.push(null);
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
hasParameterData = true;
|
|
|
|
}
|
|
|
|
}
|
2016-05-26 13:45:37 -04:00
|
|
|
const data: MethodMetadata = {__symbolic: isConstructor ? 'constructor' : 'method'};
|
|
|
|
const name = isConstructor ? '__ctor__' : evaluator.nameOf(member.name);
|
2016-05-04 12:11:04 -04:00
|
|
|
if (methodDecorators) {
|
|
|
|
data.decorators = methodDecorators;
|
|
|
|
}
|
|
|
|
if (hasDecoratorData) {
|
|
|
|
data.parameterDecorators = parameterDecoratorData;
|
|
|
|
}
|
|
|
|
if (hasParameterData) {
|
|
|
|
(<ConstructorMetadata>data).parameters = parametersData;
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
2016-05-31 14:00:39 -04:00
|
|
|
if (!isMetadataError(name)) {
|
|
|
|
recordMember(name, data);
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
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;
|
2016-07-27 22:26:59 -04:00
|
|
|
if (property.flags & ts.NodeFlags.Static) {
|
|
|
|
const name = evaluator.nameOf(property.name);
|
|
|
|
if (!isMetadataError(name)) {
|
|
|
|
if (property.initializer) {
|
|
|
|
const value = evaluator.evaluateNode(property.initializer);
|
|
|
|
recordStaticMember(name, value);
|
|
|
|
} else {
|
|
|
|
recordStaticMember(name, errorSym('Variable not initialized', property.name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
const propertyDecorators = getDecorators(property.decorators);
|
|
|
|
if (propertyDecorators) {
|
2016-07-27 22:26:59 -04:00
|
|
|
const name = evaluator.nameOf(property.name);
|
2016-05-31 14:00:39 -04:00
|
|
|
if (!isMetadataError(name)) {
|
|
|
|
recordMember(name, {__symbolic: 'property', decorators: propertyDecorators});
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (members) {
|
|
|
|
result.members = members;
|
|
|
|
}
|
2016-07-26 13:18:35 -04:00
|
|
|
if (statics) {
|
|
|
|
result.statics = statics;
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
|
2016-07-26 13:18:35 -04:00
|
|
|
return result.decorators || members || statics ? result : undefined;
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
|
2016-05-31 14:00:39 -04:00
|
|
|
// Predeclare classes
|
|
|
|
ts.forEachChild(sourceFile, node => {
|
|
|
|
switch (node.kind) {
|
|
|
|
case ts.SyntaxKind.ClassDeclaration:
|
|
|
|
const classDeclaration = <ts.ClassDeclaration>node;
|
|
|
|
const className = classDeclaration.name.text;
|
|
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
|
|
locals.define(className, {__symbolic: 'reference', name: className});
|
|
|
|
} else {
|
|
|
|
locals.define(
|
2016-06-03 18:43:09 -04:00
|
|
|
className, errorSym('Reference to non-exported class', node, {className}));
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
ts.forEachChild(sourceFile, node => {
|
|
|
|
switch (node.kind) {
|
2016-08-02 14:45:14 -04:00
|
|
|
case ts.SyntaxKind.ExportDeclaration:
|
|
|
|
// Record export declarations
|
|
|
|
const exportDeclaration = <ts.ExportDeclaration>node;
|
|
|
|
const moduleSpecifier = exportDeclaration.moduleSpecifier;
|
|
|
|
if (moduleSpecifier && moduleSpecifier.kind == ts.SyntaxKind.StringLiteral) {
|
|
|
|
// Ignore exports that don't have string literals as exports.
|
|
|
|
// This is allowed by the syntax but will be flagged as an error by the type checker.
|
|
|
|
const from = (<ts.StringLiteral>moduleSpecifier).text;
|
|
|
|
const moduleExport: ModuleExportMetadata = {from};
|
|
|
|
if (exportDeclaration.exportClause) {
|
|
|
|
moduleExport.export = exportDeclaration.exportClause.elements.map(
|
|
|
|
element => element.propertyName ?
|
|
|
|
{name: element.propertyName.text, as: element.name.text} :
|
|
|
|
element.name.text)
|
|
|
|
}
|
|
|
|
if (!exports) exports = [];
|
|
|
|
exports.push(moduleExport);
|
|
|
|
}
|
|
|
|
break;
|
2016-05-31 14:00:39 -04:00
|
|
|
case ts.SyntaxKind.ClassDeclaration:
|
|
|
|
const classDeclaration = <ts.ClassDeclaration>node;
|
|
|
|
const className = classDeclaration.name.text;
|
|
|
|
if (node.flags & ts.NodeFlags.Export) {
|
2016-03-22 20:11:42 -04:00
|
|
|
if (classDeclaration.decorators) {
|
|
|
|
if (!metadata) metadata = {};
|
2016-05-31 14:00:39 -04:00
|
|
|
metadata[className] = classMetadataOf(classDeclaration);
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
|
|
|
// Otherwise don't record metadata for the class.
|
|
|
|
break;
|
2016-06-13 18:56:51 -04:00
|
|
|
case ts.SyntaxKind.FunctionDeclaration:
|
|
|
|
// Record functions that return a single value. Record the parameter
|
|
|
|
// names substitution will be performed by the StaticReflector.
|
|
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
|
|
const functionDeclaration = <ts.FunctionDeclaration>node;
|
2016-07-26 13:18:35 -04:00
|
|
|
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
|
|
|
|
if (maybeFunc) {
|
|
|
|
if (!metadata) metadata = {};
|
|
|
|
metadata[maybeFunc.name] = maybeFunc.func;
|
2016-06-13 18:56:51 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Otherwise don't record the function.
|
|
|
|
break;
|
2016-07-11 14:50:33 -04:00
|
|
|
case ts.SyntaxKind.EnumDeclaration:
|
2016-08-22 13:26:22 -04:00
|
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
|
|
const enumDeclaration = <ts.EnumDeclaration>node;
|
|
|
|
let enumValueHolder: {[name: string]: MetadataValue} = {};
|
|
|
|
const enumName = enumDeclaration.name.text;
|
|
|
|
let nextDefaultValue: MetadataValue = 0;
|
|
|
|
let writtenMembers = 0;
|
|
|
|
for (const member of enumDeclaration.members) {
|
|
|
|
let enumValue: MetadataValue;
|
|
|
|
if (!member.initializer) {
|
|
|
|
enumValue = nextDefaultValue;
|
|
|
|
} else {
|
|
|
|
enumValue = evaluator.evaluateNode(member.initializer);
|
|
|
|
}
|
|
|
|
let name: string = undefined;
|
|
|
|
if (member.name.kind == ts.SyntaxKind.Identifier) {
|
|
|
|
const identifier = <ts.Identifier>member.name;
|
|
|
|
name = identifier.text;
|
|
|
|
enumValueHolder[name] = enumValue;
|
|
|
|
writtenMembers++;
|
|
|
|
}
|
|
|
|
if (typeof enumValue === 'number') {
|
|
|
|
nextDefaultValue = enumValue + 1;
|
|
|
|
} else if (name) {
|
|
|
|
nextDefaultValue = {
|
|
|
|
__symbolic: 'binary',
|
|
|
|
operator: '+',
|
|
|
|
left: {
|
|
|
|
__symbolic: 'select',
|
|
|
|
expression: {__symbolic: 'reference', name: enumName}, name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
nextDefaultValue = errorSym('Unsuppported enum member name', member.name);
|
|
|
|
};
|
2016-07-11 14:50:33 -04:00
|
|
|
}
|
2016-08-22 13:26:22 -04:00
|
|
|
if (writtenMembers) {
|
|
|
|
if (!metadata) metadata = {};
|
|
|
|
metadata[enumName] = enumValueHolder;
|
2016-07-11 14:50:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2016-05-31 14:00:39 -04:00
|
|
|
case ts.SyntaxKind.VariableStatement:
|
|
|
|
const variableStatement = <ts.VariableStatement>node;
|
|
|
|
for (let variableDeclaration of variableStatement.declarationList.declarations) {
|
|
|
|
if (variableDeclaration.name.kind == ts.SyntaxKind.Identifier) {
|
|
|
|
let nameNode = <ts.Identifier>variableDeclaration.name;
|
|
|
|
let varValue: MetadataValue;
|
|
|
|
if (variableDeclaration.initializer) {
|
|
|
|
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
|
|
|
|
} else {
|
2016-06-03 18:43:09 -04:00
|
|
|
varValue = errorSym('Variable not initialized', nameNode);
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
2016-08-02 17:38:31 -04:00
|
|
|
let exported = false;
|
2016-05-31 14:00:39 -04:00
|
|
|
if (variableStatement.flags & ts.NodeFlags.Export ||
|
|
|
|
variableDeclaration.flags & ts.NodeFlags.Export) {
|
2016-03-22 20:11:42 -04:00
|
|
|
if (!metadata) metadata = {};
|
2016-05-31 14:00:39 -04:00
|
|
|
metadata[nameNode.text] = varValue;
|
2016-08-02 17:38:31 -04:00
|
|
|
exported = true;
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
|
|
|
if (isPrimitive(varValue)) {
|
|
|
|
locals.define(nameNode.text, varValue);
|
2016-08-02 17:38:31 -04:00
|
|
|
} else if (!exported) {
|
|
|
|
locals.define(
|
|
|
|
nameNode.text,
|
|
|
|
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}));
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
2016-05-31 14:00:39 -04:00
|
|
|
} else {
|
|
|
|
// Destructuring (or binding) declarations are not supported,
|
|
|
|
// var {<identifier>[, <identifer>]+} = <expression>;
|
|
|
|
// or
|
|
|
|
// var [<identifier>[, <identifier}+] = <expression>;
|
|
|
|
// are not supported.
|
|
|
|
const report = (nameNode: ts.Node) => {
|
|
|
|
switch (nameNode.kind) {
|
|
|
|
case ts.SyntaxKind.Identifier:
|
|
|
|
const name = <ts.Identifier>nameNode;
|
2016-06-03 18:43:09 -04:00
|
|
|
const varValue = errorSym('Destructuring not supported', nameNode);
|
2016-05-31 14:00:39 -04:00
|
|
|
locals.define(name.text, varValue);
|
|
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
|
|
if (!metadata) metadata = {};
|
|
|
|
metadata[name.text] = varValue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.BindingElement:
|
|
|
|
const bindingElement = <ts.BindingElement>nameNode;
|
|
|
|
report(bindingElement.name);
|
|
|
|
break;
|
|
|
|
case ts.SyntaxKind.ObjectBindingPattern:
|
|
|
|
case ts.SyntaxKind.ArrayBindingPattern:
|
|
|
|
const bindings = <ts.BindingPattern>nameNode;
|
|
|
|
bindings.elements.forEach(report);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
report(variableDeclaration.name);
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
2016-05-31 14:00:39 -04:00
|
|
|
}
|
|
|
|
break;
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
2016-05-31 14:00:39 -04:00
|
|
|
});
|
2016-04-26 00:29:06 -04:00
|
|
|
|
2016-08-02 14:45:14 -04:00
|
|
|
if (metadata || exports) {
|
|
|
|
if (!metadata) metadata = {};
|
|
|
|
const result: ModuleMetadata = {__symbolic: 'module', version: VERSION, metadata};
|
|
|
|
if (exports) result.exports = exports;
|
|
|
|
return result;
|
|
|
|
}
|
2016-03-22 20:11:42 -04:00
|
|
|
}
|
|
|
|
}
|
2016-06-13 18:56:51 -04:00
|
|
|
|
|
|
|
// Collect parameter names from a function.
|
|
|
|
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
|
|
|
|
let result: string[] = [];
|
|
|
|
|
|
|
|
function addNamesOf(name: ts.Identifier | ts.BindingPattern) {
|
|
|
|
if (name.kind == ts.SyntaxKind.Identifier) {
|
|
|
|
const identifier = <ts.Identifier>name;
|
|
|
|
result.push(identifier.text);
|
|
|
|
} else {
|
|
|
|
const bindingPattern = <ts.BindingPattern>name;
|
|
|
|
for (let element of bindingPattern.elements) {
|
|
|
|
addNamesOf(element.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let parameter of parameters) {
|
|
|
|
addNamesOf(parameter.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|