refactor(compiler): rename /compiler_cli to /compiler-cli

This commit is contained in:
Victor Berchet
2016-06-02 11:33:53 -07:00
parent 01dd7dde24
commit 1090601e8b
33 changed files with 16 additions and 16 deletions

View File

@ -0,0 +1,93 @@
# Angular Template Compiler
Angular applications are built with templates, which may be `.html` or `.css` files,
or may be inline `template` attributes on Decorators like `@Component`.
These templates are compiled into executable JS at application runtime (except in `interpretation` mode).
This compilation can occur on the client, but it results in slower bootstrap time, and also
requires that the compiler be included in the code downloaded to the client.
You can produce smaller, faster applications by running Angular's compiler as a build step,
and then downloading only the executable JS to the client.
## Install and use
```
# First install angular, see https://github.com/angular/angular/blob/master/CHANGELOG.md#200-rc0-2016-05-02
$ npm install @angular/compiler-cli typescript@next @angular/platform-server @angular/compiler
# Optional sanity check, make sure TypeScript can compile.
$ ./node_modules/.bin/tsc -p path/to/project
# ngc is a drop-in replacement for tsc.
$ ./node_modules/.bin/ngc -p path/to/project
```
In order to write a `bootstrap` that imports the generated code, you should first write your
top-level component, and run `ngc` once to produce a generated `.ngfactory.ts` file.
Then you can add an import statement in the `bootstrap` allowing you to bootstrap off the
generated code.
## Configuration
The `tsconfig.json` file may contain an additional configuration block:
```
"angularCompilerOptions": {
"genDir": "."
}
```
the `genDir` option controls the path (relative to `tsconfig.json`) where the generated file tree
will be written. If `genDir` is not set, then the code will be generated in the source tree, next
to your original sources. More options may be added as we implement more features.
We recommend you avoid checking generated files into version control. This permits a state where
the generated files in the repository were created from sources that were never checked in,
making it impossible to reproduce the current state. Also, your changes will effectively appear
twice in code reviews, with the generated version inscrutible by the reviewer.
In TypeScript 1.8, the generated sources will have to be written alongside your originals,
so set `genDir` to the same location as your files (typicially the same as `rootDir`).
Add `**/*.ngfactory.ts` to your `.gitignore` or other mechanism for your version control system.
In TypeScript 1.9 and above, you can add a generated folder into your application,
such as `codegen`. Using the `rootDirs` option, you can allow relative imports like
`import {} from './foo.ngfactory'` even though the `src` and `codegen` trees are distinct.
Add `**/codegen` to your `.gitignore` or similar.
Note that in the second option, TypeScript will emit the code into two parallel directories
as well. This is by design, see https://github.com/Microsoft/TypeScript/issues/8245.
This makes the configuration of your runtime module loader more complex, so we don't recommend
this option yet.
See the example in the `test/` directory for a working example.
## Compiler CLI
This program mimics the TypeScript tsc command line. It accepts a `-p` flag which points to a
`tsconfig.json` file, or a directory containing one.
This CLI is intended for demos, prototyping, or for users with simple build systems
that run bare `tsc`.
Users with a build system should expect an Angular 2 template plugin. Such a plugin would be
based on the `index.ts` in this directory, but should share the TypeScript compiler instance
with the one already used in the plugin for TypeScript typechecking and emit.
## Design
At a high level, this program
- collects static metadata about the sources using the `tsc-wrapped` package in angular2
- uses the `OfflineCompiler` from `angular2/src/compiler/compiler` to codegen additional `.ts` files
- these `.ts` files are written to the `genDir` path, then compiled together with the application.
## For developers
```
# Build angular2 and the compiler
./build.sh
# Run the test once
# (First edit the LINKABLE_PKGS to use npm link instead of npm install)
$ ./scripts/ci-lite/offline_compiler_test.sh
# Keep a package fresh in watch mode
./node_modules/.bin/tsc -p modules/@angular/compiler/tsconfig-es5.json -w
# Iterate on the test
cd /tmp/wherever/e2e_test.1464388257/
./node_modules/.bin/ngc
./node_modules/.bin/jasmine test/*_spec.js
```

View File

@ -0,0 +1,4 @@
export {CodeGenerator} from './src/codegen';
export {NodeReflectorHost} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
export * from '@angular/tsc-wrapped';

View File

@ -0,0 +1 @@
<div></div>

View File

@ -0,0 +1,27 @@
import {Component} from '@angular/core';
@Component({
selector: 'my-comp',
template: '<div></div>',
})
export class MyComp {
}
@Component({
selector: 'next-comp',
templateUrl: './multiple_components.html',
})
export class NextComp {
}
// Verify that exceptions from DirectiveResolver don't propagate
export function NotADirective(c: any): void {}
@NotADirective
export class HasCustomDecorator {
}
// Verify that custom decorators have metadata collected, eg Ionic
export function Page(c: any): (f: Function) => void {return c;}
@Page({template: 'Ionic template'})
export class AnIonicPage {}

View File

@ -0,0 +1,3 @@
@import './shared.css';
.green { color: green }

View File

@ -0,0 +1,4 @@
<div [attr.array]="[0]" [attr.map]="{a:1}">{{ctxProp}}</div>
<form><input type="button" [(ngModel)]="ctxProp"/></form>
<my-comp *ngIf="ctxBool"></my-comp>
<div *ngFor="let x of ctxArr" [attr.value]="x"></div>

View File

@ -0,0 +1,17 @@
import {Component, Inject} from '@angular/core';
import {FORM_DIRECTIVES, NgIf, NgFor} from '@angular/common';
import {MyComp} from './a/multiple_components';
@Component({
selector: 'basic',
templateUrl: './basic.html',
styles: ['.red { color: red }'],
styleUrls: ['./basic.css'],
directives: [MyComp, FORM_DIRECTIVES, NgIf, NgFor]
})
export class Basic {
ctxProp: string;
ctxBool: boolean;
ctxArr: any[] = [];
constructor() { this.ctxProp = 'initialValue'; }
}

