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.
330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {beforeEach, ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
|
|
import * as ts from 'typescript';
|
|
|
|
import {ReflectorHost} from '../src/reflector_host';
|
|
|
|
import {Directory, Entry, MockCompilerHost, MockContext} from './mocks';
|
|
|
|
describe('reflector_host', () => {
|
|
var context: MockContext;
|
|
var host: ts.CompilerHost;
|
|
var program: ts.Program;
|
|
var reflectorNestedGenDir: ReflectorHost;
|
|
var reflectorSiblingGenDir: ReflectorHost;
|
|
|
|
beforeEach(() => {
|
|
context = new MockContext('/tmp/src', clone(FILES));
|
|
host = new MockCompilerHost(context);
|
|
program = ts.createProgram(
|
|
['main.ts'], {
|
|
module: ts.ModuleKind.CommonJS,
|
|
},
|
|
host);
|
|
// Force a typecheck
|
|
let errors = program.getSemanticDiagnostics();
|
|
if (errors && errors.length) {
|
|
throw new Error('Expected no errors');
|
|
}
|
|
reflectorNestedGenDir = new ReflectorHost(
|
|
program, host, {
|
|
genDir: '/tmp/project/src/gen/',
|
|
basePath: '/tmp/project/src',
|
|
skipMetadataEmit: false,
|
|
strictMetadataEmit: false,
|
|
skipTemplateCodegen: false,
|
|
trace: false
|
|
},
|
|
context);
|
|
reflectorSiblingGenDir = new ReflectorHost(
|
|
program, host, {
|
|
genDir: '/tmp/project/gen',
|
|
basePath: '/tmp/project/src/',
|
|
skipMetadataEmit: false,
|
|
strictMetadataEmit: false,
|
|
skipTemplateCodegen: false,
|
|
trace: false
|
|
},
|
|
context);
|
|
});
|
|
|
|
describe('nestedGenDir', () => {
|
|
it('should import node_module from factory', () => {
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/gen/my.ngfactory.ts',
|
|
'/tmp/project/node_modules/@angular/core.d.ts'))
|
|
.toEqual('@angular/core');
|
|
});
|
|
|
|
it('should import factory from factory', () => {
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
|
.toEqual('./my.other.ngfactory');
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
|
.toEqual('../my.other.css');
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
|
.toEqual('./a/my.other.css.shim');
|
|
});
|
|
|
|
it('should import application from factory', () => {
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
.toEqual('../my.other');
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
.toEqual('../../my.other');
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
|
.toEqual('../a/my.other');
|
|
});
|
|
});
|
|
|
|
describe('nestedGenDir', () => {
|
|
it('should import node_module from factory', () => {
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/gen/my.ngfactory.ts',
|
|
'/tmp/project/node_modules/@angular/core.d.ts'))
|
|
.toEqual('@angular/core');
|
|
});
|
|
|
|
it('should import factory from factory', () => {
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
|
.toEqual('./my.other.ngfactory');
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
|
.toEqual('../my.other.css');
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
|
.toEqual('./a/my.other.css.shim');
|
|
});
|
|
|
|
it('should import application from factory', () => {
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
.toEqual('./my.other');
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
.toEqual('../my.other');
|
|
expect(reflectorSiblingGenDir.getImportPath(
|
|
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
|
.toEqual('./a/my.other');
|
|
});
|
|
});
|
|
|
|
it('should provide the import locations for angular', () => {
|
|
let {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
|
|
reflectorNestedGenDir.angularImportLocations();
|
|
expect(coreDecorators).toEqual('@angular/core/src/metadata');
|
|
expect(diDecorators).toEqual('@angular/core/src/di/decorators');
|
|
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
|
|
expect(animationMetadata).toEqual('@angular/core/src/animation/metadata');
|
|
expect(provider).toEqual('@angular/core/src/di/provider');
|
|
});
|
|
|
|
it('should be able to produce an import from main @angular/core', () => {
|
|
expect(reflectorNestedGenDir.getImportPath(
|
|
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
|
|
.toEqual('@angular/core');
|
|
});
|
|
|
|
it('should be able to produce an import from main to a sub-directory', () => {
|
|
expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
|
|
});
|
|
|
|
it('should be able to produce an import from to a peer file', () => {
|
|
expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts'))
|
|
.toEqual('./collections');
|
|
});
|
|
|
|
it('should be able to produce an import from to a sibling directory', () => {
|
|
expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts'))
|
|
.toEqual('../lib/utils');
|
|
});
|
|
|
|
it('should be able to produce a symbol for an exported symbol', () => {
|
|
expect(reflectorNestedGenDir.findDeclaration('@angular/router', 'foo', 'main.ts'))
|
|
.toBeDefined();
|
|
});
|
|
|
|
it('should be able to produce a symbol for values space only reference', () => {
|
|
expect(reflectorNestedGenDir.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
|
|
.toBeDefined();
|
|
});
|
|
|
|
|
|
it('should be produce the same symbol if asked twice', () => {
|
|
let foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
|
let foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
|
expect(foo1).toBe(foo2);
|
|
});
|
|
|
|
it('should be able to produce a symbol for a module with no file', () => {
|
|
expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
|
|
});
|
|
|
|
it('should be able to read a metadata file', () => {
|
|
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
|
|
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
|
|
});
|
|
|
|
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
|
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts'))
|
|
.toBeUndefined();
|
|
});
|
|
|
|
it('should be able to read empty metadata ', () => {
|
|
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts'))
|
|
.toBeUndefined();
|
|
});
|
|
|
|
it('should return undefined for missing modules', () => {
|
|
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts'))
|
|
.toBeUndefined();
|
|
});
|
|
|
|
it('should be able to trace a named export', () => {
|
|
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
'./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
|
|
expect(symbol.name).toEqual('One');
|
|
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
|
});
|
|
|
|
it('should be able to trace a renamed export', () => {
|
|
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
'./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
|
|
expect(symbol.name).toEqual('Three');
|
|
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
|
});
|
|
|
|
it('should be able to trace an export * export', () => {
|
|
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
'./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
|
|
expect(symbol.name).toEqual('Five');
|
|
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
|
|
});
|
|
|
|
it('should be able to trace a multi-level re-export', () => {
|
|
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
'./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
|
|
expect(symbol.name).toEqual('Thirty');
|
|
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
|
|
});
|
|
});
|
|
|
|
const dummyModule = 'export let foo: any[];';
|
|
|
|
const FILES: Entry = {
|
|
'tmp': {
|
|
'src': {
|
|
'main.ts': `
|
|
import * as c from '@angular/core';
|
|
import * as r from '@angular/router';
|
|
import * as u from './lib/utils';
|
|
import * as cs from './lib/collections';
|
|
import * as u2 from './lib2/utils2';
|
|
`,
|
|
'lib': {
|
|
'utils.ts': dummyModule,
|
|
'collections.ts': dummyModule,
|
|
},
|
|
'lib2': {'utils2.ts': dummyModule},
|
|
'reexport': {
|
|
'reexport.d.ts': `
|
|
import * as c from '@angular/core';
|
|
`,
|
|
'reexport.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {},
|
|
exports: [
|
|
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
|
|
{from: './src/origin5'}, {from: './src/reexport2'}
|
|
]
|
|
}),
|
|
'src': {
|
|
'origin1.d.ts': `
|
|
export class One {}
|
|
export class Two {}
|
|
export class Three {}
|
|
`,
|
|
'origin1.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {
|
|
One: {__symbolic: 'class'},
|
|
Two: {__symbolic: 'class'},
|
|
Three: {__symbolic: 'class'},
|
|
},
|
|
}),
|
|
'origin5.d.ts': `
|
|
export class Five {}
|
|
`,
|
|
'origin5.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {
|
|
Five: {__symbolic: 'class'},
|
|
},
|
|
}),
|
|
'origin30.d.ts': `
|
|
export class Thirty {}
|
|
`,
|
|
'origin30.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {
|
|
Thirty: {__symbolic: 'class'},
|
|
},
|
|
}),
|
|
'originNone.d.ts': dummyModule,
|
|
'originNone.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {},
|
|
}),
|
|
'reexport2.d.ts': dummyModule,
|
|
'reexport2.metadata.json': JSON.stringify({
|
|
__symbolic: 'module',
|
|
version: 1,
|
|
metadata: {},
|
|
exports: [{from: './originNone'}, {from: './origin30'}]
|
|
})
|
|
}
|
|
},
|
|
'node_modules': {
|
|
'@angular': {
|
|
'core.d.ts': dummyModule,
|
|
'core.metadata.json':
|
|
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
|
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
|
|
'unused.d.ts': dummyModule,
|
|
'empty.d.ts': 'export declare var a: string;',
|
|
'empty.metadata.json': '[]',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
function clone(entry: Entry): Entry {
|
|
if (typeof entry === 'string') {
|
|
return entry;
|
|
} else {
|
|
let result: Directory = {};
|
|
for (let name in entry) {
|
|
result[name] = clone(entry[name]);
|
|
}
|
|
return result;
|
|
}
|
|
}
|