mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
This change retracts support for metadata version 2. The collector used to produce version 2 metadata was incomplete and can cause the AOT compiler to fail to resolve symbols or produce other spurious errors. All libraries compiled and published with 2.3.0 ngc will need to be recompiled and updated with this change.
642 lines
26 KiB
TypeScript
642 lines
26 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
import {Evaluator, errorSymbol, isPrimitive} from './evaluator';
|
|
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression, isMethodMetadata} from './schema';
|
|
import {Symbols} from './symbols';
|
|
|
|
|
|
/**
|
|
* A set of collector options to use when collecting metadata.
|
|
*/
|
|
export class CollectorOptions {
|
|
/**
|
|
* Version of the metadata to collect.
|
|
*/
|
|
version?: number;
|
|
|
|
/**
|
|
* Collect a hidden field "$quoted$" in objects literals that record when the key was quoted in
|
|
* the source.
|
|
*/
|
|
quotedNames?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Collect decorator metadata from a TypeScript module.
|
|
*/
|
|
export class MetadataCollector {
|
|
constructor(private options: CollectorOptions = {}) {}
|
|
|
|
/**
|
|
* 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, strict: boolean = false): ModuleMetadata {
|
|
const locals = new Symbols(sourceFile);
|
|
const nodeMap = new Map<MetadataValue|ClassMetadata|FunctionMetadata, ts.Node>();
|
|
const evaluator = new Evaluator(locals, nodeMap, this.options);
|
|
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
|
|
let exports: ModuleExportMetadata[];
|
|
|
|
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
|
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
|
}
|
|
|
|
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
|
|
nodeMap.set(entry, node);
|
|
return entry;
|
|
}
|
|
|
|
function errorSym(
|
|
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
|
|
return errorSymbol(message, node, context, sourceFile);
|
|
}
|
|
|
|
function maybeGetSimpleFunction(
|
|
functionDeclaration: ts.FunctionDeclaration |
|
|
ts.MethodDeclaration): {func: FunctionMetadata, 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) {
|
|
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));
|
|
}
|
|
return recordEntry({func, name: functionName}, functionDeclaration);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
|
const 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|
|
|
MetadataSymbolicSelectExpression {
|
|
const result = evaluator.evaluateNode(node);
|
|
if (isMetadataError(result) || isMetadataSymbolicReferenceExpression(result) ||
|
|
isMetadataSymbolicSelectExpression(result)) {
|
|
return result;
|
|
} else {
|
|
return errorSym('Symbol reference expected', node);
|
|
}
|
|
}
|
|
|
|
// Add class parents
|
|
if (classDeclaration.heritageClauses) {
|
|
classDeclaration.heritageClauses.forEach((hc) => {
|
|
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {
|
|
hc.types.forEach(type => result.extends = referenceFrom(type.expression));
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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 = {};
|
|
const data = members.hasOwnProperty(name) ? members[name] : [];
|
|
data.push(metadata);
|
|
members[name] = data;
|
|
}
|
|
|
|
// static member
|
|
let statics: {[name: string]: MetadataValue | FunctionMetadata} = null;
|
|
function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) {
|
|
if (!statics) statics = {};
|
|
statics[name] = value;
|
|
}
|
|
|
|
for (const member of classDeclaration.members) {
|
|
let isConstructor = false;
|
|
switch (member.kind) {
|
|
case ts.SyntaxKind.Constructor:
|
|
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)[][] = [];
|
|
const parametersData:
|
|
(MetadataSymbolicReferenceExpression | MetadataError |
|
|
MetadataSymbolicSelectExpression | 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;
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
const propertyDecorators = getDecorators(property.decorators);
|
|
if (propertyDecorators) {
|
|
const name = evaluator.nameOf(property.name);
|
|
if (!isMetadataError(name)) {
|
|
recordMember(name, {__symbolic: 'property', decorators: propertyDecorators});
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (members) {
|
|
result.members = members;
|
|
}
|
|
if (statics) {
|
|
result.statics = statics;
|
|
}
|
|
|
|
return recordEntry(result, classDeclaration);
|
|
}
|
|
|
|
// Predeclare classes and functions
|
|
ts.forEachChild(sourceFile, node => {
|
|
switch (node.kind) {
|
|
case ts.SyntaxKind.ClassDeclaration:
|
|
const classDeclaration = <ts.ClassDeclaration>node;
|
|
if (classDeclaration.name) {
|
|
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;
|
|
|
|
case ts.SyntaxKind.FunctionDeclaration:
|
|
if (!(node.flags & ts.NodeFlags.Export)) {
|
|
// Report references to this function as an error.
|
|
const functionDeclaration = <ts.FunctionDeclaration>node;
|
|
const nameNode = functionDeclaration.name;
|
|
if (nameNode && nameNode.text) {
|
|
locals.define(
|
|
nameNode.text,
|
|
errorSym(
|
|
'Reference to a non-exported function', nameNode, {name: nameNode.text}));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
ts.forEachChild(sourceFile, node => {
|
|
switch (node.kind) {
|
|
case ts.SyntaxKind.ExportDeclaration:
|
|
// Record export declarations
|
|
const exportDeclaration = <ts.ExportDeclaration>node;
|
|
const {moduleSpecifier, exportClause} = exportDeclaration;
|
|
|
|
if (!moduleSpecifier) {
|
|
// no module specifier -> export {propName as name};
|
|
if (exportClause) {
|
|
exportClause.elements.forEach(spec => {
|
|
const name = spec.name.text;
|
|
const propNode = spec.propertyName || spec.name;
|
|
const value: MetadataValue = evaluator.evaluateNode(propNode);
|
|
if (!metadata) metadata = {};
|
|
metadata[name] = recordEntry(value, node);
|
|
});
|
|
}
|
|
}
|
|
|
|
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 (exportClause) {
|
|
moduleExport.export = exportClause.elements.map(
|
|
spec => spec.propertyName ? {name: spec.propertyName.text, as: spec.name.text} :
|
|
spec.name.text);
|
|
}
|
|
if (!exports) exports = [];
|
|
exports.push(moduleExport);
|
|
}
|
|
break;
|
|
case ts.SyntaxKind.ClassDeclaration:
|
|
const classDeclaration = <ts.ClassDeclaration>node;
|
|
if (classDeclaration.name) {
|
|
const className = classDeclaration.name.text;
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
if (!metadata) metadata = {};
|
|
metadata[className] = classMetadataOf(classDeclaration);
|
|
}
|
|
}
|
|
// Otherwise don't record metadata for the class.
|
|
break;
|
|
|
|
case ts.SyntaxKind.FunctionDeclaration:
|
|
// Record functions that return a single value. Record the parameter
|
|
// names substitution will be performed by the StaticReflector.
|
|
const functionDeclaration = <ts.FunctionDeclaration>node;
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
if (!metadata) metadata = {};
|
|
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
|
|
if (maybeFunc) {
|
|
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node);
|
|
} else if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
|
|
const nameNode = <ts.Identifier>functionDeclaration.name;
|
|
const functionName = nameNode.text;
|
|
metadata[functionName] = {__symbolic: 'function'};
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ts.SyntaxKind.EnumDeclaration:
|
|
if (node.flags & ts.NodeFlags.Export) {
|
|
const enumDeclaration = <ts.EnumDeclaration>node;
|
|
const 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: recordEntry({__symbolic: 'reference', name: enumName}, node), name
|
|
}
|
|
};
|
|
} else {
|
|
nextDefaultValue =
|
|
recordEntry(errorSym('Unsuppported enum member name', member.name), node);
|
|
}
|
|
}
|
|
if (writtenMembers) {
|
|
if (!metadata) metadata = {};
|
|
metadata[enumName] = recordEntry(enumValueHolder, node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ts.SyntaxKind.VariableStatement:
|
|
const variableStatement = <ts.VariableStatement>node;
|
|
for (const variableDeclaration of variableStatement.declarationList.declarations) {
|
|
if (variableDeclaration.name.kind == ts.SyntaxKind.Identifier) {
|
|
const nameNode = <ts.Identifier>variableDeclaration.name;
|
|
let varValue: MetadataValue;
|
|
if (variableDeclaration.initializer) {
|
|
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
|
|
} else {
|
|
varValue = recordEntry(errorSym('Variable not initialized', nameNode), nameNode);
|
|
}
|
|
let exported = false;
|
|
if (variableStatement.flags & ts.NodeFlags.Export ||
|
|
variableDeclaration.flags & ts.NodeFlags.Export) {
|
|
if (!metadata) metadata = {};
|
|
metadata[nameNode.text] = recordEntry(varValue, node);
|
|
exported = true;
|
|
}
|
|
if (isPrimitive(varValue)) {
|
|
locals.define(nameNode.text, varValue);
|
|
} else if (!exported) {
|
|
if (varValue && !isMetadataError(varValue)) {
|
|
locals.define(nameNode.text, recordEntry(varValue, node));
|
|
} else {
|
|
locals.define(
|
|
nameNode.text,
|
|
recordEntry(
|
|
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}),
|
|
node));
|
|
}
|
|
}
|
|
} else {
|
|
// Destructuring (or binding) declarations are not supported,
|
|
// var {<identifier>[, <identifier>]+} = <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;
|
|
}
|
|
});
|
|
|
|
if (metadata || exports) {
|
|
if (!metadata)
|
|
metadata = {};
|
|
else if (strict) {
|
|
validateMetadata(sourceFile, nodeMap, metadata);
|
|
}
|
|
const result: ModuleMetadata = {
|
|
__symbolic: 'module',
|
|
version: this.options.version || VERSION, metadata
|
|
};
|
|
if (exports) result.exports = exports;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This will throw if the metadata entry given contains an error node.
|
|
function validateMetadata(
|
|
sourceFile: ts.SourceFile, nodeMap: Map<MetadataEntry, ts.Node>,
|
|
metadata: {[name: string]: MetadataEntry}) {
|
|
let locals: Set<string> = new Set(['Array', 'Object', 'Set', 'Map', 'string', 'number', 'any']);
|
|
|
|
function validateExpression(
|
|
expression: MetadataValue | MetadataSymbolicExpression | MetadataError) {
|
|
if (!expression) {
|
|
return;
|
|
} else if (Array.isArray(expression)) {
|
|
expression.forEach(validateExpression);
|
|
} else if (typeof expression === 'object' && !expression.hasOwnProperty('__symbolic')) {
|
|
Object.getOwnPropertyNames(expression).forEach(v => validateExpression((<any>expression)[v]));
|
|
} else if (isMetadataError(expression)) {
|
|
reportError(expression);
|
|
} else if (isMetadataGlobalReferenceExpression(expression)) {
|
|
if (!locals.has(expression.name)) {
|
|
const reference = <MetadataValue>metadata[expression.name];
|
|
if (reference) {
|
|
validateExpression(reference);
|
|
}
|
|
}
|
|
} else if (isFunctionMetadata(expression)) {
|
|
validateFunction(<any>expression);
|
|
} else if (isMetadataSymbolicExpression(expression)) {
|
|
switch (expression.__symbolic) {
|
|
case 'binary':
|
|
const binaryExpression = <MetadataSymbolicBinaryExpression>expression;
|
|
validateExpression(binaryExpression.left);
|
|
validateExpression(binaryExpression.right);
|
|
break;
|
|
case 'call':
|
|
case 'new':
|
|
const callExpression = <MetadataSymbolicCallExpression>expression;
|
|
validateExpression(callExpression.expression);
|
|
if (callExpression.arguments) callExpression.arguments.forEach(validateExpression);
|
|
break;
|
|
case 'index':
|
|
const indexExpression = <MetadataSymbolicIndexExpression>expression;
|
|
validateExpression(indexExpression.expression);
|
|
validateExpression(indexExpression.index);
|
|
break;
|
|
case 'pre':
|
|
const prefixExpression = <MetadataSymbolicPrefixExpression>expression;
|
|
validateExpression(prefixExpression.operand);
|
|
break;
|
|
case 'select':
|
|
const selectExpression = <MetadataSymbolicSelectExpression>expression;
|
|
validateExpression(selectExpression.expression);
|
|
break;
|
|
case 'spread':
|
|
const spreadExpression = <MetadataSymbolicSpreadExpression>expression;
|
|
validateExpression(spreadExpression.expression);
|
|
break;
|
|
case 'if':
|
|
const ifExpression = <MetadataSymbolicIfExpression>expression;
|
|
validateExpression(ifExpression.condition);
|
|
validateExpression(ifExpression.elseExpression);
|
|
validateExpression(ifExpression.thenExpression);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function validateMember(classData: ClassMetadata, member: MemberMetadata) {
|
|
if (member.decorators) {
|
|
member.decorators.forEach(validateExpression);
|
|
}
|
|
if (isMethodMetadata(member) && member.parameterDecorators) {
|
|
member.parameterDecorators.forEach(validateExpression);
|
|
}
|
|
// Only validate parameters of classes for which we know that are used with our DI
|
|
if (classData.decorators && isConstructorMetadata(member) && member.parameters) {
|
|
member.parameters.forEach(validateExpression);
|
|
}
|
|
}
|
|
|
|
function validateClass(classData: ClassMetadata) {
|
|
if (classData.decorators) {
|
|
classData.decorators.forEach(validateExpression);
|
|
}
|
|
if (classData.members) {
|
|
Object.getOwnPropertyNames(classData.members)
|
|
.forEach(name => classData.members[name].forEach((m) => validateMember(classData, m)));
|
|
}
|
|
}
|
|
|
|
function validateFunction(functionDeclaration: FunctionMetadata) {
|
|
if (functionDeclaration.value) {
|
|
const oldLocals = locals;
|
|
if (functionDeclaration.parameters) {
|
|
locals = new Set(oldLocals.values());
|
|
if (functionDeclaration.parameters)
|
|
functionDeclaration.parameters.forEach(n => locals.add(n));
|
|
}
|
|
validateExpression(functionDeclaration.value);
|
|
locals = oldLocals;
|
|
}
|
|
}
|
|
|
|
function shouldReportNode(node: ts.Node) {
|
|
if (node) {
|
|
const nodeStart = node.getStart();
|
|
return !(
|
|
node.pos != nodeStart &&
|
|
sourceFile.text.substring(node.pos, nodeStart).indexOf('@dynamic') >= 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function reportError(error: MetadataError) {
|
|
const node = nodeMap.get(error);
|
|
if (shouldReportNode(node)) {
|
|
const lineInfo = error.line != undefined ?
|
|
error.character != undefined ? `:${error.line + 1}:${error.character + 1}` :
|
|
`:${error.line + 1}` :
|
|
'';
|
|
throw new Error(
|
|
`${sourceFile.fileName}${lineInfo}: Metadata collected contains an error that will be reported at runtime: ${expandedMessage(error)}.\n ${JSON.stringify(error)}`);
|
|
}
|
|
}
|
|
|
|
Object.getOwnPropertyNames(metadata).forEach(name => {
|
|
const entry = metadata[name];
|
|
try {
|
|
if (isClassMetadata(entry)) {
|
|
validateClass(entry);
|
|
}
|
|
} catch (e) {
|
|
const node = nodeMap.get(entry);
|
|
if (shouldReportNode(node)) {
|
|
if (node) {
|
|
const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
throw new Error(
|
|
`${sourceFile.fileName}:${line + 1}:${character + 1}: Error encountered in metadata generated for exported symbol '${name}': \n ${e.message}`);
|
|
}
|
|
throw new Error(
|
|
`Error encountered in metadata generated for exported symbol ${name}: \n ${e.message}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Collect parameter names from a function.
|
|
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
|
|
const 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 (const element of bindingPattern.elements) {
|
|
addNamesOf(element.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const parameter of parameters) {
|
|
addNamesOf(parameter.name);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function expandedMessage(error: any): string {
|
|
switch (error.message) {
|
|
case 'Reference to non-exported class':
|
|
if (error.context && error.context.className) {
|
|
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
|
|
}
|
|
break;
|
|
case 'Variable not initialized':
|
|
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
|
|
case 'Destructuring not supported':
|
|
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
|
|
case 'Could not resolve type':
|
|
if (error.context && error.context.typeName) {
|
|
return `Could not resolve type ${error.context.typeName}`;
|
|
}
|
|
break;
|
|
case 'Function call not supported':
|
|
let prefix =
|
|
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
|
|
return prefix +
|
|
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
|
|
case 'Reference to a local symbol':
|
|
if (error.context && error.context.name) {
|
|
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
|
|
}
|
|
}
|
|
return error.message;
|
|
}
|