fix(metadata): Do not attach module names to metadata.
The filename contains the module name as resolved by users, so the top-level module name is uneeded. Module names on references are replaced by capturing the import syntax from the module. This allows readers of the metadata to do the module resolution themselves. Fixes #8225 Fixes #8082 Closes #8256
This commit is contained in:
parent
35cd0ded22
commit
d9648887b8
|
@ -1,12 +1,8 @@
|
||||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
import {
|
import {
|
||||||
isArray,
|
isArray,
|
||||||
isBlank,
|
|
||||||
isNumber,
|
|
||||||
isPresent,
|
isPresent,
|
||||||
isPrimitive,
|
isPrimitive,
|
||||||
isString,
|
|
||||||
Type
|
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
import {
|
import {
|
||||||
AttributeMetadata,
|
AttributeMetadata,
|
||||||
|
@ -35,13 +31,19 @@ import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
|
||||||
*/
|
*/
|
||||||
export interface StaticReflectorHost {
|
export interface StaticReflectorHost {
|
||||||
/**
|
/**
|
||||||
* Return a ModuleMetadata for the give module.
|
* Return a ModuleMetadata for the given module.
|
||||||
*
|
*
|
||||||
* @param moduleId is a string identifier for a module in the form that would expected in a
|
* @param moduleId is a string identifier for a module as an absolute path.
|
||||||
* module import of an import statement.
|
|
||||||
* @returns the metadata for the given module.
|
* @returns the metadata for the given module.
|
||||||
*/
|
*/
|
||||||
getMetadataFor(moduleId: string): {[key: string]: any};
|
getMetadataFor(moduleId: string): {[key: string]: any};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a module from an import statement form to an absolute path.
|
||||||
|
* @param moduleName the location imported from
|
||||||
|
* @param containingFile for relative imports, the path of the file containing the import
|
||||||
|
*/
|
||||||
|
resolveModule(moduleName: string, containingFile?: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,10 +70,10 @@ export class StaticReflector implements ReflectorReader {
|
||||||
importUri(typeOrFunc: any): string { return (<StaticType>typeOrFunc).moduleId; }
|
importUri(typeOrFunc: any): string { return (<StaticType>typeOrFunc).moduleId; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getStatictype produces a Type whose metadata is known but whose implementation is not loaded.
|
* getStaticType produces a Type whose metadata is known but whose implementation is not loaded.
|
||||||
* All types passed to the StaticResolver should be pseudo-types returned by this method.
|
* All types passed to the StaticResolver should be pseudo-types returned by this method.
|
||||||
*
|
*
|
||||||
* @param moduleId the module identifier as would be passed to an import statement.
|
* @param moduleId the module identifier as an absolute path.
|
||||||
* @param name the name of the type.
|
* @param name the name of the type.
|
||||||
*/
|
*/
|
||||||
public getStaticType(moduleId: string, name: string): StaticType {
|
public getStaticType(moduleId: string, name: string): StaticType {
|
||||||
|
@ -137,7 +139,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
|
|
||||||
private conversionMap = new Map<StaticType, (moduleContext: string, expression: any) => any>();
|
private conversionMap = new Map<StaticType, (moduleContext: string, expression: any) => any>();
|
||||||
private initializeConversionMap(): any {
|
private initializeConversionMap(): any {
|
||||||
let core_metadata = 'angular2/src/core/metadata';
|
let core_metadata = this.host.resolveModule('angular2/src/core/metadata');
|
||||||
let conversionMap = this.conversionMap;
|
let conversionMap = this.conversionMap;
|
||||||
conversionMap.set(this.getStaticType(core_metadata, 'Directive'),
|
conversionMap.set(this.getStaticType(core_metadata, 'Directive'),
|
||||||
(moduleContext, expression) => {
|
(moduleContext, expression) => {
|
||||||
|
@ -272,7 +274,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
if (isMetadataSymbolicCallExpression(expression)) {
|
if (isMetadataSymbolicCallExpression(expression)) {
|
||||||
let target = expression['expression'];
|
let target = expression['expression'];
|
||||||
if (isMetadataSymbolicReferenceExpression(target)) {
|
if (isMetadataSymbolicReferenceExpression(target)) {
|
||||||
let moduleId = this.normalizeModuleName(moduleContext, target['module']);
|
let moduleId = this.host.resolveModule(target['module'], moduleContext);
|
||||||
return this.getStaticType(moduleId, target['name']);
|
return this.getStaticType(moduleId, target['name']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,7 +419,7 @@ export class StaticReflector implements ReflectorReader {
|
||||||
return null;
|
return null;
|
||||||
case "reference":
|
case "reference":
|
||||||
let referenceModuleName =
|
let referenceModuleName =
|
||||||
_this.normalizeModuleName(moduleContext, expression['module']);
|
_this.host.resolveModule(expression['module'], moduleContext);
|
||||||
let referenceModule = _this.getModuleMetadata(referenceModuleName);
|
let referenceModule = _this.getModuleMetadata(referenceModuleName);
|
||||||
let referenceValue = referenceModule['metadata'][expression['name']];
|
let referenceValue = referenceModule['metadata'][expression['name']];
|
||||||
if (isClassMetadata(referenceValue)) {
|
if (isClassMetadata(referenceValue)) {
|
||||||
|
@ -440,6 +442,9 @@ export class StaticReflector implements ReflectorReader {
|
||||||
return simplify(value);
|
return simplify(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param module an absolute path to a module file.
|
||||||
|
*/
|
||||||
public getModuleMetadata(module: string): {[key: string]: any} {
|
public getModuleMetadata(module: string): {[key: string]: any} {
|
||||||
let moduleMetadata = this.metadataCache.get(module);
|
let moduleMetadata = this.metadataCache.get(module);
|
||||||
if (!isPresent(moduleMetadata)) {
|
if (!isPresent(moduleMetadata)) {
|
||||||
|
@ -460,13 +465,6 @@ export class StaticReflector implements ReflectorReader {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private normalizeModuleName(from: string, to: string): string {
|
|
||||||
if (to.startsWith('.')) {
|
|
||||||
return pathTo(from, to);
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMetadataSymbolicCallExpression(expression: any): boolean {
|
function isMetadataSymbolicCallExpression(expression: any): boolean {
|
||||||
|
@ -481,35 +479,3 @@ function isMetadataSymbolicReferenceExpression(expression: any): boolean {
|
||||||
function isClassMetadata(expression: any): boolean {
|
function isClassMetadata(expression: any): boolean {
|
||||||
return !isPrimitive(expression) && !isArray(expression) && expression['__symbolic'] == 'class';
|
return !isPrimitive(expression) && !isArray(expression) && expression['__symbolic'] == 'class';
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitPath(path: string): string[] {
|
|
||||||
return path.split(/\/|\\/g);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolvePath(pathParts: string[]): string {
|
|
||||||
let result = [];
|
|
||||||
ListWrapper.forEachWithIndex(pathParts, (part, index) => {
|
|
||||||
switch (part) {
|
|
||||||
case '':
|
|
||||||
case '.':
|
|
||||||
if (index > 0) return;
|
|
||||||
break;
|
|
||||||
case '..':
|
|
||||||
if (index > 0 && result.length != 0) result.pop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
result.push(part);
|
|
||||||
});
|
|
||||||
return result.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function pathTo(from: string, to: string): string {
|
|
||||||
let result = to;
|
|
||||||
if (to.startsWith('.')) {
|
|
||||||
let fromParts = splitPath(from);
|
|
||||||
fromParts.pop(); // remove the file name.
|
|
||||||
let toParts = splitPath(to);
|
|
||||||
result = resolvePath(fromParts.concat(toParts));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,27 +1,20 @@
|
||||||
import {
|
import {
|
||||||
ddescribe,
|
|
||||||
describe,
|
describe,
|
||||||
xdescribe,
|
|
||||||
it,
|
it,
|
||||||
iit,
|
|
||||||
xit,
|
|
||||||
expect,
|
expect,
|
||||||
beforeEach,
|
|
||||||
afterEach,
|
|
||||||
AsyncTestCompleter,
|
|
||||||
inject,
|
|
||||||
beforeEachProviders
|
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {StaticReflector, StaticReflectorHost} from 'angular2/src/compiler/static_reflector';
|
import {StaticReflector, StaticReflectorHost} from 'angular2/src/compiler/static_reflector';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('StaticRefelector', () => {
|
describe('StaticReflector', () => {
|
||||||
it('should get annotations for NgFor', () => {
|
it('should get annotations for NgFor', () => {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let NgFor = reflector.getStaticType('angular2/src/common/directives/ng_for', 'NgFor');
|
let NgFor = reflector.getStaticType(
|
||||||
|
host.resolveModule('angular2/src/common/directives/ng_for'), 'NgFor');
|
||||||
let annotations = reflector.annotations(NgFor);
|
let annotations = reflector.annotations(NgFor);
|
||||||
expect(annotations.length).toEqual(1);
|
expect(annotations.length).toEqual(1);
|
||||||
let annotation = annotations[0];
|
let annotation = annotations[0];
|
||||||
|
@ -33,15 +26,18 @@ export function main() {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let NgFor = reflector.getStaticType('angular2/src/common/directives/ng_for', 'NgFor');
|
let NgFor = reflector.getStaticType(
|
||||||
let ViewContainerRef = reflector.getStaticType('angular2/src/core/linker/view_container_ref',
|
host.resolveModule('angular2/src/common/directives/ng_for'), 'NgFor');
|
||||||
'ViewContainerRef');
|
let ViewContainerRef = reflector.getStaticType(
|
||||||
let TemplateRef =
|
host.resolveModule('angular2/src/core/linker/view_container_ref'), 'ViewContainerRef');
|
||||||
reflector.getStaticType('angular2/src/core/linker/template_ref', 'TemplateRef');
|
let TemplateRef = reflector.getStaticType(
|
||||||
|
host.resolveModule('angular2/src/core/linker/template_ref'), 'TemplateRef');
|
||||||
let IterableDiffers = reflector.getStaticType(
|
let IterableDiffers = reflector.getStaticType(
|
||||||
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
|
host.resolveModule('angular2/src/core/change_detection/differs/iterable_differs'),
|
||||||
|
'IterableDiffers');
|
||||||
let ChangeDetectorRef = reflector.getStaticType(
|
let ChangeDetectorRef = reflector.getStaticType(
|
||||||
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
|
host.resolveModule('angular2/src/core/change_detection/change_detector_ref'),
|
||||||
|
'ChangeDetectorRef');
|
||||||
|
|
||||||
let parameters = reflector.parameters(NgFor);
|
let parameters = reflector.parameters(NgFor);
|
||||||
expect(parameters)
|
expect(parameters)
|
||||||
|
@ -53,7 +49,7 @@ export function main() {
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let HeroDetailComponent =
|
let HeroDetailComponent =
|
||||||
reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent');
|
reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent');
|
||||||
let annotations = reflector.annotations(HeroDetailComponent);
|
let annotations = reflector.annotations(HeroDetailComponent);
|
||||||
expect(annotations.length).toEqual(1);
|
expect(annotations.length).toEqual(1);
|
||||||
let annotation = annotations[0];
|
let annotation = annotations[0];
|
||||||
|
@ -64,7 +60,7 @@ export function main() {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass');
|
let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass');
|
||||||
let annotations = reflector.annotations(UnknownClass);
|
let annotations = reflector.annotations(UnknownClass);
|
||||||
expect(annotations).toEqual([]);
|
expect(annotations).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -74,7 +70,7 @@ export function main() {
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let HeroDetailComponent =
|
let HeroDetailComponent =
|
||||||
reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent');
|
reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent');
|
||||||
let props = reflector.propMetadata(HeroDetailComponent);
|
let props = reflector.propMetadata(HeroDetailComponent);
|
||||||
expect(props['hero']).toBeTruthy();
|
expect(props['hero']).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -83,7 +79,7 @@ export function main() {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass');
|
let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass');
|
||||||
let properties = reflector.propMetadata(UnknownClass);
|
let properties = reflector.propMetadata(UnknownClass);
|
||||||
expect(properties).toEqual({});
|
expect(properties).toEqual({});
|
||||||
});
|
});
|
||||||
|
@ -92,7 +88,7 @@ export function main() {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
let UnknownClass = reflector.getStaticType('./app/app.component', 'UnknownClass');
|
let UnknownClass = reflector.getStaticType('/src/app/app.component.ts', 'UnknownClass');
|
||||||
let parameters = reflector.parameters(UnknownClass);
|
let parameters = reflector.parameters(UnknownClass);
|
||||||
expect(parameters).toEqual([]);
|
expect(parameters).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -301,7 +297,7 @@ export function main() {
|
||||||
expect(reflector.simplify('', ({ __symbolic: 'pre', operator: '!', operand: false}))).toBe(!false);
|
expect(reflector.simplify('', ({ __symbolic: 'pre', operator: '!', operand: false}))).toBe(!false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should simpify an array index', () => {
|
it('should simplify an array index', () => {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
|
@ -320,20 +316,56 @@ export function main() {
|
||||||
let host = new MockReflectorHost();
|
let host = new MockReflectorHost();
|
||||||
let reflector = new StaticReflector(host);
|
let reflector = new StaticReflector(host);
|
||||||
|
|
||||||
expect(
|
expect(reflector.simplify('/src/cases',
|
||||||
reflector.simplify('./cases', ({__symbolic: "reference", module: "./extern", name: "s"})))
|
({__symbolic: "reference", module: "./extern", name: "s"})))
|
||||||
.toEqual("s");
|
.toEqual("s");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockReflectorHost implements StaticReflectorHost {
|
class MockReflectorHost implements StaticReflectorHost {
|
||||||
|
resolveModule(moduleName: string, containingFile?: string): string {
|
||||||
|
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
|
||||||
|
|
||||||
|
function resolvePath(pathParts: string[]): string {
|
||||||
|
let result = [];
|
||||||
|
ListWrapper.forEachWithIndex(pathParts, (part, index) => {
|
||||||
|
switch (part) {
|
||||||
|
case '':
|
||||||
|
case '.':
|
||||||
|
if (index > 0) return;
|
||||||
|
break;
|
||||||
|
case '..':
|
||||||
|
if (index > 0 && result.length != 0) result.pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.push(part);
|
||||||
|
});
|
||||||
|
return result.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathTo(from: string, to: string): string {
|
||||||
|
let result = to;
|
||||||
|
if (to.startsWith('.')) {
|
||||||
|
let fromParts = splitPath(from);
|
||||||
|
fromParts.pop(); // remove the file name.
|
||||||
|
let toParts = splitPath(to);
|
||||||
|
result = resolvePath(fromParts.concat(toParts));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleName.indexOf('.') === 0) {
|
||||||
|
return pathTo(containingFile, moduleName) + '.d.ts';
|
||||||
|
}
|
||||||
|
return '/tmp/' + moduleName + '.d.ts';
|
||||||
|
}
|
||||||
|
|
||||||
getMetadataFor(moduleId: string): any {
|
getMetadataFor(moduleId: string): any {
|
||||||
return {
|
return {
|
||||||
'angular2/src/common/directives/ng_for':
|
'/tmp/angular2/src/common/directives/ng_for.d.ts':
|
||||||
{
|
{
|
||||||
"__symbolic": "module",
|
"__symbolic": "module",
|
||||||
"module": "./ng_for",
|
|
||||||
"metadata":
|
"metadata":
|
||||||
{
|
{
|
||||||
"NgFor":
|
"NgFor":
|
||||||
|
@ -393,27 +425,17 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'angular2/src/core/linker/view_container_ref':
|
'/tmp/angular2/src/core/linker/view_container_ref.d.ts':
|
||||||
{
|
{"metadata": {"ViewContainerRef": {"__symbolic": "class"}}},
|
||||||
"module": "./view_container_ref",
|
'/tmp/angular2/src/core/linker/template_ref.d.ts':
|
||||||
"metadata": {"ViewContainerRef": {"__symbolic": "class"}}
|
|
||||||
},
|
|
||||||
'angular2/src/core/linker/template_ref':
|
|
||||||
{"module": "./template_ref", "metadata": {"TemplateRef": {"__symbolic": "class"}}},
|
{"module": "./template_ref", "metadata": {"TemplateRef": {"__symbolic": "class"}}},
|
||||||
'angular2/src/core/change_detection/differs/iterable_differs':
|
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
|
||||||
{
|
{"metadata": {"IterableDiffers": {"__symbolic": "class"}}},
|
||||||
"module": "./iterable_differs",
|
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
|
||||||
"metadata": {"IterableDiffers": {"__symbolic": "class"}}
|
{"metadata": {"ChangeDetectorRef": {"__symbolic": "class"}}},
|
||||||
},
|
'/src/app/hero-detail.component.ts':
|
||||||
'angular2/src/core/change_detection/change_detector_ref':
|
|
||||||
{
|
|
||||||
"module": "./change_detector_ref",
|
|
||||||
"metadata": {"ChangeDetectorRef": {"__symbolic": "class"}}
|
|
||||||
},
|
|
||||||
'./app/hero-detail.component':
|
|
||||||
{
|
{
|
||||||
"__symbolic": "module",
|
"__symbolic": "module",
|
||||||
"module": "./hero-detail.component",
|
|
||||||
"metadata":
|
"metadata":
|
||||||
{
|
{
|
||||||
"HeroDetailComponent":
|
"HeroDetailComponent":
|
||||||
|
@ -459,8 +481,8 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'./extern': {
|
'/src/extern.d.ts': {
|
||||||
"__symbolic": "module", module: './extern', metadata: { s: "s" }
|
"__symbolic": "module", metadata: { s: "s" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[moduleId];
|
[moduleId];
|
||||||
|
|
|
@ -88,16 +88,7 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin {
|
||||||
this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths,
|
this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths,
|
||||||
this.fileRegistry, this.inputPath);
|
this.fileRegistry, this.inputPath);
|
||||||
this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry());
|
this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry());
|
||||||
this.metadataCollector = new MetadataCollector({
|
this.metadataCollector = new MetadataCollector();
|
||||||
// Since our code isn't under a node_modules directory, we need to reverse the module
|
|
||||||
// resolution to get metadata rooted at 'angular2'.
|
|
||||||
// see https://github.com/angular/angular/issues/8144
|
|
||||||
reverseModuleResolution(fileName: string) {
|
|
||||||
if (/\.tmp\/angular2/.test(fileName)) {
|
|
||||||
return fileName.substr(fileName.lastIndexOf('.tmp/angular2/') + 5).replace(/\.ts$/, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {Evaluator} from './evaluator';
|
import {Evaluator, ImportMetadata, ImportSpecifierMetadata} from './evaluator';
|
||||||
import {Symbols} from './symbols';
|
import {Symbols} from './symbols';
|
||||||
import {
|
import {
|
||||||
ClassMetadata,
|
ClassMetadata,
|
||||||
|
@ -13,46 +13,56 @@ import {
|
||||||
MethodMetadata
|
MethodMetadata
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
const EXT_REGEX = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
|
||||||
const NODE_MODULES = '/node_modules/';
|
|
||||||
const NODE_MODULES_PREFIX = 'node_modules/';
|
|
||||||
|
|
||||||
function pathTo(from: string, to: string): string {
|
|
||||||
var result = path.relative(path.dirname(from), to);
|
|
||||||
if (path.dirname(result) === '.') {
|
|
||||||
result = '.' + path.sep + result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetadataCollectorHost {
|
|
||||||
reverseModuleResolution: (moduleFileName: string) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeModuleResolutionHost: MetadataCollectorHost = {
|
|
||||||
// Reverse moduleResolution=node for packages resolved in node_modules
|
|
||||||
reverseModuleResolution(fileName: string) {
|
|
||||||
// Remove the extension
|
|
||||||
const moduleFileName = fileName.replace(EXT_REGEX, '');
|
|
||||||
// Check for node_modules
|
|
||||||
const nodeModulesIndex = moduleFileName.lastIndexOf(NODE_MODULES);
|
|
||||||
if (nodeModulesIndex >= 0) {
|
|
||||||
return moduleFileName.substr(nodeModulesIndex + NODE_MODULES.length);
|
|
||||||
}
|
|
||||||
if (moduleFileName.lastIndexOf(NODE_MODULES_PREFIX, NODE_MODULES_PREFIX.length) !== -1) {
|
|
||||||
return moduleFileName.substr(NODE_MODULES_PREFIX.length);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect decorator metadata from a TypeScript module.
|
* Collect decorator metadata from a TypeScript module.
|
||||||
*/
|
*/
|
||||||
export class MetadataCollector {
|
export class MetadataCollector {
|
||||||
constructor(private host: MetadataCollectorHost = nodeModuleResolutionHost) {}
|
constructor() {}
|
||||||
|
|
||||||
|
collectImports(sourceFile: ts.SourceFile) {
|
||||||
|
let imports: ImportMetadata[] = [];
|
||||||
|
const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, '');
|
||||||
|
function visit(node: ts.Node) {
|
||||||
|
switch (node.kind) {
|
||||||
|
case ts.SyntaxKind.ImportDeclaration:
|
||||||
|
const importDecl = <ts.ImportDeclaration>node;
|
||||||
|
const from = stripQuotes(importDecl.moduleSpecifier.getText());
|
||||||
|
const newImport = {from};
|
||||||
|
if (!importDecl.importClause) {
|
||||||
|
// Bare imports do not bring symbols into scope, so we don't need to record them
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (importDecl.importClause.name) {
|
||||||
|
newImport['defaultName'] = importDecl.importClause.name.text;
|
||||||
|
}
|
||||||
|
const bindings = importDecl.importClause.namedBindings;
|
||||||
|
if (bindings) {
|
||||||
|
switch (bindings.kind) {
|
||||||
|
case ts.SyntaxKind.NamedImports:
|
||||||
|
const namedImports: ImportSpecifierMetadata[] = [];
|
||||||
|
(<ts.NamedImports>bindings)
|
||||||
|
.elements.forEach(i => {
|
||||||
|
const namedImport = {name: i.name.text};
|
||||||
|
if (i.propertyName) {
|
||||||
|
namedImport['propertyName'] = i.propertyName.text;
|
||||||
|
}
|
||||||
|
namedImports.push(namedImport);
|
||||||
|
});
|
||||||
|
newImport['namedImports'] = namedImports;
|
||||||
|
break;
|
||||||
|
case ts.SyntaxKind.NamespaceImport:
|
||||||
|
newImport['namespace'] = (<ts.NamespaceImport>bindings).name.text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imports.push(newImport);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit);
|
||||||
|
}
|
||||||
|
ts.forEachChild(sourceFile, visit);
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -60,17 +70,7 @@ export class MetadataCollector {
|
||||||
*/
|
*/
|
||||||
public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata {
|
public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata {
|
||||||
const locals = new Symbols();
|
const locals = new Symbols();
|
||||||
const moduleNameOf = (fileName: string) => {
|
const evaluator = new Evaluator(typeChecker, locals, this.collectImports(sourceFile));
|
||||||
// If the module was resolved with TS moduleResolution, reverse that mapping
|
|
||||||
const hostResolved = this.host.reverseModuleResolution(fileName);
|
|
||||||
if (hostResolved) {
|
|
||||||
return hostResolved;
|
|
||||||
}
|
|
||||||
// Construct a simplified path from the file to the module
|
|
||||||
return pathTo(sourceFile.fileName, fileName).replace(EXT_REGEX, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const evaluator = new Evaluator(typeChecker, locals, moduleNameOf);
|
|
||||||
|
|
||||||
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
||||||
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
||||||
|
@ -80,18 +80,7 @@ export class MetadataCollector {
|
||||||
if (type) {
|
if (type) {
|
||||||
let symbol = type.getSymbol();
|
let symbol = type.getSymbol();
|
||||||
if (symbol) {
|
if (symbol) {
|
||||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
return evaluator.symbolReference(symbol);
|
||||||
symbol = typeChecker.getAliasedSymbol(symbol);
|
|
||||||
}
|
|
||||||
if (symbol.declarations.length) {
|
|
||||||
const declaration = symbol.declarations[0];
|
|
||||||
const sourceFile = declaration.getSourceFile();
|
|
||||||
return {
|
|
||||||
__symbolic: "reference",
|
|
||||||
module: moduleNameOf(sourceFile.fileName),
|
|
||||||
name: symbol.name
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,6 +195,7 @@ export class MetadataCollector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return metadata && {__symbolic: "module", module: moduleNameOf(sourceFile.fileName), metadata};
|
|
||||||
|
return metadata && {__symbolic: "module", metadata};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import {Symbols} from './symbols';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MetadataValue,
|
MetadataValue,
|
||||||
MetadataObject,
|
|
||||||
MetadataSymbolicCallExpression,
|
MetadataSymbolicCallExpression,
|
||||||
MetadataSymbolicReferenceExpression
|
MetadataSymbolicReferenceExpression
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
@ -58,40 +57,72 @@ function isDefined(obj: any): boolean {
|
||||||
return obj !== undefined;
|
return obj !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// import {propertyName as name} from 'place'
|
||||||
|
// import {name} from 'place'
|
||||||
|
export interface ImportSpecifierMetadata {
|
||||||
|
name: string;
|
||||||
|
propertyName?: string;
|
||||||
|
}
|
||||||
|
export interface ImportMetadata {
|
||||||
|
defaultName?: string; // import d from 'place'
|
||||||
|
namespace?: string; // import * as d from 'place'
|
||||||
|
namedImports?: ImportSpecifierMetadata[]; // import {a} from 'place'
|
||||||
|
from: string; // from 'place'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produce a symbolic representation of an expression folding values into their final value when
|
* Produce a symbolic representation of an expression folding values into their final value when
|
||||||
* possible.
|
* possible.
|
||||||
*/
|
*/
|
||||||
export class Evaluator {
|
export class Evaluator {
|
||||||
constructor(private typeChecker: ts.TypeChecker, private symbols: Symbols,
|
constructor(private typeChecker: ts.TypeChecker, private symbols: Symbols,
|
||||||
private moduleNameOf: (fileName: string) => string) {}
|
private imports: ImportMetadata[]) {}
|
||||||
|
|
||||||
// TODO: Determine if the first declaration is deterministic.
|
symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression {
|
||||||
private symbolFileName(symbol: ts.Symbol): string {
|
|
||||||
if (symbol) {
|
if (symbol) {
|
||||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
let module: string;
|
||||||
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
let name = symbol.name;
|
||||||
}
|
for (const eachImport of this.imports) {
|
||||||
const declarations = symbol.getDeclarations();
|
if (symbol.name === eachImport.defaultName) {
|
||||||
if (declarations && declarations.length > 0) {
|
module = eachImport.from;
|
||||||
const sourceFile = declarations[0].getSourceFile();
|
name = undefined;
|
||||||
if (sourceFile) {
|
}
|
||||||
return sourceFile.fileName;
|
if (eachImport.namedImports) {
|
||||||
|
for (const named of eachImport.namedImports) {
|
||||||
|
if (symbol.name === named.name) {
|
||||||
|
name = named.propertyName ? named.propertyName : named.name;
|
||||||
|
module = eachImport.from;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression {
|
|
||||||
if (symbol) {
|
|
||||||
const name = symbol.name;
|
|
||||||
const module = this.moduleNameOf(this.symbolFileName(symbol));
|
|
||||||
return {__symbolic: "reference", name, module};
|
return {__symbolic: "reference", name, module};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private findImportNamespace(node: ts.Node) {
|
||||||
|
if (node.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
||||||
|
const lhs = (<ts.PropertyAccessExpression>node).expression;
|
||||||
|
if (lhs.kind === ts.SyntaxKind.Identifier) {
|
||||||
|
// TOOD: Use Array.find when tools directory is upgraded to support es6 target
|
||||||
|
for (const eachImport of this.imports) {
|
||||||
|
if (eachImport.namespace === (<ts.Identifier>lhs).text) {
|
||||||
|
return eachImport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private nodeSymbolReference(node: ts.Node): MetadataSymbolicReferenceExpression {
|
private nodeSymbolReference(node: ts.Node): MetadataSymbolicReferenceExpression {
|
||||||
|
const importNamespace = this.findImportNamespace(node);
|
||||||
|
if (importNamespace) {
|
||||||
|
const result = this.symbolReference(
|
||||||
|
this.typeChecker.getSymbolAtLocation((<ts.PropertyAccessExpression>node).name));
|
||||||
|
result.module = importNamespace.from;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
return this.symbolReference(this.typeChecker.getSymbolAtLocation(node));
|
return this.symbolReference(this.typeChecker.getSymbolAtLocation(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +146,7 @@ export class Evaluator {
|
||||||
* - A array index is foldable if index expression is foldable and the array is foldable.
|
* - A array index is foldable if index expression is foldable and the array is foldable.
|
||||||
* - Binary operator expressions are foldable if the left and right expressions are foldable and
|
* - Binary operator expressions are foldable if the left and right expressions are foldable and
|
||||||
* it is one of '+', '-', '*', '/', '%', '||', and '&&'.
|
* it is one of '+', '-', '*', '/', '%', '||', and '&&'.
|
||||||
* - An identifier is foldable if a value can be found for its symbol is in the evaluator symbol
|
* - An identifier is foldable if a value can be found for its symbol in the evaluator symbol
|
||||||
* table.
|
* table.
|
||||||
*/
|
*/
|
||||||
public isFoldable(node: ts.Node): boolean {
|
public isFoldable(node: ts.Node): boolean {
|
||||||
|
@ -129,7 +160,7 @@ export class Evaluator {
|
||||||
return everyNodeChild(node, child => {
|
return everyNodeChild(node, child => {
|
||||||
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
|
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
|
||||||
const propertyAssignment = <ts.PropertyAssignment>child;
|
const propertyAssignment = <ts.PropertyAssignment>child;
|
||||||
return this.isFoldableWorker(propertyAssignment.initializer, folding)
|
return this.isFoldableWorker(propertyAssignment.initializer, folding);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -272,6 +303,9 @@ export class Evaluator {
|
||||||
const expression = this.evaluateNode(propertyAccessExpression.expression);
|
const expression = this.evaluateNode(propertyAccessExpression.expression);
|
||||||
const member = this.nameOf(propertyAccessExpression.name);
|
const member = this.nameOf(propertyAccessExpression.name);
|
||||||
if (this.isFoldable(propertyAccessExpression.expression)) return expression[member];
|
if (this.isFoldable(propertyAccessExpression.expression)) return expression[member];
|
||||||
|
if (this.findImportNamespace(propertyAccessExpression)) {
|
||||||
|
return this.nodeSymbolReference(propertyAccessExpression);
|
||||||
|
}
|
||||||
if (isDefined(expression)) {
|
if (isDefined(expression)) {
|
||||||
return {__symbolic: "select", expression, member};
|
return {__symbolic: "select", expression, member};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
// TODO: fix typings for __symbolic once angular moves to 1.8
|
|
||||||
|
|
||||||
export interface ModuleMetadata {
|
export interface ModuleMetadata {
|
||||||
__symbolic: string; // "module";
|
__symbolic: "module";
|
||||||
module: string;
|
|
||||||
metadata: {[name: string]: (ClassMetadata | MetadataValue)};
|
metadata: {[name: string]: (ClassMetadata | MetadataValue)};
|
||||||
}
|
}
|
||||||
export function isModuleMetadata(value: any): value is ModuleMetadata {
|
export function isModuleMetadata(value: any): value is ModuleMetadata {
|
||||||
|
@ -10,7 +7,7 @@ export function isModuleMetadata(value: any): value is ModuleMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClassMetadata {
|
export interface ClassMetadata {
|
||||||
__symbolic: string; // "class";
|
__symbolic: "class";
|
||||||
decorators?: MetadataSymbolicExpression[];
|
decorators?: MetadataSymbolicExpression[];
|
||||||
members?: MetadataMap;
|
members?: MetadataMap;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +18,7 @@ export function isClassMetadata(value: any): value is ClassMetadata {
|
||||||
export interface MetadataMap { [name: string]: MemberMetadata[]; }
|
export interface MetadataMap { [name: string]: MemberMetadata[]; }
|
||||||
|
|
||||||
export interface MemberMetadata {
|
export interface MemberMetadata {
|
||||||
__symbolic: string; // "constructor" | "method" | "property";
|
__symbolic: "constructor" | "method" | "property";
|
||||||
decorators?: MetadataSymbolicExpression[];
|
decorators?: MetadataSymbolicExpression[];
|
||||||
}
|
}
|
||||||
export function isMemberMetadata(value: any): value is MemberMetadata {
|
export function isMemberMetadata(value: any): value is MemberMetadata {
|
||||||
|
@ -37,7 +34,7 @@ export function isMemberMetadata(value: any): value is MemberMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MethodMetadata extends MemberMetadata {
|
export interface MethodMetadata extends MemberMetadata {
|
||||||
// __symbolic: "constructor" | "method";
|
__symbolic: "constructor" | "method";
|
||||||
parameterDecorators?: MetadataSymbolicExpression[][];
|
parameterDecorators?: MetadataSymbolicExpression[][];
|
||||||
}
|
}
|
||||||
export function isMethodMetadata(value: any): value is MemberMetadata {
|
export function isMethodMetadata(value: any): value is MemberMetadata {
|
||||||
|
@ -45,7 +42,7 @@ export function isMethodMetadata(value: any): value is MemberMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConstructorMetadata extends MethodMetadata {
|
export interface ConstructorMetadata extends MethodMetadata {
|
||||||
// __symbolic: "constructor";
|
__symbolic: "constructor";
|
||||||
parameters?: MetadataSymbolicExpression[];
|
parameters?: MetadataSymbolicExpression[];
|
||||||
}
|
}
|
||||||
export function isConstructorMetadata(value: any): value is ConstructorMetadata {
|
export function isConstructorMetadata(value: any): value is ConstructorMetadata {
|
||||||
|
@ -60,7 +57,7 @@ export interface MetadataObject { [name: string]: MetadataValue; }
|
||||||
export interface MetadataArray { [name: number]: MetadataValue; }
|
export interface MetadataArray { [name: number]: MetadataValue; }
|
||||||
|
|
||||||
export interface MetadataSymbolicExpression {
|
export interface MetadataSymbolicExpression {
|
||||||
__symbolic: string; // "binary" | "call" | "index" | "pre" | "reference" | "select"
|
__symbolic: "binary" | "call" | "index" | "pre" | "reference" | "select"
|
||||||
}
|
}
|
||||||
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
|
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -78,10 +75,9 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "binary";
|
__symbolic: "binary";
|
||||||
operator: string; // "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" |
|
operator: "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">=" |
|
||||||
// "<=" | ">=" | "instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" |
|
"instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "**";
|
||||||
// "*" | "/" | "%" | "**";
|
|
||||||
left: MetadataValue;
|
left: MetadataValue;
|
||||||
right: MetadataValue;
|
right: MetadataValue;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +87,7 @@ export function isMetadataSymbolicBinaryExpression(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "index";
|
__symbolic: "index";
|
||||||
expression: MetadataValue;
|
expression: MetadataValue;
|
||||||
index: MetadataValue;
|
index: MetadataValue;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +97,7 @@ export function isMetadataSymbolicIndexExpression(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "call";
|
__symbolic: "call";
|
||||||
expression: MetadataValue;
|
expression: MetadataValue;
|
||||||
arguments?: MetadataValue[];
|
arguments?: MetadataValue[];
|
||||||
}
|
}
|
||||||
|
@ -111,8 +107,8 @@ export function isMetadataSymbolicCallExpression(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "pre";
|
__symbolic: "pre";
|
||||||
operator: string; // "+" | "-" | "~" | "!";
|
operator: "+" | "-" | "~" | "!";
|
||||||
operand: MetadataValue;
|
operand: MetadataValue;
|
||||||
}
|
}
|
||||||
export function isMetadataSymbolicPrefixExpression(
|
export function isMetadataSymbolicPrefixExpression(
|
||||||
|
@ -121,7 +117,7 @@ export function isMetadataSymbolicPrefixExpression(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicReferenceExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicReferenceExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "reference";
|
__symbolic: "reference";
|
||||||
name: string;
|
name: string;
|
||||||
module: string;
|
module: string;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +127,7 @@ export function isMetadataSymbolicReferenceExpression(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression {
|
export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression {
|
||||||
// __symbolic: "select";
|
__symbolic: "select";
|
||||||
expression: MetadataValue;
|
expression: MetadataValue;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,26 @@ describe('Collector', () => {
|
||||||
expect(metadata).toBeUndefined();
|
expect(metadata).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should be able to collect import statements", () => {
|
||||||
|
const sourceFile = program.getSourceFile('app/app.component.ts');
|
||||||
|
expect(collector.collectImports(sourceFile))
|
||||||
|
.toEqual([
|
||||||
|
{
|
||||||
|
from: 'angular2/core',
|
||||||
|
namedImports: [{name: 'MyComponent', propertyName: 'Component'}, {name: 'OnInit'}]
|
||||||
|
},
|
||||||
|
{from: 'angular2/common', namespace: 'common'},
|
||||||
|
{from: './hero', namedImports: [{name: 'Hero'}]},
|
||||||
|
{from: './hero-detail.component', namedImports: [{name: 'HeroDetailComponent'}]},
|
||||||
|
{from: './hero.service', defaultName: 'HeroService'}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should be able to collect a simple component's metadata", () => {
|
it("should be able to collect a simple component's metadata", () => {
|
||||||
const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
|
const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
|
||||||
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
||||||
expect(metadata).toEqual({
|
expect(metadata).toEqual({
|
||||||
__symbolic: 'module',
|
__symbolic: 'module',
|
||||||
module: './hero-detail.component',
|
|
||||||
metadata: {
|
metadata: {
|
||||||
HeroDetailComponent: {
|
HeroDetailComponent: {
|
||||||
__symbolic: 'class',
|
__symbolic: 'class',
|
||||||
|
@ -83,7 +97,6 @@ describe('Collector', () => {
|
||||||
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
||||||
expect(metadata).toEqual({
|
expect(metadata).toEqual({
|
||||||
__symbolic: 'module',
|
__symbolic: 'module',
|
||||||
module: './app.component',
|
|
||||||
metadata: {
|
metadata: {
|
||||||
AppComponent: {
|
AppComponent: {
|
||||||
__symbolic: 'class',
|
__symbolic: 'class',
|
||||||
|
@ -113,8 +126,7 @@ describe('Collector', () => {
|
||||||
},
|
},
|
||||||
{__symbolic: 'reference', name: 'NgFor', module: 'angular2/common'}
|
{__symbolic: 'reference', name: 'NgFor', module: 'angular2/common'}
|
||||||
],
|
],
|
||||||
providers:
|
providers: [{__symbolic: 'reference', name: undefined, module: './hero.service'}],
|
||||||
[{__symbolic: 'reference', name: 'HeroService', module: './hero.service'}],
|
|
||||||
pipes: [
|
pipes: [
|
||||||
{__symbolic: 'reference', name: 'LowerCasePipe', module: 'angular2/common'},
|
{__symbolic: 'reference', name: 'LowerCasePipe', module: 'angular2/common'},
|
||||||
{
|
{
|
||||||
|
@ -131,9 +143,8 @@ describe('Collector', () => {
|
||||||
__ctor__: [
|
__ctor__: [
|
||||||
{
|
{
|
||||||
__symbolic: 'constructor',
|
__symbolic: 'constructor',
|
||||||
parameters: [
|
parameters:
|
||||||
{__symbolic: 'reference', module: './hero.service', name: 'HeroService'}
|
[{__symbolic: 'reference', name: undefined, module: './hero.service'}]
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -147,7 +158,6 @@ describe('Collector', () => {
|
||||||
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
const metadata = collector.getMetadata(sourceFile, typeChecker);
|
||||||
expect(metadata).toEqual({
|
expect(metadata).toEqual({
|
||||||
__symbolic: 'module',
|
__symbolic: 'module',
|
||||||
module: './mock-heroes',
|
|
||||||
metadata: {
|
metadata: {
|
||||||
HEROES: [
|
HEROES: [
|
||||||
{"id": 11, "name": "Mr. Nice"},
|
{"id": 11, "name": "Mr. Nice"},
|
||||||
|
@ -187,7 +197,7 @@ describe('Collector', () => {
|
||||||
expect(ctorData).toEqual([{__symbolic: 'constructor', parameters: [null]}]);
|
expect(ctorData).toEqual([{__symbolic: 'constructor', parameters: [null]}]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should record annotations on set and get declartions', () => {
|
it('should record annotations on set and get declarations', () => {
|
||||||
const propertyData = {
|
const propertyData = {
|
||||||
name: [
|
name: [
|
||||||
{
|
{
|
||||||
|
@ -215,13 +225,15 @@ describe('Collector', () => {
|
||||||
const FILES: Directory = {
|
const FILES: Directory = {
|
||||||
'app': {
|
'app': {
|
||||||
'app.component.ts': `
|
'app.component.ts': `
|
||||||
import {Component, OnInit} from 'angular2/core';
|
import {Component as MyComponent, OnInit} from 'angular2/core';
|
||||||
import {NgFor, LowerCasePipe, UpperCasePipe} from 'angular2/common';
|
import * as common from 'angular2/common';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
import {HeroDetailComponent} from './hero-detail.component';
|
import {HeroDetailComponent} from './hero-detail.component';
|
||||||
import {HeroService} from './hero.service';
|
import HeroService from './hero.service';
|
||||||
|
// thrown away
|
||||||
|
import 'angular2/core';
|
||||||
|
|
||||||
@Component({
|
@MyComponent({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
template:` + "`" + `
|
template:` + "`" + `
|
||||||
<h2>My Heroes</h2>
|
<h2>My Heroes</h2>
|
||||||
|
@ -235,9 +247,9 @@ const FILES: Directory = {
|
||||||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||||||
` +
|
` +
|
||||||
"`" + `,
|
"`" + `,
|
||||||
directives: [HeroDetailComponent, NgFor],
|
directives: [HeroDetailComponent, common.NgFor],
|
||||||
providers: [HeroService],
|
providers: [HeroService],
|
||||||
pipes: [LowerCasePipe, UpperCasePipe]
|
pipes: [common.LowerCasePipe, common.UpperCasePipe]
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
public title = 'Tour of Heroes';
|
public title = 'Tour of Heroes';
|
||||||
|
@ -282,7 +294,7 @@ const FILES: Directory = {
|
||||||
@Input() public hero: Hero;
|
@Input() public hero: Hero;
|
||||||
}`,
|
}`,
|
||||||
'mock-heroes.ts': `
|
'mock-heroes.ts': `
|
||||||
import {Hero} from './hero';
|
import {Hero as Hero} from './hero';
|
||||||
|
|
||||||
export const HEROES: Hero[] = [
|
export const HEROES: Hero[] = [
|
||||||
{"id": 11, "name": "Mr. Nice"},
|
{"id": 11, "name": "Mr. Nice"},
|
||||||
|
@ -296,13 +308,17 @@ const FILES: Directory = {
|
||||||
{"id": 19, "name": "Magma"},
|
{"id": 19, "name": "Magma"},
|
||||||
{"id": 20, "name": "Tornado"}
|
{"id": 20, "name": "Tornado"}
|
||||||
];`,
|
];`,
|
||||||
|
'default-exporter.ts': `
|
||||||
|
let a: string;
|
||||||
|
export default a;
|
||||||
|
`,
|
||||||
'hero.service.ts': `
|
'hero.service.ts': `
|
||||||
import {Injectable} from 'angular2/core';
|
import {Injectable} from 'angular2/core';
|
||||||
import {HEROES} from './mock-heroes';
|
import {HEROES} from './mock-heroes';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
class HeroService {
|
||||||
getHeros() {
|
getHeros() {
|
||||||
return Promise.resolve(HEROES);
|
return Promise.resolve(HEROES);
|
||||||
}
|
}
|
||||||
|
@ -311,7 +327,8 @@ const FILES: Directory = {
|
||||||
return new Promise<Hero[]>(resolve =>
|
return new Promise<Hero[]>(resolve =>
|
||||||
setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds
|
setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds
|
||||||
}
|
}
|
||||||
}`,
|
}
|
||||||
|
export default HeroService;`,
|
||||||
'cases-data.ts': `
|
'cases-data.ts': `
|
||||||
import {Injectable, Input} from 'angular2/core';
|
import {Injectable, Input} from 'angular2/core';
|
||||||
|
|
||||||
|
@ -348,7 +365,7 @@ const FILES: Directory = {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'cases-no-data.ts': `
|
'cases-no-data.ts': `
|
||||||
import {HeroService} from './hero.service';
|
import HeroService from './hero.service';
|
||||||
|
|
||||||
export class CaseCtor {
|
export class CaseCtor {
|
||||||
constructor(private _heroService: HeroService) { }
|
constructor(private _heroService: HeroService) { }
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe('Evaluator', () => {
|
||||||
program = service.getProgram();
|
program = service.getProgram();
|
||||||
typeChecker = program.getTypeChecker();
|
typeChecker = program.getTypeChecker();
|
||||||
symbols = new Symbols();
|
symbols = new Symbols();
|
||||||
evaluator = new Evaluator(typeChecker, symbols, f => f);
|
evaluator = new Evaluator(typeChecker, symbols, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have typescript errors in test data', () => {
|
it('should not have typescript errors in test data', () => {
|
||||||
|
@ -97,9 +97,9 @@ describe('Evaluator', () => {
|
||||||
it('should report recursive references as symbolic', () => {
|
it('should report recursive references as symbolic', () => {
|
||||||
var expressions = program.getSourceFile('expressions.ts');
|
var expressions = program.getSourceFile('expressions.ts');
|
||||||
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveA').initializer))
|
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveA').initializer))
|
||||||
.toEqual({__symbolic: "reference", name: "recursiveB", module: "expressions.ts"});
|
.toEqual({__symbolic: "reference", name: "recursiveB", module: undefined});
|
||||||
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer))
|
expect(evaluator.evaluateNode(findVar(expressions, 'recursiveB').initializer))
|
||||||
.toEqual({__symbolic: "reference", name: "recursiveA", module: "expressions.ts"});
|
.toEqual({__symbolic: "reference", name: "recursiveA", module: undefined});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,4 +160,4 @@ const FILES: Directory = {
|
||||||
|
|
||||||
@Pipe({name: someName, pure: someBool})
|
@Pipe({name: someName, pure: someBool})
|
||||||
export class B {}`
|
export class B {}`
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in New Issue