View File

@ -0,0 +1,8 @@
import {coreBootstrap, ReflectiveInjector} from '@angular/core';
import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser';
import {BasicNgFactory} from './basic.ngfactory';
import {Basic} from './basic';
const appInjector =
ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector);
coreBootstrap(BasicNgFactory, appInjector);

View File

@ -0,0 +1,2 @@
// Verify we don't try to extract metadata for .d.ts files
export declare var a: string;

View File

@ -0,0 +1,29 @@
import {Component, Inject, OpaqueToken} from '@angular/core';
import {NgIf} from '@angular/common';
export const SOME_OPAQUE_TOKEN = new OpaqueToken('opaqueToken');
@Component({
selector: 'comp-providers',
template: '',
providers: [
{provide: 'strToken', useValue: 'strValue'},
{provide: SOME_OPAQUE_TOKEN, useValue: 10},
{provide: 'reference', useValue: NgIf},
{provide: 'complexToken', useValue: {a: 1, b: ['test', SOME_OPAQUE_TOKEN]}},
]
})
export class CompWithProviders {
constructor(@Inject('strToken') public ctxProp: string) {}
}
@Component({
selector: 'cmp-reference',
template: `
<input #a>{{a.value}}
<div *ngIf="true">{{a.value}}</div>
`,
directives: [NgIf]
})
export class CompWithReferences {
}

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
@Component({
selector: 'comp-with-proj',
template: '<ng-content></ng-content>'
})
export class CompWithProjection {
}
@Component({
selector: 'main',
template: '<comp-with-proj><span greeting="Hello world!"></span></comp-with-proj>',
directives: [CompWithProjection]
})
export class MainComp {}

View File

@ -0,0 +1 @@
.blue { color: blue }

View File

@ -0,0 +1,73 @@
// Only needed to satisfy the check in core/src/util/decorators.ts
// TODO(alexeagle): maybe remove that check?
require('reflect-metadata');
require('@angular/platform-server/src/parse5_adapter.js').Parse5DomAdapter.makeCurrent();
require('zone.js/dist/zone-node.js');
require('zone.js/dist/long-stack-trace-zone.js');
import * as fs from 'fs';
import * as path from 'path';
import {BasicNgFactory} from '../src/basic.ngfactory';
import {MyComp} from '../src/a/multiple_components';
import {ReflectiveInjector, DebugElement, getDebugNode} from '@angular/core';
import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser';
describe("template codegen output", () => {
const outDir = 'src';
it("should lower Decorators without reflect-metadata", () => {
const jsOutput = path.join(outDir, 'basic.js');
expect(fs.existsSync(jsOutput)).toBeTruthy();
expect(fs.readFileSync(jsOutput, {encoding: 'utf-8'})).not.toContain('Reflect.decorate');
});
it("should produce metadata.json outputs", () => {
const metadataOutput = path.join(outDir, 'basic.metadata.json');
expect(fs.existsSync(metadataOutput)).toBeTruthy();
const output = fs.readFileSync(metadataOutput, {encoding: 'utf-8'});
expect(output).toContain('"decorators":');
expect(output).toContain('"module":"@angular/core","name":"Component"');
});
it("should write .d.ts files", () => {
const dtsOutput = path.join(outDir, 'basic.d.ts');
expect(fs.existsSync(dtsOutput)).toBeTruthy();
expect(fs.readFileSync(dtsOutput, {encoding: 'utf-8'})).toContain('Basic');
});
it("should be able to create the basic component", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS,
browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector);
expect(comp.instance).toBeTruthy();
});
it("should support ngIf", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS,
browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector);
var debugElement = <DebugElement>getDebugNode(comp.location.nativeElement);
expect(debugElement.children.length).toBe(2);
comp.instance.ctxBool = true;
comp.changeDetectorRef.detectChanges();
expect(debugElement.children.length).toBe(3);
expect(debugElement.children[2].injector.get(MyComp)).toBeTruthy();
});
it("should support ngFor", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS,
browserPlatform().injector);
var comp = BasicNgFactory.create(appInjector);
var debugElement = <DebugElement>getDebugNode(comp.location.nativeElement);
expect(debugElement.children.length).toBe(2);
// test NgFor
comp.instance.ctxArr = [1, 2];
comp.changeDetectorRef.detectChanges();
expect(debugElement.children.length).toBe(4);
expect(debugElement.children[2].attributes['value']).toBe('1');
expect(debugElement.children[3].attributes['value']).toBe('2');
});
});

View File

@ -0,0 +1,16 @@
import {MainCompNgFactory} from '../src/projection.ngfactory';
import {CompWithProjection} from '../src/projection';
import {ReflectiveInjector, DebugElement, getDebugNode} from '@angular/core';
import {browserPlatform, BROWSER_APP_PROVIDERS, By} from '@angular/platform-browser';
describe("content projection", () => {
it("should support basic content projection", () => {
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector);
var mainComp = MainCompNgFactory.create(appInjector);
var debugElement = <DebugElement>getDebugNode(mainComp.location.nativeElement);
var compWithProjection = debugElement.query(By.directive(CompWithProjection));
expect(compWithProjection.children.length).toBe(1);
expect(compWithProjection.children[0].attributes['greeting']).toEqual('Hello world!');
});
});

View File

@ -0,0 +1,18 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": "."
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
}
}

View File

