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:
parent
45e8e73670
commit
39a2c39cef
|
@ -19,5 +19,8 @@
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,5 +20,8 @@
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -572,13 +572,13 @@ function expandedMessage(error: any): string {
|
||||||
switch (error.message) {
|
switch (error.message) {
|
||||||
case 'Reference to non-exported class':
|
case 'Reference to non-exported class':
|
||||||
if (error.context && error.context.className) {
|
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;
|
break;
|
||||||
case 'Variable not initialized':
|
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':
|
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':
|
case 'Could not resolve type':
|
||||||
if (error.context && error.context.typeName) {
|
if (error.context && error.context.typeName) {
|
||||||
return `Could not resolve type ${error.context.typeName}`;
|
return `Could not resolve type ${error.context.typeName}`;
|
||||||
|
|
|
@ -38,6 +38,7 @@ describe('reflector_host', () => {
|
||||||
genDir: '/tmp/project/src/gen/',
|
genDir: '/tmp/project/src/gen/',
|
||||||
basePath: '/tmp/project/src',
|
basePath: '/tmp/project/src',
|
||||||
skipMetadataEmit: false,
|
skipMetadataEmit: false,
|
||||||
|
strictMetadataEmit: false,
|
||||||
skipTemplateCodegen: false,
|
skipTemplateCodegen: false,
|
||||||
trace: false
|
trace: false
|
||||||
},
|
},
|
||||||
|
@ -47,6 +48,7 @@ describe('reflector_host', () => {
|
||||||
genDir: '/tmp/project/gen',
|
genDir: '/tmp/project/gen',
|
||||||
basePath: '/tmp/project/src/',
|
basePath: '/tmp/project/src/',
|
||||||
skipMetadataEmit: false,
|
skipMetadataEmit: false,
|
||||||
|
strictMetadataEmit: false,
|
||||||
skipTemplateCodegen: false,
|
skipTemplateCodegen: false,
|
||||||
trace: false
|
trace: false
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,5 +20,8 @@
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts",
|
"../../../node_modules/zone.js/dist/zone.js.d.ts",
|
||||||
"../../system.d.ts"
|
"../../system.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,8 @@
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts",
|
"../../../node_modules/zone.js/dist/zone.js.d.ts",
|
||||||
"../../system.d.ts"
|
"../../system.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,8 @@
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts",
|
"testing.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {NgProbeToken} from './src/dom/debug/ng_probe';
|
||||||
export * from './src/worker_render';
|
export * from './src/worker_render';
|
||||||
export * from './src/worker_app';
|
export {platformWorkerApp, WorkerAppModule} from './src/worker_app';
|
||||||
export * from './private_export';
|
export * from './private_export';
|
|
@ -21,7 +21,12 @@ import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_w
|
||||||
import {WebWorkerRootRenderer} from './web_workers/worker/renderer';
|
import {WebWorkerRootRenderer} from './web_workers/worker/renderer';
|
||||||
import {WorkerDomAdapter} from './web_workers/worker/worker_adapter';
|
import {WorkerDomAdapter} from './web_workers/worker/worker_adapter';
|
||||||
|
|
||||||
class PrintLogger {
|
/**
|
||||||
|
* Logger for web workers.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export class PrintLogger {
|
||||||
log = print;
|
log = print;
|
||||||
logError = print;
|
logError = print;
|
||||||
logGroup = print;
|
logGroup = print;
|
||||||
|
@ -33,7 +38,12 @@ class PrintLogger {
|
||||||
*/
|
*/
|
||||||
export const platformWorkerApp = createPlatformFactory(platformCore, 'workerApp');
|
export const platformWorkerApp = createPlatformFactory(platformCore, 'workerApp');
|
||||||
|
|
||||||
function _exceptionHandler(): ExceptionHandler {
|
/**
|
||||||
|
* Exception handler factory function.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export function exceptionHandler(): ExceptionHandler {
|
||||||
return new ExceptionHandler(new PrintLogger());
|
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 sink = new PostMessageBusSink(_postMessage);
|
||||||
let source = new PostMessageBusSource();
|
let source = new PostMessageBusSource();
|
||||||
let bus = new PostMessageBus(sink, source);
|
let bus = new PostMessageBus(sink, source);
|
||||||
|
@ -52,7 +67,12 @@ function createMessageBus(zone: NgZone): MessageBus {
|
||||||
return bus;
|
return bus;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupWebWorker(): void {
|
/**
|
||||||
|
* Application initializer for web workers.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export function setupWebWorker(): void {
|
||||||
WorkerDomAdapter.makeCurrent();
|
WorkerDomAdapter.makeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +88,7 @@ function setupWebWorker(): void {
|
||||||
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
||||||
WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer},
|
WebWorkerRootRenderer, {provide: RootRenderer, useExisting: WebWorkerRootRenderer},
|
||||||
{provide: ON_WEB_WORKER, useValue: true}, RenderStore,
|
{provide: ON_WEB_WORKER, useValue: true}, RenderStore,
|
||||||
{provide: ExceptionHandler, useFactory: _exceptionHandler, deps: []},
|
{provide: ExceptionHandler, useFactory: exceptionHandler, deps: []},
|
||||||
{provide: MessageBus, useFactory: createMessageBus, deps: [NgZone]},
|
{provide: MessageBus, useFactory: createMessageBus, deps: [NgZone]},
|
||||||
{provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true}
|
{provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true}
|
||||||
],
|
],
|
||||||
|
|
|
@ -13,17 +13,13 @@ import {BrowserDomAdapter} from '../src/browser/browser_adapter';
|
||||||
import {AnimationDriver} from '../src/dom/animation_driver';
|
import {AnimationDriver} from '../src/dom/animation_driver';
|
||||||
import {ELEMENT_PROBE_PROVIDERS} from '../src/dom/debug/ng_probe';
|
import {ELEMENT_PROBE_PROVIDERS} from '../src/dom/debug/ng_probe';
|
||||||
|
|
||||||
import {BrowserDetection} from './browser_util';
|
import {BrowserDetection, createNgZone} from './browser_util';
|
||||||
|
|
||||||
function initBrowserTests() {
|
function initBrowserTests() {
|
||||||
BrowserDomAdapter.makeCurrent();
|
BrowserDomAdapter.makeCurrent();
|
||||||
BrowserDetection.setup();
|
BrowserDetection.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNgZone(): NgZone {
|
|
||||||
return new NgZone({enableLongStackTrace: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
const _TEST_BROWSER_PLATFORM_PROVIDERS: Provider[] =
|
const _TEST_BROWSER_PLATFORM_PROVIDERS: Provider[] =
|
||||||
[{provide: PLATFORM_INITIALIZER, useValue: initBrowserTests, multi: true}];
|
[{provide: PLATFORM_INITIALIZER, useValue: initBrowserTests, multi: true}];
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {NgZone} from '@angular/core';
|
||||||
import {getDOM} from '../src/dom/dom_adapter';
|
import {getDOM} from '../src/dom/dom_adapter';
|
||||||
import {ListWrapper} from '../src/facade/collection';
|
import {ListWrapper} from '../src/facade/collection';
|
||||||
import {RegExp, StringWrapper, global, isPresent, isString} from '../src/facade/lang';
|
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 var browserDetection: BrowserDetection = new BrowserDetection(null);
|
||||||
|
|
||||||
|
export function createNgZone(): NgZone {
|
||||||
|
return new NgZone({enableLongStackTrace: true});
|
||||||
|
}
|
||||||
|
|
|
@ -29,5 +29,8 @@
|
||||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||||
"../../../node_modules/@types/protractor/index.d.ts",
|
"../../../node_modules/@types/protractor/index.d.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,5 +30,8 @@
|
||||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||||
"../../../node_modules/@types/protractor/index.d.ts",
|
"../../../node_modules/@types/protractor/index.d.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,5 +30,8 @@
|
||||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||||
"../../../node_modules/@types/node/index.d.ts",
|
"../../../node_modules/@types/node/index.d.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,5 +31,8 @@
|
||||||
"../../../node_modules/@types/jasmine/index.d.ts",
|
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||||
"../../../node_modules/@types/node/index.d.ts",
|
"../../../node_modules/@types/node/index.d.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location,
|
||||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) {
|
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) {
|
||||||
return new Router(
|
return new Router(
|
||||||
|
|
|
@ -24,5 +24,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts"
|
"testing.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -24,7 +24,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"testing.ts"
|
"testing.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"strictMetadataEmit": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ export class GreetingService {
|
||||||
// Directives are light-weight. They don't allow new
|
// Directives are light-weight. They don't allow new
|
||||||
// expression contexts (use @Component for those needs).
|
// expression contexts (use @Component for those needs).
|
||||||
@Directive({selector: '[red]'})
|
@Directive({selector: '[red]'})
|
||||||
class RedDec {
|
export class RedDec {
|
||||||
// ElementRef is always injectable and it wraps the element on which the
|
// ElementRef is always injectable and it wraps the element on which the
|
||||||
// directive was found by the compiler.
|
// directive was found by the compiler.
|
||||||
constructor(el: ElementRef, renderer: Renderer) {
|
constructor(el: ElementRef, renderer: Renderer) {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Evaluator, errorSymbol, isPrimitive} from './evaluator';
|
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';
|
import {Symbols} from './symbols';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect decorator metadata from a TypeScript module.
|
* 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
|
* 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.
|
* 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 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 metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
|
||||||
let exports: ModuleExportMetadata[];
|
let exports: ModuleExportMetadata[];
|
||||||
|
|
||||||
|
@ -26,6 +26,11 @@ export class MetadataCollector {
|
||||||
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
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(
|
function errorSym(
|
||||||
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
|
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
|
||||||
return errorSymbol(message, node, context, sourceFile);
|
return errorSymbol(message, node, context, sourceFile);
|
||||||
|
@ -53,7 +58,7 @@ export class MetadataCollector {
|
||||||
func.defaults = functionDeclaration.parameters.map(
|
func.defaults = functionDeclaration.parameters.map(
|
||||||
p => p.initializer && evaluator.evaluateNode(p.initializer));
|
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;
|
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 => {
|
ts.forEachChild(sourceFile, node => {
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.ClassDeclaration:
|
case ts.SyntaxKind.ClassDeclaration:
|
||||||
|
@ -199,6 +205,16 @@ export class MetadataCollector {
|
||||||
className, errorSym('Reference to non-exported class', node, {className}));
|
className, errorSym('Reference to non-exported class', node, {className}));
|
||||||
}
|
}
|
||||||
break;
|
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 => {
|
ts.forEachChild(sourceFile, node => {
|
||||||
|
@ -236,15 +252,14 @@ export class MetadataCollector {
|
||||||
case ts.SyntaxKind.FunctionDeclaration:
|
case ts.SyntaxKind.FunctionDeclaration:
|
||||||
// Record functions that return a single value. Record the parameter
|
// Record functions that return a single value. Record the parameter
|
||||||
// names substitution will be performed by the StaticReflector.
|
// names substitution will be performed by the StaticReflector.
|
||||||
if (node.flags & ts.NodeFlags.Export) {
|
|
||||||
const functionDeclaration = <ts.FunctionDeclaration>node;
|
const functionDeclaration = <ts.FunctionDeclaration>node;
|
||||||
|
if (node.flags & ts.NodeFlags.Export) {
|
||||||
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
|
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
|
||||||
if (maybeFunc) {
|
if (maybeFunc) {
|
||||||
if (!metadata) metadata = {};
|
if (!metadata) metadata = {};
|
||||||
metadata[maybeFunc.name] = maybeFunc.func;
|
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise don't record the function.
|
|
||||||
break;
|
break;
|
||||||
case ts.SyntaxKind.EnumDeclaration:
|
case ts.SyntaxKind.EnumDeclaration:
|
||||||
if (node.flags & ts.NodeFlags.Export) {
|
if (node.flags & ts.NodeFlags.Export) {
|
||||||
|
@ -275,16 +290,17 @@ export class MetadataCollector {
|
||||||
operator: '+',
|
operator: '+',
|
||||||
left: {
|
left: {
|
||||||
__symbolic: 'select',
|
__symbolic: 'select',
|
||||||
expression: {__symbolic: 'reference', name: enumName}, name
|
expression: recordEntry({__symbolic: 'reference', name: enumName}, node), name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nextDefaultValue = errorSym('Unsuppported enum member name', member.name);
|
nextDefaultValue =
|
||||||
|
recordEntry(errorSym('Unsuppported enum member name', member.name), node);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (writtenMembers) {
|
if (writtenMembers) {
|
||||||
if (!metadata) metadata = {};
|
if (!metadata) metadata = {};
|
||||||
metadata[enumName] = enumValueHolder;
|
metadata[enumName] = recordEntry(enumValueHolder, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -297,21 +313,27 @@ export class MetadataCollector {
|
||||||
if (variableDeclaration.initializer) {
|
if (variableDeclaration.initializer) {
|
||||||
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
|
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
|
||||||
} else {
|
} else {
|
||||||
varValue = errorSym('Variable not initialized', nameNode);
|
varValue = recordEntry(errorSym('Variable not initialized', nameNode), nameNode);
|
||||||
}
|
}
|
||||||
let exported = false;
|
let exported = false;
|
||||||
if (variableStatement.flags & ts.NodeFlags.Export ||
|
if (variableStatement.flags & ts.NodeFlags.Export ||
|
||||||
variableDeclaration.flags & ts.NodeFlags.Export) {
|
variableDeclaration.flags & ts.NodeFlags.Export) {
|
||||||
if (!metadata) metadata = {};
|
if (!metadata) metadata = {};
|
||||||
metadata[nameNode.text] = varValue;
|
metadata[nameNode.text] = recordEntry(varValue, node);
|
||||||
exported = true;
|
exported = true;
|
||||||
}
|
}
|
||||||
if (isPrimitive(varValue)) {
|
if (isPrimitive(varValue)) {
|
||||||
locals.define(nameNode.text, varValue);
|
locals.define(nameNode.text, varValue);
|
||||||
} else if (!exported) {
|
} else if (!exported) {
|
||||||
|
if (varValue && !isMetadataError(varValue)) {
|
||||||
|
locals.define(nameNode.text, recordEntry(varValue, node));
|
||||||
|
} else {
|
||||||
locals.define(
|
locals.define(
|
||||||
nameNode.text,
|
nameNode.text,
|
||||||
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}));
|
recordEntry(
|
||||||
|
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}),
|
||||||
|
node));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Destructuring (or binding) declarations are not supported,
|
// Destructuring (or binding) declarations are not supported,
|
||||||
|
@ -349,7 +371,11 @@ export class MetadataCollector {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (metadata || exports) {
|
if (metadata || exports) {
|
||||||
if (!metadata) metadata = {};
|
if (!metadata)
|
||||||
|
metadata = {};
|
||||||
|
else if (strict) {
|
||||||
|
validateMetadata(sourceFile, nodeMap, metadata);
|
||||||
|
}
|
||||||
const result: ModuleMetadata = {__symbolic: 'module', version: VERSION, metadata};
|
const result: ModuleMetadata = {__symbolic: 'module', version: VERSION, metadata};
|
||||||
if (exports) result.exports = exports;
|
if (exports) result.exports = exports;
|
||||||
return result;
|
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.
|
// Collect parameter names from a function.
|
||||||
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
|
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
|
||||||
let result: string[] = [];
|
let result: string[] = [];
|
||||||
|
@ -379,3 +548,32 @@ function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
|
||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {writeFileSync} from 'fs';
|
||||||
import {convertDecorators} from 'tsickle';
|
import {convertDecorators} from 'tsickle';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import NgOptions from './options';
|
||||||
import {MetadataCollector} from './collector';
|
import {MetadataCollector} from './collector';
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,14 +67,18 @@ const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
||||||
|
|
||||||
export class MetadataWriterHost extends DelegatingHost {
|
export class MetadataWriterHost extends DelegatingHost {
|
||||||
private metadataCollector = new MetadataCollector();
|
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) {
|
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||||
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||||
// released
|
// released
|
||||||
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
|
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
|
||||||
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
|
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) {
|
if (metadata && metadata.metadata) {
|
||||||
const metadataText = JSON.stringify(metadata);
|
const metadataText = JSON.stringify(metadata);
|
||||||
writeFileSync(path, metadataText, {encoding: 'utf-8'});
|
writeFileSync(path, metadataText, {encoding: 'utf-8'});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as ts from 'typescript';
|
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';
|
import {Symbols} from './symbols';
|
||||||
|
|
||||||
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
|
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
|
||||||
|
@ -70,7 +70,8 @@ export function errorSymbol(
|
||||||
if (node) {
|
if (node) {
|
||||||
sourceFile = sourceFile || getSourceFileOfNode(node);
|
sourceFile = sourceFile || getSourceFileOfNode(node);
|
||||||
if (sourceFile) {
|
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};
|
result = {__symbolic: 'error', message, line, character};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ export function errorSymbol(
|
||||||
* possible.
|
* possible.
|
||||||
*/
|
*/
|
||||||
export class Evaluator {
|
export class Evaluator {
|
||||||
constructor(private symbols: Symbols) {}
|
constructor(private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>) {}
|
||||||
|
|
||||||
nameOf(node: ts.Node): string|MetadataError {
|
nameOf(node: ts.Node): string|MetadataError {
|
||||||
if (node.kind == ts.SyntaxKind.Identifier) {
|
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`.
|
* tree are folded. For example, a node representing `1 + 2` is folded into `3`.
|
||||||
*/
|
*/
|
||||||
public evaluateNode(node: ts.Node): MetadataValue {
|
public evaluateNode(node: ts.Node): MetadataValue {
|
||||||
|
const t = this;
|
||||||
let error: MetadataError|undefined;
|
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) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.ObjectLiteralExpression:
|
case ts.SyntaxKind.ObjectLiteralExpression:
|
||||||
let obj: {[name: string]: any} = {};
|
let obj: {[name: string]: any} = {};
|
||||||
|
@ -258,14 +266,14 @@ export class Evaluator {
|
||||||
case ts.SyntaxKind.SpreadElementExpression:
|
case ts.SyntaxKind.SpreadElementExpression:
|
||||||
let spread = <ts.SpreadElementExpression>node;
|
let spread = <ts.SpreadElementExpression>node;
|
||||||
let spreadExpression = this.evaluateNode(spread.expression);
|
let spreadExpression = this.evaluateNode(spread.expression);
|
||||||
return {__symbolic: 'spread', expression: spreadExpression};
|
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
|
||||||
case ts.SyntaxKind.CallExpression:
|
case ts.SyntaxKind.CallExpression:
|
||||||
const callExpression = <ts.CallExpression>node;
|
const callExpression = <ts.CallExpression>node;
|
||||||
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
|
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
|
||||||
const firstArgument = callExpression.arguments[0];
|
const firstArgument = callExpression.arguments[0];
|
||||||
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
|
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
|
||||||
const arrowFunction = <ts.ArrowFunction>firstArgument;
|
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));
|
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.
|
// Always fold a CONST_EXPR even if the argument is not foldable.
|
||||||
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) {
|
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) {
|
||||||
return args[0];
|
return recordEntry(args[0], node);
|
||||||
}
|
}
|
||||||
const expression = this.evaluateNode(callExpression.expression);
|
const expression = this.evaluateNode(callExpression.expression);
|
||||||
if (isMetadataError(expression)) {
|
if (isMetadataError(expression)) {
|
||||||
return expression;
|
return recordEntry(expression, node);
|
||||||
}
|
}
|
||||||
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
|
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
|
||||||
if (args && args.length) {
|
if (args && args.length) {
|
||||||
result.arguments = args;
|
result.arguments = args;
|
||||||
}
|
}
|
||||||
return result;
|
return recordEntry(result, node);
|
||||||
case ts.SyntaxKind.NewExpression:
|
case ts.SyntaxKind.NewExpression:
|
||||||
const newExpression = <ts.NewExpression>node;
|
const newExpression = <ts.NewExpression>node;
|
||||||
const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg));
|
const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg));
|
||||||
if (newArgs.some(isMetadataError)) {
|
if (newArgs.some(isMetadataError)) {
|
||||||
return newArgs.find(isMetadataError);
|
return recordEntry(newArgs.find(isMetadataError), node);
|
||||||
}
|
}
|
||||||
const newTarget = this.evaluateNode(newExpression.expression);
|
const newTarget = this.evaluateNode(newExpression.expression);
|
||||||
if (isMetadataError(newTarget)) {
|
if (isMetadataError(newTarget)) {
|
||||||
return newTarget;
|
return recordEntry(newTarget, node);
|
||||||
}
|
}
|
||||||
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
|
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
|
||||||
if (newArgs.length) {
|
if (newArgs.length) {
|
||||||
call.arguments = newArgs;
|
call.arguments = newArgs;
|
||||||
}
|
}
|
||||||
return call;
|
return recordEntry(call, node);
|
||||||
case ts.SyntaxKind.PropertyAccessExpression: {
|
case ts.SyntaxKind.PropertyAccessExpression: {
|
||||||
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
|
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
|
||||||
const expression = this.evaluateNode(propertyAccessExpression.expression);
|
const expression = this.evaluateNode(propertyAccessExpression.expression);
|
||||||
if (isMetadataError(expression)) {
|
if (isMetadataError(expression)) {
|
||||||
return expression;
|
return recordEntry(expression, node);
|
||||||
}
|
}
|
||||||
const member = this.nameOf(propertyAccessExpression.name);
|
const member = this.nameOf(propertyAccessExpression.name);
|
||||||
if (isMetadataError(member)) {
|
if (isMetadataError(member)) {
|
||||||
return member;
|
return recordEntry(member, node);
|
||||||
}
|
}
|
||||||
if (expression && this.isFoldable(propertyAccessExpression.expression))
|
if (expression && this.isFoldable(propertyAccessExpression.expression))
|
||||||
return (<any>expression)[<string>member];
|
return (<any>expression)[<string>member];
|
||||||
if (isMetadataModuleReferenceExpression(expression)) {
|
if (isMetadataModuleReferenceExpression(expression)) {
|
||||||
// A select into a module refrence and be converted into a reference to the symbol
|
// A select into a module refrence and be converted into a reference to the symbol
|
||||||
// in the module
|
// 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: {
|
case ts.SyntaxKind.ElementAccessExpression: {
|
||||||
const elementAccessExpression = <ts.ElementAccessExpression>node;
|
const elementAccessExpression = <ts.ElementAccessExpression>node;
|
||||||
const expression = this.evaluateNode(elementAccessExpression.expression);
|
const expression = this.evaluateNode(elementAccessExpression.expression);
|
||||||
if (isMetadataError(expression)) {
|
if (isMetadataError(expression)) {
|
||||||
return expression;
|
return recordEntry(expression, node);
|
||||||
}
|
}
|
||||||
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
|
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
|
||||||
if (isMetadataError(expression)) {
|
if (isMetadataError(expression)) {
|
||||||
return expression;
|
return recordEntry(expression, node);
|
||||||
}
|
}
|
||||||
if (this.isFoldable(elementAccessExpression.expression) &&
|
if (this.isFoldable(elementAccessExpression.expression) &&
|
||||||
this.isFoldable(elementAccessExpression.argumentExpression))
|
this.isFoldable(elementAccessExpression.argumentExpression))
|
||||||
return (<any>expression)[<string|number>index];
|
return (<any>expression)[<string|number>index];
|
||||||
return {__symbolic: 'index', expression, index};
|
return recordEntry({__symbolic: 'index', expression, index}, node);
|
||||||
}
|
}
|
||||||
case ts.SyntaxKind.Identifier:
|
case ts.SyntaxKind.Identifier:
|
||||||
const identifier = <ts.Identifier>node;
|
const identifier = <ts.Identifier>node;
|
||||||
|
@ -348,7 +357,7 @@ export class Evaluator {
|
||||||
const reference = this.symbols.resolve(name);
|
const reference = this.symbols.resolve(name);
|
||||||
if (reference === undefined) {
|
if (reference === undefined) {
|
||||||
// Encode as a global reference. StaticReflector will check the reference.
|
// Encode as a global reference. StaticReflector will check the reference.
|
||||||
return { __symbolic: 'reference', name }
|
return recordEntry({__symbolic: 'reference', name}, node);
|
||||||
}
|
}
|
||||||
return reference;
|
return reference;
|
||||||
case ts.SyntaxKind.TypeReference:
|
case ts.SyntaxKind.TypeReference:
|
||||||
|
@ -360,9 +369,13 @@ export class Evaluator {
|
||||||
const qualifiedName = <ts.QualifiedName>node;
|
const qualifiedName = <ts.QualifiedName>node;
|
||||||
const left = this.evaluateNode(qualifiedName.left);
|
const left = this.evaluateNode(qualifiedName.left);
|
||||||
if (isMetadataModuleReferenceExpression(left)) {
|
if (isMetadataModuleReferenceExpression(left)) {
|
||||||
return <MetadataImportedSymbolReferenceExpression> {
|
return recordEntry(
|
||||||
__symbolic: 'reference', module: left.module, name: qualifiedName.right.text
|
<MetadataImportedSymbolReferenceExpression>{
|
||||||
}
|
__symbolic: 'reference',
|
||||||
|
module: left.module,
|
||||||
|
name: qualifiedName.right.text
|
||||||
|
},
|
||||||
|
node)
|
||||||
}
|
}
|
||||||
// Record a type reference to a declared type as a select.
|
// Record a type reference to a declared type as a select.
|
||||||
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
|
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
|
||||||
|
@ -370,14 +383,15 @@ export class Evaluator {
|
||||||
const identifier = <ts.Identifier>typeNameNode;
|
const identifier = <ts.Identifier>typeNameNode;
|
||||||
let symbol = this.symbols.resolve(identifier.text);
|
let symbol = this.symbols.resolve(identifier.text);
|
||||||
if (isMetadataError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
|
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);
|
const typeReference = getReference(typeNameNode);
|
||||||
if (isMetadataError(typeReference)) {
|
if (isMetadataError(typeReference)) {
|
||||||
return typeReference;
|
return recordEntry(typeReference, node);
|
||||||
}
|
}
|
||||||
if (!isMetadataModuleReferenceExpression(typeReference) &&
|
if (!isMetadataModuleReferenceExpression(typeReference) &&
|
||||||
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
|
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
|
||||||
|
@ -386,7 +400,7 @@ export class Evaluator {
|
||||||
// Some versions of 1.9 do not infer this correctly.
|
// Some versions of 1.9 do not infer this correctly.
|
||||||
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
|
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
|
||||||
}
|
}
|
||||||
return typeReference;
|
return recordEntry(typeReference, node);
|
||||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||||
return (<ts.LiteralExpression>node).text;
|
return (<ts.LiteralExpression>node).text;
|
||||||
case ts.SyntaxKind.StringLiteral:
|
case ts.SyntaxKind.StringLiteral:
|
||||||
|
@ -394,20 +408,22 @@ export class Evaluator {
|
||||||
case ts.SyntaxKind.NumericLiteral:
|
case ts.SyntaxKind.NumericLiteral:
|
||||||
return parseFloat((<ts.LiteralExpression>node).text);
|
return parseFloat((<ts.LiteralExpression>node).text);
|
||||||
case ts.SyntaxKind.AnyKeyword:
|
case ts.SyntaxKind.AnyKeyword:
|
||||||
return {__symbolic: 'reference', name: 'any'};
|
return recordEntry({__symbolic: 'reference', name: 'any'}, node);
|
||||||
case ts.SyntaxKind.StringKeyword:
|
case ts.SyntaxKind.StringKeyword:
|
||||||
return {__symbolic: 'reference', name: 'string'};
|
return recordEntry({__symbolic: 'reference', name: 'string'}, node);
|
||||||
case ts.SyntaxKind.NumberKeyword:
|
case ts.SyntaxKind.NumberKeyword:
|
||||||
return {__symbolic: 'reference', name: 'number'};
|
return recordEntry({__symbolic: 'reference', name: 'number'}, node);
|
||||||
case ts.SyntaxKind.BooleanKeyword:
|
case ts.SyntaxKind.BooleanKeyword:
|
||||||
return {__symbolic: 'reference', name: 'boolean'};
|
return recordEntry({__symbolic: 'reference', name: 'boolean'}, node);
|
||||||
case ts.SyntaxKind.ArrayType:
|
case ts.SyntaxKind.ArrayType:
|
||||||
const arrayTypeNode = <ts.ArrayTypeNode>node;
|
const arrayTypeNode = <ts.ArrayTypeNode>node;
|
||||||
return {
|
return recordEntry(
|
||||||
|
{
|
||||||
__symbolic: 'reference',
|
__symbolic: 'reference',
|
||||||
name: 'Array',
|
name: 'Array',
|
||||||
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
|
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
|
||||||
};
|
},
|
||||||
|
node);
|
||||||
case ts.SyntaxKind.NullKeyword:
|
case ts.SyntaxKind.NullKeyword:
|
||||||
return null;
|
return null;
|
||||||
case ts.SyntaxKind.TrueKeyword:
|
case ts.SyntaxKind.TrueKeyword:
|
||||||
|
@ -452,7 +468,7 @@ export class Evaluator {
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {__symbolic: 'pre', operator: operatorText, operand: operand};
|
return recordEntry({__symbolic: 'pre', operator: operatorText, operand: operand}, node);
|
||||||
case ts.SyntaxKind.BinaryExpression:
|
case ts.SyntaxKind.BinaryExpression:
|
||||||
const binaryExpression = <ts.BinaryExpression>node;
|
const binaryExpression = <ts.BinaryExpression>node;
|
||||||
const left = this.evaluateNode(binaryExpression.left);
|
const left = this.evaluateNode(binaryExpression.left);
|
||||||
|
@ -503,12 +519,14 @@ export class Evaluator {
|
||||||
case ts.SyntaxKind.PercentToken:
|
case ts.SyntaxKind.PercentToken:
|
||||||
return <any>left % <any>right;
|
return <any>left % <any>right;
|
||||||
}
|
}
|
||||||
return {
|
return recordEntry(
|
||||||
|
{
|
||||||
__symbolic: 'binop',
|
__symbolic: 'binop',
|
||||||
operator: binaryExpression.operatorToken.getText(),
|
operator: binaryExpression.operatorToken.getText(),
|
||||||
left: left,
|
left: left,
|
||||||
right: right
|
right: right
|
||||||
};
|
},
|
||||||
|
node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ts.SyntaxKind.ConditionalExpression:
|
case ts.SyntaxKind.ConditionalExpression:
|
||||||
|
@ -519,12 +537,12 @@ export class Evaluator {
|
||||||
if (isPrimitive(condition)) {
|
if (isPrimitive(condition)) {
|
||||||
return condition ? thenExpression : elseExpression;
|
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.FunctionExpression:
|
||||||
case ts.SyntaxKind.ArrowFunction:
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function main(
|
||||||
// decorators which we want to read or document.
|
// decorators which we want to read or document.
|
||||||
// Do this emit second since TypeScript will create missing directories for us
|
// Do this emit second since TypeScript will create missing directories for us
|
||||||
// in the standard emit.
|
// in the standard emit.
|
||||||
const metadataWriter = new MetadataWriterHost(host, newProgram);
|
const metadataWriter = new MetadataWriterHost(host, newProgram, ngOptions);
|
||||||
tsc.emit(metadataWriter, newProgram);
|
tsc.emit(metadataWriter, newProgram);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,9 @@ interface Options extends ts.CompilerOptions {
|
||||||
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
||||||
skipMetadataEmit: boolean;
|
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
|
// Don't produce .ngfactory.ts or .css.shim.ts files
|
||||||
skipTemplateCodegen: boolean;
|
skipTemplateCodegen: boolean;
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
|
|
||||||
export const VERSION = 1;
|
export const VERSION = 1;
|
||||||
|
|
||||||
|
export type MetadataEntry = ClassMetadata | FunctionMetadata | MetadataValue;
|
||||||
|
|
||||||
export interface ModuleMetadata {
|
export interface ModuleMetadata {
|
||||||
__symbolic: 'module';
|
__symbolic: 'module';
|
||||||
version: number;
|
version: number;
|
||||||
exports?: ModuleExportMetadata[];
|
exports?: ModuleExportMetadata[];
|
||||||
metadata: {[name: string]: (ClassMetadata | FunctionMetadata | MetadataValue)};
|
metadata: {[name: string]: MetadataEntry};
|
||||||
}
|
}
|
||||||
export function isModuleMetadata(value: any): value is ModuleMetadata {
|
export function isModuleMetadata(value: any): value is ModuleMetadata {
|
||||||
return value && value.__symbolic === 'module';
|
return value && value.__symbolic === 'module';
|
||||||
|
@ -56,7 +58,7 @@ export interface MethodMetadata extends MemberMetadata {
|
||||||
__symbolic: 'constructor'|'method';
|
__symbolic: 'constructor'|'method';
|
||||||
parameterDecorators?: (MetadataSymbolicExpression|MetadataError)[][];
|
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');
|
return value && (value.__symbolic === 'constructor' || value.__symbolic === 'method');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ describe('Collector', () => {
|
||||||
'exported-enum.ts',
|
'exported-enum.ts',
|
||||||
'exported-consts.ts',
|
'exported-consts.ts',
|
||||||
'local-symbol-ref.ts',
|
'local-symbol-ref.ts',
|
||||||
|
'local-function-ref.ts',
|
||||||
|
'local-symbol-ref-func.ts',
|
||||||
|
'local-symbol-ref-func-dynamic.ts',
|
||||||
'private-enum.ts',
|
'private-enum.ts',
|
||||||
're-exports.ts',
|
're-exports.ts',
|
||||||
'static-field-reference.ts',
|
'static-field-reference.ts',
|
||||||
|
@ -230,10 +233,10 @@ describe('Collector', () => {
|
||||||
version: 1,
|
version: 1,
|
||||||
metadata: {
|
metadata: {
|
||||||
a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16},
|
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},
|
c: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 16},
|
||||||
d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 18},
|
d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 19},
|
||||||
e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 14}
|
e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 15}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -247,8 +250,8 @@ describe('Collector', () => {
|
||||||
expect(parameter).toEqual({
|
expect(parameter).toEqual({
|
||||||
__symbolic: 'error',
|
__symbolic: 'error',
|
||||||
message: 'Reference to non-exported class',
|
message: 'Reference to non-exported class',
|
||||||
line: 1,
|
line: 3,
|
||||||
character: 45,
|
character: 4,
|
||||||
context: {className: 'Foo'}
|
context: {className: 'Foo'}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -506,7 +509,7 @@ describe('Collector', () => {
|
||||||
__symbolic: 'error',
|
__symbolic: 'error',
|
||||||
message: 'Reference to a local symbol',
|
message: 'Reference to a local symbol',
|
||||||
line: 3,
|
line: 3,
|
||||||
character: 9,
|
character: 8,
|
||||||
context: {name: 'REQUIRED'}
|
context: {name: 'REQUIRED'}
|
||||||
},
|
},
|
||||||
SomeComponent: {
|
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
|
// TODO: Do not use \` in a template literal as it confuses clang-format
|
||||||
|
@ -835,7 +897,7 @@ const FILES: Directory = {
|
||||||
'local-symbol-ref.ts': `
|
'local-symbol-ref.ts': `
|
||||||
import {Component, Validators} from 'angular2/core';
|
import {Component, Validators} from 'angular2/core';
|
||||||
|
|
||||||
const REQUIRED = Validators.required;
|
var REQUIRED;
|
||||||
|
|
||||||
export const REQUIRED_VALIDATOR: any = {
|
export const REQUIRED_VALIDATOR: any = {
|
||||||
provide: 'SomeToken',
|
provide: 'SomeToken',
|
||||||
|
@ -852,6 +914,29 @@ const FILES: Directory = {
|
||||||
export enum PublicEnum { a, b, c }
|
export enum PublicEnum { a, b, c }
|
||||||
enum PrivateEnum { e, f, g }
|
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': {
|
'node_modules': {
|
||||||
'angular2': {
|
'angular2': {
|
||||||
'core.d.ts': `
|
'core.d.ts': `
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe('Evaluator', () => {
|
||||||
program = service.getProgram();
|
program = service.getProgram();
|
||||||
typeChecker = program.getTypeChecker();
|
typeChecker = program.getTypeChecker();
|
||||||
symbols = new Symbols(null);
|
symbols = new Symbols(null);
|
||||||
evaluator = new Evaluator(symbols);
|
evaluator = new Evaluator(symbols, new Map());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have typescript errors in test data', () => {
|
it('should not have typescript errors in test data', () => {
|
||||||
|
@ -138,7 +138,7 @@ describe('Evaluator', () => {
|
||||||
|
|
||||||
it('should return new expressions', () => {
|
it('should return new expressions', () => {
|
||||||
symbols.define('Value', {__symbolic: 'reference', module: './classes', name: 'Value'});
|
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');
|
const newExpression = program.getSourceFile('newExpression.ts');
|
||||||
expect(evaluator.evaluateNode(findVar(newExpression, 'someValue').initializer)).toEqual({
|
expect(evaluator.evaluateNode(findVar(newExpression, 'someValue').initializer)).toEqual({
|
||||||
__symbolic: 'new',
|
__symbolic: 'new',
|
||||||
|
@ -167,13 +167,13 @@ describe('Evaluator', () => {
|
||||||
const fDecl = findVar(errors, 'f');
|
const fDecl = findVar(errors, 'f');
|
||||||
expect(evaluator.evaluateNode(fDecl.initializer))
|
expect(evaluator.evaluateNode(fDecl.initializer))
|
||||||
.toEqual(
|
.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');
|
const eDecl = findVar(errors, 'e');
|
||||||
expect(evaluator.evaluateNode(eDecl.type)).toEqual({
|
expect(evaluator.evaluateNode(eDecl.type)).toEqual({
|
||||||
__symbolic: 'error',
|
__symbolic: 'error',
|
||||||
message: 'Could not resolve type',
|
message: 'Could not resolve type',
|
||||||
line: 2,
|
line: 2,
|
||||||
character: 10,
|
character: 11,
|
||||||
context: {typeName: 'NotFound'}
|
context: {typeName: 'NotFound'}
|
||||||
});
|
});
|
||||||
const sDecl = findVar(errors, 's');
|
const sDecl = findVar(errors, 's');
|
||||||
|
@ -181,7 +181,7 @@ describe('Evaluator', () => {
|
||||||
__symbolic: 'error',
|
__symbolic: 'error',
|
||||||
message: 'Name expected',
|
message: 'Name expected',
|
||||||
line: 3,
|
line: 3,
|
||||||
character: 13,
|
character: 14,
|
||||||
context: {received: '1'}
|
context: {received: '1'}
|
||||||
});
|
});
|
||||||
const tDecl = findVar(errors, 't');
|
const tDecl = findVar(errors, 't');
|
||||||
|
@ -189,7 +189,7 @@ describe('Evaluator', () => {
|
||||||
__symbolic: 'error',
|
__symbolic: 'error',
|
||||||
message: 'Expression form not supported',
|
message: 'Expression form not supported',
|
||||||
line: 4,
|
line: 4,
|
||||||
character: 11
|
character: 12
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue