diff --git a/modules/@angular/common/tsconfig-es2015.json b/modules/@angular/common/tsconfig-es2015.json index eb3aafe295..6404ded7a9 100644 --- a/modules/@angular/common/tsconfig-es2015.json +++ b/modules/@angular/common/tsconfig-es2015.json @@ -19,5 +19,8 @@ "index.ts", "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/common/tsconfig-es5.json b/modules/@angular/common/tsconfig-es5.json index 1ddbe55386..eb441a3b19 100644 --- a/modules/@angular/common/tsconfig-es5.json +++ b/modules/@angular/common/tsconfig-es5.json @@ -20,5 +20,8 @@ "index.ts", "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/compiler-cli/src/static_reflector.ts b/modules/@angular/compiler-cli/src/static_reflector.ts index 3713c4b4dd..14ee9f382f 100644 --- a/modules/@angular/compiler-cli/src/static_reflector.ts +++ b/modules/@angular/compiler-cli/src/static_reflector.ts @@ -572,13 +572,13 @@ 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}`; + 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'; + 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'; + 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}`; diff --git a/modules/@angular/compiler-cli/test/reflector_host_spec.ts b/modules/@angular/compiler-cli/test/reflector_host_spec.ts index 267870aceb..3385ba137f 100644 --- a/modules/@angular/compiler-cli/test/reflector_host_spec.ts +++ b/modules/@angular/compiler-cli/test/reflector_host_spec.ts @@ -38,6 +38,7 @@ describe('reflector_host', () => { genDir: '/tmp/project/src/gen/', basePath: '/tmp/project/src', skipMetadataEmit: false, + strictMetadataEmit: false, skipTemplateCodegen: false, trace: false }, @@ -47,6 +48,7 @@ describe('reflector_host', () => { genDir: '/tmp/project/gen', basePath: '/tmp/project/src/', skipMetadataEmit: false, + strictMetadataEmit: false, skipTemplateCodegen: false, trace: false }, diff --git a/modules/@angular/core/tsconfig-es2015.json b/modules/@angular/core/tsconfig-es2015.json index 27bb922c15..a383c0c4b9 100644 --- a/modules/@angular/core/tsconfig-es2015.json +++ b/modules/@angular/core/tsconfig-es2015.json @@ -20,5 +20,8 @@ "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts", "../../system.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/core/tsconfig-es5.json b/modules/@angular/core/tsconfig-es5.json index c19294c60a..fbe48be900 100644 --- a/modules/@angular/core/tsconfig-es5.json +++ b/modules/@angular/core/tsconfig-es5.json @@ -21,5 +21,8 @@ "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts", "../../system.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/forms/tsconfig-es2015.json b/modules/@angular/forms/tsconfig-es2015.json index e4a9aaa623..4e78598a70 100644 --- a/modules/@angular/forms/tsconfig-es2015.json +++ b/modules/@angular/forms/tsconfig-es2015.json @@ -23,5 +23,8 @@ "files": [ "index.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/forms/tsconfig-es5.json b/modules/@angular/forms/tsconfig-es5.json index 7e4b6be8dc..a3d73aacd9 100644 --- a/modules/@angular/forms/tsconfig-es5.json +++ b/modules/@angular/forms/tsconfig-es5.json @@ -24,5 +24,8 @@ "files": [ "index.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/http/tsconfig-es2015.json b/modules/@angular/http/tsconfig-es2015.json index f39ddf63ac..6f7742cb74 100644 --- a/modules/@angular/http/tsconfig-es2015.json +++ b/modules/@angular/http/tsconfig-es2015.json @@ -21,5 +21,8 @@ "index.ts", "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/http/tsconfig-es5.json b/modules/@angular/http/tsconfig-es5.json index d79d6b9838..30cda4be59 100644 --- a/modules/@angular/http/tsconfig-es5.json +++ b/modules/@angular/http/tsconfig-es5.json @@ -22,5 +22,8 @@ "index.ts", "testing.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/platform-browser/index.ts b/modules/@angular/platform-browser/index.ts index a96c2c798d..ebf812c658 100644 --- a/modules/@angular/platform-browser/index.ts +++ b/modules/@angular/platform-browser/index.ts @@ -27,5 +27,5 @@ export {WORKER_UI_LOCATION_PROVIDERS} from './src/web_workers/ui/location_provid export {NgProbeToken} from './src/dom/debug/ng_probe'; export * from './src/worker_render'; -export * from './src/worker_app'; +export {platformWorkerApp, WorkerAppModule} from './src/worker_app'; export * from './private_export'; \ No newline at end of file diff --git a/modules/@angular/platform-browser/src/worker_app.ts b/modules/@angular/platform-browser/src/worker_app.ts index 7db89f5d4e..97aac6e8c6 100644 --- a/modules/@angular/platform-browser/src/worker_app.ts +++ b/modules/@angular/platform-browser/src/worker_app.ts @@ -21,7 +21,12 @@ import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_w import {WebWorkerRootRenderer} from './web_workers/worker/renderer'; import {WorkerDomAdapter} from './web_workers/worker/worker_adapter'; -class PrintLogger { +/** + * Logger for web workers. + * + * @experimental + */ +export class PrintLogger { log = print; logError = print; logGroup = print; @@ -33,7 +38,12 @@ class PrintLogger { */ export const platformWorkerApp = createPlatformFactory(platformCore, 'workerApp'); -function _exceptionHandler(): ExceptionHandler { +/** + * Exception handler factory function. + * + * @experimental + */ +export function exceptionHandler(): ExceptionHandler { return new ExceptionHandler(new PrintLogger()); } @@ -44,7 +54,12 @@ let _postMessage = { } }; -function createMessageBus(zone: NgZone): MessageBus { +/** + * MessageBus factory function. + * + * @experimental + */ +export function createMessageBus(zone: NgZone): MessageBus { let sink = new PostMessageBusSink(_postMessage); let source = new PostMessageBusSource(); let bus = new PostMessageBus(sink, source); @@ -52,7 +67,12 @@ function createMessageBus(zone: NgZone): MessageBus { return bus; } -function setupWebWorker(): void { +/** + * Application initializer for web workers. + * + * @experimental + */ +export function setupWebWorker(): void { WorkerDomAdapter.makeCurrent(); } @@ -68,7 +88,7 @@ function setupWebWorker(): void { {provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_}, WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer}, {provide: ON_WEB_WORKER, useValue: true}, RenderStore, - {provide: ExceptionHandler, useFactory: _exceptionHandler, deps: []}, + {provide: ExceptionHandler, useFactory: exceptionHandler, deps: []}, {provide: MessageBus, useFactory: createMessageBus, deps: [NgZone]}, {provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true} ], diff --git a/modules/@angular/platform-browser/testing/browser.ts b/modules/@angular/platform-browser/testing/browser.ts index 740a4b5683..90f414a118 100644 --- a/modules/@angular/platform-browser/testing/browser.ts +++ b/modules/@angular/platform-browser/testing/browser.ts @@ -13,17 +13,13 @@ import {BrowserDomAdapter} from '../src/browser/browser_adapter'; import {AnimationDriver} from '../src/dom/animation_driver'; import {ELEMENT_PROBE_PROVIDERS} from '../src/dom/debug/ng_probe'; -import {BrowserDetection} from './browser_util'; +import {BrowserDetection, createNgZone} from './browser_util'; function initBrowserTests() { BrowserDomAdapter.makeCurrent(); BrowserDetection.setup(); } -function createNgZone(): NgZone { - return new NgZone({enableLongStackTrace: true}); -} - const _TEST_BROWSER_PLATFORM_PROVIDERS: Provider[] = [{provide: PLATFORM_INITIALIZER, useValue: initBrowserTests, multi: true}]; diff --git a/modules/@angular/platform-browser/testing/browser_util.ts b/modules/@angular/platform-browser/testing/browser_util.ts index 4d5f2a0274..eebd2088e7 100644 --- a/modules/@angular/platform-browser/testing/browser_util.ts +++ b/modules/@angular/platform-browser/testing/browser_util.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {NgZone} from '@angular/core'; import {getDOM} from '../src/dom/dom_adapter'; import {ListWrapper} from '../src/facade/collection'; import {RegExp, StringWrapper, global, isPresent, isString} from '../src/facade/lang'; @@ -129,3 +130,7 @@ export function stringifyElement(el: any /** TODO #9100 */): string { } export var browserDetection: BrowserDetection = new BrowserDetection(null); + +export function createNgZone(): NgZone { + return new NgZone({enableLongStackTrace: true}); +} diff --git a/modules/@angular/platform-browser/tsconfig-es2015.json b/modules/@angular/platform-browser/tsconfig-es2015.json index 78570bee61..992c4bea76 100644 --- a/modules/@angular/platform-browser/tsconfig-es2015.json +++ b/modules/@angular/platform-browser/tsconfig-es2015.json @@ -29,5 +29,8 @@ "../../../node_modules/@types/jasmine/index.d.ts", "../../../node_modules/@types/protractor/index.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/platform-browser/tsconfig-es5.json b/modules/@angular/platform-browser/tsconfig-es5.json index f4f2cf6ca7..700f4f8874 100644 --- a/modules/@angular/platform-browser/tsconfig-es5.json +++ b/modules/@angular/platform-browser/tsconfig-es5.json @@ -30,5 +30,8 @@ "../../../node_modules/@types/jasmine/index.d.ts", "../../../node_modules/@types/protractor/index.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/platform-server/tsconfig-es2015.json b/modules/@angular/platform-server/tsconfig-es2015.json index 9526981fe1..b2d87a3e9f 100644 --- a/modules/@angular/platform-server/tsconfig-es2015.json +++ b/modules/@angular/platform-server/tsconfig-es2015.json @@ -30,5 +30,8 @@ "../../../node_modules/@types/jasmine/index.d.ts", "../../../node_modules/@types/node/index.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/platform-server/tsconfig-es5.json b/modules/@angular/platform-server/tsconfig-es5.json index d43cf9fc59..bb9fb7ac9e 100644 --- a/modules/@angular/platform-server/tsconfig-es5.json +++ b/modules/@angular/platform-server/tsconfig-es5.json @@ -31,5 +31,8 @@ "../../../node_modules/@types/jasmine/index.d.ts", "../../../node_modules/@types/node/index.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/router/testing/router_testing_module.ts b/modules/@angular/router/testing/router_testing_module.ts index cb9d20c247..9f37ddbbc4 100644 --- a/modules/@angular/router/testing/router_testing_module.ts +++ b/modules/@angular/router/testing/router_testing_module.ts @@ -39,7 +39,12 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { } } -function setupTestingRouter( +/** + * Router setup factory function used for testing. + * + * @experimental + */ +export function setupTestingRouter( urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) { return new Router( diff --git a/modules/@angular/router/tsconfig-es2015.json b/modules/@angular/router/tsconfig-es2015.json index 3bd4b4097e..ee18acc791 100644 --- a/modules/@angular/router/tsconfig-es2015.json +++ b/modules/@angular/router/tsconfig-es2015.json @@ -24,5 +24,8 @@ "files": [ "index.ts", "testing.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } \ No newline at end of file diff --git a/modules/@angular/router/tsconfig-es5.json b/modules/@angular/router/tsconfig-es5.json index 4d8c4878e9..08e863d47e 100644 --- a/modules/@angular/router/tsconfig-es5.json +++ b/modules/@angular/router/tsconfig-es5.json @@ -24,7 +24,8 @@ "files": [ "index.ts", "testing.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } - - diff --git a/modules/@angular/upgrade/tsconfig-es2015.json b/modules/@angular/upgrade/tsconfig-es2015.json index 9cff0e3641..dc2556a986 100644 --- a/modules/@angular/upgrade/tsconfig-es2015.json +++ b/modules/@angular/upgrade/tsconfig-es2015.json @@ -22,5 +22,8 @@ "files": [ "index.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/@angular/upgrade/tsconfig-es5.json b/modules/@angular/upgrade/tsconfig-es5.json index da42c7e2bc..70bc5648b0 100644 --- a/modules/@angular/upgrade/tsconfig-es5.json +++ b/modules/@angular/upgrade/tsconfig-es5.json @@ -23,5 +23,8 @@ "files": [ "index.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts" - ] + ], + "angularCompilerOptions": { + "strictMetadataEmit": true + } } diff --git a/modules/playground/src/web_workers/kitchen_sink/index_common.ts b/modules/playground/src/web_workers/kitchen_sink/index_common.ts index d4b1216143..0a2ecf2a93 100644 --- a/modules/playground/src/web_workers/kitchen_sink/index_common.ts +++ b/modules/playground/src/web_workers/kitchen_sink/index_common.ts @@ -19,7 +19,7 @@ export class GreetingService { // Directives are light-weight. They don't allow new // expression contexts (use @Component for those needs). @Directive({selector: '[red]'}) -class RedDec { +export class RedDec { // ElementRef is always injectable and it wraps the element on which the // directive was found by the compiler. constructor(el: ElementRef, renderer: Renderer) { diff --git a/tools/@angular/tsc-wrapped/src/collector.ts b/tools/@angular/tsc-wrapped/src/collector.ts index bf328a2473..f610b01549 100644 --- a/tools/@angular/tsc-wrapped/src/collector.ts +++ b/tools/@angular/tsc-wrapped/src/collector.ts @@ -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(); + 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 evaluator.evaluateNode(decoratorNode.expression); } + function recordEntry(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 = 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 = node; if (node.flags & ts.NodeFlags.Export) { - const 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, + metadata: {[name: string]: MetadataEntry}) { + let locals: Set = 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((expression)[v])); + } else if (isMetadataError(expression)) { + reportError(expression); + } else if (isMetadataGlobalReferenceExpression(expression)) { + if (!locals.has(expression.name)) { + const reference = metadata[expression.name]; + if (reference) { + validateExpression(reference); + } + } + } else if (isFunctionMetadata(expression)) { + validateFunction(expression); + } else if (isMetadataSymbolicExpression(expression)) { + switch (expression.__symbolic) { + case 'binary': + const binaryExpression = expression; + validateExpression(binaryExpression.left); + validateExpression(binaryExpression.right); + break; + case 'call': + case 'new': + const callExpression = expression; + validateExpression(callExpression.expression); + if (callExpression.arguments) callExpression.arguments.forEach(validateExpression); + break; + case 'index': + const indexExpression = expression; + validateExpression(indexExpression.expression); + validateExpression(indexExpression.index); + break; + case 'pre': + const prefixExpression = expression; + validateExpression(prefixExpression.operand); + break; + case 'select': + const selectExpression = expression; + validateExpression(selectExpression.expression); + break; + case 'spread': + const spreadExpression = expression; + validateExpression(spreadExpression.expression); + break; + case 'if': + const ifExpression = 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): string[] { let result: string[] = []; @@ -378,4 +547,33 @@ function namesOf(parameters: ts.NodeArray): string[] { } return result; -} \ No newline at end of file +} + +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; +} diff --git a/tools/@angular/tsc-wrapped/src/compiler_host.ts b/tools/@angular/tsc-wrapped/src/compiler_host.ts index a2bcaa9a6f..373e8938e0 100644 --- a/tools/@angular/tsc-wrapped/src/compiler_host.ts +++ b/tools/@angular/tsc-wrapped/src/compiler_host.ts @@ -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'}); diff --git a/tools/@angular/tsc-wrapped/src/evaluator.ts b/tools/@angular/tsc-wrapped/src/evaluator.ts index 4ed3480cb4..b723e74647 100644 --- a/tools/@angular/tsc-wrapped/src/evaluator.ts +++ b/tools/@angular/tsc-wrapped/src/evaluator.ts @@ -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) {} 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(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 = 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 = node; if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) { const firstArgument = callExpression.arguments[0]; if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) { const 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 = 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 = 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 (expression)[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 = 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 (expression)[index]; - return {__symbolic: 'index', expression, index}; + return recordEntry({__symbolic: 'index', expression, index}, node); } case ts.SyntaxKind.Identifier: const 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 = node; const left = this.evaluateNode(qualifiedName.left); if (isMetadataModuleReferenceExpression(left)) { - return { - __symbolic: 'reference', module: left.module, name: qualifiedName.right.text - } + return recordEntry( + { + __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 = 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. (typeReference).arguments = args; } - return typeReference; + return recordEntry(typeReference, node); case ts.SyntaxKind.NoSubstitutionTemplateLiteral: return (node).text; case ts.SyntaxKind.StringLiteral: @@ -394,20 +408,22 @@ export class Evaluator { case ts.SyntaxKind.NumericLiteral: return parseFloat((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 = 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 = node; const left = this.evaluateNode(binaryExpression.left); @@ -503,12 +519,14 @@ export class Evaluator { case ts.SyntaxKind.PercentToken: return left % 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); } } diff --git a/tools/@angular/tsc-wrapped/src/main.ts b/tools/@angular/tsc-wrapped/src/main.ts index 4fcf328554..c1a1de777f 100644 --- a/tools/@angular/tsc-wrapped/src/main.ts +++ b/tools/@angular/tsc-wrapped/src/main.ts @@ -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); } }); diff --git a/tools/@angular/tsc-wrapped/src/options.ts b/tools/@angular/tsc-wrapped/src/options.ts index 9275b5a68d..c4f32d3d26 100644 --- a/tools/@angular/tsc-wrapped/src/options.ts +++ b/tools/@angular/tsc-wrapped/src/options.ts @@ -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; diff --git a/tools/@angular/tsc-wrapped/src/schema.ts b/tools/@angular/tsc-wrapped/src/schema.ts index fea3146b3f..56ae62839d 100644 --- a/tools/@angular/tsc-wrapped/src/schema.ts +++ b/tools/@angular/tsc-wrapped/src/schema.ts @@ -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'); } diff --git a/tools/@angular/tsc-wrapped/test/collector.spec.ts b/tools/@angular/tsc-wrapped/test/collector.spec.ts index 284ad6fce2..760fd1ea64 100644 --- a/tools/@angular/tsc-wrapped/test/collector.spec.ts +++ b/tools/@angular/tsc-wrapped/test/collector.spec.ts @@ -25,6 +25,9 @@ describe('Collector', () => { 'exported-enum.ts', 'exported-consts.ts', 'local-symbol-ref.ts', + 'local-function-ref.ts', + 'local-symbol-ref-func.ts', + 'local-symbol-ref-func-dynamic.ts', 'private-enum.ts', 're-exports.ts', 'static-field-reference.ts', @@ -230,10 +233,10 @@ describe('Collector', () => { version: 1, metadata: { a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16}, - b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 18}, + b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 19}, c: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 16}, - d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 18}, - e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 14} + d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 19}, + e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 15} } }); }); @@ -247,8 +250,8 @@ describe('Collector', () => { expect(parameter).toEqual({ __symbolic: 'error', message: 'Reference to non-exported class', - line: 1, - character: 45, + line: 3, + character: 4, context: {className: 'Foo'} }); }); @@ -506,7 +509,7 @@ describe('Collector', () => { __symbolic: 'error', message: 'Reference to a local symbol', line: 3, - character: 9, + character: 8, context: {name: 'REQUIRED'} }, SomeComponent: { @@ -519,6 +522,65 @@ describe('Collector', () => { } }); }); + + it('should collect an error symbol if collecting a reference to a non-exported function', () => { + let source = program.getSourceFile('/local-function-ref.ts'); + let metadata = collector.getMetadata(source); + expect(metadata.metadata).toEqual({ + REQUIRED_VALIDATOR: { + __symbolic: 'error', + message: 'Reference to a non-exported function', + line: 3, + character: 13, + context: {name: 'required'} + }, + SomeComponent: { + __symbolic: 'class', + decorators: [{ + __symbolic: 'call', + expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'}, + arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}] + }] + } + }) + }); + + it('should collect an error for a simple function that references a local variable', () => { + let source = program.getSourceFile('/local-symbol-ref-func.ts'); + let metadata = collector.getMetadata(source); + expect(metadata.metadata).toEqual({ + foo: { + __symbolic: 'function', + parameters: ['index'], + value: { + __symbolic: 'error', + message: 'Reference to a local symbol', + line: 1, + character: 8, + context: {name: 'localSymbol'} + } + } + }) + }); + + describe('in strict mode', () => { + it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => { + let source = program.getSourceFile('/local-symbol-ref.ts'); + expect(() => collector.getMetadata(source, true)).toThrowError(/Reference to a local symbol/); + }); + + it('should throw if an error if collecting a reference to a non-exported function', () => { + let source = program.getSourceFile('/local-function-ref.ts'); + expect(() => collector.getMetadata(source, true)) + .toThrowError(/Reference to a non-exported function/); + }); + + it('should throw for references to unexpected types', () => { + let unsupported1 = program.getSourceFile('/unsupported-2.ts'); + expect(() => collector.getMetadata(unsupported1, true)) + .toThrowError(/Reference to non-exported class/); + }); + }) }); // TODO: Do not use \` in a template literal as it confuses clang-format @@ -835,7 +897,7 @@ const FILES: Directory = { 'local-symbol-ref.ts': ` import {Component, Validators} from 'angular2/core'; - const REQUIRED = Validators.required; + var REQUIRED; export const REQUIRED_VALIDATOR: any = { provide: 'SomeToken', @@ -852,6 +914,29 @@ const FILES: Directory = { export enum PublicEnum { a, b, c } enum PrivateEnum { e, f, g } `, + 'local-function-ref.ts': ` + import {Component, Validators} from 'angular2/core'; + + function required() {} + + export const REQUIRED_VALIDATOR: any = { + provide: 'SomeToken', + useValue: required, + multi: true + }; + + @Component({ + providers: [REQUIRED_VALIDATOR] + }) + export class SomeComponent {} + `, + 'local-symbol-ref-func.ts': ` + var localSymbol: any[]; + + export function foo(index: number): string { + return localSymbol[index]; + } + `, 'node_modules': { 'angular2': { 'core.d.ts': ` diff --git a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts index 6798fe4dbc..48018eaf54 100644 --- a/tools/@angular/tsc-wrapped/test/evaluator.spec.ts +++ b/tools/@angular/tsc-wrapped/test/evaluator.spec.ts @@ -24,7 +24,7 @@ describe('Evaluator', () => { program = service.getProgram(); typeChecker = program.getTypeChecker(); symbols = new Symbols(null); - evaluator = new Evaluator(symbols); + evaluator = new Evaluator(symbols, new Map()); }); it('should not have typescript errors in test data', () => { @@ -138,7 +138,7 @@ describe('Evaluator', () => { it('should return new expressions', () => { symbols.define('Value', {__symbolic: 'reference', module: './classes', name: 'Value'}); - evaluator = new Evaluator(symbols); + evaluator = new Evaluator(symbols, new Map()); const newExpression = program.getSourceFile('newExpression.ts'); expect(evaluator.evaluateNode(findVar(newExpression, 'someValue').initializer)).toEqual({ __symbolic: 'new', @@ -167,13 +167,13 @@ describe('Evaluator', () => { const fDecl = findVar(errors, 'f'); expect(evaluator.evaluateNode(fDecl.initializer)) .toEqual( - {__symbolic: 'error', message: 'Function call not supported', line: 1, character: 11}); + {__symbolic: 'error', message: 'Function call not supported', line: 1, character: 12}); const eDecl = findVar(errors, 'e'); expect(evaluator.evaluateNode(eDecl.type)).toEqual({ __symbolic: 'error', message: 'Could not resolve type', line: 2, - character: 10, + character: 11, context: {typeName: 'NotFound'} }); const sDecl = findVar(errors, 's'); @@ -181,7 +181,7 @@ describe('Evaluator', () => { __symbolic: 'error', message: 'Name expected', line: 3, - character: 13, + character: 14, context: {received: '1'} }); const tDecl = findVar(errors, 't'); @@ -189,7 +189,7 @@ describe('Evaluator', () => { __symbolic: 'error', message: 'Expression form not supported', line: 4, - character: 11 + character: 12 }); });