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:
Alex Eagle 2016-04-25 21:29:06 -07:00 committed by Alex Eagle
parent 35cd0ded22
commit d9648887b8
8 changed files with 252 additions and 236 deletions

View File

@ -1,12 +1,8 @@
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {
isArray,
isBlank,
isNumber,
isPresent,
isPrimitive,
isString,
Type
} from 'angular2/src/facade/lang';
import {
AttributeMetadata,
@ -35,13 +31,19 @@ import {ReflectorReader} from 'angular2/src/core/reflection/reflector_reader';
*/
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
* module import of an import statement.
* @param moduleId is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
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; }
/**
* 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.
*
* @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.
*/
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 initializeConversionMap(): any {
let core_metadata = 'angular2/src/core/metadata';
let core_metadata = this.host.resolveModule('angular2/src/core/metadata');
let conversionMap = this.conversionMap;
conversionMap.set(this.getStaticType(core_metadata, 'Directive'),
(moduleContext, expression) => {
@ -272,7 +274,7 @@ export class StaticReflector implements ReflectorReader {
if (isMetadataSymbolicCallExpression(expression)) {
let target = expression['expression'];
if (isMetadataSymbolicReferenceExpression(target)) {
let moduleId = this.normalizeModuleName(moduleContext, target['module']);
let moduleId = this.host.resolveModule(target['module'], moduleContext);
return this.getStaticType(moduleId, target['name']);
}
}
@ -417,7 +419,7 @@ export class StaticReflector implements ReflectorReader {
return null;
case "reference":
let referenceModuleName =
_this.normalizeModuleName(moduleContext, expression['module']);
_this.host.resolveModule(expression['module'], moduleContext);
let referenceModule = _this.getModuleMetadata(referenceModuleName);
let referenceValue = referenceModule['metadata'][expression['name']];
if (isClassMetadata(referenceValue)) {
@ -440,6 +442,9 @@ export class StaticReflector implements ReflectorReader {
return simplify(value);
}
/**
* @param module an absolute path to a module file.
*/
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!isPresent(moduleMetadata)) {
@ -460,13 +465,6 @@ export class StaticReflector implements ReflectorReader {
}
return result;
}
private normalizeModuleName(from: string, to: string): string {
if (to.startsWith('.')) {
return pathTo(from, to);
}
return to;
}
}
function isMetadataSymbolicCallExpression(expression: any): boolean {
@ -481,35 +479,3 @@ function isMetadataSymbolicReferenceExpression(expression: any): boolean {
function isClassMetadata(expression: any): boolean {
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;
}

View File

@ -1,27 +1,20 @@
import {
ddescribe,
describe,
xdescribe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
AsyncTestCompleter,
inject,
beforeEachProviders
} from 'angular2/testing_internal';
import {ListWrapper} from 'angular2/src/facade/collection';
import {StaticReflector, StaticReflectorHost} from 'angular2/src/compiler/static_reflector';
export function main() {
describe('StaticRefelector', () => {
describe('StaticReflector', () => {
it('should get annotations for NgFor', () => {
let host = new MockReflectorHost();
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);
expect(annotations.length).toEqual(1);
let annotation = annotations[0];
@ -33,15 +26,18 @@ export function main() {
let host = new MockReflectorHost();
let reflector = new StaticReflector(host);
let NgFor = reflector.getStaticType('angular2/src/common/directives/ng_for', 'NgFor');
let ViewContainerRef = reflector.getStaticType('angular2/src/core/linker/view_container_ref',
'ViewContainerRef');
let TemplateRef =
reflector.getStaticType('angular2/src/core/linker/template_ref', 'TemplateRef');
let NgFor = reflector.getStaticType(
host.resolveModule('angular2/src/common/directives/ng_for'), 'NgFor');
let ViewContainerRef = reflector.getStaticType(
host.resolveModule('angular2/src/core/linker/view_container_ref'), 'ViewContainerRef');
let TemplateRef = reflector.getStaticType(
host.resolveModule('angular2/src/core/linker/template_ref'), 'TemplateRef');
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(
'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);
expect(parameters)
@ -53,7 +49,7 @@ export function main() {
let reflector = new StaticReflector(host);
let HeroDetailComponent =
reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent');
reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent');
let annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1);
let annotation = annotations[0];
@ -64,7 +60,7 @@ export function main() {
let host = new MockReflectorHost();
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);
expect(annotations).toEqual([]);
});
@ -74,7 +70,7 @@ export function main() {
let reflector = new StaticReflector(host);
let HeroDetailComponent =
reflector.getStaticType('./app/hero-detail.component', 'HeroDetailComponent');
reflector.getStaticType('/src/app/hero-detail.component.ts', 'HeroDetailComponent');
let props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy();
});
@ -83,7 +79,7 @@ export function main() {
let host = new MockReflectorHost();
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);
expect(properties).toEqual({});
});
@ -92,7 +88,7 @@ export function main() {
let host = new MockReflectorHost();
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);
expect(parameters).toEqual([]);
});
@ -301,7 +297,7 @@ export function main() {
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 reflector = new StaticReflector(host);
@ -320,20 +316,56 @@ export function main() {
let host = new MockReflectorHost();
let reflector = new StaticReflector(host);
expect(
reflector.simplify('./cases', ({__symbolic: "reference", module: "./extern", name: "s"})))
expect(reflector.simplify('/src/cases',
({__symbolic: "reference", module: "./extern", name: "s"})))
.toEqual("s");
});
});
}
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 {
return {
'angular2/src/common/directives/ng_for':
'/tmp/angular2/src/common/directives/ng_for.d.ts':
{
"__symbolic": "module",
"module": "./ng_for",
"metadata":
{
"NgFor":
@ -393,27 +425,17 @@ class MockReflectorHost implements StaticReflectorHost {
}
}
},
'angular2/src/core/linker/view_container_ref':
{
"module": "./view_container_ref",
"metadata": {"ViewContainerRef": {"__symbolic": "class"}}
},
'angular2/src/core/linker/template_ref':
'/tmp/angular2/src/core/linker/view_container_ref.d.ts':
{"metadata": {"ViewContainerRef": {"__symbolic": "class"}}},
'/tmp/angular2/src/core/linker/template_ref.d.ts':
{"module": "./template_ref", "metadata": {"TemplateRef": {"__symbolic": "class"}}},
'angular2/src/core/change_detection/differs/iterable_differs':
{
"module": "./iterable_differs",
"metadata": {"IterableDiffers": {"__symbolic": "class"}}
},
'angular2/src/core/change_detection/change_detector_ref':
{
"module": "./change_detector_ref",
"metadata": {"ChangeDetectorRef": {"__symbolic": "class"}}
},
'./app/hero-detail.component':
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
{"metadata": {"IterableDiffers": {"__symbolic": "class"}}},
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
{"metadata": {"ChangeDetectorRef": {"__symbolic": "class"}}},
'/src/app/hero-detail.component.ts':
{
"__symbolic": "module",
"module": "./hero-detail.component",
"metadata":
{
"HeroDetailComponent":
@ -459,8 +481,8 @@ class MockReflectorHost implements StaticReflectorHost {
}
}
},
'./extern': {
"__symbolic": "module", module: './extern', metadata: { s: "s" }
'/src/extern.d.ts': {
"__symbolic": "module", metadata: { s: "s" }
}
}
[moduleId];

View File

@ -88,16 +88,7 @@ class DiffingTSCompiler implements DiffingBroccoliPlugin {
this.tsServiceHost = new CustomLanguageServiceHost(this.tsOpts, this.rootFilePaths,
this.fileRegistry, this.inputPath);
this.tsService = ts.createLanguageService(this.tsServiceHost, ts.createDocumentRegistry());
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$/, '');
}
}
});
this.metadataCollector = new MetadataCollector();
}

View File

@ -1,5 +1,5 @@
import * as ts from 'typescript';
import {Evaluator} from './evaluator';
import {Evaluator, ImportMetadata, ImportSpecifierMetadata} from './evaluator';
import {Symbols} from './symbols';
import {
ClassMetadata,
@ -13,46 +13,56 @@ import {
MethodMetadata
} 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.
*/
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
@ -60,17 +70,7 @@ export class MetadataCollector {
*/
public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata {
const locals = new Symbols();
const moduleNameOf = (fileName: string) => {
// 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);
const evaluator = new Evaluator(typeChecker, locals, this.collectImports(sourceFile));
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
@ -80,18 +80,7 @@ export class MetadataCollector {
if (type) {
let symbol = type.getSymbol();
if (symbol) {
if (symbol.flags & ts.SymbolFlags.Alias) {
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
};
}
return evaluator.symbolReference(symbol);
}
}
}
@ -206,6 +195,7 @@ export class MetadataCollector {
}
}
}
return metadata && {__symbolic: "module", module: moduleNameOf(sourceFile.fileName), metadata};
return metadata && {__symbolic: "module", metadata};
}
}