@ -0,0 +1,38 @@
{
"name": "@angular/compiler-cli",
"version": "0.0.0-PLACEHOLDER",
"description": "Execute angular2 template compiler in nodejs.",
"main": "index.js",
"typings": "index.d.ts",
"bin": {
"ngc": "./src/main.js"
},
"dependencies": {
"@angular/tsc-wrapped": "^0.1.0",
"reflect-metadata": "^0.1.2",
"parse5": "1.3.2"
},
"peerDependencies": {
"typescript": "^1.9.0-dev",
"@angular/compiler": "0.0.0-PLACEHOLDER",
"@angular/platform-server": "0.0.0-PLACEHOLDER",
"@angular/core": "0.0.0-PLACEHOLDER"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
},
"keywords": [
"angular",
"compiler"
],
"contributors": [
"Tobias Bosch <tbosch@google.com> (https://angular.io/)",
"Alex Eagle <alexeagle@google.com> (https://angular.io/)"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/angular/issues"
},
"homepage": "https://github.com/angular/angular/tree/master/tools/compiler-cli"
}

View File

@ -0,0 +1,181 @@
/**
* Transform template html and css into executable code.
* Intended to be used in a build step.
*/
import * as ts from 'typescript';
import * as path from 'path';
import {AngularCompilerOptions} from '@angular/tsc-wrapped';
import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import {StaticReflector} from './static_reflector';
import {
CompileMetadataResolver,
HtmlParser,
DirectiveNormalizer,
Lexer,
Parser,
TemplateParser,
DomElementSchemaRegistry,
StyleCompiler,
ViewCompiler,
TypeScriptEmitter
} from './compiler_private';
import {Parse5DomAdapter} from '@angular/platform-server';
import {NodeReflectorHost} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const PREAMBLE = `/**
* This file is generated by the Angular 2 template compiler.
* Do not edit.
*/
/* tslint:disable */
`;
export class CodeGenerator {
constructor(private options: AngularCompilerOptions,
private program: ts.Program, public host: ts.CompilerHost,
private staticReflector: StaticReflector, private resolver: CompileMetadataResolver,
private compiler: compiler.OfflineCompiler,
private reflectorHost: NodeReflectorHost) {}
private generateSource(metadatas: compiler.CompileDirectiveMetadata[]) {
const normalize = (metadata: compiler.CompileDirectiveMetadata) => {
const directiveType = metadata.type.runtime;
const directives = this.resolver.getViewDirectivesMetadata(directiveType);
return Promise.all(directives.map(d => this.compiler.normalizeDirectiveMetadata(d)))
.then(normalizedDirectives => {
const pipes = this.resolver.getViewPipesMetadata(directiveType);
return new compiler.NormalizedComponentWithViewDirectives(metadata,
normalizedDirectives, pipes);
});
};
return Promise.all(metadatas.map(normalize))
.then(normalizedCompWithDirectives =>
this.compiler.compileTemplates(normalizedCompWithDirectives));
}
private readComponents(absSourcePath: string) {
const result: Promise<compiler.CompileDirectiveMetadata>[] = [];
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
let directive: compiler.CompileDirectiveMetadata;
directive = this.resolver.maybeGetDirectiveMetadata(<any>staticType);
if (!directive || !directive.isComponent) {
continue;
}
result.push(this.compiler.normalizeDirectiveMetadata(directive));
}
return result;
}
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string) {
let root = this.options.basePath;
for (let eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
return path.join(this.options.genDir, path.relative(root, filePath));
}
// TODO(tbosch): add a cache for shared css files
// TODO(tbosch): detect cycles!
private generateStylesheet(filepath: string, shim: boolean): Promise<any> {
return this.compiler.loadAndCompileStylesheet(filepath, shim, '.ts')
.then((sourceWithImports) => {
const emitPath = this.calculateEmitPath(sourceWithImports.source.moduleUrl);
// TODO(alexeagle): should include the sourceFile to the WriteFileCallback
this.host.writeFile(emitPath, PREAMBLE + sourceWithImports.source.source, false);
return Promise.all(
sourceWithImports.importedUrls.map(url => this.generateStylesheet(url, shim)));
});
}
codegen(): Promise<any> {
Parse5DomAdapter.makeCurrent();
let stylesheetPromises: Promise<any>[] = [];
const generateOneFile = (absSourcePath: string) =>
Promise.all(this.readComponents(absSourcePath))
.then((metadatas: compiler.CompileDirectiveMetadata[]) => {
if (!metadatas || !metadatas.length) {
return;
}
metadatas.forEach((metadata) => {
let stylesheetPaths = metadata && metadata.template && metadata.template.styleUrls;
if (stylesheetPaths) {
stylesheetPaths.forEach((path) => {
stylesheetPromises.push(this.generateStylesheet(
path, metadata.template.encapsulation === ViewEncapsulation.Emulated));
});
}
});
return this.generateSource(metadatas);
})
.then(generated => {
if (generated) {
const sourceFile = this.program.getSourceFile(absSourcePath);
const emitPath = this.calculateEmitPath(generated.moduleUrl);
this.host.writeFile(emitPath, PREAMBLE + generated.source, false, () => {},
[sourceFile]);
}
})
.catch((e) => { console.error(e.stack); });
var compPromises = this.program.getSourceFiles()
.map(sf => sf.fileName)
.filter(f => !GENERATED_FILES.test(f))
.map(generateOneFile);
return Promise.all(stylesheetPromises.concat(compPromises));
}
static create(options: AngularCompilerOptions, program: ts.Program,
compilerHost: ts.CompilerHost): CodeGenerator {
const xhr: compiler.XHR = {get: (s: string) => Promise.resolve(compilerHost.readFile(s))};
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
const reflectorHost = new NodeReflectorHost(program, compilerHost, options);
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new HtmlParser();
const config = new compiler.CompilerConfig(true, true, true);
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
const parser = new Parser(new Lexer());
const tmplParser = new TemplateParser(parser, new DomElementSchemaRegistry(), htmlParser,
/*console*/ null, []);
const offlineCompiler = new compiler.OfflineCompiler(
normalizer, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(new compiler.CompilerConfig(true, true, true)),
new TypeScriptEmitter(reflectorHost), xhr);
const resolver = new CompileMetadataResolver(
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
new compiler.ViewResolver(staticReflector), null, null, staticReflector);
return new CodeGenerator(options, program, compilerHost, staticReflector, resolver,
offlineCompiler, reflectorHost);
}
}

