feat(compiler): Added "strictMetadataEmit" option to ngc (#10951)

ngc can now validate metadata before emitting to verify it doesn't
contain an error symbol that will result in a runtime error if
it is used by the StaticReflector.

To enable this add the section,

  "angularCompilerOptions": {
    "strictMetadataEmit": true
  }

to the top level of the tsconfig.json file passed to ngc.

Enabled metadata validation for packages that are intended to be
used statically.
This commit is contained in:
Chuck Jazdzewski
2016-08-22 17:37:48 -07:00
committed by Kara
parent 45e8e73670
commit 39a2c39cef
32 changed files with 502 additions and 117 deletions

View File

@ -1,11 +1,10 @@
import * as ts from 'typescript';
import {Evaluator, errorSymbol, isPrimitive} from './evaluator';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataError, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isMetadataError, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression} from './schema';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataObject, 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';
/**
* Collect decorator metadata from a TypeScript module.
*/
@ -16,9 +15,10 @@ export class MetadataCollector {
* 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 {
public getMetadata(sourceFile: ts.SourceFile, strict: boolean = false): ModuleMetadata {
const locals = new Symbols(sourceFile);
const evaluator = new Evaluator(locals);
const nodeMap = new Map<MetadataValue|ClassMetadata|FunctionMetadata, ts.Node>();
const evaluator = new Evaluator(locals, nodeMap);
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
let exports: ModuleExportMetadata[];
@ -26,6 +26,11 @@ export class MetadataCollector {
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);
@ -53,7 +58,7 @@ export class MetadataCollector {
func.defaults = functionDeclaration.parameters.map(
p => p.initializer && evaluator.evaluateNode(p.initializer));
}
return { func, name: functionName }
return recordEntry({func, name: functionName}, functionDeclaration);
}
}
}
@ -183,10 +188,11 @@ export class MetadataCollector {
result.statics = statics;
}
return result.decorators || members || statics ? result : undefined;
return result.decorators || members || statics ? recordEntry(result, classDeclaration) :
undefined;
}
// Predeclare classes
// Predeclare classes and functions
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
@ -199,6 +205,16 @@ export class MetadataCollector {
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;
locals.define(
nameNode.text,
errorSym('Reference to a non-exported function', nameNode, {name: nameNode.text}));
}
break;
}
});
ts.forEachChild(sourceFile, node => {
@ -236,15 +252,14 @@ export class MetadataCollector {
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) {
const functionDeclaration = <ts.FunctionDeclaration>node;
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (maybeFunc) {
if (!metadata) metadata = {};
metadata[maybeFunc.name] = maybeFunc.func;
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node);
}
}
// Otherwise don't record the function.
break;
case ts.SyntaxKind.EnumDeclaration:
if (node.flags & ts.NodeFlags.Export) {
@ -275,16 +290,17 @@ export class MetadataCollector {
operator: '+',
left: {
__symbolic: 'select',
expression: {__symbolic: 'reference', name: enumName}, name
expression: recordEntry({__symbolic: 'reference', name: enumName}, node), name
}
}
} else {
nextDefaultValue = errorSym('Unsuppported enum member name', member.name);
nextDefaultValue =
recordEntry(errorSym('Unsuppported enum member name', member.name), node);
};
}
if (writtenMembers) {
if (!metadata) metadata = {};
metadata[enumName] = enumValueHolder;
metadata[enumName] = recordEntry(enumValueHolder, node);
}
}
break;
@ -297,21 +313,27 @@ export class MetadataCollector {
if (variableDeclaration.initializer) {
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
} else {
varValue = errorSym('Variable not initialized', nameNode);
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] = varValue;
metadata[nameNode.text] = recordEntry(varValue, node);
exported = true;
}
if (isPrimitive(varValue)) {
locals.define(nameNode.text, varValue);
} else if (!exported) {
locals.define(
nameNode.text,
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}));
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,
@ -349,7 +371,11 @@ export class MetadataCollector {
});
if (metadata || exports) {
if (!metadata) metadata = {};
if (!metadata)
metadata = {};
else if (strict) {
validateMetadata(sourceFile, nodeMap, metadata);
}
const result: ModuleMetadata = {__symbolic: 'module', version: VERSION, metadata};
if (exports) result.exports = exports;
return result;
@ -357,6 +383,149 @@ export class MetadataCollector {
}
}
// 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(member: MemberMetadata) {
if (member.decorators) {
member.decorators.forEach(validateExpression);
}
if (isMethodMetadata(member) && member.parameterDecorators) {
member.parameterDecorators.forEach(validateExpression);
}
if (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(validateMember));
}
}
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) {
let {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[] {
let result: string[] = [];
@ -378,4 +547,33 @@ function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
}
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;
}

View File

@ -2,6 +2,7 @@ import {writeFileSync} from 'fs';
import {convertDecorators} from 'tsickle';
import * as ts from 'typescript';
import NgOptions from './options';
import {MetadataCollector} from './collector';
@ -66,14 +67,18 @@ const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
export class MetadataWriterHost extends DelegatingHost {
private metadataCollector = new MetadataCollector();
constructor(delegate: ts.CompilerHost, private program: ts.Program) { super(delegate); }
constructor(
delegate: ts.CompilerHost, private program: ts.Program, private ngOptions: NgOptions) {
super(delegate);
}
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
// released
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
const metadata = this.metadataCollector.getMetadata(sourceFile);
const metadata =
this.metadataCollector.getMetadata(sourceFile, !!this.ngOptions.strictMetadataEmit);
if (metadata && metadata.metadata) {
const metadataText = JSON.stringify(metadata);
writeFileSync(path, metadataText, {encoding: 'utf-8'});

View File

@ -1,6 +1,6 @@
import * as ts from 'typescript';
import {MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {MetadataEntry, MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
@ -70,7 +70,8 @@ export function errorSymbol(
if (node) {
sourceFile = sourceFile || getSourceFileOfNode(node);
if (sourceFile) {
let {line, character} = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
let {line, character} =
ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
result = {__symbolic: 'error', message, line, character};
};
}
@ -88,7 +89,7 @@ export function errorSymbol(
* possible.
*/
export class Evaluator {
constructor(private symbols: Symbols) {}
constructor(private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>) {}
nameOf(node: ts.Node): string|MetadataError {
if (node.kind == ts.SyntaxKind.Identifier) {
@ -203,7 +204,14 @@ export class Evaluator {
* tree are folded. For example, a node representing `1 + 2` is folded into `3`.
*/
public evaluateNode(node: ts.Node): MetadataValue {
const t = this;
let error: MetadataError|undefined;
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
t.nodeMap.set(entry, node);
return entry;
}
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
let obj: {[name: string]: any} = {};
@ -258,14 +266,14 @@ export class Evaluator {
case ts.SyntaxKind.SpreadElementExpression:
let spread = <ts.SpreadElementExpression>node;
let spreadExpression = this.evaluateNode(spread.expression);
return {__symbolic: 'spread', expression: spreadExpression};
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
const firstArgument = callExpression.arguments[0];
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
const arrowFunction = <ts.ArrowFunction>firstArgument;
return this.evaluateNode(arrowFunction.body);
return recordEntry(this.evaluateNode(arrowFunction.body), node);
}
}
const args = callExpression.arguments.map(arg => this.evaluateNode(arg));
@ -282,65 +290,66 @@ export class Evaluator {
}
// Always fold a CONST_EXPR even if the argument is not foldable.
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) {
return args[0];
return recordEntry(args[0], node);
}
const expression = this.evaluateNode(callExpression.expression);
if (isMetadataError(expression)) {
return expression;
return recordEntry(expression, node);
}
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
if (args && args.length) {
result.arguments = args;
}
return result;
return recordEntry(result, node);
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.NewExpression>node;
const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg));
if (newArgs.some(isMetadataError)) {
return newArgs.find(isMetadataError);
return recordEntry(newArgs.find(isMetadataError), node);
}
const newTarget = this.evaluateNode(newExpression.expression);
if (isMetadataError(newTarget)) {
return newTarget;
return recordEntry(newTarget, node);
}
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
if (newArgs.length) {
call.arguments = newArgs;
}
return call;
return recordEntry(call, node);
case ts.SyntaxKind.PropertyAccessExpression: {
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
const expression = this.evaluateNode(propertyAccessExpression.expression);
if (isMetadataError(expression)) {
return expression;
return recordEntry(expression, node);
}
const member = this.nameOf(propertyAccessExpression.name);
if (isMetadataError(member)) {
return member;
return recordEntry(member, node);
}
if (expression && this.isFoldable(propertyAccessExpression.expression))
return (<any>expression)[<string>member];
if (isMetadataModuleReferenceExpression(expression)) {
// A select into a module refrence and be converted into a reference to the symbol
// in the module
return {__symbolic: 'reference', module: expression.module, name: member};
return recordEntry(
{__symbolic: 'reference', module: expression.module, name: member}, node);
}
return {__symbolic: 'select', expression, member};
return recordEntry({__symbolic: 'select', expression, member}, node);
}
case ts.SyntaxKind.ElementAccessExpression: {
const elementAccessExpression = <ts.ElementAccessExpression>node;
const expression = this.evaluateNode(elementAccessExpression.expression);
if (isMetadataError(expression)) {
return expression;
return recordEntry(expression, node);
}
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
if (isMetadataError(expression)) {
return expression;
return recordEntry(expression, node);
}
if (this.isFoldable(elementAccessExpression.expression) &&
this.isFoldable(elementAccessExpression.argumentExpression))
return (<any>expression)[<string|number>index];
return {__symbolic: 'index', expression, index};
return recordEntry({__symbolic: 'index', expression, index}, node);
}
case ts.SyntaxKind.Identifier:
const identifier = <ts.Identifier>node;
@ -348,7 +357,7 @@ export class Evaluator {
const reference = this.symbols.resolve(name);
if (reference === undefined) {
// Encode as a global reference. StaticReflector will check the reference.
return { __symbolic: 'reference', name }
return recordEntry({__symbolic: 'reference', name}, node);
}
return reference;
case ts.SyntaxKind.TypeReference:
@ -360,9 +369,13 @@ export class Evaluator {
const qualifiedName = <ts.QualifiedName>node;
const left = this.evaluateNode(qualifiedName.left);
if (isMetadataModuleReferenceExpression(left)) {
return <MetadataImportedSymbolReferenceExpression> {
__symbolic: 'reference', module: left.module, name: qualifiedName.right.text
}
return recordEntry(
<MetadataImportedSymbolReferenceExpression>{
__symbolic: 'reference',
module: left.module,
name: qualifiedName.right.text
},
node)
}
// Record a type reference to a declared type as a select.
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
@ -370,14 +383,15 @@ export class Evaluator {
const identifier = <ts.Identifier>typeNameNode;
let symbol = this.symbols.resolve(identifier.text);
if (isMetadataError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
return symbol;
return recordEntry(symbol, node);
}
return errorSymbol('Could not resolve type', node, {typeName: identifier.text});
return recordEntry(
errorSymbol('Could not resolve type', node, {typeName: identifier.text}), node);
}
};
const typeReference = getReference(typeNameNode);
if (isMetadataError(typeReference)) {
return typeReference;
return recordEntry(typeReference, node);
}
if (!isMetadataModuleReferenceExpression(typeReference) &&
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
@ -386,7 +400,7 @@ export class Evaluator {
// Some versions of 1.9 do not infer this correctly.
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
}
return typeReference;
return recordEntry(typeReference, node);
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
return (<ts.LiteralExpression>node).text;
case ts.SyntaxKind.StringLiteral:
@ -394,20 +408,22 @@ export class Evaluator {
case ts.SyntaxKind.NumericLiteral:
return parseFloat((<ts.LiteralExpression>node).text);
case ts.SyntaxKind.AnyKeyword:
return {__symbolic: 'reference', name: 'any'};
return recordEntry({__symbolic: 'reference', name: 'any'}, node);
case ts.SyntaxKind.StringKeyword:
return {__symbolic: 'reference', name: 'string'};
return recordEntry({__symbolic: 'reference', name: 'string'}, node);
case ts.SyntaxKind.NumberKeyword:
return {__symbolic: 'reference', name: 'number'};
return recordEntry({__symbolic: 'reference', name: 'number'}, node);
case ts.SyntaxKind.BooleanKeyword:
return {__symbolic: 'reference', name: 'boolean'};
return recordEntry({__symbolic: 'reference', name: 'boolean'}, node);
case ts.SyntaxKind.ArrayType:
const arrayTypeNode = <ts.ArrayTypeNode>node;
return {
__symbolic: 'reference',
name: 'Array',
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
};
return recordEntry(
{
__symbolic: 'reference',
name: 'Array',
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
},
node);
case ts.SyntaxKind.NullKeyword:
return null;
case ts.SyntaxKind.TrueKeyword:
@ -452,7 +468,7 @@ export class Evaluator {
default:
return undefined;
}
return {__symbolic: 'pre', operator: operatorText, operand: operand};
return recordEntry({__symbolic: 'pre', operator: operatorText, operand: operand}, node);
case ts.SyntaxKind.BinaryExpression:
const binaryExpression = <ts.BinaryExpression>node;
const left = this.evaluateNode(binaryExpression.left);
@ -503,12 +519,14 @@ export class Evaluator {
case ts.SyntaxKind.PercentToken:
return <any>left % <any>right;
}
return {
__symbolic: 'binop',
operator: binaryExpression.operatorToken.getText(),
left: left,
right: right
};
return recordEntry(
{
__symbolic: 'binop',
operator: binaryExpression.operatorToken.getText(),
left: left,
right: right
},
node);
}
break;
case ts.SyntaxKind.ConditionalExpression:
@ -519,12 +537,12 @@ export class Evaluator {
if (isPrimitive(condition)) {
return condition ? thenExpression : elseExpression;
}
return {__symbolic: 'if', condition, thenExpression, elseExpression};
return recordEntry({__symbolic: 'if', condition, thenExpression, elseExpression}, node);
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return errorSymbol('Function call not supported', node);
return recordEntry(errorSymbol('Function call not supported', node), node);
}
return errorSymbol('Expression form not supported', node);
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
}

View File

@ -50,7 +50,7 @@ export function main(
// decorators which we want to read or document.
// Do this emit second since TypeScript will create missing directories for us
// in the standard emit.
const metadataWriter = new MetadataWriterHost(host, newProgram);
const metadataWriter = new MetadataWriterHost(host, newProgram, ngOptions);
tsc.emit(metadataWriter, newProgram);
}
});

View File

@ -10,6 +10,9 @@ interface Options extends ts.CompilerOptions {
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
skipMetadataEmit: boolean;
// Produce an error if the metadata written for a class would produce an error if used.
strictMetadataEmit: boolean;
// Don't produce .ngfactory.ts or .css.shim.ts files
skipTemplateCodegen: boolean;

View File

@ -9,11 +9,13 @@
export const VERSION = 1;
export type MetadataEntry = ClassMetadata | FunctionMetadata | MetadataValue;
export interface ModuleMetadata {
__symbolic: 'module';
version: number;
exports?: ModuleExportMetadata[];
metadata: {[name: string]: (ClassMetadata | FunctionMetadata | MetadataValue)};
metadata: {[name: string]: MetadataEntry};
}
export function isModuleMetadata(value: any): value is ModuleMetadata {
return value && value.__symbolic === 'module';
@ -56,7 +58,7 @@ export interface MethodMetadata extends MemberMetadata {
__symbolic: 'constructor'|'method';
parameterDecorators?: (MetadataSymbolicExpression|MetadataError)[][];
}
export function isMethodMetadata(value: any): value is MemberMetadata {
export function isMethodMetadata(value: any): value is MethodMetadata {
return value && (value.__symbolic === 'constructor' || value.__symbolic === 'method');
}