Chuck Jazdzewski cf3548a02f fix(compiler): Improved error reporting of the static reflector.
StaticReflector provides more context on errors reported by the
collector.

The metadata collector now records the line and character of the node that
caused it to report the error.

Includes other minor fixes to error reporting and a wording change.

Fixes #8978
Closes #9011
2016-06-07 08:38:32 -07:00

211 lines
8.7 KiB
TypeScript

import * as ts from 'typescript';
import {Evaluator, ImportMetadata, ImportSpecifierMetadata, errorSymbol, isPrimitive} from './evaluator';
import {ClassMetadata, ConstructorMetadata, ModuleMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, isMetadataError, isMetadataSymbolicReferenceExpression, VERSION} from './schema';
import {Symbols} from './symbols';
/**
* Collect decorator metadata from a TypeScript module.
*/
export class MetadataCollector {
constructor() {}
/**
* 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): ModuleMetadata {
const locals = new Symbols(sourceFile);
const evaluator = new Evaluator(locals);
let metadata: {[name: string]: MetadataValue | ClassMetadata}|undefined;
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
}
function errorSym(
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
return errorSymbol(message, node, context, sourceFile);
}
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
let result: ClassMetadata = {__symbolic: 'class'};
function getDecorators(decorators: ts.Decorator[]): MetadataSymbolicExpression[] {
if (decorators && decorators.length)
return decorators.map(decorator => objFromDecorator(decorator));
return undefined;
}
function referenceFrom(node: ts.Node): MetadataSymbolicReferenceExpression|MetadataError {
const result = evaluator.evaluateNode(node);
if (isMetadataError(result) || isMetadataSymbolicReferenceExpression(result)) {
return result;
} else {
return errorSym('Symbol reference expected', node);
}
}
// 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:
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
const methodDecorators = getDecorators(method.decorators);
const parameters = method.parameters;
const parameterDecoratorData: (MetadataSymbolicExpression | MetadataError)[][] = [];
const parametersData: (MetadataSymbolicReferenceExpression | MetadataError | null)[] =
[];
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) {
if (parameter.type) {
parametersData.push(referenceFrom(parameter.type));
} else {
parametersData.push(null);
}
hasParameterData = true;
}
}
const data: MethodMetadata = {__symbolic: isConstructor ? 'constructor' : 'method'};
const name = isConstructor ? '__ctor__' : evaluator.nameOf(member.name);
if (methodDecorators) {
data.decorators = methodDecorators;
}
if (hasDecoratorData) {
data.parameterDecorators = parameterDecoratorData;
}
if (hasParameterData) {
(<ConstructorMetadata>data).parameters = parametersData;
}
if (!isMetadataError(name)) {
recordMember(name, data);
}
break;
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
const property = <ts.PropertyDeclaration>member;
const propertyDecorators = getDecorators(property.decorators);
if (propertyDecorators) {
let name = evaluator.nameOf(property.name);
if (!isMetadataError(name)) {
recordMember(name, {__symbolic: 'property', decorators: propertyDecorators});
}
}
break;
}
}
if (members) {
result.members = members;
}
return result.decorators || members ? result : undefined;
}
// 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(
className, errorSym('Reference to non-exported class', node, {className}));
}
break;
}
});
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) {
if (classDeclaration.decorators) {
if (!metadata) metadata = {};
metadata[className] = classMetadataOf(classDeclaration);
}
}
// Otherwise don't record metadata for the class.
break;
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 {
varValue = errorSym('Variable not initialized', nameNode);
}
if (variableStatement.flags & ts.NodeFlags.Export ||
variableDeclaration.flags & ts.NodeFlags.Export) {
if (!metadata) metadata = {};
metadata[nameNode.text] = varValue;
}
if (isPrimitive(varValue)) {
locals.define(nameNode.text, varValue);
}
} 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;
const varValue = errorSym('Destructuring not supported', nameNode);
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);
}
}
break;
}
});
return metadata && {__symbolic: 'module', version: VERSION, metadata};
}
}