View File

@ -0,0 +1,38 @@
import {__compiler_private__ as _c} from '@angular/compiler';
export var AssetUrl: typeof _c.AssetUrl = _c.AssetUrl;
export type AssetUrl = _c.AssetUrl;
export var ImportGenerator: typeof _c.ImportGenerator = _c.ImportGenerator;
export type ImportGenerator = _c.ImportGenerator;
export type CompileMetadataResolver = _c.CompileMetadataResolver;
export var CompileMetadataResolver: typeof _c.CompileMetadataResolver = _c.CompileMetadataResolver;
export type HtmlParser = _c.HtmlParser;
export var HtmlParser: typeof _c.HtmlParser = _c.HtmlParser;
export type DirectiveNormalizer = _c.DirectiveNormalizer;
export var DirectiveNormalizer: typeof _c.DirectiveNormalizer = _c.DirectiveNormalizer;
export type Lexer = _c.Lexer;
export var Lexer: typeof _c.Lexer = _c.Lexer;
export type Parser = _c.Parser;
export var Parser: typeof _c.Parser = _c.Parser;
export type TemplateParser = _c.TemplateParser;
export var TemplateParser: typeof _c.TemplateParser = _c.TemplateParser;
export type DomElementSchemaRegistry = _c.DomElementSchemaRegistry;
export var DomElementSchemaRegistry: typeof _c.DomElementSchemaRegistry =
_c.DomElementSchemaRegistry;
export type StyleCompiler = _c.StyleCompiler;
export var StyleCompiler: typeof _c.StyleCompiler = _c.StyleCompiler;
export type ViewCompiler = _c.ViewCompiler;
export var ViewCompiler: typeof _c.ViewCompiler = _c.ViewCompiler;
export type TypeScriptEmitter = _c.TypeScriptEmitter;
export var TypeScriptEmitter: typeof _c.TypeScriptEmitter = _c.TypeScriptEmitter;

View File

@ -0,0 +1,9 @@
import {__core_private__ as r, __core_private_types__ as t} from '@angular/core';
export type ReflectorReader = t.ReflectorReader;
export var ReflectorReader: typeof t.ReflectorReader = r.ReflectorReader;
export type ReflectionCapabilities = t.ReflectionCapabilities;
export var ReflectionCapabilities: typeof t.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof t.reflector = r.reflector;

View File

@ -0,0 +1,25 @@
#!/usr/bin/env node
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import {CodeGenerator} from './codegen';
function codegen(ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, program, host).codegen();
}
// CLI entry point
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2));
tsc.main(args.p || args.project || '.', args.basePath, codegen)
.then(exitCode => process.exit(exitCode))
.catch(e => {
console.error(e.stack);
console.error("Compilation failed");
process.exit(1);
});
}

View File

