Compare commits

..

30 Commits
2.2.1 ... 2.2.2

Author SHA1 Message Date
11ed8f56ab docs(changelog): add changelog for 2.2.2 2016-11-22 14:36:49 -08:00
a49acbf027 chore(release): cut the 2.2.2 release 2016-11-22 14:33:08 -08:00
8e41910429 fix(build): update versions of umd bundles (#13038)
Fixes #13037
2016-11-22 14:26:20 -08:00
a4ab14bf74 test(upgrade): remove setTimeout from lifecycle hook tests (#13027)
* test(upgrade): remove unnecessary NO_ERRORS_SCHEMA

* test(upgrade): remove `setTimeout` from lifecycle hook tests

Closes #13019
2016-11-22 14:26:20 -08:00
ea4fc9b421 fix(animations): blend in all previously transitioned styles into next animation if interrupted (#13014)
Closes #13013
Closes #13014
2016-11-22 14:26:20 -08:00
0956acee58 fix(closure): quote date pattern aliases (#13012)
Quota the pattern aliases to prevent closure renaming. These are quoted in DatePipe and also need to be quoted here.
2016-11-22 14:26:20 -08:00
2ca67e1674 refactor(compiler): allow control of StaticSymbol lifetime (#12986) 2016-11-22 14:26:20 -08:00
472666fc2b refactor(ngUpgrade): Small cleanup with Testability API and resumeBootstrap (#12926)
* With non-static ngUpgrade apps, callbacks to `whenStable` were being invoked with the wrong
  context
* With non-static ngUpgrade apps, `resumeBootstrap` was being run outside the NgZone
* Remove redundent `whenStableContext` variable

Neither of the first two problems were actually causing bugs (as far as I know), but they *might*
have caused problems in the future.

Inspired by https://github.com/angular/angular/pull/12910, but for non-static apps.
2016-11-22 14:26:20 -08:00
462316b0f1 fix(upgrade): call ng1 lifecycle hooks (#12875) 2016-11-22 14:26:20 -08:00
96c2b2cc25 fix(changelog): replace beta.1 with beta.0 (#12961) 2016-11-22 14:26:19 -08:00
3d407fc010 refactor(compiler): further minor fixes 2016-11-22 14:26:19 -08:00
64bd672e3a refactor(compiler): Reintroduce ReflectorHost and move Extractor into @angular/compiler 2016-11-22 14:26:19 -08:00
ef38676091 refactor(comiler): various cleanups 2016-11-22 14:26:19 -08:00
38be2b81c6 refactor(compiler): renames
- `NgHost` to `CompilerHost`
- `AotCompilerHost.resolveFileToImport` to `AotCompilerHost.fileNameToModuleName`
- `AotCompilerHoset.resolveImportToFile` to `AotCompilerHost.moduleNameToFileName`
2016-11-22 14:26:19 -08:00
39a71eb0ec refactor(tsc-wrapped): collect all exported functions and classes and bump metadata version from 1 to 2
This is needed to resolve symbols without `.d.ts` files.
This bumps the version of the metadata from 1 to 2.
This adds logic into `ng_host.ts` to automatically upgrade
version 1 to version 2 metadata by adding the exported symbols
from the `.d.ts` file.
2016-11-22 14:26:19 -08:00
2fe6fb1163 fix(compiler): fix versions of @angular/tsc-wrapped 2016-11-22 14:26:19 -08:00
b5afe51b26 refactor(compiler): move symbol extraction to AotCompiler 2016-11-22 14:26:19 -08:00
170525a225 refactor(compiler): add createAotCompiler factory
Also adds 2 more methods to the `AotCompilerHost`:
- `loadResource`
- `resolveFileToImport`
2016-11-22 14:26:19 -08:00
0c98f45105 refactor(compiler): remove asset: urls
These urls were just relicts from Dart.
2016-11-22 14:26:19 -08:00
e7025c9423 refactor(compiler): move findDeclaration into the StaticReflector
Previously, this was part of the `AotCompilerHost`.
The `AotCompilerHost` is now also greatly simplified.
2016-11-22 14:26:18 -08:00
8f295287a2 refactor(compiler): move static_reflector into @angular/compiler and rename files
- `src/runtime_compiler.ts` -> `src/jit/compiler.ts`
- `src/compiler.ts` -> `src/jit/compiler_factory.ts`
- `src/offline_compiler` -> `src/aot/compiler.ts`

Part of #12867
2016-11-22 14:26:18 -08:00
030facc66a chore(build): update package.json versions during build (#12957) 2016-11-22 14:26:18 -08:00
45af8f6752 fix(ci): pin version of npm on CircleCI (#12954) 2016-11-22 14:26:18 -08:00
33a79028be fix(benchmarks): use sanitized style values (#12943) 2016-11-22 14:26:18 -08:00
09226d96f8 fix(router): support redirects to named outlets
Closes #12740, #9921
2016-11-22 14:26:18 -08:00
6c3166e6e4 chore(release): cut the 2.3.0-beta.0 realse and add change log 2016-11-22 14:26:18 -08:00
8df328b15a fix(router): add a banner file for the router (#12919) 2016-11-22 14:26:17 -08:00
115f18fa06 fix(router): removes a peer dependency from router to upgrade 2016-11-22 14:26:16 -08:00
511cd4d182 fix(router): add a banner file for the router (#12919) 2016-11-22 14:26:14 -08:00
87d5d49530 fix(router): removes a peer dependency from router to upgrade 2016-11-22 14:26:14 -08:00
63 changed files with 2026 additions and 1595 deletions

View File

@ -1,3 +1,44 @@
<a name="2.2.2"></a>
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
<a name="2.3.0-beta.0"></a>
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
### Bug Fixes
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
### Features
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
<a name="2.2.1"></a>
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)

View File

@ -20,6 +20,10 @@ PACKAGES=(core
benchpress)
BUILD_ALL=true
BUNDLE=true
VERSION_PREFIX=$(node -p "require('./package.json').version")
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
REMOVE_BENCHPRESS=false
for ARG in "$@"; do
case "$ARG" in
@ -31,6 +35,10 @@ for ARG in "$@"; do
--bundle=*)
BUNDLE=( "${ARG#--bundle=}" )
;;
--publish)
VERSION_SUFFIX=""
REMOVE_BENCHPRESS=true
;;
*)
echo "Unknown option $ARG."
exit 1
@ -38,6 +46,10 @@ for ARG in "$@"; do
esac
done
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
@ -197,9 +209,24 @@ do
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi
) 2>&1 | grep -v "as external dependency"
fi
(
echo "====== VERSION: Updating version references"
cd ${DESTDIR}
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
)
done
echo ""
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
./modules/@angular/examples/build.sh
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
echo ""
echo "==== Removing benchpress from publication"
rm -r dist/packages-dist/benchpress
fi

View File

@ -4,7 +4,7 @@ machine:
dependencies:
pre:
- npm install -g npm
- npm install -g npm@3.6.0
test:
override:

View File

@ -50,6 +50,7 @@ module.exports = function(config) {
'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/router/**',
'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js',

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host';
export {Extractor} from './src/extractor';
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
export * from '@angular/tsc-wrapped';

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "^0.3.0",
"@angular/tsc-wrapped": "^0.4.0",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},

View File

@ -17,11 +17,9 @@ import {readFileSync} from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {PathMappedReflectorHost} from './path_mapped_reflector_host';
import {CompilerHost, CompilerHostContext} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
@ -38,8 +36,8 @@ const PREAMBLE = `/**
export class CodeGenerator {
constructor(
private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector,
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private ngCompilerHost: CompilerHost) {}
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
@ -64,33 +62,30 @@ export class CodeGenerator {
return path.join(this.options.genDir, relativePath);
}
codegen(options: {transitiveModules: boolean}): Promise<any> {
const staticSymbols =
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
});
codegen(): Promise<any> {
return this.compiler
.compileAll(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
});
}
static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator {
resourceLoader = resourceLoader || {
get: (s: string) => {
if (!compilerHost.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(compilerHost.readFile(s));
}
};
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
ngCompilerHost?: CompilerHost): CodeGenerator {
if (!ngCompilerHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
ngCompilerHost = usePathMapping ?
new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) :
new CompilerHost(program, tsCompilerHost, options, compilerHostContext);
}
const transFile = cliOptions.i18nFile;
const locale = cliOptions.locale;
let transContent: string = '';
@ -101,84 +96,18 @@ export class CodeGenerator {
}
transContent = readFileSync(transFile, 'utf8');
}
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
reflectorHost = usePathMapping ?
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) :
new ReflectorHost(program, compilerHost, options, reflectorHostContext);
}
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser =
new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat);
const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
debug: options.debug === true,
translations: transContent,
i18nFormat: cliOptions.i18nFormat,
locale: cliOptions.locale,
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
GENERATED_FILES
});
const normalizer =
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
const expressionParser = new compiler.Parser(new compiler.Lexer());
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
const console = new Console();
const tmplParser = new compiler.TemplateParser(
expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler(
resolver, tmplParser, new compiler.StyleCompiler(urlResolver),
new compiler.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
cliOptions.locale, cliOptions.i18nFormat,
new compiler.AnimationParser(elementSchemaRegistry));
return new CodeGenerator(
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
}
}
export function extractProgramSymbols(
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
options: AngularCompilerOptions): StaticSymbol[] {
// Compare with false since the default should be true
const skipFileNames =
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
const staticSymbols: StaticSymbol[] = [];
program.getSourceFiles()
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
.forEach(sourceFile => {
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName);
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSrcPath}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath));
}
});
return staticSymbols;
export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
}

View File

@ -0,0 +1,247 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export interface CompilerHostContext {
fileExists(fileName: string): boolean;
directoryExists(directoryName: string): boolean;
readFile(fileName: string): string;
readResource(fileName: string): Promise<string>;
assumeFileExists(fileName: string): void;
}
export class CompilerHost implements AotCompilerHost {
protected metadataCollector = new MetadataCollector();
protected context: CompilerHostContext;
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>();
constructor(
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
protected options: AngularCompilerOptions, context?: CompilerHostContext) {
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
this.context = context || new NodeCompilerHostContext(compilerHost);
const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string) {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
.resolvedModule;
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
};
/**
* 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.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
* generated | relative | relative | n/a
* existing file | n/a | absolute | relative(*)
*
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
fileNameToModuleName(importedFile: string, containingFile: string): string {
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}
containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);
if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
}
return this.dotRelative(containingDir, importedFile);
}
}
}
private dotRelative(from: string, to: string): string {
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;
}
/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modulse, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}
protected getSourceFile(filePath: string): ts.SourceFile {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return sf;
}
getMetadataFor(filePath: string): ModuleMetadata[] {
if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user refernced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
return;
}
if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath);
}
} else {
const sf = this.getSourceFile(filePath);
const metadata = this.metadataCollector.getMetadata(sf);
return metadata ? [metadata] : [];
}
}
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
let metadatas = this.resolverCache.get(filePath);
if (metadatas) {
return metadatas;
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadatas = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
const v1Metadata = metadatas.find(m => m['version'] === 1);
let v2Metadata = metadatas.find(m => m['version'] === 2);
if (!v2Metadata && v1Metadata) {
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
// as the only difference between the versions is whether all exports are contained in
// the metadata
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
if (v1Metadata.exports) {
v2Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const sourceText = this.context.readFile(dtsFilePath);
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v2Metadata.metadata[prop]) {
v2Metadata.metadata[prop] = exports.metadata[prop];
}
}
}
metadatas.push(v2Metadata);
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
}
export class NodeCompilerHostContext implements CompilerHostContext {
constructor(private host: ts.CompilerHost) {}
private assumedExists: {[fileName: string]: boolean} = {};
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
directoryExists(directoryName: string): boolean {
try {
return fs.statSync(directoryName).isDirectory();
} catch (e) {
return false;
}
}
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
readResource(s: string) {
if (!this.host.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(this.host.readFile(s));
}
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}

View File

@ -24,17 +24,7 @@ import {Extractor} from './extractor';
function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) {
const resourceLoader: compiler.ResourceLoader = {
get: (s: string) => {
if (!host.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(host.readFile(s));
}
};
const extractor =
Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader);
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();

View File

@ -14,86 +14,28 @@
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as tsc from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {extractProgramSymbols} from './codegen';
import {ReflectorHost} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
import {excludeFilePattern} from './codegen';
import {CompilerHost} from './compiler_host';
export class Extractor {
constructor(
private options: tsc.AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector,
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
private metadataResolver: compiler.CompileMetadataResolver) {}
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
private program: ts.Program) {}
extract(): Promise<compiler.MessageBundle> {
const programSymbols: StaticSymbol[] =
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
const {ngModules, files} = compiler.analyzeAndValidateNgModules(
programSymbols, {transitiveModules: true}, this.metadataResolver);
return compiler.loadNgModuleDirectives(ngModules).then(() => {
const errors: compiler.ParseError[] = [];
files.forEach(file => {
const compMetas: compiler.CompileDirectiveMetadata[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta && dirMeta.isComponent) {
compMetas.push(dirMeta);
}
});
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
});
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return this.messageBundle;
});
return this.ngExtractor.extract(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
}
static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
reflectorHost?: ReflectorHost): Extractor {
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options);
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
const normalizer =
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): implicit tags & attributes
const messageBundle = new compiler.MessageBundle(htmlParser, [], {});
return new Extractor(
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver);
tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options);
const {extractor: ngExtractor} = compiler.Extractor.create(
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
return new Extractor(ngExtractor, ngCompilerHost, program);
}
}
}

View File

@ -19,9 +19,7 @@ import {CodeGenerator} from './codegen';
function codegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
transitiveModules: true
});
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
}
// CLI entry point

View File

@ -6,28 +6,28 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticSymbol} from './static_reflector';
import {CompilerHost, CompilerHostContext} from './compiler_host';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
/**
* This version of the reflector host expects that the program will be compiled
* This version of the AotCompilerHost expects that the program will be compiled
* and executed with a "path mapped" directory structure, where generated files
* are in a parallel tree with the sources, and imported using a `./` relative
* import. This requires using TS `rootDirs` option and also teaching the module
* loader what to do.
*/
export class PathMappedReflectorHost extends ReflectorHost {
export class PathMappedCompilerHost extends CompilerHost {
constructor(
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
context?: ReflectorHostContext) {
context?: CompilerHostContext) {
super(program, compilerHost, options, context);
}
@ -42,7 +42,14 @@ export class PathMappedReflectorHost extends ReflectorHost {
return fileName;
}
protected resolve(m: string, containingFile: string) {
moduleNameToFileName(m: string, containingFile: string) {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
for (const root of this.options.rootDirs || ['']) {
const rootedContainingFile = path.join(root, containingFile);
const resolved =
@ -51,7 +58,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
if (this.options.traceResolution) {
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
}
return resolved.resolvedFileName;
return this.getCanonicalFileName(resolved.resolvedFileName);
}
}
}
@ -62,10 +69,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
* 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): string {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');
fileNameToModuleName(importedFile: string, containingFile: string): string {
if (this.options.traceResolution) {
console.log(
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
@ -82,7 +86,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
}
const resolvable = (candidate: string) => {
const resolved = this.getCanonicalFileName(this.resolve(candidate, importedFile));
const resolved = this.moduleNameToFileName(candidate, importedFile);
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
};
@ -112,7 +116,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
}
getMetadataFor(filePath: string): ModuleMetadata {
getMetadataFor(filePath: string): ModuleMetadata[] {
for (const root of this.options.rootDirs || []) {
const rootedPath = path.join(root, filePath);
if (!this.compilerHost.fileExists(rootedPath)) {
@ -124,16 +128,13 @@ export class PathMappedReflectorHost extends ReflectorHost {
if (DTS.test(rootedPath)) {
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
const metadata = this.readMetadata(metadataPath);
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
return this.readMetadata(metadataPath, rootedPath);
}
} else {
const sf = this.program.getSourceFile(rootedPath);
if (!sf) {
throw new Error(`Source file ${rootedPath} not present in program.`);
}
sf.fileName = this.getCanonicalFileName(sf.fileName);
return this.metadataCollector.getMetadata(sf);
const sf = this.getSourceFile(rootedPath);
sf.fileName = sf.fileName;
const metadata = this.metadataCollector.getMetadata(sf);
return metadata ? [metadata] : [];
}
}
}

View File

@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
export type Console = typeof r._Console;
export var Console: typeof r.Console = r.Console;
export var reflector: typeof r.reflector = r.reflector;
export type SetterFn = typeof r._SetterFn;
export type GetterFn = typeof r._GetterFn;
export type MethodFn = typeof r._MethodFn;

View File

@ -1,354 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AssetUrl, ImportGenerator} from '@angular/compiler';
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export interface ReflectorHostContext {
fileExists(fileName: string): boolean;
directoryExists(directoryName: string): boolean;
readFile(fileName: string): string;
assumeFileExists(fileName: string): void;
}
export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
protected metadataCollector = new MetadataCollector();
protected context: ReflectorHostContext;
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
constructor(
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
protected options: AngularCompilerOptions, context?: ReflectorHostContext) {
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
this.context = context || new NodeReflectorHostContext(compilerHost);
const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
}
angularImportLocations() {
return {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
protected resolve(m: string, containingFile: string) {
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
};
protected normalizeAssetUrl(url: string): string {
const assetUrl = AssetUrl.parse(url);
const path = assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null;
return this.getCanonicalFileName(path);
}
protected resolveAssetUrl(url: string, containingFile: string): string {
const assetUrl = this.normalizeAssetUrl(url);
if (assetUrl) {
return this.getCanonicalFileName(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.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
* generated | relative | relative | n/a
* existing file | n/a | absolute | relative(*)
*
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
getImportPath(containingFile: string, importedFile: string): string {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}
containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);
if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
}
return this.dotRelative(containingDir, importedFile);
}
}
}
private dotRelative(from: string, to: string): string {
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;
}
/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modulse, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}
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.basePath, 'index.ts');
}
try {
const assetUrl = this.normalizeAssetUrl(module);
if (assetUrl) {
module = assetUrl;
}
const filePath = this.resolve(module, containingFile);
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
return this.getStaticSymbol(module, symbolName);
}
const tc = this.program.getTypeChecker();
const sf = this.program.getSourceFile(filePath);
if (!sf || !(<any>sf).symbol) {
// The source file was not needed in the compile but we do need the values from
// the corresponding .ts files stored in the .metadata.json file. Check the file
// for exports to see if the file is exported.
return this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
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 = this.getCanonicalFileName(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>();
private resolverCache = new Map<string, ModuleMetadata>();
/**
* 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, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.typeCache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.typeCache.set(key, result);
}
return result;
}
getMetadataFor(filePath: string): ModuleMetadata {
if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user refernced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
return;
}
if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
const metadata = this.readMetadata(metadataPath);
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
}
} else {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return this.metadataCollector.getMetadata(
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return this.metadataCollector.getMetadata(sf);
}
}
readMetadata(filePath: string) {
try {
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
private getResolverMetadata(filePath: string): ModuleMetadata {
let metadata = this.resolverCache.get(filePath);
if (!metadata) {
metadata = this.getMetadataFor(filePath);
this.resolverCache.set(filePath, metadata);
}
return metadata;
}
protected resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.getCanonicalFileName(this.resolve(moduleName, filePath));
if (!resolvedModulePath) {
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
}
return resolvedModulePath;
};
const metadata = this.getResolverMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata.metadata[symbolName]) {
return this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (metadata.exports) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata.exports) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find(symbol => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
}
}
}
// Try to find the symbol via export * directives.
for (const moduleExport of metadata.exports) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) return candidateSymbol;
}
}
}
}
return null;
}
}
export class NodeReflectorHostContext implements ReflectorHostContext {
constructor(private host: ts.CompilerHost) {}
private assumedExists: {[fileName: string]: boolean} = {};
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
directoryExists(directoryName: string): boolean {
try {
return fs.statSync(directoryName).isDirectory();
} catch (e) {
return false;
}
}
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}

View File

@ -0,0 +1,219 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
describe('CompilerHost', () => {
let context: MockAotContext;
let host: ts.CompilerHost;
let program: ts.Program;
let hostNestedGenDir: CompilerHost;
let hostSiblingGenDir: CompilerHost;
beforeEach(() => {
context = new MockAotContext('/tmp/src', clone(FILES));
host = new MockCompilerHost(context);
program = ts.createProgram(
['main.ts'], {
module: ts.ModuleKind.CommonJS,
},
host);
// Force a typecheck
const errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
hostNestedGenDir = new CompilerHost(
program, host, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
hostSiblingGenDir = new CompilerHost(
program, host, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts', ))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other');
});
});
describe('siblingGenDir', () => {
it('should import node_module from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other');
});
});
it('should be able to produce an import from main @angular/core', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
});
it('should be able to produce an import from main to a sub-directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
.toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
.toEqual('../lib/utils');
});
it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
});
it('should be able to read empty metadata ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
});
it('should return undefined for missing modules', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
});
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: 2,
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': 'export declare class bar {}',
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ReflectorHostContext} from '@angular/compiler-cli/src/reflector_host';
import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host';
import * as ts from 'typescript';
export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; }
export class MockContext implements ReflectorHostContext {
export class MockAotContext implements CompilerHostContext {
constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
@ -28,6 +28,14 @@ export class MockContext implements ReflectorHostContext {
return undefined;
}
readResource(fileName: string): Promise<string> {
const result = this.readFile(fileName);
if (result == null) {
return Promise.reject(new Error(`Resource not found: ${fileName}`));
}
return Promise.resolve(result);
}
writeFile(fileName: string, data: string): void {
const parts = fileName.split('/');
const name = parts.pop();
@ -89,7 +97,7 @@ function normalize(parts: string[]): string[] {
}
export class MockCompilerHost implements ts.CompilerHost {
constructor(private context: MockContext) {}
constructor(private context: MockAotContext) {}
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); }

View File

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

View File

@ -25,11 +25,16 @@ export * from './src/template_parser/template_ast';
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
export {CompilerConfig, RenderTypes} from './src/config';
export * from './src/compile_metadata';
export * from './src/offline_compiler';
export {RuntimeCompiler} from './src/runtime_compiler';
export * from './src/aot/compiler_factory';
export * from './src/aot/compiler';
export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol';
export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory';
export * from './src/url_resolver';
export * from './src/resource_loader';
export * from './src/compiler';
export {DirectiveResolver} from './src/directive_resolver';
export {PipeResolver} from './src/pipe_resolver';
export {NgModuleResolver} from './src/ng_module_resolver';

View File

@ -8,114 +8,30 @@
import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {ListWrapper} from './facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler';
import {OutputEmitter} from './output/abstract_emitter';
import * as o from './output/output_ast';
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, createHostComponentMeta} from '../compile_metadata';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {ListWrapper} from '../facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerOptions} from './compiler_options';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
symbolsMissingModule?: StaticSymbol[];
}
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return result;
}
// Wait for the directives in the given modules have been loaded
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
return Promise
.all(ListWrapper.flatten(ngModules.map(
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
.then(() => {});
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, ngModules});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
}
export class OfflineCompiler {
export class AotCompiler {
private _animationCompiler = new AnimationCompiler();
constructor(
@ -124,14 +40,15 @@ export class OfflineCompiler {
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {}
private _animationParser: AnimationParser, private _staticReflector: StaticReflector,
private _options: AotCompilerOptions) {}
clearCache() { this._metadataResolver.clearCache(); }
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> {
compileAll(rootFiles: string[]): Promise<SourceModule[]> {
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver);
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
@ -356,11 +273,130 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, ''];
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
symbolsMissingModule?: StaticSymbol[];
}
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return result;
}
// Wait for the directives in the given modules have been loaded
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
return Promise
.all(ListWrapper.flatten(ngModules.map(
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
.then(() => {});
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, ngModules});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
}
export function extractProgramSymbols(
staticReflector: StaticReflector, files: string[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = [];
files.filter(fileName => _filterFileByPatterns(fileName, options)).forEach(sourceFile => {
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${sourceFile}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
}
});
return staticSymbols;
}
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -368,7 +404,7 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) {
if (ngModules.has(staticSymbol) || !_filterFileByPatterns(staticSymbol.filePath, options)) {
return false;
}
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
@ -376,10 +412,8 @@ function _createNgModules(
ngModules.set(ngModule.type.reference, ngModule);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
if (options.transitiveModules) {
// For every input modules add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
}
// For every input module add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
}
return !!ngModule;
};
@ -396,3 +430,15 @@ function _createNgModules(
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
}
function _filterFileByPatterns(
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
if (options.includeFilePattern) {
match = match && !!options.includeFilePattern.exec(fileName);
}
if (options.excludeFilePattern) {
match = match && !options.excludeFilePattern.exec(fileName);
}
return match;
}

View File

@ -0,0 +1,75 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ViewEncapsulation} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from '../ng_module_resolver';
import {TypeScriptEmitter} from '../output/ts_emitter';
import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler';
import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
/**
* Creates a new AotCompiler based on options and a host.
*/
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
{compiler: AotCompiler, reflector: StaticReflector} {
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(compilerHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
const normalizer = new DirectiveNormalizer(
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
const expressionParser = new Parser(new Lexer());
const elementSchemaRegistry = new DomElementSchemaRegistry();
const console = new Console();
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
return {compiler, reflector: staticReflector};
}

View File

@ -0,0 +1,24 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver {
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
}

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface AotCompilerOptions {
debug?: boolean;
locale?: string;
i18nFormat?: string;
translations?: string;
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from './private_import_core';
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
import {StaticReflector} from './static_reflector';
export class StaticAndDynamicReflectionCapabilities {

View File

@ -7,54 +7,60 @@
*/
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
import {ReflectorReader} from './private_import_core';
const SUPPORTED_SCHEMA_VERSION = 1;
const SUPPORTED_SCHEMA_VERSION = 2;
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
/**
* 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.
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface StaticReflectorHost {
/**
* Return a ModuleMetadata for the given module.
* 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.
*
* @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}|{[key: string]: any}[];
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
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol;
angularImportLocations(): {
coreDecorators: string,
diDecorators: string,
diMetadata: string,
diOpaqueToken: string,
animationMetadata: string,
provider: string
};
getCanonicalFileName(fileName: string): string;
moduleNameToFileName(moduleName: string, containingFile: string): 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.
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {}
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}
/**
@ -62,6 +68,7 @@ export class StaticSymbol {
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, any[]>();
@ -69,20 +76,24 @@ export class StaticReflector implements ReflectorReader {
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol;
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
this.initializeConversionMap();
}
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.host.findDeclaration(moduleUrl, name, '');
return this.findDeclaration(moduleUrl, name, '');
}
resolveEnum(enumIdentifier: any, name: string): any {
const staticSymbol: StaticSymbol = enumIdentifier;
return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
}
public annotations(type: StaticSymbol): any[] {
@ -179,59 +190,145 @@ export class StaticReflector implements ReflectorReader {
private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
this.host.angularImportLocations();
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
ANGULAR_IMPORT_LOCATIONS;
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self);
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Optional'), Optional);
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Output'), Output);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule);
this.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
// Note: Some metadata classes can be used directly with Provider.deps.
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group);
this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
}
/**
* 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, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
}
return resolvedModulePath;
};
const cacheKey = `${filePath}|${symbolName}`;
let staticSymbol = this.declarationCache.get(cacheKey);
if (staticSymbol) {
return staticSymbol;
}
const metadata = this.getModuleMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata['metadata'][symbolName]) {
staticSymbol = this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (!staticSymbol && metadata['exports']) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata['exports']) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find((symbol: any) => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
staticSymbol = this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) {
staticSymbol = candidateSymbol;
break;
}
}
}
}
}
}
this.declarationCache.set(cacheKey, staticSymbol);
return staticSymbol;
}
findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol {
try {
const filePath = this.host.moduleNameToFileName(module, containingFile);
let symbol: StaticSymbol;
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
symbol = this.getStaticSymbol(module, symbolName);
} else {
symbol = this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
return symbol;
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
/** @internal */
@ -244,10 +341,10 @@ export class StaticReflector implements ReflectorReader {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol;
if (expression['module']) {
staticSymbol = _this.host.findDeclaration(
expression['module'], expression['name'], context.filePath);
staticSymbol =
_this.findDeclaration(expression['module'], expression['name'], context.filePath);
} else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
}
return staticSymbol;
}
@ -456,8 +553,7 @@ export class StaticReflector implements ReflectorReader {
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
return _this.host.getStaticSymbol(
selectTarget.filePath, selectTarget.name, members);
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
}
}
const member = simplify(expression['member']);
@ -492,10 +588,10 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion
let target = expression['expression'];
if (target['module']) {
staticSymbol = _this.host.findDeclaration(
target['module'], target['name'], context.filePath);
staticSymbol =
_this.findDeclaration(target['module'], target['name'], context.filePath);
} else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
}
let converter = _this.conversionMap.get(staticSymbol);
if (converter) {
@ -551,10 +647,15 @@ export class StaticReflector implements ReflectorReader {
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
moduleMetadata = this.host.getMetadataFor(module);
if (Array.isArray(moduleMetadata)) {
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
moduleMetadata[0];
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
@ -600,6 +701,7 @@ function expandedMessage(error: any): string {
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
break;
}
return error.message;
}

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export function isStaticSymbol(value: any): value is StaticSymbol {
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
}
/**
* 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, public members?: string[]) {}
}

View File

@ -718,15 +718,6 @@ function _normalizeArray(obj: any[]): any[] {
return obj || [];
}
export function isStaticSymbol(value: any): value is StaticSymbol {
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
}
export interface StaticSymbol {
name: string;
filePath: string;
}
export class ProviderMeta {
token: any;
useClass: Type<any>;

View File

@ -0,0 +1,117 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Extract i18n messages from source code
*/
import {ViewEncapsulation} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols, loadNgModuleDirectives} from '../aot/compiler';
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
import {CompileDirectiveMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {NgModuleResolver} from '../ng_module_resolver';
import {ParseError} from '../parse_util';
import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {I18NHtmlParser} from './i18n_html_parser';
import {MessageBundle} from './message_bundle';
export interface ExtractorOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
/**
* The host of the Extractor disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface ExtractorHost extends StaticReflectorHost {
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
}
export class Extractor {
constructor(
private options: ExtractorOptions, public host: ExtractorHost,
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
private metadataResolver: CompileMetadataResolver) {}
extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options);
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const errors: ParseError[] = [];
files.forEach(file => {
const compMetas: CompileDirectiveMetadata[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta && dirMeta.isComponent) {
compMetas.push(dirMeta);
}
});
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
});
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return this.messageBundle;
});
}
static create(host: ExtractorHost, options: ExtractorOptions):
{extractor: Extractor, staticReflector: StaticReflector} {
const htmlParser = new I18NHtmlParser(new HtmlParser());
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(host);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new CompilerConfig({
genDebugInfo: false,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
const normalizer = new DirectiveNormalizer(
{get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config);
const elementSchemaRegistry = new DomElementSchemaRegistry();
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): implicit tags & attributes
const messageBundle = new MessageBundle(htmlParser, [], {});
const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver);
return {extractor, staticReflector};
}
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
export {Extractor, ExtractorHost, ExtractorOptions} from './extractor';
export {I18NHtmlParser} from './i18n_html_parser';
export {MessageBundle} from './message_bundle';
export {Serializer} from './serializers/serializer';

View File

@ -8,6 +8,7 @@
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {StaticSymbol, isStaticSymbol} from './aot/static_symbol';
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
@ -339,19 +340,21 @@ export class Identifiers {
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `asset:@angular/lib/${pkg}/index`;
return `@angular/${pkg}/index`;
} else {
return `asset:@angular/lib/${pkg}/src/${path}`;
return `@angular/${pkg}/${type}/${path}`;
}
}
export function resolveIdentifier(identifier: IdentifierSpec) {
return new CompileIdentifierMetadata({
name: identifier.name,
moduleUrl: identifier.moduleUrl,
reference:
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime)
});
let moduleUrl = identifier.moduleUrl;
const reference =
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
if (isStaticSymbol(reference)) {
moduleUrl = reference.filePath;
}
return new CompileIdentifierMetadata(
{name: identifier.name, moduleUrl: moduleUrl, reference: reference});
}
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {

View File

@ -8,22 +8,22 @@
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata';
import {CompilerConfig} from './config';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {stringify} from './facade/lang';
import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler';
import * as ir from './output/output_ast';
import {interpretStatements} from './output/output_interpreter';
import {jitStatements} from './output/output_jit';
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser';
import {SyncAsyncResult} from './util';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from './view_compiler/view_compiler';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {stringify} from '../facade/lang';
import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler';
import * as ir from '../output/output_ast';
import {interpretStatements} from '../output/output_interpreter';
import {jitStatements} from '../output/output_jit';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {SyncAsyncResult} from '../util';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from '../view_compiler/view_compiler';
@ -37,7 +37,7 @@ import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDepende
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/
@Injectable()
export class RuntimeCompiler implements Compiler {
export class JitCompiler implements Compiler {
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
@ -385,10 +385,10 @@ function assertComponent(meta: CompileDirectiveMetadata) {
}
/**
* Implements `Compiler` by delegating to the RuntimeCompiler using a known module.
* Implements `Compiler` by delegating to the JitCompiler using a known module.
*/
class ModuleBoundCompiler implements Compiler {
constructor(private _delegate: RuntimeCompiler, private _ngModule: Type<any>) {}
constructor(private _delegate: JitCompiler, private _ngModule: Type<any>) {}
get _injector(): Injector { return this._delegate.injector; }

View File

@ -8,28 +8,29 @@
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from './animation/animation_parser';
import {CompilerConfig} from './config';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
import {DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {Lexer} from './expression_parser/lexer';
import {Parser} from './expression_parser/parser';
import * as i18n from './i18n/index';
import {CompileMetadataResolver} from './metadata_resolver';
import {HtmlParser} from './ml_parser/html_parser';
import {NgModuleCompiler} from './ng_module_compiler';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from './private_import_core';
import {ResourceLoader} from './resource_loader';
import {RuntimeCompiler} from './runtime_compiler';
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser';
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from './url_resolver';
import {ViewCompiler} from './view_compiler/view_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import * as i18n from '../i18n/index';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from '../ng_module_resolver';
import {PipeResolver} from '../pipe_resolver';
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from '../private_import_core';
import {ResourceLoader} from '../resource_loader';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {JitCompiler} from './compiler';
const _NO_RESOURCE_LOADER: ResourceLoader = {
get(url: string): Promise<string>{
@ -38,7 +39,7 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
};
/**
* A set of providers that provide `RuntimeCompiler` and its dependencies to use for
* A set of providers that provide `JitCompiler` and its dependencies to use for
* template compilation.
*/
export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> = [
@ -68,8 +69,8 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
NgModuleCompiler,
DirectiveWrapperCompiler,
{provide: CompilerConfig, useValue: new CompilerConfig()},
RuntimeCompiler,
{provide: Compiler, useExisting: RuntimeCompiler},
JitCompiler,
{provide: Compiler, useExisting: JitCompiler},
DomElementSchemaRegistry,
{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
UrlResolver,
@ -81,7 +82,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
@Injectable()
export class RuntimeCompilerFactory implements CompilerFactory {
export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[];
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
this._defaultOptions = [<CompilerOptions>{
@ -128,7 +129,7 @@ function _initReflector() {
*/
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: CompilerFactory, useClass: RuntimeCompilerFactory},
{provide: CompilerFactory, useClass: JitCompilerFactory},
{provide: PLATFORM_INITIALIZER, useValue: _initReflector, multi: true},
]);

View File

@ -8,6 +8,7 @@
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Host, Inject, Injectable, ModuleWithProviders, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
import {isStaticSymbol} from './aot/static_symbol';
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
@ -921,16 +922,16 @@ function flattenAndDedupeArray(tree: any[]): Array<any> {
}
function isValidType(value: any): boolean {
return cpl.isStaticSymbol(value) || (value instanceof Type);
return isStaticSymbol(value) || (value instanceof Type);
}
function staticTypeModuleUrl(value: any): string {
return cpl.isStaticSymbol(value) ? value.filePath : null;
return isStaticSymbol(value) ? value.filePath : null;
}
function componentModuleUrl(
reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string {
if (cpl.isStaticSymbol(type)) {
if (isStaticSymbol(type)) {
return staticTypeModuleUrl(type);
}
@ -956,7 +957,7 @@ function convertToCompileValue(
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
let identifier: cpl.CompileIdentifierMetadata;
if (cpl.isStaticSymbol(value)) {
if (isStaticSymbol(value)) {
identifier = new cpl.CompileIdentifierMetadata(
{name: value.name, moduleUrl: value.filePath, reference: value});
} else {

View File

@ -12,10 +12,10 @@ import {isBlank, isPresent} from '../facade/lang';
import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
import * as o from './output_ast';
import {ImportGenerator} from './path_util';
import {ImportResolver} from './path_util';
export class JavaScriptEmitter implements OutputEmitter {
constructor(private _importGenerator: ImportGenerator) {}
constructor(private _importGenerator: ImportResolver) {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
const converter = new JsEmitterVisitor(moduleUrl);
const ctx = EmitterVisitorContext.createRoot(exportedVars);
@ -25,7 +25,7 @@ export class JavaScriptEmitter implements OutputEmitter {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(
`var ${prefix} = req` +
`uire('${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}');`);
`uire('${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}');`);
});
srcParts.push(ctx.toSource());
return srcParts.join('\n');

View File

@ -6,30 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
// asset:<package-name>/<realm>/<path-to-module>
const _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/;
/**
* Interface that defines how import statements should be generated.
*/
export abstract class ImportGenerator {
static parseAssetUrl(url: string): AssetUrl { return AssetUrl.parse(url); }
abstract getImportPath(moduleUrlStr: string, importedUrlStr: string): string;
}
export class AssetUrl {
static parse(url: string, allowNonMatching: boolean = true): AssetUrl {
const match = url.match(_ASSET_URL_RE);
if (match !== null) {
return new AssetUrl(match[1], match[2], match[3]);
}
if (allowNonMatching) {
return null;
}
throw new Error(`Url ${url} is not a valid asset: url`);
}
constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) {
}
export abstract class ImportResolver {
/**
* Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*/
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
}

View File

@ -12,9 +12,9 @@ import {isBlank, isPresent} from '../facade/lang';
import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
import * as o from './output_ast';
import {ImportGenerator} from './path_util';
import {ImportResolver} from './path_util';
const _debugModuleUrl = 'asset://debug/lib';
const _debugModuleUrl = '/debug/lib';
export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.Type | any[]):
string {
@ -37,7 +37,7 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
}
export class TypeScriptEmitter implements OutputEmitter {
constructor(private _importGenerator: ImportGenerator) {}
constructor(private _importGenerator: ImportResolver) {}
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
const converter = new _TsEmitterVisitor(moduleUrl);
const ctx = EmitterVisitorContext.createRoot(exportedVars);
@ -47,7 +47,7 @@ export class TypeScriptEmitter implements OutputEmitter {
// Note: can't write the real word for import as it screws up system.js auto detection...
srcParts.push(
`imp` +
`ort * as ${prefix} from '${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}';`);
`ort * as ${prefix} from '${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}';`);
});
srcParts.push(ctx.toSource());
return srcParts.join('\n');

View File

@ -73,3 +73,6 @@ export type ComponentStillLoadingError = typeof r._ComponentStillLoadingError;
export const ComponentStillLoadingError: typeof r.ComponentStillLoadingError =
r.ComponentStillLoadingError;
export const AnimationTransition: typeof r.AnimationTransition = r.AnimationTransition;
export type SetterFn = typeof r._SetterFn;
export type GetterFn = typeof r._GetterFn;
export type MethodFn = typeof r._MethodFn;

View File

@ -10,9 +10,6 @@ import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core';
import {isBlank, isPresent} from './facade/lang';
const _ASSET_SCHEME = 'asset:';
/**
* Create a {@link UrlResolver} with no package prefix.
*/
@ -21,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
}
export function createOfflineCompileUrlResolver(): UrlResolver {
return new UrlResolver(_ASSET_SCHEME);
return new UrlResolver('.');
}
/**
@ -70,14 +67,9 @@ export class UrlResolver {
if (isPresent(prefix) && isPresent(resolvedParts) &&
resolvedParts[_ComponentIndex.Scheme] == 'package') {
let path = resolvedParts[_ComponentIndex.Path];
if (this._packagePrefix === _ASSET_SCHEME) {
const pathSegements = path.split(/\//);
resolvedUrl = `asset:${pathSegements[0]}/lib/${pathSegements.slice(1).join('/')}`;
} else {
prefix = prefix.replace(/\/+$/, '');
path = path.replace(/^\/+/, '');
return `${prefix}/${path}`;
}
prefix = prefix.replace(/\/+$/, '');
path = path.replace(/^\/+/, '');
return `${prefix}/${path}`;
}
return resolvedUrl;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler';
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
@ -22,7 +22,7 @@ describe('StaticReflector', () => {
let reflector: StaticReflector;
beforeEach(() => {
host = new MockReflectorHost();
host = new MockStaticReflectorHost();
reflector = new StaticReflector(host);
});
@ -31,7 +31,7 @@ describe('StaticReflector', () => {
}
it('should get annotations for NgFor', () => {
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
const annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1);
const annotation = annotations[0];
@ -40,15 +40,15 @@ describe('StaticReflector', () => {
});
it('should get constructor for NgFor', () => {
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const ViewContainerRef =
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef');
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
const ViewContainerRef = reflector.findDeclaration(
'@angular/core/src/linker/view_container_ref', 'ViewContainerRef');
const TemplateRef =
host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef');
const IterableDiffers = host.findDeclaration(
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = host.findDeclaration(
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef');
const IterableDiffers = reflector.findDeclaration(
'@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = reflector.findDeclaration(
'@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
const parameters = reflector.parameters(NgFor);
expect(parameters).toEqual([
@ -58,7 +58,7 @@ describe('StaticReflector', () => {
it('should get annotations for HeroDetailComponent', () => {
const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1);
const annotation = annotations[0];
@ -73,41 +73,40 @@ describe('StaticReflector', () => {
])]);
});
it('should throw and exception for unsupported metadata versions', () => {
const e = host.findDeclaration('src/version-error', 'e');
expect(() => reflector.annotations(e))
it('should throw an exception for unsupported metadata versions', () => {
expect(() => reflector.findDeclaration('src/version-error', 'e'))
.toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 1'));
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 2'));
});
it('should get and empty annotation list for an unknown class', () => {
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]);
});
it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy();
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
});
it('should get an empty object from propMetadata for an unknown class', () => {
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({});
});
it('should get empty parameters list for an unknown class ', () => {
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]);
});
it('should provide context for errors reported by the collector', () => {
const SomeClass = host.findDeclaration('src/error-reporting', 'SomeClass');
const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass');
expect(() => reflector.annotations(SomeClass))
.toThrow(new Error(
'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts'));
@ -308,14 +307,14 @@ describe('StaticReflector', () => {
expect(simplify(
new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
.toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
.toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
});
it('should simplify a function reference as a static symbol', () => {
expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []})))
.toEqual(host.getStaticSymbol('/src/cases', 'myFunction'));
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction'));
});
it('should simplify values initialized with a function call', () => {
@ -343,13 +342,11 @@ describe('StaticReflector', () => {
try {
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts');
expect(metadata).toBeDefined();
if (!Array.isArray(metadata)) {
const moduleMetadata: any = metadata['metadata'];
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
}
const moduleMetadata: any = metadata[0]['metadata'];
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
} catch (e) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true;
@ -377,7 +374,7 @@ describe('StaticReflector', () => {
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
Foo: {
__symbolic: 'class',
@ -406,35 +403,35 @@ describe('StaticReflector', () => {
it('should be able to get metadata for a class containing a custom decorator', () => {
const props = reflector.propMetadata(
host.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
expect(props).toEqual({foo: []});
});
it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts';
const dep = host.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(host.getStaticSymbol(src, 'Forward'));
const dep = reflector.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward'));
expect(props).toEqual([[dep, new Inject(dep)]]);
});
it('should report an error for invalid function calls', () => {
expect(
() =>
reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
() => reflector.annotations(
reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
.toThrow(new Error(
`Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`));
});
it('should be able to get metadata for a class containing a static method call', () => {
const annotations = reflector.annotations(
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
});
it('should be able to get metadata for a class containing a static field reference', () => {
const annotations =
reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
});
@ -442,7 +439,7 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with a conditional expression',
() => {
const annotations = reflector.annotations(
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
@ -452,50 +449,81 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with default parameters',
() => {
const annotations = reflector.annotations(
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([['a', true, false]]);
});
it('should be able to get metadata with a reference to a static method', () => {
const annotations = reflector.annotations(
host.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
});
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
const foo1 = reflector.getStaticSymbol('main.ts', 'foo');
const foo2 = reflector.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file',
() => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); });
it('should be able to trace a named export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Thirty');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
it('should cache tracing a named export', () => {
const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough();
const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough();
reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
moduleNameToFileNameSpy.calls.reset();
getMetadataForSpy.calls.reset();
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(moduleNameToFileNameSpy.calls.count()).toBe(1);
expect(getMetadataForSpy.calls.count()).toBe(0);
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
});
class MockReflectorHost implements StaticReflectorHost {
private staticTypeCache = new Map<string, StaticSymbol>();
class MockStaticReflectorHost implements StaticReflectorHost {
private collector = new MetadataCollector();
constructor() {}
angularImportLocations() {
return {
coreDecorators: 'angular2/src/core/metadata',
diDecorators: 'angular2/src/core/di/metadata',
diMetadata: 'angular2/src/core/di/metadata',
diOpaqueToken: 'angular2/src/core/di/opaque_token',
animationMetadata: 'angular2/src/core/animation/metadata',
provider: 'angular2/src/core/di/provider'
};
}
getCanonicalFileName(fileName: string): string { return fileName; }
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`;
let result = this.staticTypeCache.get(cacheKey);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.staticTypeCache.set(cacheKey, result);
}
return result;
}
// In tests, assume that symbols are not re-exported
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol {
moduleNameToFileName(modulePath: string, containingFile?: string): string {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string {
@ -529,32 +557,34 @@ class MockReflectorHost implements StaticReflectorHost {
if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts';
if (this.getMetadataFor(tsName)) {
return this.getStaticSymbol(tsName, symbolName);
if (this._getMetadataFor(tsName)) {
return tsName;
}
return this.getStaticSymbol(baseName + '.d.ts', symbolName);
return baseName + '.d.ts';
}
return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
return '/tmp/' + modulePath + '.d.ts';
}
getMetadataFor(moduleId: string): any {
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
private _getMetadataFor(moduleId: string): any {
const data: {[key: string]: any} = {
'/tmp/angular2/src/common/forms-deprecated/directives.d.ts': [{
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
'__symbolic': 'module',
'version': 1,
'version': 2,
'metadata': {
'FORM_DIRECTIVES': [
{
'__symbolic': 'reference',
'name': 'NgFor',
'module': 'angular2/src/common/directives/ng_for'
'module': '@angular/common/src/directives/ng_for'
}
]
}
}],
'/tmp/angular2/src/common/directives/ng_for.d.ts': {
'/tmp/@angular/common/src/directives/ng_for.d.ts': {
'__symbolic': 'module',
'version': 1,
'version': 2,
'metadata': {
'NgFor': {
'__symbolic': 'class',
@ -564,7 +594,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'Directive',
'module': '../../core/metadata'
'module': '@angular/core/src/metadata'
},
'arguments': [
{
@ -581,22 +611,22 @@ class MockReflectorHost implements StaticReflectorHost {
'parameters': [
{
'__symbolic': 'reference',
'module': '../../core/linker/view_container_ref',
'module': '@angular/core/src/linker/view_container_ref',
'name': 'ViewContainerRef'
},
{
'__symbolic': 'reference',
'module': '../../core/linker/template_ref',
'module': '@angular/core/src/linker/template_ref',
'name': 'TemplateRef'
},
{
'__symbolic': 'reference',
'module': '../../core/change_detection/differs/iterable_differs',
'module': '@angular/core/src/change_detection/differs/iterable_differs',
'name': 'IterableDiffers'
},
{
'__symbolic': 'reference',
'module': '../../core/change_detection/change_detector_ref',
'module': '@angular/core/src/change_detection/change_detector_ref',
'name': 'ChangeDetectorRef'
}
]
@ -606,17 +636,17 @@ class MockReflectorHost implements StaticReflectorHost {
}
}
},
'/tmp/angular2/src/core/linker/view_container_ref.d.ts':
{version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/linker/template_ref.d.ts':
{version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
{version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
{version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/linker/view_container_ref.d.ts':
{version: 2, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/linker/template_ref.d.ts':
{version: 2, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts':
{version: 2, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts':
{version: 2, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/src/app/hero-detail.component.d.ts': {
'__symbolic': 'module',
'version': 1,
'version': 2,
'metadata': {
'HeroDetailComponent': {
'__symbolic': 'class',
@ -626,7 +656,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'Component',
'module': 'angular2/src/core/metadata'
'module': '@angular/core/src/metadata'
},
'arguments': [
{
@ -638,7 +668,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'trigger',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments': [
'myAnimation',
@ -646,7 +676,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'state',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments': [
'state1',
@ -654,7 +684,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'style',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments': [
{ 'background':'white' }
@ -666,7 +696,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic':'reference',
'name':'transition',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments': [
'* => *',
@ -675,20 +705,20 @@ class MockReflectorHost implements StaticReflectorHost {
'expression':{
'__symbolic':'reference',
'name':'sequence',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[[{ '__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'group',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[[{
'__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'animate',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[
'1s 0.5s',
@ -696,13 +726,13 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic':'reference',
'name':'keyframes',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[[{ '__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'style',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[ { 'background': 'blue'} ]
}, {
@ -710,7 +740,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic':'reference',
'name':'style',
'module': 'angular2/src/core/animation/metadata'
'module': '@angular/core/src/animation/metadata'
},
'arguments':[ { 'background': 'red'} ]
}]]
@ -736,7 +766,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': {
'__symbolic': 'reference',
'name': 'Input',
'module': 'angular2/src/core/metadata'
'module': '@angular/core/src/metadata'
}
}
]
@ -750,7 +780,7 @@ class MockReflectorHost implements StaticReflectorHost {
'__symbolic': 'call',
'expression': {
'__symbolic': 'reference',
'module': 'angular2/src/core/metadata',
'module': '@angular/core/src/metadata',
'name': 'HostListener'
},
'arguments': [
@ -767,11 +797,11 @@ class MockReflectorHost implements StaticReflectorHost {
}
}
},
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 1, metadata: {s: 's'}},
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {s: 's'}},
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/error-reporting.d.ts': {
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
SomeClass: {
__symbolic: 'class',
@ -781,7 +811,7 @@ class MockReflectorHost implements StaticReflectorHost {
expression: {
__symbolic: 'reference',
name: 'Component',
module: 'angular2/src/core/metadata'
module: '@angular/core/src/metadata'
},
arguments: [
{
@ -801,7 +831,7 @@ class MockReflectorHost implements StaticReflectorHost {
},
'/tmp/src/error-references.d.ts': {
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
Link1: {
__symbolic: 'reference',
@ -823,7 +853,7 @@ class MockReflectorHost implements StaticReflectorHost {
},
'/tmp/src/function-declaration.d.ts': {
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
one: {
__symbolic: 'function',
@ -852,7 +882,7 @@ class MockReflectorHost implements StaticReflectorHost {
},
'/tmp/src/function-reference.ts': {
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
one: {
__symbolic: 'call',
@ -894,7 +924,7 @@ class MockReflectorHost implements StaticReflectorHost {
},
'/tmp/src/function-recursive.d.ts': {
__symbolic: 'modules',
version: 1,
version: 2,
metadata: {
recursive: {
__symbolic: 'function',
@ -954,7 +984,7 @@ class MockReflectorHost implements StaticReflectorHost {
},
'/tmp/src/spread.ts': {
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
}
@ -982,8 +1012,8 @@ class MockReflectorHost implements StaticReflectorHost {
`,
'/tmp/src/invalid-calls.ts': `
import {someFunction} from './nvalid-calll-definitions.ts';
import {Component} from 'angular2/src/core/metadata';
import {NgIf} from 'angular2/common';
import {Component} from '@angular/core/src/metadata';
import {NgIf} from '@angular/common';
@Component({
selector: 'my-component',
@ -999,7 +1029,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyOtherComponent { }
`,
'/tmp/src/static-method.ts': `
import {Component} from 'angular2/src/core/metadata';
import {Component} from '@angular/core/src/metadata';
@Component({
selector: 'stub'
@ -1017,7 +1047,7 @@ class MockReflectorHost implements StaticReflectorHost {
}
`,
'/tmp/src/static-method-call.ts': `
import {Component} from 'angular2/src/core/metadata';
import {Component} from '@angular/core/src/metadata';
import {MyModule} from './static-method';
@Component({
@ -1036,7 +1066,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyDefaultsComponent { }
`,
'/tmp/src/static-field.ts': `
import {Injectable} from 'angular2/core';
import {Injectable} from '@angular/core';
@Injectable()
export class MyModule {
@ -1044,7 +1074,7 @@ class MockReflectorHost implements StaticReflectorHost {
}
`,
'/tmp/src/static-field-reference.ts': `
import {Component} from 'angular2/src/core/metadata';
import {Component} from '@angular/core/src/metadata';
import {MyModule} from './static-field';
@Component({
@ -1058,7 +1088,7 @@ class MockReflectorHost implements StaticReflectorHost {
}
`,
'/tmp/src/static-method-ref.ts': `
import {Component} from 'angular2/src/core/metadata';
import {Component} from '@angular/core/src/metadata';
import {ClassWithStatics} from './static-method-def';
@Component({
@ -1069,7 +1099,7 @@ class MockReflectorHost implements StaticReflectorHost {
}
`,
'/tmp/src/invalid-metadata.ts': `
import {Component} from 'angular2/src/core/metadata';
import {Component} from '@angular/core/src/metadata';
@Component({
providers: [ { provider: 'a', useValue: (() => 1)() }]
@ -1077,9 +1107,9 @@ class MockReflectorHost implements StaticReflectorHost {
export class InvalidMetadata {}
`,
'/tmp/src/forward-ref.ts': `
import {forwardRef} from 'angular2/core';
import {Component} from 'angular2/src/core/metadata';
import {Inject} from 'angular2/src/core/di/metadata';
import {forwardRef} from '@angular/core';
import {Component} from '@angular/core/src/metadata';
import {Inject} from '@angular/core/src/di/metadata';
@Component({})
export class Forward {
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
@ -1087,7 +1117,50 @@ class MockReflectorHost implements StaticReflectorHost {
export class Dep {
@Input f: Forward;
}
`
`,
'/tmp/src/reexport/reexport.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
exports: [
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
{from: './src/origin5'}, {from: './src/reexport2'}
]
},
'/tmp/src/reexport/src/origin1.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin5.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
Five: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin30.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
Thirty: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/originNone.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
},
'/tmp/src/reexport/src/reexport2.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
}
};
@ -1100,9 +1173,14 @@ class MockReflectorHost implements StaticReflectorHost {
if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`);
}
return this.collector.getMetadata(sf);
return [this.collector.getMetadata(sf)];
}
}
return data[moduleId];
const result = data[moduleId];
if (result) {
return Array.isArray(result) ? result : [result];
} else {
return null;
}
}
}

View File

@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
import {SimpleJsImportGenerator} from './output_emitter_util';
const someModuleUrl = 'asset:somePackage/lib/somePath';
const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
const someModuleUrl = 'somePackage/somePath';
const anotherModuleUrl = 'somePackage/someOtherPath';
const sameModuleIdentifier =
new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl});

View File

@ -9,7 +9,7 @@
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import {assetUrl} from '@angular/compiler/src/identifiers';
import * as o from '@angular/compiler/src/output/output_ast';
import {ImportGenerator} from '@angular/compiler/src/output/path_util';
import {ImportResolver} from '@angular/compiler/src/output/path_util';
import {EventEmitter} from '@angular/core';
import {BaseError} from '@angular/core/src/facade/errors';
import {ViewType} from '@angular/core/src/linker/view_type';
@ -22,7 +22,7 @@ export class ExternalClass {
const testDataIdentifier = new CompileIdentifierMetadata({
name: 'ExternalClass',
moduleUrl: `asset:@angular/lib/compiler/test/output/output_emitter_util`,
moduleUrl: `@angular/compiler/test/output/output_emitter_util`,
reference: ExternalClass
});
@ -252,13 +252,8 @@ function createOperatorFn(op: o.BinaryOperator) {
o.DYNAMIC_TYPE);
}
export class SimpleJsImportGenerator implements ImportGenerator {
getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
const importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr);
if (importedAssetUrl) {
return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`;
} else {
return importedUrlStr;
}
export class SimpleJsImportGenerator implements ImportResolver {
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
return importedUrlStr;
}
}

View File

@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
import {SimpleJsImportGenerator} from './output_emitter_util';
const someModuleUrl = 'asset:somePackage/lib/somePath';
const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
const someModuleUrl = 'somePackage/somePath';
const anotherModuleUrl = 'somePackage/someOtherPath';
const sameModuleIdentifier =
new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl});

View File

@ -91,9 +91,6 @@ export function main() {
it('should resolve package: urls',
() => { expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true); });
it('should resolve asset: urls',
() => { expect(isStyleUrlResolvable('asset:someUrl.css')).toBe(true); });
it('should not resolve empty urls', () => {
expect(isStyleUrlResolvable(null)).toBe(false);
expect(isStyleUrlResolvable('')).toBe(false);

View File

@ -106,16 +106,6 @@ export function main() {
});
});
describe('asset urls', () => {
let resolver: UrlResolver;
beforeEach(() => { resolver = createOfflineCompileUrlResolver(); });
it('should resolve package: urls into asset: urls', () => {
expect(resolver.resolve(null, 'package:somePkg/somePath'))
.toEqual('asset:somePkg/lib/somePath');
});
});
describe('corner and error cases', () => {
it('should encode URLs before resolving',
() => {

View File

@ -29,7 +29,7 @@ export * from './pipe_resolver_mock';
import {createPlatformFactory, ModuleWithComponentFactories, Injectable, CompilerOptions, COMPILER_OPTIONS, CompilerFactory, NgModuleFactory, Injector, NgModule, Component, Directive, Pipe, Type, PlatformRef} from '@angular/core';
import {MetadataOverride} from '@angular/core/testing';
import {TestingCompilerFactory, TestingCompiler} from './private_import_core';
import {platformCoreDynamic, RuntimeCompiler, DirectiveResolver, NgModuleResolver, PipeResolver} from '@angular/compiler';
import {platformCoreDynamic, JitCompiler, DirectiveResolver, NgModuleResolver, PipeResolver} from '@angular/compiler';
import {MockDirectiveResolver} from './directive_resolver_mock';
import {MockNgModuleResolver} from './ng_module_resolver_mock';
import {MockPipeResolver} from './pipe_resolver_mock';
@ -40,7 +40,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
constructor(private _compilerFactory: CompilerFactory) {}
createTestingCompiler(options: CompilerOptions[]): TestingCompiler {
const compiler = <RuntimeCompiler>this._compilerFactory.createCompiler(options);
const compiler = <JitCompiler>this._compilerFactory.createCompiler(options);
return new TestingCompilerImpl(
compiler, compiler.injector.get(MockDirectiveResolver),
compiler.injector.get(MockPipeResolver), compiler.injector.get(MockNgModuleResolver));
@ -50,7 +50,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
export class TestingCompilerImpl implements TestingCompiler {
private _overrider = new MetadataOverrider();
constructor(
private _compiler: RuntimeCompiler, private _directiveResolver: MockDirectiveResolver,
private _compiler: JitCompiler, private _directiveResolver: MockDirectiveResolver,
private _pipeResolver: MockPipeResolver, private _moduleResolver: MockNgModuleResolver) {}
get injector(): Injector { return this._compiler.injector; }

View File

@ -44,7 +44,7 @@ const DATE_FORMATS_SPLIT =
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
yMMMdjms: datePartGetterFactory(combine([
'yMMMdjms': datePartGetterFactory(combine([
digitCondition('year', 1),
nameCondition('month', 3),
digitCondition('day', 1),
@ -52,23 +52,23 @@ const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
digitCondition('minute', 1),
digitCondition('second', 1),
])),
yMdjm: datePartGetterFactory(combine([
'yMdjm': datePartGetterFactory(combine([
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
digitCondition('hour', 1), digitCondition('minute', 1)
])),
yMMMMEEEEd: datePartGetterFactory(combine([
'yMMMMEEEEd': datePartGetterFactory(combine([
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
digitCondition('day', 1)
])),
yMMMMd: datePartGetterFactory(
'yMMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
yMMMd: datePartGetterFactory(
'yMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
yMd: datePartGetterFactory(
'yMd': datePartGetterFactory(
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
jms: datePartGetterFactory(combine(
'jms': datePartGetterFactory(combine(
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
jm: datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
};
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {

View File

@ -20,10 +20,8 @@ export class WebAnimationsDriver implements AnimationDriver {
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
let formattedSteps: {[key: string]: string | number}[] = [];
let startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
if (isPresent(startingStyles)) {
startingStyleLookup = _populateStyles(startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
}
keyframes.forEach((keyframe: AnimationKeyframe) => {

View File

@ -69,12 +69,21 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const previousStyleProps = Object.keys(this.previousStyles);
if (previousStyleProps.length) {
let startingKeyframe = findStartingKeyframe(keyframes);
let startingKeyframe = keyframes[0];
let missingStyleProps: string[] = [];
previousStyleProps.forEach(prop => {
if (isPresent(startingKeyframe[prop])) {
startingKeyframe[prop] = this.previousStyles[prop];
if (!isPresent(startingKeyframe[prop])) {
missingStyleProps.push(prop);
}
startingKeyframe[prop] = this.previousStyles[prop];
});
if (missingStyleProps.length) {
for (let i = 1; i < keyframes.length; i++) {
let kf = keyframes[i];
missingStyleProps.forEach(prop => { kf[prop] = _computeStyle(this.element, prop); });
}
}
}
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
@ -176,17 +185,3 @@ function _copyKeyframeStyles(styles: {[style: string]: string | number}):
});
return newStyles;
}
function findStartingKeyframe(keyframes: {[prop: string]: string | number}[]):
{[prop: string]: string | number} {
let startingKeyframe = keyframes[0];
// it's important that we find the LAST keyframe
// to ensure that style overidding is final.
for (let i = 1; i < keyframes.length; i++) {
const kf = keyframes[i];
const offset = kf['offset'];
if (offset !== 0) break;
startingKeyframe = kf;
}
return startingKeyframe;
}

View File

@ -202,6 +202,32 @@ export function main() {
]);
});
it('should allow previous styles to be merged into the starting keyframe of the animation that were not apart of the animation to begin with',
() => {
if (!getDOM().supportsWebAnimation()) return;
const elm = el('<div></div>');
document.body.appendChild(elm);
elm.style.color = 'rgb(0,0,0)';
const previousStyles = {color: 'red'};
const previousPlayer =
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
previousPlayer.play();
previousPlayer.finish();
const player = new ExtendedWebAnimationsPlayer(
elm, [{opacity: '0'}, {opacity: '1'}], {duration: 1000}, [previousPlayer]);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([
{opacity: '0', color: 'red'},
{opacity: '1', color: 'rgb(0, 0, 0)'},
]);
});
it('should properly calculate the previous styles for the player even when its currently playing',
() => {
if (!getDOM().supportsWebAnimation()) return;

View File

@ -20,8 +20,8 @@ import {EmptyError} from 'rxjs/util/EmptyError';
import {Route, Routes, UrlMatchResult} from './config';
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
import {NavigationCancelingError, PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
import {NavigationCancelingError, PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {andObservables, forEach, merge, waitForMap, wrapIntoObservable} from './utils/collection';
class NoMatch {
@ -29,7 +29,7 @@ class NoMatch {
}
class AbsoluteRedirect {
constructor(public segments: UrlSegment[]) {}
constructor(public urlTree: UrlTree) {}
}
function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
@ -37,9 +37,15 @@ function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
(obs: Observer<UrlSegmentGroup>) => obs.error(new NoMatch(segmentGroup)));
}
function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
function absoluteRedirect(newTree: UrlTree): Observable<any> {
return new Observable<UrlSegmentGroup>(
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(segments)));
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(newTree)));
}
function namedOutletsRedirect(redirectTo: string): Observable<any> {
return new Observable<UrlSegmentGroup>(
(obs: Observer<UrlSegmentGroup>) => obs.error(new Error(
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`)));
}
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
@ -50,9 +56,9 @@ function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
export function applyRedirects(
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
config: Routes): Observable<UrlTree> {
return new ApplyRedirects(injector, configLoader, urlTree, config).apply();
injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
urlTree: UrlTree, config: Routes): Observable<UrlTree> {
return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
}
class ApplyRedirects {
@ -60,21 +66,20 @@ class ApplyRedirects {
constructor(
private injector: Injector, private configLoader: RouterConfigLoader,
private urlTree: UrlTree, private config: Routes) {}
private urlSerializer: UrlSerializer, private urlTree: UrlTree, private config: Routes) {}
apply(): Observable<UrlTree> {
const expanded$ =
this.expandSegmentGroup(this.injector, this.config, this.urlTree.root, PRIMARY_OUTLET);
const urlTrees$ = map.call(
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment));
return _catch.call(urlTrees$, (e: any) => {
if (e instanceof AbsoluteRedirect) {
// after an absolute redirect we do not apply any more redirects!
this.allowRedirects = false;
const group =
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: new UrlSegmentGroup(e.segments, {})});
// we need to run matching, so we can fetch all lazy-loaded modules
return this.match(group);
return this.match(e.urlTree);
} else if (e instanceof NoMatch) {
throw this.noMatchError(e);
} else {
@ -83,11 +88,12 @@ class ApplyRedirects {
});
}
private match(segmentGroup: UrlSegmentGroup): Observable<UrlTree> {
private match(tree: UrlTree): Observable<UrlTree> {
const expanded$ =
this.expandSegmentGroup(this.injector, this.config, segmentGroup, PRIMARY_OUTLET);
this.expandSegmentGroup(this.injector, this.config, tree.root, PRIMARY_OUTLET);
const mapped$ = map.call(
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
expanded$, (rootSegmentGroup: UrlSegmentGroup) =>
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment));
return _catch.call(mapped$, (e: any): Observable<UrlTree> => {
if (e instanceof NoMatch) {
throw this.noMatchError(e);
@ -101,11 +107,12 @@ class ApplyRedirects {
return new Error(`Cannot match any routes. URL Segment: '${e.segmentGroup}'`);
}
private createUrlTree(rootCandidate: UrlSegmentGroup): UrlTree {
private createUrlTree(rootCandidate: UrlSegmentGroup, queryParams: Params, fragment: string):
UrlTree {
const root = rootCandidate.segments.length > 0 ?
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: rootCandidate}) :
rootCandidate;
return new UrlTree(root, this.urlTree.queryParams, this.urlTree.fragment);
return new UrlTree(root, queryParams, fragment);
}
private expandSegmentGroup(
@ -191,12 +198,14 @@ class ApplyRedirects {
private expandWildCardWithParamsAgainstRouteUsingRedirect(
injector: Injector, routes: Route[], route: Route,
outlet: string): Observable<UrlSegmentGroup> {
const newSegments = applyRedirectCommands([], route.redirectTo, {});
const newTree = this.applyRedirectCommands([], route.redirectTo, {});
if (route.redirectTo.startsWith('/')) {
return absoluteRedirect(newSegments);
return absoluteRedirect(newTree);
} else {
const group = new UrlSegmentGroup(newSegments, {});
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
const group = new UrlSegmentGroup(newSegments, {});
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
});
}
}
@ -207,14 +216,16 @@ class ApplyRedirects {
match(segmentGroup, route, segments);
if (!matched) return noMatch(segmentGroup);
const newSegments =
applyRedirectCommands(consumedSegments, route.redirectTo, <any>positionalParamSegments);
const newTree = this.applyRedirectCommands(
consumedSegments, route.redirectTo, <any>positionalParamSegments);
if (route.redirectTo.startsWith('/')) {
return absoluteRedirect(newSegments);
return absoluteRedirect(newTree);
} else {
return this.expandSegment(
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
false);
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
return this.expandSegment(
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
false);
});
}
}
@ -284,6 +295,92 @@ class ApplyRedirects {
return of (new LoadedRouterConfig([], injector, null, null));
}
}
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
let res: UrlSegment[] = [];
let c = urlTree.root;
while (true) {
res = res.concat(c.segments);
if (c.numberOfChildren === 0) {
return of (res);
} else if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
return namedOutletsRedirect(route.redirectTo);
} else {
c = c.children[PRIMARY_OUTLET];
}
}
}
private applyRedirectCommands(
segments: UrlSegment[], redirectTo: string, posParams: {[k: string]: UrlSegment}): UrlTree {
const t = this.urlSerializer.parse(redirectTo);
return this.applyRedirectCreatreUrlTree(
redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams);
}
private applyRedirectCreatreUrlTree(
redirectTo: string, urlTree: UrlTree, segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlTree {
const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
return new UrlTree(
newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams),
urlTree.fragment);
}
private createQueryParams(redirectToParams: Params, actualParams: Params): Params {
const res: Params = {};
forEach(redirectToParams, (v: any, k: string) => {
if (v.startsWith(':')) {
res[k] = actualParams[v.substring(1)];
} else {
res[k] = v;
}
});
return res;
}
private createSegmentGroup(
redirectTo: string, group: UrlSegmentGroup, segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegmentGroup {
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
let children: {[n: string]: UrlSegmentGroup} = {};
forEach(group.children, (child: UrlSegmentGroup, name: string) => {
children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
});
return new UrlSegmentGroup(updatedSegments, children);
}
private createSegments(
redirectTo: string, redirectToSegments: UrlSegment[], actualSegments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
return redirectToSegments.map(
s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) :
this.findOrReturn(s, actualSegments));
}
private findPosParam(
redirectTo: string, redirectToUrlSegment: UrlSegment,
posParams: {[k: string]: UrlSegment}): UrlSegment {
const pos = posParams[redirectToUrlSegment.path.substring(1)];
if (!pos)
throw new Error(
`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
return pos;
}
private findOrReturn(redirectToUrlSegment: UrlSegment, actualSegments: UrlSegment[]): UrlSegment {
let idx = 0;
for (const s of actualSegments) {
if (s.path === redirectToUrlSegment.path) {
actualSegments.splice(idx);
return s;
}
idx++;
}
return redirectToUrlSegment;
}
}
function runGuards(injector: Injector, route: Route): Observable<boolean> {
@ -328,46 +425,6 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
};
}
function applyRedirectCommands(
segments: UrlSegment[], redirectTo: string,
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
const r = redirectTo.startsWith('/') ? redirectTo.substring(1) : redirectTo;
if (r === '') {
return [];
} else {
return createSegments(redirectTo, r.split('/'), segments, posParams);
}
}
function createSegments(
redirectTo: string, parts: string[], segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
return parts.map(
p => p.startsWith(':') ? findPosParam(p, posParams, redirectTo) :
findOrCreateSegment(p, segments));
}
function findPosParam(
part: string, posParams: {[k: string]: UrlSegment}, redirectTo: string): UrlSegment {
const paramName = part.substring(1);
const pos = posParams[paramName];
if (!pos) throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${part}'.`);
return pos;
}
function findOrCreateSegment(part: string, segments: UrlSegment[]): UrlSegment {
let idx = 0;
for (const s of segments) {
if (s.path === part) {
segments.splice(idx);
return s;
}
idx++;
}
return new UrlSegment(part, {});
}
function split(
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
config: Route[]) {

View File

@ -650,7 +650,7 @@ export class Router {
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
if (!precreatedState) {
const redirectsApplied$ =
applyRedirects(this.injector, this.configLoader, url, this.config);
applyRedirects(this.injector, this.configLoader, this.urlSerializer, url, this.config);
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
return map.call(

View File

@ -15,6 +15,7 @@ import {LoadedRouterConfig} from '../src/router_config_loader';
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
describe('applyRedirects', () => {
const serializer = new DefaultUrlSerializer();
it('should return the same url tree when no redirects', () => {
checkRedirect(
@ -38,7 +39,7 @@ describe('applyRedirects', () => {
});
it('should throw when cannot handle a positional parameter', () => {
applyRedirects(null, null, tree('/a/1'), [
applyRedirects(null, null, serializer, tree('/a/1'), [
{path: 'a/:id', redirectTo: 'a/:other'}
]).subscribe(() => {}, (e) => {
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
@ -133,11 +134,11 @@ describe('applyRedirects', () => {
{
path: 'a',
component: ComponentA,
children: [{path: 'b/:id', redirectTo: '/absolute/:id'}]
children: [{path: 'b/:id', redirectTo: '/absolute/:id?a=1&b=:b#f1'}]
},
{path: '**', component: ComponentC}
],
'/a/b/1', (t: UrlTree) => { compareTrees(t, tree('/absolute/1')); });
'/a/b/1?b=2', (t: UrlTree) => { compareTrees(t, tree('/absolute/1?a=1&b=2#f1')); });
});
describe('lazy loading', () => {
@ -153,10 +154,11 @@ describe('applyRedirects', () => {
};
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('a/b'), config).forEach(r => {
compareTrees(r, tree('/a/b'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a/b'), config)
.forEach(r => {
compareTrees(r, tree('/a/b'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
});
it('should handle the case when the loader errors', () => {
@ -165,9 +167,8 @@ describe('applyRedirects', () => {
};
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
applyRedirects(null, <any>loader, tree('a/b'), config).subscribe(() => {}, (e) => {
expect(e.message).toEqual('Loading Error');
});
applyRedirects(null, <any>loader, serializer, tree('a/b'), config)
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
});
it('should load when all canLoad guards return true', () => {
@ -186,7 +187,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config).forEach(r => {
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config).forEach(r => {
compareTrees(r, tree('/a/b'));
});
});
@ -208,7 +209,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
() => { throw 'Should not reach'; },
(e) => {
@ -234,7 +235,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
() => { throw 'Should not reach'; }, (e) => { expect(e).toEqual('someError'); });
});
@ -251,7 +252,7 @@ describe('applyRedirects', () => {
const config =
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
(r) => { compareTrees(r, tree('/a/b')); }, (e) => { throw 'Should not reach'; });
@ -267,10 +268,11 @@ describe('applyRedirects', () => {
const config =
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree(''), config).forEach(r => {
compareTrees(r, tree('a'));
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree(''), config)
.forEach(r => {
compareTrees(r, tree('a'));
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
});
});
it('should load the configuration only once', () => {
@ -289,12 +291,13 @@ describe('applyRedirects', () => {
const config = [{path: 'a', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k1'), config).subscribe(r => {});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k1'), config)
.subscribe(r => {});
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k2'), config)
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k2'), config)
.subscribe(
r => {
compareTrees(r, tree('a'));
compareTrees(r, tree('a?k2'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
},
(e) => { throw 'Should not reach'; });
@ -309,9 +312,8 @@ describe('applyRedirects', () => {
const config = [{path: '**', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
it('should load the configuration after a local redirect from a wildcard route', () => {
@ -324,9 +326,8 @@ describe('applyRedirects', () => {
const config =
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
it('should load the configuration after an absolute redirect from a wildcard route', () => {
@ -339,9 +340,8 @@ describe('applyRedirects', () => {
const config =
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
});
@ -388,7 +388,7 @@ describe('applyRedirects', () => {
{path: '', redirectTo: 'a', pathMatch: 'full'}
];
applyRedirects(null, null, tree('b'), config)
applyRedirects(null, null, serializer, tree('b'), config)
.subscribe(
(_) => { throw 'Should not be reached'; },
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
@ -518,7 +518,7 @@ describe('applyRedirects', () => {
]
}];
applyRedirects(null, null, tree('a/(d//aux:e)'), config)
applyRedirects(null, null, serializer, tree('a/(d//aux:e)'), config)
.subscribe(
(_) => { throw 'Should not be reached'; },
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
@ -549,7 +549,7 @@ describe('applyRedirects', () => {
it('should error when no children matching and some url is left', () => {
applyRedirects(
null, null, tree('/a/c'),
null, null, serializer, tree('/a/c'),
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
.subscribe(
(_) => { throw 'Should not be reached'; },
@ -576,10 +576,46 @@ describe('applyRedirects', () => {
'/a/1/b', (t: UrlTree) => { compareTrees(t, tree('a/1/b')); });
});
});
describe('redirecting to named outlets', () => {
it('should work when using absolute redirects', () => {
checkRedirect(
[
{path: 'a/:id', redirectTo: '/b/:id(aux:c/:id)'},
{path: 'b/:id', component: ComponentB},
{path: 'c/:id', component: ComponentC, outlet: 'aux'}
],
'a/1;p=99', (t: UrlTree) => { compareTrees(t, tree('/b/1;p=99(aux:c/1;p=99)')); });
});
it('should work when using absolute redirects (wildcard)', () => {
checkRedirect(
[
{path: '**', redirectTo: '/b(aux:c)'}, {path: 'b', component: ComponentB},
{path: 'c', component: ComponentC, outlet: 'aux'}
],
'a/1', (t: UrlTree) => { compareTrees(t, tree('/b(aux:c)')); });
});
it('should throw when using non-absolute redirects', () => {
applyRedirects(
null, null, serializer, tree('a'),
[
{path: 'a', redirectTo: 'b(aux:c)'},
])
.subscribe(
() => { throw new Error('should not be reached'); },
(e) => {
expect(e.message).toEqual(
'Only absolute redirects can have named outlets. redirectTo: \'b(aux:c)\'');
});
});
});
});
function checkRedirect(config: Routes, url: string, callback: any): void {
applyRedirects(null, null, tree(url), config).subscribe(callback, e => { throw e; });
applyRedirects(null, null, new DefaultUrlSerializer(), tree(url), config)
.subscribe(callback, e => { throw e; });
}
function tree(url: string): UrlTree {
@ -591,6 +627,8 @@ function compareTrees(actual: UrlTree, expected: UrlTree): void {
const error =
`"${serializer.serialize(actual)}" is not equal to "${serializer.serialize(expected)}"`;
compareSegments(actual.root, expected.root, error);
expect(actual.queryParams).toEqual(expected.queryParams);
expect(actual.fragment).toEqual(expected.fragment);
}
function compareSegments(actual: UrlSegmentGroup, expected: UrlSegmentGroup, error: string): void {

View File

@ -166,16 +166,14 @@ export class UpgradeModule {
(testabilityDelegate: angular.ITestabilityService) => {
const originalWhenStable: Function = testabilityDelegate.whenStable;
const injector = this.injector;
// Cannot use arrow function below because we need to grab the context
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
const whenStableContext: any = this;
originalWhenStable.call(this, function() {
const ng2Testability: Testability = injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
} else {
ng2Testability.whenStable(
newWhenStable.bind(whenStableContext, callback));
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};

View File

@ -355,14 +355,14 @@ export class UpgradeAdapter {
function(testabilityDelegate: angular.ITestabilityService) {
const originalWhenStable: Function = testabilityDelegate.whenStable;
const newWhenStable = (callback: Function): void => {
const whenStableContext: any = this;
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
originalWhenStable.call(this, function() {
const ng2Testability: Testability = moduleRef.injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
} else {
ng2Testability.whenStable(newWhenStable.bind(whenStableContext, callback));
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};
@ -433,8 +433,9 @@ export class UpgradeAdapter {
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
windowAngular.resumeBootstrap = function() {
let args = arguments;
windowAngular.resumeBootstrap = originalResumeBootstrap;
windowAngular.resumeBootstrap.apply(this, arguments);
ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); });
resolve();
};
} else {

View File

@ -49,7 +49,8 @@ export class UpgradeNg1ComponentAdapterBuilder {
],
ngOnInit: function() { /* needs to be here for ng2 to properly detect it */ },
ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ },
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ }
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ },
ngOnDestroy: function() { /* needs to be here for ng2 to properly detect it */ },
});
}
@ -262,16 +263,18 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
}
ngOnChanges(changes: SimpleChanges) {
for (const name in changes) {
if ((<Object>changes).hasOwnProperty(name)) {
const change: SimpleChange = changes[name];
this.setComponentProperty(name, change.currentValue);
}
const ng1Changes: any = {};
Object.keys(changes).forEach(name => {
const change: SimpleChange = changes[name];
this.setComponentProperty(name, change.currentValue);
ng1Changes[this.propertyMap[name]] = change;
});
if (this.destinationObj.$onChanges) {
this.destinationObj.$onChanges(ng1Changes);
}
}
ngDoCheck(): number {
const count = 0;
ngDoCheck() {
const destinationObj = this.destinationObj;
const lastValues = this.checkLastValues;
const checkProperties = this.checkProperties;
@ -287,7 +290,15 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
}
}
}
return count;
if (this.destinationObj.$doCheck && this.directive.controller) {
this.destinationObj.$doCheck();
}
}
ngOnDestroy() {
if (this.destinationObj.$onDestroy && this.directive.controller) {
this.destinationObj.$onDestroy();
}
}
setComponentProperty(name: string, value: any) {

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {async, fakeAsync, flushMicrotasks} from '@angular/core/testing';
import {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {UpgradeAdapter} from '@angular/upgrade';
@ -96,7 +96,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function Ng2Module() {}});
ng1Module.directive('ng1', () => {
@ -170,7 +169,6 @@ export function main() {
adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2
],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -276,7 +274,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
const element = html(`<div>
@ -320,7 +317,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -354,7 +350,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -409,7 +404,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -453,7 +447,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -501,7 +494,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -547,7 +539,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -580,7 +571,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -608,7 +598,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -633,7 +622,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -658,7 +646,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -684,7 +671,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -728,7 +714,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -761,7 +746,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -794,7 +778,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -834,7 +817,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -879,7 +861,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -892,32 +873,155 @@ export function main() {
it('should call $onInit of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);
const valueToFind = '$onInit';
const $onInitSpy = jasmine.createSpy('$onInit');
const ng1 = {
bindings: {},
template: '{{$ctrl.value}}',
controller: Class(
{constructor: function() {}, $onInit: function() { this.value = valueToFind; }})
};
ng1Module.component('ng1', ng1);
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
}
const Ng2 = Component({selector: 'ng2', template: '<ng1></ng1>'}).Class({
constructor: function() {}
});
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '',
controller: function() { this.$onInit = $onInitSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(multiTrim(document.body.textContent)).toEqual(valueToFind);
expect($onInitSpy).toHaveBeenCalled();
ref.dispose();
});
}));
it('should call $doCheck of components', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $doCheckSpy = jasmine.createSpy('$doCheck');
let changeDetector: ChangeDetectorRef;
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
constructor(cd: ChangeDetectorRef) { changeDetector = cd; }
}
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '{{$ctrl.value}}',
controller: function() { this.$doCheck = $doCheckSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect($doCheckSpy).toHaveBeenCalled();
$doCheckSpy.calls.reset();
changeDetector.detectChanges();
expect($doCheckSpy).toHaveBeenCalled();
ref.dispose();
});
}));
it('should call $onChanges of components', fakeAsync(() => {
const EXPECTED_VALUE = '$onChanges called';
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onChangesSpy = jasmine.createSpy('$onChanges');
let ng2Instance: any;
@Component({selector: 'ng2', template: '<ng1 [val]="val"></ng1>'})
class Ng2Component {
constructor() { ng2Instance = this; }
}
angular.module('ng1Module', [])
.component('ng1', {
bindings: {val: '<'},
template: '',
controller: function() { this.$onChanges = $onChangesSpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1Module']).ready((ref) => {
ng2Instance.val = EXPECTED_VALUE;
tick();
ref.ng1RootScope.$digest();
expect($onChangesSpy).toHaveBeenCalled();
const changes = $onChangesSpy.calls.mostRecent().args[0] as SimpleChanges;
expect(changes['val'].currentValue).toEqual(EXPECTED_VALUE);
ref.dispose();
});
}));
it('should call $onDestroy of components', fakeAsync(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const $onDestroySpy = jasmine.createSpy('$onDestroy');
@Component({selector: 'ng2', template: '<ng1></ng1>'})
class Ng2Component {
}
angular.module('ng1', [])
.component('ng1', {
bindings: {},
template: '<div>ng1</div>',
controller: function() { this.$onDestroy = $onDestroySpy; }
})
.directive('ng2', adapter.downgradeNg2Component(Ng2Component));
@NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2Component],
imports: [BrowserModule],
})
class Ng2Module {
}
const element = html(`<div ng-if="!destroy"><ng2></ng2></div>`);
adapter.bootstrap(element, ['ng1']).ready((ref) => {
const $rootScope = ref.ng1RootScope as any;
$rootScope.destroy = false;
tick();
$rootScope.$digest();
expect($onDestroySpy).not.toHaveBeenCalled();
$rootScope.destroy = true;
tick();
$rootScope.$digest();
expect($onDestroySpy).toHaveBeenCalled();
ref.dispose();
});
}));
@ -941,7 +1045,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
@ -974,7 +1077,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2a, Ng2b],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
const element = html(`<div><ng2a></ng2a></div>`);
@ -991,7 +1093,6 @@ export function main() {
const MyNg2Module = NgModule({
providers: [{provide: SomeToken, useValue: 'correct_value'}],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module);
@ -1119,7 +1220,6 @@ export function main() {
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
schemas: [NO_ERRORS_SCHEMA],
}).Class({constructor: function() {}});
module.directive('ng2', adapter.downgradeNg2Component(Ng2));

View File

@ -7,15 +7,18 @@
*/
import {Component, Input, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {TableCell, emptyTable} from '../util';
let trustedEmptyColor: SafeStyle;
let trustedGreyColor: SafeStyle;
@Component({
selector: 'largetable',
template: `<table><tbody>
<tr *ngFor="let row of data; trackBy: trackByIndex">
<td *ngFor="let cell of row; trackBy: trackByIndex" [style.backgroundColor]="cell.row % 2 ? '' : 'grey'">
<td *ngFor="let cell of row; trackBy: trackByIndex" [style.backgroundColor]="getColor(cell.row)">
{{cell.value}}
</td>
</tr>
@ -26,8 +29,14 @@ export class TableComponent {
data: TableCell[][] = emptyTable;
trackByIndex(index: number, item: any) { return index; }
getColor(row: number) { return row % 2 ? trustedEmptyColor : trustedGreyColor; }
}
@NgModule({imports: [BrowserModule], bootstrap: [TableComponent], declarations: [TableComponent]})
export class AppModule {
constructor(sanitizer: DomSanitizer) {
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
}
}

View File

@ -7,20 +7,28 @@
*/
import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {TreeNode, emptyTree} from '../util';
let trustedEmptyColor: SafeStyle;
let trustedGreyColor: SafeStyle;
@Component({
selector: 'tree',
inputs: ['data'],
template:
`<span [style.backgroundColor]="data.depth % 2 ? '' : 'grey'"> {{data.value}} </span><tree *ngIf='data.right != null' [data]='data.right'></tree><tree *ngIf='data.left != null' [data]='data.left'></tree>`
`<span [style.backgroundColor]="bgColor"> {{data.value}} </span><tree *ngIf='data.right != null' [data]='data.right'></tree><tree *ngIf='data.left != null' [data]='data.left'></tree>`
})
export class TreeComponent {
data: TreeNode = emptyTree;
get bgColor() { return this.data.depth % 2 ? trustedEmptyColor : trustedGreyColor; }
}
@NgModule({imports: [BrowserModule], bootstrap: [TreeComponent], declarations: [TreeComponent]})
export class AppModule {
constructor(sanitizer: DomSanitizer) {
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
}
}

View File

@ -7,14 +7,16 @@
*/
import {Component, Input, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserModule, DomSanitizer, SafeStyle} from '@angular/platform-browser';
import {TreeNode, emptyTree, maxDepth} from '../util';
let trustedEmptyColor: SafeStyle;
let trustedGreyColor: SafeStyle;
function createTreeComponent(level: number, isLeaf: boolean) {
const nextTreeEl = `tree${level+1}`;
let template =
`<span [style.backgroundColor]="data.depth % 2 ? '' : 'grey'"> {{data.value}} </span>`;
let template = `<span [style.backgroundColor]="bgColor"> {{data.value}} </span>`;
if (!isLeaf) {
template +=
`<${nextTreeEl} [data]='data.right'></${nextTreeEl}><${nextTreeEl} [data]='data.left'></${nextTreeEl}>`;
@ -24,6 +26,7 @@ function createTreeComponent(level: number, isLeaf: boolean) {
class TreeComponent {
@Input()
data: TreeNode;
get bgColor() { return this.data.depth % 2 ? trustedEmptyColor : trustedGreyColor; }
}
return TreeComponent;
@ -43,6 +46,10 @@ function createModule(): any {
@NgModule({imports: [BrowserModule], bootstrap: [RootTreeComponent], declarations: [components]})
class AppModule {
constructor(sanitizer: DomSanitizer) {
trustedEmptyColor = sanitizer.bypassSecurityTrustStyle('');
trustedGreyColor = sanitizer.bypassSecurityTrustStyle('grey');
}
}
return AppModule;

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "2.2.1",
"version": "2.2.2",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular 2 - a web framework for modern web apps",

View File

@ -1,6 +1,6 @@
{
"name": "@angular/tsc-wrapped",
"version": "0.2.2",
"version": "0.4.0",
"description": "Wraps the tsc CLI, allowing extensions.",
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
"bugs": "https://github.com/angular/angular/issues",

View File

@ -256,9 +256,11 @@ export class MetadataCollector {
if (classDeclaration.name) {
const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) {
if (!metadata) metadata = {};
if (classDeclaration.decorators) {
if (!metadata) metadata = {};
metadata[className] = classMetadataOf(classDeclaration);
} else {
metadata[className] = {__symbolic: 'class'};
}
}
}
@ -269,10 +271,14 @@ export class MetadataCollector {
// names substitution will be performed by the StaticReflector.
const functionDeclaration = <ts.FunctionDeclaration>node;
if (node.flags & ts.NodeFlags.Export) {
if (!metadata) metadata = {};
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (maybeFunc) {
if (!metadata) metadata = {};
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node);
} else if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text;
metadata[functionName] = {__symbolic: 'function'};
}
}
break;

View File

@ -15,7 +15,7 @@
// semantics of the file in an array. For example, when generating a version 2 file, if version 1
// can accurately represent the metadata, generate both version 1 and version 2 in an array.
export const VERSION = 1;
export const VERSION = 2;
export type MetadataEntry = ClassMetadata | FunctionMetadata | MetadataValue;

View File

@ -29,6 +29,7 @@ describe('Collector', () => {
'/unsupported-1.ts',
'/unsupported-2.ts',
'import-star.ts',
'exported-classes.ts',
'exported-functions.ts',
'exported-enum.ts',
'exported-consts.ts',
@ -62,7 +63,7 @@ describe('Collector', () => {
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
HeroDetailComponent: {
__symbolic: 'class',
@ -103,7 +104,7 @@ describe('Collector', () => {
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
AppComponent: {
__symbolic: 'class',
@ -157,7 +158,7 @@ describe('Collector', () => {
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
HEROES: [
{'id': 11, 'name': 'Mr. Nice'}, {'id': 12, 'name': 'Narco'},
@ -170,13 +171,6 @@ describe('Collector', () => {
});
});
it('should return undefined for modules that have no metadata', () => {
const sourceFile = program.getSourceFile('/app/error-cases.ts');
expect(sourceFile).toBeTruthy(sourceFile);
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toBeUndefined();
});
let casesFile: ts.SourceFile;
let casesMetadata: ModuleMetadata;
@ -238,7 +232,7 @@ describe('Collector', () => {
const metadata = collector.getMetadata(unsupported1);
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16},
b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 19},
@ -275,12 +269,26 @@ describe('Collector', () => {
]);
});
it('should record all exported classes', () => {
const sourceFile = program.getSourceFile('/exported-classes.ts');
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toEqual({
__symbolic: 'module',
version: 2,
metadata: {
SimpleClass: {__symbolic: 'class'},
AbstractClass: {__symbolic: 'class'},
DeclaredClass: {__symbolic: 'class'}
}
});
});
it('should be able to record functions', () => {
const exportedFunctions = program.getSourceFile('/exported-functions.ts');
const metadata = collector.getMetadata(exportedFunctions);
expect(metadata).toEqual({
__symbolic: 'module',
version: 1,
version: 2,
metadata: {
one: {
__symbolic: 'function',
@ -328,7 +336,9 @@ describe('Collector', () => {
}
}
}
}
},
complexFn: {__symbolic: 'function'},
declaredFn: {__symbolic: 'function'}
}
});
});
@ -829,6 +839,11 @@ const FILES: Directory = {
constructor(private f: common.NgFor) {}
}
`,
'exported-classes.ts': `
export class SimpleClass {}
export abstract class AbstractClass {}
export declare class DeclaredClass {}
`,
'exported-functions.ts': `
export function one(a: string, b: string, c: string) {
return {a: a, b: b, c: c};
@ -842,6 +857,14 @@ const FILES: Directory = {
export function supportsState(): boolean {
return !!window.history.pushState;
}
export function complexFn(x: any): boolean {
if (x) {
return true;
} else {
return false;
}
}
export declare function declaredFn();
`,
'exported-enum.ts': `
import {constValue} from './exported-consts';