View File

@ -3,7 +3,6 @@ import {Symbols} from './symbols';
import {
MetadataValue,
MetadataObject,
MetadataSymbolicCallExpression,
MetadataSymbolicReferenceExpression
} from './schema';
@ -58,40 +57,72 @@ function isDefined(obj: any): boolean {
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
* possible.
*/
export class Evaluator {
constructor(private typeChecker: ts.TypeChecker, private symbols: Symbols,
private moduleNameOf: (fileName: string) => string) {}
private imports: ImportMetadata[]) {}
// TODO: Determine if the first declaration is deterministic.
private symbolFileName(symbol: ts.Symbol): string {
symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression {
if (symbol) {
if (symbol.flags & ts.SymbolFlags.Alias) {
symbol = this.typeChecker.getAliasedSymbol(symbol);
}
const declarations = symbol.getDeclarations();
if (declarations && declarations.length > 0) {
const sourceFile = declarations[0].getSourceFile();
if (sourceFile) {
return sourceFile.fileName;
let module: string;
let name = symbol.name;
for (const eachImport of this.imports) {
if (symbol.name === eachImport.defaultName) {
module = eachImport.from;
name = undefined;
}
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};
}
}
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 {
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));
}
@ -115,7 +146,7 @@ export class Evaluator {
* - 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
* 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.
*/
public isFoldable(node: ts.Node): boolean {
@ -129,7 +160,7 @@ export class Evaluator {
return everyNodeChild(node, child => {
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
const propertyAssignment = <ts.PropertyAssignment>child;
return this.isFoldableWorker(propertyAssignment.initializer, folding)
return this.isFoldableWorker(propertyAssignment.initializer, folding);
}
return false;
});
@ -272,6 +303,9 @@ export class Evaluator {
const expression = this.evaluateNode(propertyAccessExpression.expression);
const member = this.nameOf(propertyAccessExpression.name);
if (this.isFoldable(propertyAccessExpression.expression)) return expression[member];
if (this.findImportNamespace(propertyAccessExpression)) {
return this.nodeSymbolReference(propertyAccessExpression);
}
if (isDefined(expression)) {
return {__symbolic: "select", expression, member};
}

View File

@ -1,8 +1,5 @@
// TODO: fix typings for __symbolic once angular moves to 1.8
export interface ModuleMetadata {
__symbolic: string; // "module";
module: string;
__symbolic: "module";
metadata: {[name: string]: (ClassMetadata | MetadataValue)};
}
export function isModuleMetadata(value: any): value is ModuleMetadata {
@ -10,7 +7,7 @@ export function isModuleMetadata(value: any): value is ModuleMetadata {
}
export interface ClassMetadata {
__symbolic: string; // "class";
__symbolic: "class";
decorators?: MetadataSymbolicExpression[];
members?: MetadataMap;
}
@ -21,7 +18,7 @@ export function isClassMetadata(value: any): value is ClassMetadata {
export interface MetadataMap { [name: string]: MemberMetadata[]; }
export interface MemberMetadata {
__symbolic: string; // "constructor" | "method" | "property";
__symbolic: "constructor" | "method" | "property";
decorators?: MetadataSymbolicExpression[];
}
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 {
// __symbolic: "constructor" | "method";
__symbolic: "constructor" | "method";
parameterDecorators?: MetadataSymbolicExpression[][];
}
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 {
// __symbolic: "constructor";
__symbolic: "constructor";
parameters?: MetadataSymbolicExpression[];
}
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 MetadataSymbolicExpression {
__symbolic: string; // "binary" | "call" | "index" | "pre" | "reference" | "select"
__symbolic: "binary" | "call" | "index" | "pre" | "reference" | "select"
}
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
if (value) {
@ -78,10 +75,9 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
}
export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression {
// __symbolic: "binary";
operator: string; // "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" |
// "<=" | ">=" | "instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" |
// "*" | "/" | "%" | "**";
__symbolic: "binary";
operator: "&&" | "||" | "|" | "^" | "&" | "==" | "!=" | "===" | "!==" | "<" | ">" | "<=" | ">=" |
"instanceof" | "in" | "as" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "**";
left: MetadataValue;
right: MetadataValue;
}
@ -91,7 +87,7 @@ export function isMetadataSymbolicBinaryExpression(
}
export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression {
// __symbolic: "index";
__symbolic: "index";
expression: MetadataValue;
index: MetadataValue;
}
@ -101,7 +97,7 @@ export function isMetadataSymbolicIndexExpression(
}
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
// __symbolic: "call";
__symbolic: "call";
expression: MetadataValue;
arguments?: MetadataValue[];
}
@ -111,8 +107,8 @@ export function isMetadataSymbolicCallExpression(
}
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {
// __symbolic: "pre";
operator: string; // "+" | "-" | "~" | "!";
__symbolic: "pre";
operator: "+" | "-" | "~" | "!";
operand: MetadataValue;
}
export function isMetadataSymbolicPrefixExpression(
@ -121,7 +117,7 @@ export function isMetadataSymbolicPrefixExpression(
}
export interface MetadataSymbolicReferenceExpression extends MetadataSymbolicExpression {
// __symbolic: "reference";
__symbolic: "reference";
name: string;
module: string;
}
@ -131,7 +127,7 @@ export function isMetadataSymbolicReferenceExpression(
}
export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression {
// __symbolic: "select";
__symbolic: "select";
expression: MetadataValue;
name: string;
}

View File

@ -29,12 +29,26 @@ describe('Collector', () => {
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", () => {
const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
const metadata = collector.getMetadata(sourceFile, typeChecker);
expect(metadata).toEqual({
__symbolic: 'module',
module: './hero-detail.component',
metadata: {
HeroDetailComponent: {
__symbolic: 'class',
@ -83,7 +97,6 @@ describe('Collector', () => {
const metadata = collector.getMetadata(sourceFile, typeChecker);
expect(metadata).toEqual({
__symbolic: 'module',
module: './app.component',
metadata: {
AppComponent: {
__symbolic: 'class',
@ -113,8 +126,7 @@ describe('Collector', () => {
},
{__symbolic: 'reference', name: 'NgFor', module: 'angular2/common'}
],
providers:
[{__symbolic: 'reference', name: 'HeroService', module: './hero.service'}],
providers: [{__symbolic: 'reference', name: undefined, module: './hero.service'}],
pipes: [
{__symbolic: 'reference', name: 'LowerCasePipe', module: 'angular2/common'},
{
@ -131,9 +143,8 @@ describe('Collector', () => {
__ctor__: [
{
__symbolic: 'constructor',
parameters: [
{__symbolic: 'reference', module: './hero.service', name: 'HeroService'}
]
parameters:
[{__symbolic: 'reference', name: undefined, module: './hero.service'}]
}
]
}
@ -147,7 +158,6 @@ describe('Collector', () => {
const metadata = collector.getMetadata(sourceFile, typeChecker);
expect(metadata).toEqual({
__symbolic: 'module',
module: './mock-heroes',
metadata: {
HEROES: [
{"id": 11, "name": "Mr. Nice"},
@ -187,7 +197,7 @@ describe('Collector', () => {
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 = {
name: [
{
@ -215,13 +225,15 @@ describe('Collector', () => {
const FILES: Directory = {
'app': {
'app.component.ts': `
import {Component, OnInit} from 'angular2/core';
import {NgFor, LowerCasePipe, UpperCasePipe} from 'angular2/common';
import {Component as MyComponent, OnInit} from 'angular2/core';
import * as common from 'angular2/common';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroService} from './hero.service';
@Component({
import HeroService from './hero.service';
// thrown away
import 'angular2/core';
@MyComponent({
selector: 'my-app',
template:` + "`" + `
<h2>My Heroes</h2>
@ -235,9 +247,9 @@ const FILES: Directory = {
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
` +
"`" + `,
directives: [HeroDetailComponent, NgFor],
directives: [HeroDetailComponent, common.NgFor],
providers: [HeroService],
pipes: [LowerCasePipe, UpperCasePipe]
pipes: [common.LowerCasePipe, common.UpperCasePipe]
})
export class AppComponent implements OnInit {
public title = 'Tour of Heroes';
@ -282,7 +294,7 @@ const FILES: Directory = {
@Input() public hero: Hero;
}`,
'mock-heroes.ts': `
import {Hero} from './hero';
import {Hero as Hero} from './hero';
export const HEROES: Hero[] = [
{"id": 11, "name": "Mr. Nice"},
@ -296,13 +308,17 @@ const FILES: Directory = {
{"id": 19, "name": "Magma"},
{"id": 20, "name": "Tornado"}
];`,
'default-exporter.ts': `
let a: string;
export default a;
`,
'hero.service.ts': `
import {Injectable} from 'angular2/core';
import {HEROES} from './mock-heroes';
import {Hero} from './hero';
@Injectable()
export class HeroService {
class HeroService {
getHeros() {
return Promise.resolve(HEROES);
}
@ -311,7 +327,8 @@ const FILES: Directory = {
return new Promise<Hero[]>(resolve =>
setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds
}
}`,
}
export default HeroService;`,
'cases-data.ts': `
import {Injectable, Input} from 'angular2/core';
@ -348,7 +365,7 @@ const FILES: Directory = {
}
`,
'cases-no-data.ts': `
import {HeroService} from './hero.service';
import HeroService from './hero.service';
export class CaseCtor {
constructor(private _heroService: HeroService) { }

View File

@ -18,7 +18,7 @@ describe('Evaluator', () => {
program = service.getProgram();
typeChecker = program.getTypeChecker();
symbols = new Symbols();
evaluator = new Evaluator(typeChecker, symbols, f => f);
evaluator = new Evaluator(typeChecker, symbols, []);
});
it('should not have typescript errors in test data', () => {
@ -97,9 +97,9 @@ describe('Evaluator', () => {
it('should report recursive references as symbolic', () => {
var expressions = program.getSourceFile('expressions.ts');
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))
.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})
export class B {}`
}
};