@ -0,0 +1,169 @@
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
import * as ts from 'typescript';
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import {ImportGenerator, AssetUrl} from './compiler_private';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
export class NodeReflectorHost implements StaticReflectorHost, ImportGenerator {
private metadataCollector = new MetadataCollector();
constructor(private program: ts.Program, private compilerHost: ts.CompilerHost,
private options: AngularCompilerOptions) {}
angularImportLocations() {
return {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/decorators',
diMetadata: '@angular/core/src/di/metadata',
provider: '@angular/core/src/di/provider'
};
}
private resolve(m: string, containingFile: string) {
const resolved =
ts.resolveModuleName(m, containingFile, this.options, this.compilerHost).resolvedModule;
return resolved ? resolved.resolvedFileName : null;
};
private normalizeAssetUrl(url: string): string {
let assetUrl = AssetUrl.parse(url);
return assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null;
}
private resolveAssetUrl(url: string, containingFile: string): string {
let assetUrl = this.normalizeAssetUrl(url);
if (assetUrl) {
return this.resolve(assetUrl, containingFile);
}
return url;
}
/**
* We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work.
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
* they are resolvable by the moduleResolution strategy from the CompilerHost.
*/
getImportPath(containingFile: string, importedFile: string) {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');
// TODO(tbosch): if a file does not yet exist (because we compile it later),
// we still need to create it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
if (this.options.trace) {
console.log(`Generating empty file ${importedFile} to allow resolution of import`);
}
this.compilerHost.writeFile(importedFile, '', false);
fs.writeFileSync(importedFile, '');
}
const parts = importedFile.replace(EXT, '').split(path.sep).filter(p => !!p);
for (let index = parts.length - 1; index >= 0; index--) {
let candidate = parts.slice(index, parts.length).join(path.sep);
if (this.resolve('.' + path.sep + candidate, containingFile) === importedFile) {
return `./${candidate}`;
}
if (this.resolve(candidate, containingFile) === importedFile) {
return candidate;
}
}
throw new Error(
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
}
findDeclaration(module: string, symbolName: string, containingFile: string,
containingModule?: string): StaticSymbol {
if (!containingFile || !containingFile.length) {
if (module.indexOf(".") === 0) {
throw new Error("Resolution of relative paths requires a containing file.");
}
// Any containing file gives the same result for absolute imports
containingFile = path.join(this.options.basePath, 'index.ts');
}
try {
let assetUrl = this.normalizeAssetUrl(module);
if (assetUrl) {
module = assetUrl;
}
const filePath = this.resolve(module, containingFile);
if (!filePath) {
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
}
const tc = this.program.getTypeChecker();
const sf = this.program.getSourceFile(filePath);
let symbol = tc.getExportsOfModule((<any>sf).symbol).find(m => m.name === symbolName);
if (!symbol) {
throw new Error(`can't find symbol ${symbolName} exported from module ${filePath}`);
}
if (symbol &&
symbol.flags & ts.SymbolFlags.Alias) { // This is an alias, follow what it aliases
symbol = tc.getAliasedSymbol(symbol);
}
const declaration = symbol.getDeclarations()[0];
const declarationFile = declaration.getSourceFile().fileName;
return this.getStaticSymbol(declarationFile, symbol.getName());
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
private typeCache = new Map<string, StaticSymbol>();
/**
* getStaticSymbol 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 declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string): StaticSymbol {
let key = `"${declarationFile}".${name}`;
let result = this.typeCache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name);
this.typeCache.set(key, result);
}
return result;
}
// TODO(alexeagle): take a statictype
getMetadataFor(filePath: string): ModuleMetadata {
if (!fs.existsSync(filePath)) {
throw new Error(`No such file '${filePath}'`);
}
if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (fs.existsSync(metadataPath)) {
return this.readMetadata(metadataPath);
}
}
let sf = this.program.getSourceFile(filePath);
if (!sf) {
throw new Error(`Source file ${filePath} not present in program.`);
}
const metadata = this.metadataCollector.getMetadata(sf);
return metadata;
}
readMetadata(filePath: string) {
try {
const result = JSON.parse(fs.readFileSync(filePath, {encoding: 'utf-8'}));
return result;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
}

View File

@ -0,0 +1,41 @@
import {ReflectionCapabilities, reflector} from './core_private';
import {StaticReflector} from './static_reflector';
export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) {
reflector.updateCapabilities(new StaticAndDynamicReflectionCapabilities(staticDelegate));
}
private dynamicDelegate = new ReflectionCapabilities();
constructor(private staticDelegate: StaticReflector) {}
isReflectionEnabled(): boolean { return true; }
factory(type: any): Function { return this.dynamicDelegate.factory(type); }
interfaces(type: any): any[] { return this.dynamicDelegate.interfaces(type); }
hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean {
return isStaticType(type) ?
this.staticDelegate.hasLifecycleHook(type, lcInterface, lcProperty) :
this.dynamicDelegate.hasLifecycleHook(type, lcInterface, lcProperty);
}
parameters(type: any): any[][] {
return isStaticType(type) ? this.staticDelegate.parameters(type) :
this.dynamicDelegate.parameters(type);
}
annotations(type: any): any[] {
return isStaticType(type) ? this.staticDelegate.annotations(type) :
this.dynamicDelegate.annotations(type);
}
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
return isStaticType(typeOrFunc) ? this.staticDelegate.propMetadata(typeOrFunc) :
this.dynamicDelegate.propMetadata(typeOrFunc);
}
getter(name: string) { return this.dynamicDelegate.getter(name); }
setter(name: string) { return this.dynamicDelegate.setter(name); }
method(name: string) { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
}
function isStaticType(type: any): boolean {
return typeof type === 'object' && type.name && type.filePath;
}

View File

@ -0,0 +1,410 @@
import {
AttributeMetadata,
DirectiveMetadata,
ComponentMetadata,
ContentChildrenMetadata,
ContentChildMetadata,
InputMetadata,
HostBindingMetadata,
HostListenerMetadata,
OutputMetadata,
PipeMetadata,
ViewChildMetadata,
ViewChildrenMetadata,
ViewQueryMetadata,
QueryMetadata,
Provider,
HostMetadata,
OptionalMetadata,
InjectableMetadata,
SelfMetadata,
SkipSelfMetadata,
InjectMetadata,
} from "@angular/core";
import {ReflectorReader} from "./core_private";
/**
* The host of the static resolver is expected to be able to provide module metadata in the form of
* ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*/
export interface StaticReflectorHost {
/**
* Return a ModuleMetadata for the given module.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any};
/**
* Resolve a symbol from an import statement form, to the file where it is declared.
* @param module the location imported from
* @param containingFile for relative imports, the path of the file containing the import
*/
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
getStaticSymbol(declarationFile: string, name: string): StaticSymbol;
angularImportLocations():
{coreDecorators: string, diDecorators: string, diMetadata: string, provider: string};
}
/**
* A token representing the a reference to a static type.
*
* This token is unique for a filePath and name and can be used as a hash table key.
*/
export class StaticSymbol {
constructor(public filePath: string, public name: string) {}
}
/**
* A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
importUri(typeOrFunc: StaticSymbol): string {
var staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
return staticSymbol ? staticSymbol.filePath : null;
}
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
if (!annotations) {
let classMetadata = this.getTypeMetadata(type);
if (classMetadata['decorators']) {
annotations = this.simplify(type, classMetadata['decorators']);
} else {
annotations = [];
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
}
public propMetadata(type: StaticSymbol): {[key: string]: any} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
let classMetadata = this.getTypeMetadata(type);
let members = classMetadata ? classMetadata['members'] : {};
propMetadata = mapStringMap(members, (propData, propName) => {
let prop = (<any[]>propData).find(a => a['__symbolic'] == 'property');
if (prop && prop['decorators']) {
return this.simplify(type, prop['decorators']);
} else {
return [];
}
});
this.propertyCache.set(type, propMetadata);
}
return propMetadata;
}
public parameters(type: StaticSymbol): any[] {
if (!(type instanceof StaticSymbol)) {
throw new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`);
}
try {
let parameters = this.parameterCache.get(type);
if (!parameters) {
let classMetadata = this.getTypeMetadata(type);
let members = classMetadata ? classMetadata['members'] : null;
let ctorData = members ? members['__ctor__'] : null;
if (ctorData) {
let ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
let parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
let parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = [];
parameterTypes.forEach((paramType, index) => {
let nestedResult: any[] = [];
if (paramType) {
nestedResult.push(paramType);
}
let decorators = parameterDecorators ? parameterDecorators[index] : null;
if (decorators) {
nestedResult.push(...decorators);
}
parameters.push(nestedResult);
});
}
if (!parameters) {
parameters = [];
}
this.parameterCache.set(type, parameters);
}
return parameters;
} catch (e) {
console.log(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}
hasLifecycleHook(type: any, lcInterface: /*Type*/ any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
throw new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
}
let classMetadata = this.getTypeMetadata(type);
let members = classMetadata ? classMetadata['members'] : null;
let member:any[] = members ? members[lcProperty] : null;
return member ? member.some(a => a['__symbolic'] == 'method') : false;
}
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => {
let argValues: any[] = [];
args.forEach((arg, index) => {
let argValue: any;
if (typeof arg === 'object' && !arg['__symbolic']) {
argValue = mapStringMap(arg, (value, key) => this.simplify(context, value));
} else {
argValue = this.simplify(context, arg);
}
argValues.push(argValue);
});
var metadata = Object.create(ctor.prototype);
ctor.apply(metadata, argValues);
return metadata;
});
}
private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, provider} = this.host.angularImportLocations();
this.registerDecoratorOrConstructor(this.host.findDeclaration(provider, 'Provider'), Provider);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'),
HostMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Injectable'),
InjectableMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'),
SelfMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'SkipSelf'),
SkipSelfMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'),
InjectMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Optional'),
OptionalMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Attribute'),
AttributeMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Query'),
QueryMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewQuery'),
ViewQueryMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ContentChild'),
ContentChildMetadata);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildrenMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewChild'),
ViewChildMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'ViewChildren'),
ViewChildrenMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'),
InputMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Output'),
OutputMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'),
PipeMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'HostBinding'),
HostBindingMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'HostListener'),
HostListenerMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Directive'),
DirectiveMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Component'),
ComponentMetadata);
// Note: Some metadata classes can be used directly with Provider.deps.
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'HostMetadata'),
HostMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'SelfMetadata'),
SelfMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'SkipSelfMetadata'),
SkipSelfMetadata);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'OptionalMetadata'),
OptionalMetadata);
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
let _this = this;
function simplify(expression: any): any {
if (isPrimitive(expression)) {
return expression;
}
if (expression instanceof Array) {
let result: any[] = [];
for (let item of(<any>expression)) {
result.push(simplify(item));
}
return result;
}
if (expression) {
if (expression['__symbolic']) {
let staticSymbol: StaticSymbol;
switch (expression['__symbolic']) {
case "binop":
let left = simplify(expression['left']);
let right = simplify(expression['right']);
switch (expression['operator']) {
case '&&':
return left && right;
case '||':
return left || right;
case '|':
return left | right;
case '^':
return left ^ right;
case '&':
return left & right;
case '==':
return left == right;
case '!=':
return left != right;
case '===':
return left === right;
case '!==':
return left !== right;
case '<':
return left < right;
case '>':
return left > right;
case '<=':
return left <= right;
case '>=':
return left >= right;
case '<<':
return left << right;
case '>>':
return left >> right;
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
case '%':
return left % right;
}
return null;
case "pre":
let operand = simplify(expression['operand']);
switch (expression['operator']) {
case '+':
return operand;
case '-':
return -operand;
case '!':
return !operand;
case '~':
return ~operand;
}
return null;
case "index":
let indexTarget = simplify(expression['expression']);
let index = simplify(expression['index']);
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case "select":
let selectTarget = simplify(expression['expression']);
let member = simplify(expression['member']);
if (selectTarget && isPrimitive(member)) return selectTarget[member];
return null;
case "reference":
if (expression['module']) {
staticSymbol = _this.host.findDeclaration(expression['module'], expression['name'],
context.filePath);
} else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
}
let result = staticSymbol;
let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
let declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
if (declarationValue) {
result = _this.simplify(staticSymbol, declarationValue);
}
return result;
case "class":
return context;
case "new":
case "call":
let target = expression['expression'];
if (target['module']) {
staticSymbol =
_this.host.findDeclaration(target['module'], target['name'], context.filePath);
} else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
}
let converter = _this.conversionMap.get(staticSymbol);
if (converter) {
let args = expression['arguments'];
if (!args) {
args = [];
}
return converter(context, args);
} else {
return context;
}
case "error":
throw new Error(expression['message']);
}
return null;
}
return mapStringMap(expression, (value, name) => simplify(value));
}
return null;
}
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 (!moduleMetadata) {
moduleMetadata = this.host.getMetadataFor(module);
if (!moduleMetadata) {
moduleMetadata = {__symbolic: "module", module: module, metadata: {}};
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
let moduleMetadata = this.getModuleMetadata(type.filePath);
let result = moduleMetadata['metadata'][type.name];
if (!result) {
result = {__symbolic: "class"};
}
return result;
}
}
function mapStringMap(input: {[key: string]: any},
transform: (value: any, key: string) => any): {[key: string]: any} {
if (!input) return {};
var result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => { result[key] = transform(input[key], key); });
return result;
}
function isPrimitive(o: any): boolean {
return o === null || (typeof o !== "function" && typeof o !== "object");
}

View File

@ -0,0 +1,429 @@
import {
describe,
it,
iit,
expect,
ddescribe,
beforeEach
} from '@angular/core/testing/testing_internal';
import {isBlank} from '@angular/facade/src/lang';
import {ListWrapper} from '@angular/facade/src/collection';
import {
StaticReflector,
StaticReflectorHost,
StaticSymbol
} from '@angular/compiler-cli/src/static_reflector';
describe('StaticReflector', () => {
let noContext = new StaticSymbol('', '');
let host: StaticReflectorHost;
let reflector: StaticReflector;
beforeEach(() => {
host = new MockReflectorHost();
reflector = new StaticReflector(host);
});
function simplify(context: StaticSymbol, value: any) {
return reflector.simplify(context, value);
}
it('should get annotations for NgFor', () => {
let NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
let annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1);
let annotation = annotations[0];
expect(annotation.selector).toEqual('[ngFor][ngForOf]');
expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']);
});
it('should get constructor for NgFor', () => {
let NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
let ViewContainerRef =
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef');
let TemplateRef = host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef');
let IterableDiffers = host.findDeclaration(
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
let ChangeDetectorRef = host.findDeclaration(
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
let parameters = reflector.parameters(NgFor);
expect(parameters)
.toEqual([[ViewContainerRef], [TemplateRef], [IterableDiffers], [ChangeDetectorRef]]);
});
it('should get annotations for HeroDetailComponent', () => {
let HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
let annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1);
let annotation = annotations[0];
expect(annotation.selector).toEqual('my-hero-detail');
expect(annotation.directives)
.toEqual([[host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor')]]);
});
it('should get and empty annotation list for an unknown class', () => {
let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
let annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]);
});
it('should get propMetadata for HeroDetailComponent', () => {
let HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
let props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy();
});
it('should get an empty object from propMetadata for an unknown class', () => {
let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
let properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({});
});
it('should get empty parameters list for an unknown class ', () => {
let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
let parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]);
});
it('should simplify primitive into itself', () => {
expect(simplify(noContext, 1)).toBe(1);
expect(simplify(noContext, true)).toBe(true);
expect(simplify(noContext, "some value")).toBe("some value");
});
it('should simplify an array into a copy of the array',
() => { expect(simplify(noContext, [1, 2, 3])).toEqual([1, 2, 3]); });
it('should simplify an object to a copy of the object', () => {
let expr = {a: 1, b: 2, c: 3};
expect(simplify(noContext, expr)).toEqual(expr);
});
it('should simplify &&', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&&', left: true, right: true}))).toBe(true);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&&', left: true, right: false}))).toBe(false);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&&', left: false, right: true}))).toBe(false);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&&', left: false, right: false}))).toBe(false);
});
it('should simplify ||', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '||', left: true, right: true}))).toBe(true);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '||', left: true, right: false}))).toBe(true);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '||', left: false, right: true}))).toBe(true);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '||', left: false, right: false}))).toBe(false);
});
it('should simplify &', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&', left: 0x22, right: 0x0F}))).toBe(0x22 & 0x0F);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '&', left: 0x22, right: 0xF0}))).toBe(0x22 & 0xF0);
});
it('should simplify |', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '|', left: 0x22, right: 0x0F}))).toBe(0x22 | 0x0F);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '|', left: 0x22, right: 0xF0}))).toBe(0x22 | 0xF0);
});
it('should simplify ^', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '|', left: 0x22, right: 0x0F}))).toBe(0x22 | 0x0F);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '|', left: 0x22, right: 0xF0}))).toBe(0x22 | 0xF0);
});
it('should simplify ==', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '==', left: 0x22, right: 0x22}))).toBe(0x22 == 0x22);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '==', left: 0x22, right: 0xF0}))).toBe(0x22 == 0xF0);
});
it('should simplify !=', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '!=', left: 0x22, right: 0x22}))).toBe(0x22 != 0x22);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '!=', left: 0x22, right: 0xF0}))).toBe(0x22 != 0xF0);
});
it('should simplify ===', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '===', left: 0x22, right: 0x22}))).toBe(0x22 === 0x22);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '===', left: 0x22, right: 0xF0}))).toBe(0x22 === 0xF0);
});
it('should simplify !==', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '!==', left: 0x22, right: 0x22}))).toBe(0x22 !== 0x22);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '!==', left: 0x22, right: 0xF0}))).toBe(0x22 !== 0xF0);
});
it('should simplify >', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>', left: 1, right: 1}))).toBe(1 > 1);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>', left: 1, right: 0}))).toBe(1 > 0);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>', left: 0, right: 1}))).toBe(0 > 1);
});
it('should simplify >=', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>=', left: 1, right: 1}))).toBe(1 >= 1);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>=', left: 1, right: 0}))).toBe(1 >= 0);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>=', left: 0, right: 1}))).toBe(0 >= 1);
});
it('should simplify <=', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<=', left: 1, right: 1}))).toBe(1 <= 1);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<=', left: 1, right: 0}))).toBe(1 <= 0);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<=', left: 0, right: 1}))).toBe(0 <= 1);
});
it('should simplify <', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<', left: 1, right: 1}))).toBe(1 < 1);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<', left: 1, right: 0}))).toBe(1 < 0);
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<', left: 0, right: 1}))).toBe(0 < 1);
});
it('should simplify <<', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '<<', left: 0x55, right: 2}))).toBe(0x55 << 2);
});
it('should simplify >>', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '>>', left: 0x55, right: 2}))).toBe(0x55 >> 2);
});
it('should simplify +', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '+', left: 0x55, right: 2}))).toBe(0x55 + 2);
});
it('should simplify -', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '-', left: 0x55, right: 2}))).toBe(0x55 - 2);
});
it('should simplify *', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '*', left: 0x55, right: 2}))).toBe(0x55 * 2);
});
it('should simplify /', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '/', left: 0x55, right: 2}))).toBe(0x55 / 2);
});
it('should simplify %', () => {
expect(simplify(noContext, ({ __symbolic: 'binop', operator: '%', left: 0x55, right: 2}))).toBe(0x55 % 2);
});
it('should simplify prefix -', () => {
expect(simplify(noContext, ({ __symbolic: 'pre', operator: '-', operand: 2}))).toBe(-2);
});
it('should simplify prefix ~', () => {
expect(simplify(noContext, ({ __symbolic: 'pre', operator: '~', operand: 2}))).toBe(~2);
});
it('should simplify prefix !', () => {
expect(simplify(noContext, ({ __symbolic: 'pre', operator: '!', operand: true}))).toBe(!true);
expect(simplify(noContext, ({ __symbolic: 'pre', operator: '!', operand: false}))).toBe(!false);
});
it('should simplify an array index', () => {
expect(simplify(noContext, ({__symbolic: "index", expression: [1, 2, 3], index: 2}))).toBe(3);
});
it('should simplify an object index', () => {
let expr = {__symbolic: "select", expression: {a: 1, b: 2, c: 3}, member: "b"};
expect(simplify(noContext, expr)).toBe(2);
});
it('should simplify a module reference', () => {
expect(simplify(new StaticSymbol('/src/cases', ''),
({__symbolic: "reference", module: "./extern", name: "s"})))
.toEqual("s");
});
it('should simplify a non existing reference as a static symbol', () => {
expect(simplify(new StaticSymbol('/src/cases', ''),
({__symbolic: "reference", module: "./extern", name: "nonExisting"})))
.toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
});
});
class MockReflectorHost implements StaticReflectorHost {
private staticTypeCache = new Map<string, StaticSymbol>();
angularImportLocations() {
return {
coreDecorators: 'angular2/src/core/metadata',
diDecorators: 'angular2/src/core/di/decorators',
diMetadata: 'angular2/src/core/di/metadata',
provider: 'angular2/src/core/di/provider'
};
}
getStaticSymbol(declarationFile: string, name: string): StaticSymbol {
var cacheKey = `${declarationFile}:${name}`;
var result = this.staticTypeCache.get(cacheKey);
if (isBlank(result)) {
result = new StaticSymbol(declarationFile, name);
this.staticTypeCache.set(cacheKey, result);
}
return result;
}
// In tests, assume that symbols are not re-exported
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string {
let result: string[] = [];
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 (modulePath.indexOf('.') === 0) {
return this.getStaticSymbol(pathTo(containingFile, modulePath) + '.d.ts', symbolName);
}
return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
}
getMetadataFor(moduleId: string): any {
let data: {[key: string]: any} = {
'/tmp/angular2/src/common/forms/directives.d.ts': {
"__symbolic": "module",
"metadata": {
"FORM_DIRECTIVES": [
{
"__symbolic": "reference",
"name": "NgFor",
"module": "angular2/src/common/directives/ng_for"
}
]
}
},
'/tmp/angular2/src/common/directives/ng_for.d.ts': {
"__symbolic": "module",
"metadata": {
"NgFor": {
"__symbolic": "class",
"decorators": [
{
"__symbolic": "call",
"expression": {
"__symbolic": "reference",
"name": "Directive",
"module": "../../core/metadata"
},
"arguments": [
{
"selector": "[ngFor][ngForOf]",
"inputs": ["ngForTrackBy", "ngForOf", "ngForTemplate"]
}
]
}
],
"members": {
"__ctor__": [
{
"__symbolic": "constructor",
"parameters": [
{
"__symbolic": "reference",
"module": "../../core/linker/view_container_ref",
"name": "ViewContainerRef"
},
{
"__symbolic": "reference",
"module": "../../core/linker/template_ref",
"name": "TemplateRef"
},
{
"__symbolic": "reference",
"module": "../../core/change_detection/differs/iterable_differs",
"name": "IterableDiffers"
},
{
"__symbolic": "reference",
"module": "../../core/change_detection/change_detector_ref",
"name": "ChangeDetectorRef"
}
]
}
]
}
}
}
},
'/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"}}},
'/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"}}},
'/tmp/src/app/hero-detail.component.d.ts': {
"__symbolic": "module",
"metadata": {
"HeroDetailComponent": {
"__symbolic": "class",
"decorators": [
{
"__symbolic": "call",
"expression": {
"__symbolic": "reference",
"name": "Component",
"module": "angular2/src/core/metadata"
},
"arguments": [
{
"selector": "my-hero-detail",
"template":
"\n <div *ngIf=\"hero\">\n <h2>{{hero.name}} details!</h2>\n <div><label>id: </label>{{hero.id}}</div>\n <div>\n <label>name: </label>\n <input [(ngModel)]=\"hero.name\" placeholder=\"name\"/>\n </div>\n </div>\n",
"directives": [
{
"__symbolic": "reference",
"name": "FORM_DIRECTIVES",
"module": "angular2/src/common/forms/directives"
}
]
}
]
}
],
"members": {
"hero": [
{
"__symbolic": "property",
"decorators": [
{
"__symbolic": "call",
"expression": {
"__symbolic": "reference",
"name": "Input",
"module": "angular2/src/core/metadata"
}
}
]
}
]
}
}
}
},
'/src/extern.d.ts': {"__symbolic": "module", metadata: {s: "s"}}
};
return data[moduleId];
}
}

View File

@ -0,0 +1,34 @@
{
"angularCompilerOptions": {
"skipTemplateCodegen": true
},
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom"],
"noImplicitAny": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@angular/core": ["../../../dist/packages-dist/core"],
"@angular/common": ["../../../dist/packages-dist/common"],
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
"@angular/platform-server": ["../../../dist/packages-dist/platform-server"],
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
},
"experimentalDecorators": true,
"rootDir": ".",
"sourceRoot": ".",
"outDir": "../../../dist/packages-dist/compiler-cli",
"declaration": true
},
"exclude": ["integrationtest"],
"files": [
"index.ts",
"src/main.ts",
"../../../node_modules/@types/node/index.d.ts",
"../../../node_modules/@types/jasmine/index.d.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
]
}