Compare commits

...

58 Commits
4.4.2 ... 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
933caacad3 chore(release): cut angular 2.2.1 2016-11-16 16:38:28 -08:00
efe9c4f35c fix(tools): fix error when running test.sh (#12927) 2016-11-16 16:26:44 -08:00
5b0f9e2f51 refactor(compiler): allows synchronous retrieving of metadata (#12908)
Allows non-normalized metadata to be retrieved synchronously.

Related to #7482
2016-11-16 16:26:44 -08:00
462879887a fix(core): support ngTemplateOutlet in production mode (#12921)
Fixes #12911
2016-11-16 16:26:44 -08:00
dae0d0fd66 docs(upgrade/static): improve API docs with examples
Closes #12717
2016-11-16 16:26:44 -08:00
c7f750dd5a chore(public_api): remove Angular 1 types from upgrade/static API 2016-11-16 16:26:44 -08:00
73de925551 chore(examples): add upgrade/static example 2016-11-16 16:26:44 -08:00
547c22029a chore(examples): support upgrade/static examples 2016-11-16 16:26:44 -08:00
364642d58c fix(router): add a banner file for the router (#12919) 2016-11-16 16:26:44 -08:00
7b67badc43 fix(platform_browser): fix disableDebugTools() (#12918) 2016-11-16 16:26:44 -08:00
dc1662a447 fix(ngUpgrade): make AoT ngUpgrade work with the testability API and resumeBootstrap() (#12910) 2016-11-16 16:26:43 -08:00
b5f433626b build(build.sh): echo before building examples 2016-11-16 16:26:43 -08:00
dabaf858d9 fix(router): should not create a route state if navigation is canceled (#12868)
Closes #12776
2016-11-16 16:26:43 -08:00
bbc3c9ce0e refactor(forms): remove facade (#12558) 2016-11-16 16:26:43 -08:00
1dcf1f484e fix(router): removes a peer dependency from router to upgrade 2016-11-16 16:26:43 -08:00
583d2833db fix(animations): only pass in same typed players as previous players into web-animations (#12907)
Closes #12907
2016-11-16 16:26:43 -08:00
f502a768d3 chore(router): remove @angular/upgrade peer dep (#12896) 2016-11-16 16:26:43 -08:00
16303ac487 refactor(http): remove all facade methods from http module (#12870) 2016-11-16 16:26:43 -08:00
6cdc3b5c12 fix(tsickle): support ctorParams in function closure (#12876)
See https://github.com/angular/tsickle/issues/261 for context.
2016-11-16 16:26:43 -08:00
5c46c493f2 fix(animations): retain styling when transition destinations are changed (#12208)
Closes #9661
Closes #12208
2016-11-16 16:26:43 -08:00
e02c18049d fix(select): allow for null values in HTML select options bound with ngValue
closes #12829
2016-11-16 16:26:43 -08:00
e0ce5458a2 fix: allow for null values in HTML select options bound with ngValue
This corrects the case of <option [ngValue]="null"> binding a string like "{0: null}" to the model instead of an actual null object.

Closes #10349
2016-11-16 16:26:43 -08:00
6a5ba0ec81 fix: allow for null values in HTML select options bound with ngValue
This corrects the case of <option [ngValue]="null"> binding a string like "{0: null}" to the model instead of an actual null object.

Closes #10349
2016-11-16 16:26:42 -08:00
828c0d24eb refactor(core): remove dead code (#12871) 2016-11-16 16:26:42 -08:00
22536442d6 refactor(core): remove ListWrapper from i18n 2016-11-16 16:26:42 -08:00
845ea235ee fix(http): return request url if it cannot be retrieved from response
closes #12837
2016-11-16 16:26:42 -08:00
21a4de999b fix(http): correctly handle response body for 204 status code
closes #12830
fixes #12393
2016-11-16 16:26:42 -08:00
82b34838bf refactor(xhr_backend): remove facade 2016-11-16 16:26:42 -08:00
128 changed files with 3635 additions and 1992 deletions

View File

@ -1,3 +1,65 @@
<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)
### Bug Fixes
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
<a name="2.2.0"></a> <a name="2.2.0"></a>
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14) # [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)

View File

@ -20,6 +20,10 @@ PACKAGES=(core
benchpress) benchpress)
BUILD_ALL=true BUILD_ALL=true
BUNDLE=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 for ARG in "$@"; do
case "$ARG" in case "$ARG" in
@ -31,6 +35,10 @@ for ARG in "$@"; do
--bundle=*) --bundle=*)
BUNDLE=( "${ARG#--bundle=}" ) BUNDLE=( "${ARG#--bundle=}" )
;; ;;
--publish)
VERSION_SUFFIX=""
REMOVE_BENCHPRESS=true
;;
*) *)
echo "Unknown option $ARG." echo "Unknown option $ARG."
exit 1 exit 1
@ -38,6 +46,10 @@ for ARG in "$@"; do
esac esac
done 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 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" TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
@ -106,7 +118,13 @@ do
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
if [[ ${PACKAGE} != router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
fi
if [[ ${PACKAGE} == router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
fi
rm -rf ${DESTDIR} rm -rf ${DESTDIR}
@ -191,8 +209,24 @@ do
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH} $UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi fi
) 2>&1 | grep -v "as external dependency" ) 2>&1 | grep -v "as external dependency"
fi 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 done
echo ""
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
./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: dependencies:
pre: pre:
- npm install -g npm - npm install -g npm@3.6.0
test: test:
override: override:

View File

@ -50,6 +50,7 @@ module.exports = function(config) {
'dist/all/@angular/**/e2e_test/**', 'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/router/**', 'dist/all/@angular/router/**',
'dist/all/@angular/compiler-cli/**', 'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'dist/all/@angular/benchpress/**', 'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js', 'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.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 * 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 {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host';
export {Extractor} from './src/extractor'; 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'; export * from '@angular/tsc-wrapped';

View File

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

View File

@ -17,11 +17,9 @@ import {readFileSync} from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; 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 {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_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.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 { export class CodeGenerator {
constructor( constructor(
private options: AngularCompilerOptions, private program: ts.Program, private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector, public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {} private ngCompilerHost: CompilerHost) {}
// Write codegen in a directory structure matching the sources. // Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string { private calculateEmitPath(filePath: string): string {
@ -64,11 +62,11 @@ export class CodeGenerator {
return path.join(this.options.genDir, relativePath); return path.join(this.options.genDir, relativePath);
} }
codegen(options: {transitiveModules: boolean}): Promise<any> { codegen(): Promise<any> {
const staticSymbols = return this.compiler
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); .compileAll(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => { .then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
@ -80,17 +78,14 @@ export class CodeGenerator {
static create( static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext, tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator { ngCompilerHost?: CompilerHost): CodeGenerator {
resourceLoader = resourceLoader || { if (!ngCompilerHost) {
get: (s: string) => { const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
if (!compilerHost.fileExists(s)) { ngCompilerHost = usePathMapping ?
// TODO: We should really have a test for error cases like this! new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) :
throw new Error(`Compilation failed. Resource file not found: ${s}`); new CompilerHost(program, tsCompilerHost, options, compilerHostContext);
} }
return Promise.resolve(compilerHost.readFile(s));
}
};
const transFile = cliOptions.i18nFile; const transFile = cliOptions.i18nFile;
const locale = cliOptions.locale; const locale = cliOptions.locale;
let transContent: string = ''; let transContent: string = '';
@ -101,84 +96,18 @@ export class CodeGenerator {
} }
transContent = readFileSync(transFile, 'utf8'); transContent = readFileSync(transFile, 'utf8');
} }
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); debug: options.debug === true,
if (!reflectorHost) { translations: transContent,
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; i18nFormat: cliOptions.i18nFormat,
reflectorHost = usePathMapping ? locale: cliOptions.locale,
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) : excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
new ReflectorHost(program, compilerHost, options, reflectorHostContext); GENERATED_FILES
}
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 normalizer = return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
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);
} }
} }
export function extractProgramSymbols( export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost, return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
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;
} }

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( function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) { program: ts.Program, host: ts.CompilerHost) {
const resourceLoader: compiler.ResourceLoader = { const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
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 bundlePromise: Promise<compiler.MessageBundle> = extractor.extract(); const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();

View File

@ -14,86 +14,28 @@
import 'reflect-metadata'; import 'reflect-metadata';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {extractProgramSymbols} from './codegen'; import {excludeFilePattern} from './codegen';
import {ReflectorHost} from './reflector_host'; import {CompilerHost} from './compiler_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
export class Extractor { export class Extractor {
constructor( constructor(
private options: tsc.AngularCompilerOptions, private program: ts.Program, private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
public host: ts.CompilerHost, private staticReflector: StaticReflector, private program: ts.Program) {}
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
private metadataResolver: compiler.CompileMetadataResolver) {}
extract(): Promise<compiler.MessageBundle> { extract(): Promise<compiler.MessageBundle> {
const programSymbols: StaticSymbol[] = return this.ngExtractor.extract(this.program.getSourceFiles().map(
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
return compiler
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
.then(({files}) => {
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;
});
} }
static create( static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader, tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor {
reflectorHost?: ReflectorHost): Extractor { if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options);
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); const {extractor: ngExtractor} = compiler.Extractor.create(
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); return new Extractor(ngExtractor, ngCompilerHost, program);
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);
} }
} }

View File

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

View File

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

View File

@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
export type Console = typeof r._Console; export type Console = typeof r._Console;
export var Console: typeof r.Console = 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 * 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'; import * as ts from 'typescript';
export type Entry = string | Directory; export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; } export interface Directory { [name: string]: Entry; }
export class MockContext implements ReflectorHostContext { export class MockAotContext implements CompilerHostContext {
constructor(public currentDirectory: string, private files: Entry) {} constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; } fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
@ -28,6 +28,14 @@ export class MockContext implements ReflectorHostContext {
return undefined; 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 { writeFile(fileName: string, data: string): void {
const parts = fileName.split('/'); const parts = fileName.split('/');
const name = parts.pop(); const name = parts.pop();
@ -89,7 +97,7 @@ function normalize(parts: string[]): string[] {
} }
export class MockCompilerHost implements ts.CompilerHost { export class MockCompilerHost implements ts.CompilerHost {
constructor(private context: MockContext) {} constructor(private context: MockAotContext) {}
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); } 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 {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
export {CompilerConfig, RenderTypes} from './src/config'; export {CompilerConfig, RenderTypes} from './src/config';
export * from './src/compile_metadata'; export * from './src/compile_metadata';
export * from './src/offline_compiler'; export * from './src/aot/compiler_factory';
export {RuntimeCompiler} from './src/runtime_compiler'; 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/url_resolver';
export * from './src/resource_loader'; export * from './src/resource_loader';
export * from './src/compiler';
export {DirectiveResolver} from './src/directive_resolver'; export {DirectiveResolver} from './src/directive_resolver';
export {PipeResolver} from './src/pipe_resolver'; export {PipeResolver} from './src/pipe_resolver';
export {NgModuleResolver} from './src/ng_module_resolver'; export {NgModuleResolver} from './src/ng_module_resolver';

View File

@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles'); const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles'); const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles'); const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
const EMPTY_MAP = o.literalMap([]); const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
const _EMPTY_MAP = o.literalMap([]);
const _EMPTY_ARRAY = o.literalArr([]);
class _AnimationBuilder implements AnimationAstVisitor { class _AnimationBuilder implements AnimationAstVisitor {
private _fnVarName: string; private _fnVarName: string;
@ -110,10 +112,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
_callAnimateMethod( _callAnimateMethod(
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any, ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
context: _AnimationBuilderContext) { context: _AnimationBuilderContext) {
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
if (context.isExpectingFirstAnimateStep) {
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
context.isExpectingFirstAnimateStep = false;
}
context.totalTransitionTime += ast.duration + ast.delay; context.totalTransitionTime += ast.duration + ast.delay;
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [ return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration), _ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
o.literal(ast.delay), o.literal(ast.easing) o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
]); ]);
} }
@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.totalTransitionTime = 0; context.totalTransitionTime = 0;
context.isExpectingFirstStyleStep = true; context.isExpectingFirstStyleStep = true;
context.isExpectingFirstAnimateStep = true;
const stateChangePreconditions: o.Expression[] = []; const stateChangePreconditions: o.Expression[] = [];
@ -187,17 +195,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.stateMap.registerState(DEFAULT_STATE, {}); context.stateMap.registerState(DEFAULT_STATE, {});
const statements: o.Statement[] = []; const statements: o.Statement[] = [];
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT statements.push(_PREVIOUS_ANIMATION_PLAYERS
.callMethod( .set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
'cancelActiveAnimation', 'getAnimationPlayers',
[ [
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName), _ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE)) _ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
]) ]))
.toStmt()); .toDeclStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt()); statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt()); statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
@ -223,17 +230,6 @@ class _AnimationBuilder implements AnimationAstVisitor {
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles)); const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context))); ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
// this check ensures that the animation factory always returns a player // this check ensures that the animation factory always returns a player
@ -269,6 +265,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
])]) ])])
.toStmt()); .toStmt());
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
.callMethod('destroy', [])
.toStmt());
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.callMethod( .callMethod(
'queueAnimation', 'queueAnimation',
@ -304,7 +316,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
const lookupMap: any[] = []; const lookupMap: any[] = [];
Object.keys(context.stateMap.states).forEach(stateName => { Object.keys(context.stateMap.states).forEach(stateName => {
const value = context.stateMap.states[stateName]; const value = context.stateMap.states[stateName];
let variableValue = EMPTY_MAP; let variableValue = _EMPTY_MAP;
if (isPresent(value)) { if (isPresent(value)) {
const styleMap: any[] = []; const styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); }); Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
@ -324,6 +336,7 @@ class _AnimationBuilderContext {
stateMap = new _AnimationBuilderStateMap(); stateMap = new _AnimationBuilderStateMap();
endStateAnimateStep: AnimationStepAst = null; endStateAnimateStep: AnimationStepAst = null;
isExpectingFirstStyleStep = false; isExpectingFirstStyleStep = false;
isExpectingFirstAnimateStep = false;
totalTransitionTime = 0; totalTransitionTime = 0;
} }

View File

@ -8,84 +8,30 @@
import {SchemaMetadata} from '@angular/core'; import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, createHostComponentMeta} from '../compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from '../facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from './output/abstract_emitter'; import {OutputEmitter} from '../output/abstract_emitter';
import * as o from './output/output_ast'; import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from './style_compiler'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from './template_parser/template_parser'; import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler'; 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 { export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
} }
// Returns all the source files and a mapping from modules to directives export class AotCompiler {
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<{
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
}> {
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
}
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
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,
};
}
export class OfflineCompiler {
private _animationCompiler = new AnimationCompiler(); private _animationCompiler = new AnimationCompiler();
constructor( constructor(
@ -94,14 +40,16 @@ export class OfflineCompiler {
private _dirWrapperCompiler: DirectiveWrapperCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string, private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {} private _animationParser: AnimationParser, private _staticReflector: StaticReflector,
private _options: AotCompilerOptions) {}
clearCache() { this._metadataResolver.clearCache(); } clearCache() { this._metadataResolver.clearCache(); }
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): compileAll(rootFiles: string[]): Promise<SourceModule[]> {
Promise<SourceModule[]> { const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
return analyzeNgModules(staticSymbols, options, this._metadataResolver) const {ngModuleByPipeOrDirective, files, ngModules} =
.then(({ngModuleByPipeOrDirective, files}) => { analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map( const sourceModules = files.map(
file => this._compileSrcFile( file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
@ -325,32 +273,148 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, '']; 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 // Load the NgModules and check
// that all directives / pipes that are present in the program // that all directives / pipes that are present in the program
// are also declared by a module. // are also declared by a module.
function _loadNgModules( function _createNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, programStaticSymbols: StaticSymbol[],
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> { options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>(); const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = []; const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>(); const ngModulePipesAndDirective = new Set<StaticSymbol>();
const loadingPromises: Promise<any>[] = [];
const addNgModule = (staticSymbol: any) => { const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) { if (ngModules.has(staticSymbol) || !_filterFileByPatterns(staticSymbol.filePath, options)) {
return false; return false;
} }
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false); const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
if (ngModule) { if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule); ngModules.set(ngModule.type.reference, ngModule);
loadingPromises.push(loading);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference)); ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference)); ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
if (options.transitiveModules) { // For every input module add the list of transitively included modules
// For every input modules add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference)); ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
} }
}
return !!ngModule; return !!ngModule;
}; };
programStaticSymbols.forEach((staticSymbol) => { programStaticSymbols.forEach((staticSymbol) => {
@ -364,11 +428,17 @@ function _loadNgModules(
const symbolsMissingModule = const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s)); programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
if (symbolsMissingModule.length) { return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
const messages = symbolsMissingModule.map( }
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n')); function _filterFileByPatterns(
} fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values())); 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 * 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'; import {StaticReflector} from './static_reflector';
export class StaticAndDynamicReflectionCapabilities { 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 {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 = 2;
const ANGULAR_IMPORT_LOCATIONS = {
const SUPPORTED_SCHEMA_VERSION = 1; 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 * The host of the StaticReflector disconnects the implementation from TypeScript / other language
* ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is * services and from underlying file systems.
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*/ */
export interface StaticReflectorHost { export interface StaticReflectorHost {
/** /**
* Return a ModuleMetadata for the given module. * 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. * @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module. * @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. * Converts a module name that is used in an `import` to a file path.
* @param module the location imported from * I.e.
* @param containingFile for relative imports, the path of the file containing the import * `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/ */
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol; moduleNameToFileName(moduleName: string, containingFile: string): string;
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;
} }
/** /**
* A token representing the a reference to a static type. * A cache of static symbol used by the StaticReflector to return the same symbol for the
* * same symbol values.
* This token is unique for a filePath and name and can be used as a hash table key.
*/ */
export class StaticSymbol { export class StaticSymbolCache {
constructor(public filePath: string, public name: string, public members?: string[]) {} 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. * templates statically.
*/ */
export class StaticReflector implements ReflectorReader { export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>(); private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, 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 conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol; private opaqueToken: StaticSymbol;
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
this.initializeConversionMap();
}
importUri(typeOrFunc: StaticSymbol): string { 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; return staticSymbol ? staticSymbol.filePath : null;
} }
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { 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 { resolveEnum(enumIdentifier: any, name: string): any {
const staticSymbol: StaticSymbol = enumIdentifier; 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[] { public annotations(type: StaticSymbol): any[] {
@ -132,7 +143,6 @@ export class StaticReflector implements ReflectorReader {
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor'); const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []); const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []); const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = []; parameters = [];
parameterTypes.forEach((paramType, index) => { parameterTypes.forEach((paramType, index) => {
const nestedResult: any[] = []; const nestedResult: any[] = [];
@ -180,59 +190,145 @@ export class StaticReflector implements ReflectorReader {
private initializeConversionMap(): void { private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} = const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
this.host.angularImportLocations(); ANGULAR_IMPORT_LOCATIONS;
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken'); this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host); this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable); this.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self); 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.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf); this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Optional'), Optional); this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute); this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild); this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.registerDecoratorOrConstructor( 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.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild); this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren); this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Output'), Output); this.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding); this.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
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);
// Note: Some metadata classes can be used directly with Provider.deps. // Note: Some metadata classes can be used directly with Provider.deps.
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf); this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger); this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state); this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition); this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style); this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate); this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes); this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence); this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group); 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 */ /** @internal */
@ -245,10 +341,10 @@ export class StaticReflector implements ReflectorReader {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol; let staticSymbol: StaticSymbol;
if (expression['module']) { if (expression['module']) {
staticSymbol = _this.host.findDeclaration( staticSymbol =
expression['module'], expression['name'], context.filePath); _this.findDeclaration(expression['module'], expression['name'], context.filePath);
} else { } else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']); staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
} }
return staticSymbol; return staticSymbol;
} }
@ -457,8 +553,7 @@ export class StaticReflector implements ReflectorReader {
const members = selectTarget.members ? const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) : (selectTarget.members as string[]).concat(member) :
[member]; [member];
return _this.host.getStaticSymbol( return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
selectTarget.filePath, selectTarget.name, members);
} }
} }
const member = simplify(expression['member']); const member = simplify(expression['member']);
@ -493,10 +588,10 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion // Determine if the function is a built-in conversion
let target = expression['expression']; let target = expression['expression'];
if (target['module']) { if (target['module']) {
staticSymbol = _this.host.findDeclaration( staticSymbol =
target['module'], target['name'], context.filePath); _this.findDeclaration(target['module'], target['name'], context.filePath);
} else { } else {
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']); staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
} }
let converter = _this.conversionMap.get(staticSymbol); let converter = _this.conversionMap.get(staticSymbol);
if (converter) { if (converter) {
@ -552,10 +647,15 @@ export class StaticReflector implements ReflectorReader {
public getModuleMetadata(module: string): {[key: string]: any} { public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module); let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) { if (!moduleMetadata) {
moduleMetadata = this.host.getMetadataFor(module); const moduleMetadatas = this.host.getMetadataFor(module);
if (Array.isArray(moduleMetadata)) { if (moduleMetadatas) {
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) || let maxVersion = -1;
moduleMetadata[0]; moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
} }
if (!moduleMetadata) { if (!moduleMetadata) {
moduleMetadata = moduleMetadata =
@ -601,6 +701,7 @@ function expandedMessage(error: any): string {
if (error.context && error.context.name) { if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`; return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
} }
break;
} }
return error.message; 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

@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary {
exportedDirectives: CompileIdentifierMetadata[]; exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[]; exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[]; exportedModules: CompileNgModuleDirectiveSummary[];
loadingPromises: Promise<any>[]; directiveLoaders: (() => Promise<void>)[];
} }
export type CompileNgModuleSummary = export type CompileNgModuleSummary =
@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedModules: this.exportedModules, exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives, exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes, exportedPipes: this.exportedPipes,
loadingPromises: this.transitiveModule.loadingPromises directiveLoaders: this.transitiveModule.directiveLoaders
}; };
} }
@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedDirectives: this.exportedDirectives, exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes, exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules, exportedModules: this.exportedModules,
loadingPromises: this.transitiveModule.loadingPromises directiveLoaders: this.transitiveModule.directiveLoaders
}; };
} }
} }
@ -695,7 +695,7 @@ export class TransitiveCompileNgModuleMetadata {
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[], public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[], public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public loadingPromises: Promise<any>[]) { public directiveLoaders: (() => Promise<void>)[]) {
directives.forEach(dir => this.directivesSet.add(dir.reference)); directives.forEach(dir => this.directivesSet.add(dir.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference)); pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
} }
@ -718,15 +718,6 @@ function _normalizeArray(obj: any[]): any[] {
return obj || []; 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 { export class ProviderMeta {
token: any; token: any;
useClass: Type<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 * found in the LICENSE file at https://angular.io/license
*/ */
export {Extractor, ExtractorHost, ExtractorOptions} from './extractor';
export {I18NHtmlParser} from './i18n_html_parser'; export {I18NHtmlParser} from './i18n_html_parser';
export {MessageBundle} from './message_bundle'; export {MessageBundle} from './message_bundle';
export {Serializer} from './serializers/serializer'; export {Serializer} from './serializers/serializer';

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../../facade/collection';
import * as ml from '../../ml_parser/ast'; import * as ml from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser'; import {HtmlParser} from '../../ml_parser/html_parser';
import {InterpolationConfig} from '../../ml_parser/interpolation_config'; import {InterpolationConfig} from '../../ml_parser/interpolation_config';
@ -162,7 +161,7 @@ class _WriteVisitor implements i18n.Visitor {
serialize(nodes: i18n.Node[]): xml.Node[] { serialize(nodes: i18n.Node[]): xml.Node[] {
this._isInIcu = false; this._isInIcu = false;
return ListWrapper.flatten(nodes.map(node => node.visit(this))); return [].concat(...nodes.map(node => node.visit(this)));
} }
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../../facade/collection';
import * as html from '../../ml_parser/ast'; import * as html from '../../ml_parser/ast';
import * as i18n from '../i18n_ast'; import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle'; import {MessageBundle} from '../message_bundle';
@ -121,6 +120,6 @@ class _Visitor implements i18n.Visitor {
} }
serialize(nodes: i18n.Node[]): xml.Node[] { serialize(nodes: i18n.Node[]): xml.Node[] {
return ListWrapper.flatten(nodes.map(node => node.visit(this))); return [].concat(...nodes.map(node => node.visit(this)));
} }
} }

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 {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 {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'; 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 { export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) { if (path == null) {
return `asset:@angular/lib/${pkg}/index`; return `@angular/${pkg}/index`;
} else { } else {
return `asset:@angular/lib/${pkg}/src/${path}`; return `@angular/${pkg}/${type}/${path}`;
} }
} }
export function resolveIdentifier(identifier: IdentifierSpec) { export function resolveIdentifier(identifier: IdentifierSpec) {
return new CompileIdentifierMetadata({ let moduleUrl = identifier.moduleUrl;
name: identifier.name, const reference =
moduleUrl: identifier.moduleUrl, reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
reference: if (isStaticSymbol(reference)) {
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime) moduleUrl = reference.filePath;
}); }
return new CompileIdentifierMetadata(
{name: identifier.name, moduleUrl: moduleUrl, reference: reference});
} }
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata { 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 {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from '../compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {stringify} from './facade/lang'; import {stringify} from '../facade/lang';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from '../ng_module_compiler';
import * as ir from './output/output_ast'; import * as ir from '../output/output_ast';
import {interpretStatements} from './output/output_interpreter'; import {interpretStatements} from '../output/output_interpreter';
import {jitStatements} from './output/output_jit'; import {jitStatements} from '../output/output_jit';
import {CompiledStylesheet, StyleCompiler} from './style_compiler'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from './template_parser/template_parser'; import {TemplateParser} from '../template_parser/template_parser';
import {SyncAsyncResult} from './util'; import {SyncAsyncResult} from '../util';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from './view_compiler/view_compiler'; 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). * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/ */
@Injectable() @Injectable()
export class RuntimeCompiler implements Compiler { export class JitCompiler implements Compiler {
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>(); 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 { 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; } 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 {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 {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from './config'; import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from './directive_resolver'; import {DirectiveResolver} from '../directive_resolver';
import {DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from './expression_parser/lexer'; import {Lexer} from '../expression_parser/lexer';
import {Parser} from './expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import * as i18n from './i18n/index'; import * as i18n from '../i18n/index';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from './ml_parser/html_parser'; import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from './ng_module_resolver'; import {NgModuleResolver} from '../ng_module_resolver';
import {PipeResolver} from './pipe_resolver'; import {PipeResolver} from '../pipe_resolver';
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from './private_import_core'; import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from '../private_import_core';
import {ResourceLoader} from './resource_loader'; import {ResourceLoader} from '../resource_loader';
import {RuntimeCompiler} from './runtime_compiler'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {StyleCompiler} from '../style_compiler';
import {StyleCompiler} from './style_compiler'; import {TemplateParser} from '../template_parser/template_parser';
import {TemplateParser} from './template_parser/template_parser'; import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver';
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from './url_resolver'; import {ViewCompiler} from '../view_compiler/view_compiler';
import {ViewCompiler} from './view_compiler/view_compiler';
import {JitCompiler} from './compiler';
const _NO_RESOURCE_LOADER: ResourceLoader = { const _NO_RESOURCE_LOADER: ResourceLoader = {
get(url: string): Promise<string>{ 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. * template compilation.
*/ */
export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> = [ 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, NgModuleCompiler,
DirectiveWrapperCompiler, DirectiveWrapperCompiler,
{provide: CompilerConfig, useValue: new CompilerConfig()}, {provide: CompilerConfig, useValue: new CompilerConfig()},
RuntimeCompiler, JitCompiler,
{provide: Compiler, useExisting: RuntimeCompiler}, {provide: Compiler, useExisting: JitCompiler},
DomElementSchemaRegistry, DomElementSchemaRegistry,
{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}, {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
UrlResolver, UrlResolver,
@ -81,7 +82,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
@Injectable() @Injectable()
export class RuntimeCompilerFactory implements CompilerFactory { export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[]; private _defaultOptions: CompilerOptions[];
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) { constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
this._defaultOptions = [<CompilerOptions>{ this._defaultOptions = [<CompilerOptions>{
@ -128,7 +129,7 @@ function _initReflector() {
*/ */
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [ export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true}, {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: CompilerFactory, useClass: RuntimeCompilerFactory}, {provide: CompilerFactory, useClass: JitCompilerFactory},
{provide: PLATFORM_INITIALIZER, useValue: _initReflector, multi: true}, {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 {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 {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata'; import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
@ -145,14 +146,92 @@ export class CompileMetadataResolver {
if (this._directiveCache.has(directiveType)) { if (this._directiveCache.has(directiveType)) {
return; return;
} }
directiveType = resolveForwardRef(directiveType);
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
type: nonNormalizedMetadata.type,
isComponent: nonNormalizedMetadata.isComponent,
selector: nonNormalizedMetadata.selector,
exportAs: nonNormalizedMetadata.exportAs,
changeDetection: nonNormalizedMetadata.changeDetection,
inputs: nonNormalizedMetadata.inputs,
outputs: nonNormalizedMetadata.outputs,
hostListeners: nonNormalizedMetadata.hostListeners,
hostProperties: nonNormalizedMetadata.hostProperties,
hostAttributes: nonNormalizedMetadata.hostAttributes,
providers: nonNormalizedMetadata.providers,
viewProviders: nonNormalizedMetadata.viewProviders,
queries: nonNormalizedMetadata.queries,
viewQueries: nonNormalizedMetadata.viewQueries,
entryComponents: nonNormalizedMetadata.entryComponents,
template: templateMetadata
});
this._directiveCache.set(directiveType, normalizedDirMeta);
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta;
};
if (nonNormalizedMetadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
encapsulation: nonNormalizedMetadata.template.encapsulation,
template: nonNormalizedMetadata.template.template,
templateUrl: nonNormalizedMetadata.template.templateUrl,
styles: nonNormalizedMetadata.template.styles,
styleUrls: nonNormalizedMetadata.template.styleUrls,
animations: nonNormalizedMetadata.template.animations,
interpolation: nonNormalizedMetadata.template.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
directiveType = resolveForwardRef(directiveType); directiveType = resolveForwardRef(directiveType);
const dirMeta = this._directiveResolver.resolve(directiveType); const dirMeta = this._directiveResolver.resolve(directiveType);
if (!dirMeta) { if (!dirMeta) {
return null; return null;
} }
let moduleUrl = staticTypeModuleUrl(directiveType); let moduleUrl = staticTypeModuleUrl(directiveType);
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
if (dirMeta instanceof Component) {
// component
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
}
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
let changeDetectionStrategy: ChangeDetectionStrategy = null; let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = []; let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
@ -185,8 +264,7 @@ export class CompileMetadataResolver {
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata( providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
`providers for "${stringify(directiveType)}"`);
} }
let queries: cpl.CompileQueryMetadata[] = []; let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = []; let viewQueries: cpl.CompileQueryMetadata[] = [];
@ -195,12 +273,12 @@ export class CompileMetadataResolver {
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType); viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
} }
const meta = cpl.CompileDirectiveMetadata.create({ return cpl.CompileDirectiveMetadata.create({
selector: selector, selector: selector,
exportAs: dirMeta.exportAs, exportAs: dirMeta.exportAs,
isComponent: !!templateMeta, isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType, moduleUrl), type: this._getTypeMetadata(directiveType, moduleUrl),
template: templateMeta, template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy, changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs, inputs: dirMeta.inputs,
outputs: dirMeta.outputs, outputs: dirMeta.outputs,
@ -211,47 +289,6 @@ export class CompileMetadataResolver {
viewQueries: viewQueries, viewQueries: viewQueries,
entryComponents: entryComponentMetadata entryComponents: entryComponentMetadata
}); });
this._directiveCache.set(directiveType, meta);
this._directiveSummaryCache.set(directiveType, meta.toSummary());
return meta;
};
if (dirMeta instanceof Component) {
// component
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: moduleUrl,
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
} }
/** /**
@ -309,11 +346,20 @@ export class CompileMetadataResolver {
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} { {ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound); const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
const loading = const loading = ngModule ?
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null); Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
Promise.resolve(null);
return {ngModule, loading}; return {ngModule, loading};
} }
/**
* Get the NgModule metadata without loading the directives.
*/
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata {
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
}
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata { cpl.CompileNgModuleMetadata {
moduleType = resolveForwardRef(moduleType); moduleType = resolveForwardRef(moduleType);
@ -396,10 +442,8 @@ export class CompileMetadataResolver {
transitiveModule.directives.push(declaredIdentifier); transitiveModule.directives.push(declaredIdentifier);
declaredDirectives.push(declaredIdentifier); declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType); this._addTypeToModule(declaredType, moduleType);
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync); transitiveModule.directiveLoaders.push(
if (loadingPromise) { () => this._loadDirectiveMetadata(declaredType, isSync));
transitiveModule.loadingPromises.push(loadingPromise);
}
} else if (this._pipeResolver.isPipe(declaredType)) { } else if (this._pipeResolver.isPipe(declaredType)) {
transitiveModule.pipesSet.add(declaredType); transitiveModule.pipesSet.add(declaredType);
transitiveModule.pipes.push(declaredIdentifier); transitiveModule.pipes.push(declaredIdentifier);
@ -525,10 +569,10 @@ export class CompileMetadataResolver {
const directives = const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives)); flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes)); const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises = const directiveLoaders =
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises)); ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
return new cpl.TransitiveCompileNgModuleMetadata( return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises); transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
} }
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string): private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
@ -584,20 +628,26 @@ export class CompileMetadataResolver {
return pipeSummary; return pipeSummary;
} }
private _loadPipeMetadata(pipeType: Type<any>): void { getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType); let pipeMeta = this._pipeCache.get(pipeType);
const pipeMeta = this._pipeResolver.resolve(pipeType);
if (!pipeMeta) { if (!pipeMeta) {
return null; pipeMeta = this._loadPipeMetadata(pipeType);
}
return pipeMeta;
} }
const meta = new cpl.CompilePipeMetadata({ private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType);
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)), type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name, name: pipeAnnotation.name,
pure: pipeMeta.pure pure: pipeAnnotation.pure
}); });
this._pipeCache.set(pipeType, meta); this._pipeCache.set(pipeType, pipeMeta);
this._pipeSummaryCache.set(pipeType, meta.toSummary()); this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
} }
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]): private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
@ -872,16 +922,16 @@ function flattenAndDedupeArray(tree: any[]): Array<any> {
} }
function isValidType(value: any): boolean { function isValidType(value: any): boolean {
return cpl.isStaticSymbol(value) || (value instanceof Type); return isStaticSymbol(value) || (value instanceof Type);
} }
function staticTypeModuleUrl(value: any): string { function staticTypeModuleUrl(value: any): string {
return cpl.isStaticSymbol(value) ? value.filePath : null; return isStaticSymbol(value) ? value.filePath : null;
} }
function componentModuleUrl( function componentModuleUrl(
reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string { reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string {
if (cpl.isStaticSymbol(type)) { if (isStaticSymbol(type)) {
return staticTypeModuleUrl(type); return staticTypeModuleUrl(type);
} }
@ -907,7 +957,7 @@ function convertToCompileValue(
class _CompileValueConverter extends ValueTransformer { class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any { visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
let identifier: cpl.CompileIdentifierMetadata; let identifier: cpl.CompileIdentifierMetadata;
if (cpl.isStaticSymbol(value)) { if (isStaticSymbol(value)) {
identifier = new cpl.CompileIdentifierMetadata( identifier = new cpl.CompileIdentifierMetadata(
{name: value.name, moduleUrl: value.filePath, reference: value}); {name: value.name, moduleUrl: value.filePath, reference: value});
} else { } else {

View File

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

View File

@ -6,30 +6,13 @@
* found in the LICENSE file at https://angular.io/license * 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. * Interface that defines how import statements should be generated.
*/ */
export abstract class ImportGenerator { export abstract class ImportResolver {
static parseAssetUrl(url: string): AssetUrl { return AssetUrl.parse(url); } /**
* Converts a file path to a module name that can be used as an `import.
abstract getImportPath(moduleUrlStr: string, importedUrlStr: string): string; * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
} */
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: 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) {
}
} }

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

View File

@ -73,3 +73,6 @@ export type ComponentStillLoadingError = typeof r._ComponentStillLoadingError;
export const ComponentStillLoadingError: typeof r.ComponentStillLoadingError = export const ComponentStillLoadingError: typeof r.ComponentStillLoadingError =
r.ComponentStillLoadingError; r.ComponentStillLoadingError;
export const AnimationTransition: typeof r.AnimationTransition = r.AnimationTransition; 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'; import {isBlank, isPresent} from './facade/lang';
const _ASSET_SCHEME = 'asset:';
/** /**
* Create a {@link UrlResolver} with no package prefix. * Create a {@link UrlResolver} with no package prefix.
*/ */
@ -21,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
} }
export function createOfflineCompileUrlResolver(): UrlResolver { export function createOfflineCompileUrlResolver(): UrlResolver {
return new UrlResolver(_ASSET_SCHEME); return new UrlResolver('.');
} }
/** /**
@ -70,15 +67,10 @@ export class UrlResolver {
if (isPresent(prefix) && isPresent(resolvedParts) && if (isPresent(prefix) && isPresent(resolvedParts) &&
resolvedParts[_ComponentIndex.Scheme] == 'package') { resolvedParts[_ComponentIndex.Scheme] == 'package') {
let path = resolvedParts[_ComponentIndex.Path]; 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(/\/+$/, ''); prefix = prefix.replace(/\/+$/, '');
path = path.replace(/^\/+/, ''); path = path.replace(/^\/+/, '');
return `${prefix}/${path}`; return `${prefix}/${path}`;
} }
}
return resolvedUrl; return resolvedUrl;
} }
} }

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license * 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, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped'; import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -22,7 +22,7 @@ describe('StaticReflector', () => {
let reflector: StaticReflector; let reflector: StaticReflector;
beforeEach(() => { beforeEach(() => {
host = new MockReflectorHost(); host = new MockStaticReflectorHost();
reflector = new StaticReflector(host); reflector = new StaticReflector(host);
}); });
@ -31,7 +31,7 @@ describe('StaticReflector', () => {
} }
it('should get annotations for NgFor', () => { 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); const annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; const annotation = annotations[0];
@ -40,15 +40,15 @@ describe('StaticReflector', () => {
}); });
it('should get constructor for NgFor', () => { it('should get constructor 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 ViewContainerRef = const ViewContainerRef = reflector.findDeclaration(
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef'); '@angular/core/src/linker/view_container_ref', 'ViewContainerRef');
const TemplateRef = const TemplateRef =
host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef'); reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef');
const IterableDiffers = host.findDeclaration( const IterableDiffers = reflector.findDeclaration(
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers'); '@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = host.findDeclaration( const ChangeDetectorRef = reflector.findDeclaration(
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef'); '@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
const parameters = reflector.parameters(NgFor); const parameters = reflector.parameters(NgFor);
expect(parameters).toEqual([ expect(parameters).toEqual([
@ -58,7 +58,7 @@ describe('StaticReflector', () => {
it('should get annotations for HeroDetailComponent', () => { it('should get annotations for HeroDetailComponent', () => {
const HeroDetailComponent = const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const annotations = reflector.annotations(HeroDetailComponent); const annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; const annotation = annotations[0];
@ -73,41 +73,40 @@ describe('StaticReflector', () => {
])]); ])]);
}); });
it('should throw and exception for unsupported metadata versions', () => { it('should throw an exception for unsupported metadata versions', () => {
const e = host.findDeclaration('src/version-error', 'e'); expect(() => reflector.findDeclaration('src/version-error', 'e'))
expect(() => reflector.annotations(e))
.toThrow(new Error( .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', () => { 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); const annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]); expect(annotations).toEqual([]);
}); });
it('should get propMetadata for HeroDetailComponent', () => { it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent = const HeroDetailComponent =
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const props = reflector.propMetadata(HeroDetailComponent); const props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy(); expect(props['hero']).toBeTruthy();
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
}); });
it('should get an empty object from propMetadata for an unknown class', () => { 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); const properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({}); expect(properties).toEqual({});
}); });
it('should get empty parameters list for an unknown class ', () => { 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); const parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]); expect(parameters).toEqual([]);
}); });
it('should provide context for errors reported by the collector', () => { 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)) expect(() => reflector.annotations(SomeClass))
.toThrow(new Error( .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')); '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( expect(simplify(
new StaticSymbol('/src/cases', ''), new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) ({__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', () => { it('should simplify a function reference as a static symbol', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'), new StaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []}))) ({__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', () => { it('should simplify values initialized with a function call', () => {
@ -343,13 +342,11 @@ describe('StaticReflector', () => {
try { try {
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts'); const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts');
expect(metadata).toBeDefined(); expect(metadata).toBeDefined();
if (!Array.isArray(metadata)) { const moduleMetadata: any = metadata[0]['metadata'];
const moduleMetadata: any = metadata['metadata'];
expect(moduleMetadata).toBeDefined(); expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata']; const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined(); expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]); simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
}
} catch (e) { } catch (e) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts'); expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true; threw = true;
@ -377,7 +374,7 @@ describe('StaticReflector', () => {
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts'); const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
expect(metadata).toEqual({ expect(metadata).toEqual({
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
Foo: { Foo: {
__symbolic: 'class', __symbolic: 'class',
@ -406,28 +403,35 @@ describe('StaticReflector', () => {
it('should be able to get metadata for a class containing a custom decorator', () => { it('should be able to get metadata for a class containing a custom decorator', () => {
const props = reflector.propMetadata( 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: []}); expect(props).toEqual({foo: []});
}); });
it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts';
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', () => { it('should report an error for invalid function calls', () => {
expect( expect(
() => () => reflector.annotations(
reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
.toThrow(new Error( .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`)); `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', () => { it('should be able to get metadata for a class containing a static method call', () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
}); });
it('should be able to get metadata for a class containing a static field reference', () => { it('should be able to get metadata for a class containing a static field reference', () => {
const annotations = const annotations = reflector.annotations(
reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
}); });
@ -435,7 +439,7 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with a conditional expression', it('should be able to get the metadata for a class calling a method with a conditional expression',
() => { () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual([ expect(annotations[0].providers).toEqual([
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] [{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
@ -445,50 +449,81 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with default parameters', it('should be able to get the metadata for a class calling a method with default parameters',
() => { () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers).toEqual([['a', true, false]]); expect(annotations[0].providers).toEqual([['a', true, false]]);
}); });
it('should be able to get metadata with a reference to a static method', () => { it('should be able to get metadata with a reference to a static method', () => {
const annotations = reflector.annotations( 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.length).toBe(1);
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); 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 { class MockStaticReflectorHost implements StaticReflectorHost {
private staticTypeCache = new Map<string, StaticSymbol>();
private collector = new MetadataCollector(); 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 // 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 splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string { function resolvePath(pathParts: string[]): string {
@ -522,32 +557,34 @@ class MockReflectorHost implements StaticReflectorHost {
if (modulePath.indexOf('.') === 0) { if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath); const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts'; const tsName = baseName + '.ts';
if (this.getMetadataFor(tsName)) { if (this._getMetadataFor(tsName)) {
return this.getStaticSymbol(tsName, symbolName); 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} = { 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', '__symbolic': 'module',
'version': 1, 'version': 2,
'metadata': { 'metadata': {
'FORM_DIRECTIVES': [ 'FORM_DIRECTIVES': [
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'NgFor', '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', '__symbolic': 'module',
'version': 1, 'version': 2,
'metadata': { 'metadata': {
'NgFor': { 'NgFor': {
'__symbolic': 'class', '__symbolic': 'class',
@ -557,7 +594,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Directive', 'name': 'Directive',
'module': '../../core/metadata' 'module': '@angular/core/src/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -574,22 +611,22 @@ class MockReflectorHost implements StaticReflectorHost {
'parameters': [ 'parameters': [
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/linker/view_container_ref', 'module': '@angular/core/src/linker/view_container_ref',
'name': 'ViewContainerRef' 'name': 'ViewContainerRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/linker/template_ref', 'module': '@angular/core/src/linker/template_ref',
'name': 'TemplateRef' 'name': 'TemplateRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/change_detection/differs/iterable_differs', 'module': '@angular/core/src/change_detection/differs/iterable_differs',
'name': 'IterableDiffers' 'name': 'IterableDiffers'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '../../core/change_detection/change_detector_ref', 'module': '@angular/core/src/change_detection/change_detector_ref',
'name': 'ChangeDetectorRef' 'name': 'ChangeDetectorRef'
} }
] ]
@ -599,17 +636,17 @@ class MockReflectorHost implements StaticReflectorHost {
} }
} }
}, },
'/tmp/angular2/src/core/linker/view_container_ref.d.ts': '/tmp/@angular/core/src/linker/view_container_ref.d.ts':
{version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}}, {version: 2, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/linker/template_ref.d.ts': '/tmp/@angular/core/src/linker/template_ref.d.ts':
{version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}}, {version: 2, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts': '/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts':
{version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}}, {version: 2, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts': '/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts':
{version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}}, {version: 2, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/src/app/hero-detail.component.d.ts': { '/tmp/src/app/hero-detail.component.d.ts': {
'__symbolic': 'module', '__symbolic': 'module',
'version': 1, 'version': 2,
'metadata': { 'metadata': {
'HeroDetailComponent': { 'HeroDetailComponent': {
'__symbolic': 'class', '__symbolic': 'class',
@ -619,7 +656,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Component', 'name': 'Component',
'module': 'angular2/src/core/metadata' 'module': '@angular/core/src/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -631,7 +668,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'trigger', 'name': 'trigger',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'myAnimation', 'myAnimation',
@ -639,7 +676,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'state', 'name': 'state',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'state1', 'state1',
@ -647,7 +684,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'style', 'name': 'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
{ 'background':'white' } { 'background':'white' }
@ -659,7 +696,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'transition', 'name':'transition',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments': [ 'arguments': [
'* => *', '* => *',
@ -668,20 +705,20 @@ class MockReflectorHost implements StaticReflectorHost {
'expression':{ 'expression':{
'__symbolic':'reference', '__symbolic':'reference',
'name':'sequence', 'name':'sequence',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'group', 'name':'group',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ 'arguments':[[{
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'animate', 'name':'animate',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ 'arguments':[
'1s 0.5s', '1s 0.5s',
@ -689,13 +726,13 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'keyframes', 'name':'keyframes',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ { 'background': 'blue'} ] 'arguments':[ { 'background': 'blue'} ]
}, { }, {
@ -703,7 +740,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': 'angular2/src/core/animation/metadata' 'module': '@angular/core/src/animation/metadata'
}, },
'arguments':[ { 'background': 'red'} ] 'arguments':[ { 'background': 'red'} ]
}]] }]]
@ -729,7 +766,7 @@ class MockReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Input', 'name': 'Input',
'module': 'angular2/src/core/metadata' 'module': '@angular/core/src/metadata'
} }
} }
] ]
@ -743,7 +780,7 @@ class MockReflectorHost implements StaticReflectorHost {
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': 'angular2/src/core/metadata', 'module': '@angular/core/src/metadata',
'name': 'HostListener' 'name': 'HostListener'
}, },
'arguments': [ 'arguments': [
@ -760,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/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/error-reporting.d.ts': { '/tmp/src/error-reporting.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
SomeClass: { SomeClass: {
__symbolic: 'class', __symbolic: 'class',
@ -774,7 +811,7 @@ class MockReflectorHost implements StaticReflectorHost {
expression: { expression: {
__symbolic: 'reference', __symbolic: 'reference',
name: 'Component', name: 'Component',
module: 'angular2/src/core/metadata' module: '@angular/core/src/metadata'
}, },
arguments: [ arguments: [
{ {
@ -794,7 +831,7 @@ class MockReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/error-references.d.ts': { '/tmp/src/error-references.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
Link1: { Link1: {
__symbolic: 'reference', __symbolic: 'reference',
@ -816,7 +853,7 @@ class MockReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-declaration.d.ts': { '/tmp/src/function-declaration.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
one: { one: {
__symbolic: 'function', __symbolic: 'function',
@ -845,7 +882,7 @@ class MockReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-reference.ts': { '/tmp/src/function-reference.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
one: { one: {
__symbolic: 'call', __symbolic: 'call',
@ -887,7 +924,7 @@ class MockReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-recursive.d.ts': { '/tmp/src/function-recursive.d.ts': {
__symbolic: 'modules', __symbolic: 'modules',
version: 1, version: 2,
metadata: { metadata: {
recursive: { recursive: {
__symbolic: 'function', __symbolic: 'function',
@ -947,7 +984,7 @@ class MockReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/spread.ts': { '/tmp/src/spread.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 1, version: 2,
metadata: { metadata: {
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5] spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
} }
@ -975,8 +1012,8 @@ class MockReflectorHost implements StaticReflectorHost {
`, `,
'/tmp/src/invalid-calls.ts': ` '/tmp/src/invalid-calls.ts': `
import {someFunction} from './nvalid-calll-definitions.ts'; import {someFunction} from './nvalid-calll-definitions.ts';
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
import {NgIf} from 'angular2/common'; import {NgIf} from '@angular/common';
@Component({ @Component({
selector: 'my-component', selector: 'my-component',
@ -992,7 +1029,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyOtherComponent { } export class MyOtherComponent { }
`, `,
'/tmp/src/static-method.ts': ` '/tmp/src/static-method.ts': `
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
@Component({ @Component({
selector: 'stub' selector: 'stub'
@ -1010,7 +1047,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-call.ts': ` '/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'; import {MyModule} from './static-method';
@Component({ @Component({
@ -1029,7 +1066,7 @@ class MockReflectorHost implements StaticReflectorHost {
export class MyDefaultsComponent { } export class MyDefaultsComponent { }
`, `,
'/tmp/src/static-field.ts': ` '/tmp/src/static-field.ts': `
import {Injectable} from 'angular2/core'; import {Injectable} from '@angular/core';
@Injectable() @Injectable()
export class MyModule { export class MyModule {
@ -1037,7 +1074,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-field-reference.ts': ` '/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'; import {MyModule} from './static-field';
@Component({ @Component({
@ -1051,7 +1088,7 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-ref.ts': ` '/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'; import {ClassWithStatics} from './static-method-def';
@Component({ @Component({
@ -1062,13 +1099,68 @@ class MockReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/invalid-metadata.ts': ` '/tmp/src/invalid-metadata.ts': `
import {Component} from 'angular2/src/core/metadata'; import {Component} from '@angular/core/src/metadata';
@Component({ @Component({
providers: [ { provider: 'a', useValue: (() => 1)() }] providers: [ { provider: 'a', useValue: (() => 1)() }]
}) })
export class InvalidMetadata {} export class InvalidMetadata {}
` `,
'/tmp/src/forward-ref.ts': `
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) {}
}
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'}]
}
}; };
@ -1081,9 +1173,14 @@ class MockReflectorHost implements StaticReflectorHost {
if (diagnostics && diagnostics.length) { if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`); 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'; import {SimpleJsImportGenerator} from './output_emitter_util';
const someModuleUrl = 'asset:somePackage/lib/somePath'; const someModuleUrl = 'somePackage/somePath';
const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; const anotherModuleUrl = 'somePackage/someOtherPath';
const sameModuleIdentifier = const sameModuleIdentifier =
new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl}); new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl});

View File

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

View File

@ -91,9 +91,6 @@ export function main() {
it('should resolve package: urls', it('should resolve package: urls',
() => { expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true); }); () => { 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', () => { it('should not resolve empty urls', () => {
expect(isStyleUrlResolvable(null)).toBe(false); expect(isStyleUrlResolvable(null)).toBe(false);
expect(isStyleUrlResolvable('')).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', () => { describe('corner and error cases', () => {
it('should encode URLs before resolving', 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 {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 {MetadataOverride} from '@angular/core/testing';
import {TestingCompilerFactory, TestingCompiler} from './private_import_core'; 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 {MockDirectiveResolver} from './directive_resolver_mock';
import {MockNgModuleResolver} from './ng_module_resolver_mock'; import {MockNgModuleResolver} from './ng_module_resolver_mock';
import {MockPipeResolver} from './pipe_resolver_mock'; import {MockPipeResolver} from './pipe_resolver_mock';
@ -40,7 +40,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
constructor(private _compilerFactory: CompilerFactory) {} constructor(private _compilerFactory: CompilerFactory) {}
createTestingCompiler(options: CompilerOptions[]): TestingCompiler { createTestingCompiler(options: CompilerOptions[]): TestingCompiler {
const compiler = <RuntimeCompiler>this._compilerFactory.createCompiler(options); const compiler = <JitCompiler>this._compilerFactory.createCompiler(options);
return new TestingCompilerImpl( return new TestingCompilerImpl(
compiler, compiler.injector.get(MockDirectiveResolver), compiler, compiler.injector.get(MockDirectiveResolver),
compiler.injector.get(MockPipeResolver), compiler.injector.get(MockNgModuleResolver)); compiler.injector.get(MockPipeResolver), compiler.injector.get(MockNgModuleResolver));
@ -50,7 +50,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
export class TestingCompilerImpl implements TestingCompiler { export class TestingCompilerImpl implements TestingCompiler {
private _overrider = new MetadataOverrider(); private _overrider = new MetadataOverrider();
constructor( constructor(
private _compiler: RuntimeCompiler, private _directiveResolver: MockDirectiveResolver, private _compiler: JitCompiler, private _directiveResolver: MockDirectiveResolver,
private _pipeResolver: MockPipeResolver, private _moduleResolver: MockNgModuleResolver) {} private _pipeResolver: MockPipeResolver, private _moduleResolver: MockNgModuleResolver) {}
get injector(): Injector { return this._compiler.injector; } get injector(): Injector { return this._compiler.injector; }

View File

@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
this._started = false; this._started = false;
} }
setPosition(p: any /** TODO #9100 */): void { setPosition(p: number): void {
this._players.forEach(player => { player.setPosition(p); }); this._players.forEach(player => { player.setPosition(p); });
} }
@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
}); });
return min; return min;
} }
get players(): AnimationPlayer[] { return this._players; }
} }

View File

@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
finish(): void { this._onFinish(); } finish(): void { this._onFinish(); }
destroy(): void {} destroy(): void {}
reset(): void {} reset(): void {}
setPosition(p: any /** TODO #9100 */): void {} setPosition(p: number): void {}
getPosition(): number { return 0; } getPosition(): number { return 0; }
} }

View File

@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
} }
} }
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); } setPosition(p: number): void { this._players[0].setPosition(p); }
getPosition(): number { return this._players[0].getPosition(); } getPosition(): number { return this._players[0].getPosition(); }
get players(): AnimationPlayer[] { return this._players; }
} }

View File

@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles); firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
} }
collectAndResolveStyles(collectedStyles, [finalStateStyles]);
return keyframes; return keyframes;
} }

View File

@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
} }
} }
export class DebugDomRenderer implements Renderer { export class DebugDomRenderer {
constructor(private _delegate: Renderer) {} constructor(private _delegate: Renderer) {}
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any { selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing); previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._delegate.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
} }
} }

View File

@ -8,8 +8,9 @@
import {AnimationGroupPlayer} from '../animation/animation_group_player'; import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationPlayer} from '../animation/animation_player'; import {AnimationPlayer} from '../animation/animation_player';
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue'; import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
import {AnimationTransitionEvent} from '../animation/animation_transition_event'; import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
import {ViewAnimationMap} from '../animation/view_animation_map'; import {ViewAnimationMap} from '../animation/view_animation_map';
import {ListWrapper} from '../facade/collection';
export class AnimationViewContext { export class AnimationViewContext {
private _players = new ViewAnimationMap(); private _players = new ViewAnimationMap();
@ -30,15 +31,26 @@ export class AnimationViewContext {
this._players.set(element, animationName, player); this._players.set(element, animationName, player);
} }
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false): getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
void { AnimationPlayer[] {
const players: AnimationPlayer[] = [];
if (removeAllAnimations) { if (removeAllAnimations) {
this._players.findAllPlayersByElement(element).forEach(player => player.destroy()); this._players.findAllPlayersByElement(element).forEach(
player => { _recursePlayers(player, players); });
} else { } else {
const player = this._players.find(element, animationName); const currentPlayer = this._players.find(element, animationName);
if (player) { if (currentPlayer) {
player.destroy(); _recursePlayers(currentPlayer, players);
} }
} }
return players;
}
}
function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
player.players.forEach(player => _recursePlayers(player, collectedPlayers));
} else {
collectedPlayers.push(player);
} }
} }

View File

@ -232,8 +232,10 @@ export abstract class AppView<T> {
if (nextSibling) { if (nextSibling) {
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling); this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
} else { } else {
this.visitRootNodesInternal( const parentElement = this._directRenderer.parentElement(prevNode);
this._directRenderer.appendChild, this._directRenderer.parentElement(prevNode)); if (parentElement) {
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
}
} }
} else { } else {
this.renderer.attachViewAfter(prevNode, this.flatRootNodes); this.renderer.attachViewAfter(prevNode, this.flatRootNodes);

View File

@ -56,8 +56,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
} }
// API of tsickle for lowering decorators to properties on the class. // API of tsickle for lowering decorators to properties on the class.
if ((<any>type).ctorParameters) { const tsickleCtorParams = (<any>type).ctorParameters;
const ctorParameters = (<any>type).ctorParameters; if (tsickleCtorParams) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters =
typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type); const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
const paramAnnotations = ctorParameters.map( const paramAnnotations = ctorParameters.map(
(ctorParam: any) => (ctorParam: any) =>

View File

@ -88,7 +88,8 @@ export abstract class Renderer {
abstract animate( abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer; duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
} }
/** /**

View File

@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
let animation = driver.log.pop(); let animation = driver.log.pop();
let kf = animation['keyframeLookup']; let kf = animation['keyframeLookup'];
expect(kf[1]).toEqual([1, {'background': 'green'}]); expect(kf[1]).toEqual([1, {'background': 'green'}]);
let player = animation['player'];
player.finish();
cmp.exp = 'blue'; cmp.exp = 'blue';
fixture.detectChanges(); fixture.detectChanges();
@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup']; kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'green'}]); expect(kf[0]).toEqual([0, {'background': 'green'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]); expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
cmp.exp = 'red'; cmp.exp = 'red';
fixture.detectChanges(); fixture.detectChanges();
@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup']; kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'grey'}]); expect(kf[0]).toEqual([0, {'background': 'grey'}]);
expect(kf[1]).toEqual([1, {'background': 'red'}]); expect(kf[1]).toEqual([1, {'background': 'red'}]);
player = animation['player'];
player.finish();
cmp.exp = 'orange'; cmp.exp = 'orange';
fixture.detectChanges(); fixture.detectChanges();
@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup']; kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'red'}]); expect(kf[0]).toEqual([0, {'background': 'red'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]); expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
})); }));
it('should seed in the origin animation state styles into the first animation step', it('should seed in the origin animation state styles into the first animation step',
@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(animation['startingStyles']).toEqual({'height': '100px'}); expect(animation['startingStyles']).toEqual({'height': '100px'});
})); }));
it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<div class="target" [@status]="exp"></div>
`,
animations: [trigger(
'status',
[
state('*', style({ opacity: 0 })),
state('a', style({height: '100px', width: '200px'})),
state('b', style({height: '1000px' })),
transition('* => *', [
animate(1000, style({ fontSize: '20px' })),
animate(1000)
])
])]
}
});
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
const fixture = TestBed.createComponent(DummyIfCmp);
const cmp = fixture.componentInstance;
cmp.exp = 'a';
fixture.detectChanges();
flushMicrotasks();
driver.log = [];
cmp.exp = 'b';
fixture.detectChanges();
flushMicrotasks();
const animation = driver.log[0];
expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
}));
it('should perform a state change even if there is no transition that is found', it('should perform a state change even if there is no transition that is found',
fakeAsync(() => { fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, { TestBed.overrideComponent(DummyIfCmp, {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core'; import {Component, ContentChild, Injectable, Input, RenderComponentType, Renderer, RootRenderer, TemplateRef} from '@angular/core';
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer'; import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
import {DirectRenderer} from '@angular/core/src/render/api'; import {DirectRenderer} from '@angular/core/src/render/api';
import {TestBed, inject} from '@angular/core/testing'; import {TestBed, inject} from '@angular/core/testing';
@ -125,6 +125,46 @@ export function main() {
const projectedNode = childHostEl.childNodes[1]; const projectedNode = childHostEl.childNodes[1];
expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl); expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl);
}); });
it('should support using structural directives with ngTemplateOutlet', () => {
@Component({
template:
'<child [templateCtx]="templateCtx"><template let-shown="shown" #tpl><span *ngIf="shown">hello</span></template></child>'
})
class Parent {
templateCtx = {shown: false};
}
@Component({
selector: 'child',
template:
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)'
})
class Child {
@Input()
templateCtx: any;
@ContentChild('tpl')
templateRef: TemplateRef<any>;
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
let fixture = TestBed.createComponent(Parent);
fixture.componentInstance.templateCtx.shown = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('()');
fixture.destroy();
fixture = TestBed.createComponent(Parent);
fixture.componentInstance.templateCtx.shown = true;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('(hello)');
fixture.componentInstance.templateCtx.shown = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('()');
});
}); });
} }

View File

@ -89,6 +89,25 @@ export function main() {
const p = reflector.parameters(ClassWithoutDecorators); const p = reflector.parameters(ClassWithoutDecorators);
expect(p.length).toEqual(2); expect(p.length).toEqual(2);
}); });
// See https://github.com/angular/tsickle/issues/261
it('should read forwardRef down-leveled type', () => {
class Dep {}
class ForwardLegacy {
constructor(d: Dep) {}
// Older tsickle had a bug: wrote a forward reference
static ctorParameters = [{type: Dep}];
}
expect(reflector.parameters(ForwardLegacy)).toEqual([[Dep]]);
class Forward {
constructor(d: Dep) {}
// Newer tsickle generates a functionClosure
static ctorParameters = () => [{type: ForwardDep}];
}
class ForwardDep {}
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
});
}); });
describe('propMetadata', () => { describe('propMetadata', () => {

View File

@ -5,8 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
import {AnimationPlayer} from '@angular/core';
export class MockAnimationPlayer implements AnimationPlayer { export class MockAnimationPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = []; private _onDoneFns: Function[] = [];
@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
private _started = false; private _started = false;
public parentPlayer: AnimationPlayer = null; public parentPlayer: AnimationPlayer = null;
public previousStyles: {[styleName: string]: string | number} = {};
public log: any[] /** TODO #9100 */ = []; public log: any[] = [];
constructor(
public startingStyles: {[key: string]: string | number} = {},
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
previousPlayers: AnimationPlayer[] = []) {
previousPlayers.forEach(player => {
if (player instanceof MockAnimationPlayer) {
const styles = player._captureStyles();
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
}
});
}
private _onFinish(): void { private _onFinish(): void {
if (!this._finished) { if (!this._finished) {
@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
} }
} }
setPosition(p: any /** TODO #9100 */): void {} setPosition(p: number): void {}
getPosition(): number { return 0; } getPosition(): number { return 0; }
private _captureStyles(): {[styleName: string]: string | number} {
const captures: {[prop: string]: string | number} = {};
if (this.hasStarted()) {
// when assembling the captured styles, it's important that
// we build the keyframe styles in the following order:
// {startingStyles, ... other styles within keyframes, ... previousStyles }
Object.keys(this.startingStyles).forEach(prop => {
captures[prop] = this.startingStyles[prop];
});
this.keyframes.forEach(kf => {
const [offset, styles] = kf;
const newStyles: {[prop: string]: string | number} = {};
Object.keys(styles).forEach(
prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
});
}
Object.keys(this.previousStyles).forEach(prop => {
captures[prop] = this.previousStyles[prop];
});
return captures;
}
} }

View File

@ -10,6 +10,9 @@
writeScriptTag('/vendor/system.js'); writeScriptTag('/vendor/system.js');
writeScriptTag('/vendor/Reflect.js'); writeScriptTag('/vendor/Reflect.js');
writeScriptTag('/_common/system-config.js'); writeScriptTag('/_common/system-config.js');
if (location.pathname.indexOf('/upgrade/') != -1) {
writeScriptTag('/vendor/angular.js');
}
function writeScriptTag(scriptUrl: string, onload: string = '') { function writeScriptTag(scriptUrl: string, onload: string = '') {
document.write('<script src="' + scriptUrl + '" onload="' + onload + '"></script>'); document.write('<script src="' + scriptUrl + '" onload="' + onload + '"></script>');

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './module'; import * as module from './module';
platformBrowserDynamic().bootstrapModule(AppModule); if (module.AppModule) {
platformBrowserDynamic().bootstrapModule(module.AppModule);
}

View File

@ -19,6 +19,7 @@ System.config({
'/vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '/vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/router': '/vendor/@angular/router/bundles/router.umd.js', '@angular/router': '/vendor/@angular/router/bundles/router.umd.js',
'@angular/upgrade': '/vendor/@angular/upgrade/bundles/upgrade.umd.js', '@angular/upgrade': '/vendor/@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': '/vendor/@angular/upgrade/bundles/upgrade-static.umd.js',
'rxjs': '/vendor/rxjs', 'rxjs': '/vendor/rxjs',
}, },
packages: { packages: {

View File

@ -20,6 +20,7 @@ mkdir $DIST/vendor/
ln -s ../../../dist/packages-dist/ $DIST/vendor/@angular ln -s ../../../dist/packages-dist/ $DIST/vendor/@angular
for FILE in \ for FILE in \
../../../node_modules/angular/angular.js \
../../../node_modules/zone.js/dist/zone.js \ ../../../node_modules/zone.js/dist/zone.js \
../../../node_modules/systemjs/dist/system.js \ ../../../node_modules/systemjs/dist/system.js \
../../../node_modules/reflect-metadata/Reflect.js \ ../../../node_modules/reflect-metadata/Reflect.js \
@ -35,4 +36,6 @@ for MODULE in `find . -name module.ts`; do
cp _common/*.html $FINAL_DIR_PATH cp _common/*.html $FINAL_DIR_PATH
cp $DIST/_common/*.js $FINAL_DIR_PATH cp $DIST/_common/*.js $FINAL_DIR_PATH
cp $DIST/_common/*.js.map $FINAL_DIR_PATH cp $DIST/_common/*.js.map $FINAL_DIR_PATH
find `dirname $MODULE` -name \*.css -exec cp {} $FINAL_DIR_PATH \;
done done

View File

@ -18,7 +18,7 @@
"target": "es5", "target": "es5",
"lib": ["es2015", "dom"], "lib": ["es2015", "dom"],
"skipLibCheck": true, "skipLibCheck": true,
"types": ["jasmine", "node"] "types": ["jasmine", "node", "angularjs"]
}, },
"include": [ "include": [
"./_common/*.ts", "./_common/*.ts",

View File

@ -0,0 +1,54 @@
/**
* @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 {browser, by, element} from 'protractor';
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
function loadPage(url: string) {
browser.ng12Hybrid = true;
browser.rootEl = 'example-app';
browser.get(url);
}
describe('upgrade(static)', () => {
beforeEach(() => { loadPage('/upgrade/static/ts/'); });
afterEach(verifyNoBrowserErrors);
it('should render the `ng2-heroes` component', () => {
expect(element(by.css('h1')).getText()).toEqual('Heroes');
expect(element.all(by.css('p')).get(0).getText()).toEqual('There are 3 heroes.');
});
it('should render 3 ng1-hero components', () => {
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.count()).toEqual(3);
});
it('should add a new hero when the "Add Hero" button is pressed', () => {
const addHeroButton = element.all(by.css('button')).last();
expect(addHeroButton.getText()).toEqual('Add Hero');
addHeroButton.click();
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.last().element(by.css('h2')).getText()).toEqual('Kamala Khan');
});
it('should remove a hero when the "Remove" button is pressed', () => {
let firstHero = element.all(by.css('ng1-hero')).get(0);
expect(firstHero.element(by.css('h2')).getText()).toEqual('Superman');
const removeHeroButton = firstHero.element(by.css('button'));
expect(removeHeroButton.getText()).toEqual('Remove');
removeHeroButton.click();
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.count()).toEqual(2);
firstHero = element.all(by.css('ng1-hero')).get(0);
expect(firstHero.element(by.css('h2')).getText()).toEqual('Wonder Woman');
});
});

View File

@ -0,0 +1,184 @@
/**
* @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 {Component, Directive, DoCheck, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
interface Hero {
name: string;
description: string;
}
// #docregion Angular 2 Stuff
// #docregion ng2-heroes
// This Angular 2 component will be "downgraded" to be used in Angular 1
@Component({
selector: 'ng2-heroes',
// This template uses the upgraded `ng1-hero` component
// Note that because its element is compiled by Angular 2+ we must use camelCased attribute names
template: `<h1>Heroes</h1>
<p><ng-content></ng-content></p>
<div *ngFor="let hero of heroes">
<ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero>
</div>
<button (click)="addHero.emit()">Add Hero</button>`,
})
class Ng2HeroesComponent {
@Input() heroes: Hero[];
@Output() addHero = new EventEmitter();
@Output() removeHero = new EventEmitter();
}
// #enddocregion
// #docregion ng2-heroes-service
// This Angular 2 service will be "downgraded" to be used in Angular 1
@Injectable()
class HeroesService {
heroes: Hero[] = [
{name: 'superman', description: 'The man of steel'},
{name: 'wonder woman', description: 'Princess of the Amazons'},
{name: 'thor', description: 'The hammer-wielding god'}
];
// #docregion use-ng1-upgraded-service
constructor(@Inject('titleCase') titleCase: (v: string) => string) {
// Change all the hero names to title case, using the "upgraded" Angular 1 service
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name));
}
// #enddocregion
addHero() {
this.heroes =
this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]);
}
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); }
}
// #enddocregion
// #docregion ng1-hero-wrapper
// This Angular 2 directive will act as an interface to the "upgraded" Angular 1 component
@Directive({selector: 'ng1-hero'})
class Ng1HeroComponentWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck,
OnDestroy {
// The names of the input and output properties here must match the names of the
// `<` and `&` bindings in the Angular 1 component that is being wrapped
@Input() hero: Hero;
@Output() onRemove: EventEmitter<void>;
constructor(@Inject(ElementRef) elementRef: ElementRef, @Inject(Injector) injector: Injector) {
// We must pass the name of the directive as used by Angular 1 to the super
super('ng1Hero', elementRef, injector);
}
// For this class to work when compiled with AoT, we must implement these lifecycle hooks
// because the AoT compiler will not realise that the super class implements them
ngOnInit() { super.ngOnInit(); }
ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); }
ngDoCheck() { super.ngDoCheck(); }
ngOnDestroy() { super.ngOnDestroy(); }
}
// #enddocregion
// #docregion ng2-module
// This NgModule represents the Angular 2 pieces of the application
@NgModule({
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
providers: [
HeroesService,
// #docregion upgrade-ng1-service
// Register an Angular 2+ provider whose value is the "upgraded" Angular 1 service
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
// #enddocregion
],
// All components that are to be "downgraded" must be declared as `entryComponents`
entryComponents: [Ng2HeroesComponent],
// We must import `UpgradeModule` to get access to the Angular 1 core services
imports: [BrowserModule, UpgradeModule]
})
class Ng2AppModule {
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
}
}
// #enddocregion
// #enddocregion
// #docregion Angular 1 Stuff
// #docregion ng1-module
// This Angular 1 module represents the Angular 1 pieces of the application
const ng1AppModule = angular.module('ng1AppModule', []);
// #enddocregion
// #docregion ng1-hero
// This Angular 1 component will be "upgraded" to be used in Angular 2+
ng1AppModule.component('ng1Hero', {
bindings: {hero: '<', onRemove: '&'},
transclude: true,
template: `<div class="title" ng-transclude></div>
<h2>{{ $ctrl.hero.name }}</h2>
<p>{{ $ctrl.hero.description }}</p>
<button ng-click="$ctrl.onRemove()">Remove</button>`
});
// #enddocregion
// #docregion ng1-title-case-service
// This Angular 1 service will be "upgraded" to be used in Angular 2+
ng1AppModule.factory(
'titleCase',
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()));
// #enddocregion
// #docregion downgrade-ng2-heroes-service
// Register an Angular 1 service, whose value is the "downgraded" Angular 2+ injectable.
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
// #enddocregion
// #docregion ng2-heroes-wrapper
// This is directive will act as the interface to the "downgraded" Angular 2+ component
ng1AppModule.directive(
'ng2Heroes',
downgradeComponent(
// The inputs and outputs here must match the relevant names of the properties on the
// "downgraded" component
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
// #enddocregion
// #docregion example-app
// This is our top level application component
ng1AppModule.component('exampleApp', {
// We inject the "downgraded" HeroesService into this Angular 1 component
// (We don't need the `HeroesService` type for Angular 1 DI - it just helps with TypeScript
// compilation)
controller: [
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; }
],
// This template make use of the downgraded `ng2-heroes` component
// Note that because its element is compiled by Angular 1 we must use kebab-case attributes for
// inputs and outputs
template: `<link rel="stylesheet" href="./styles.css">
<ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)">
There are {{ $ctrl.heroesService.heroes.length }} heroes.
</ng2-heroes>`
});
// #enddocregion
// #enddocregion
// #docregion bootstrap
// First we bootstrap the Angular 2 HybridModule
// (We are using the dynamic browser platform as this example has not been compiled AoT)
platformBrowserDynamic().bootstrapModule(Ng2AppModule).then(ref => {
// Once Angular 2 bootstrap is complete then we bootstrap the Angular 1 module
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, [ng1AppModule.name]);
});
// #enddocregion

View File

@ -0,0 +1,17 @@
ng2-heroes {
border: solid black 2px;
display: block;
padding: 5px;
}
ng1-hero {
border: solid green 2px;
margin-top: 5px;
padding: 5px;
display: block;
}
.title {
background-color: blue;
color: white;
}

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

View File

@ -56,22 +56,12 @@ export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, N
export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] = export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] =
[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName]; [FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName];
/**
* A list of all the form directives.
*
* @stable
*/
export const FORM_DIRECTIVES: Type<any>[][] = [TEMPLATE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
/**
* @stable
*/
export const REACTIVE_FORM_DIRECTIVES: Type<any>[][] =
[REACTIVE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
/** /**
* Internal module used for sharing directives between FormsModule and ReactiveFormsModule * Internal module used for sharing directives between FormsModule and ReactiveFormsModule
*/ */
@NgModule({declarations: SHARED_FORM_DIRECTIVES, exports: SHARED_FORM_DIRECTIVES}) @NgModule({
declarations: SHARED_FORM_DIRECTIVES,
exports: SHARED_FORM_DIRECTIVES,
})
export class InternalFormsSharedModule { export class InternalFormsSharedModule {
} }

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang'; import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_VALUE_ACCESSOR: any = { export const SELECT_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor), useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true multi: true
@ -115,8 +115,8 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
/** @internal */ /** @internal */
_getOptionValue(valueString: string): any { _getOptionValue(valueString: string): any {
const value = this._optionMap.get(_extractId(valueString)); const id: string = _extractId(valueString);
return value != null ? value : valueString; return this._optionMap.has(id) ? this._optionMap.get(id) : valueString;
} }
} }
@ -158,7 +158,7 @@ export class NgSelectOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value); this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this._select) { if (this._select) {
this._select._optionMap.delete(this.id); this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value); this._select.writeValue(this._select.value);

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang'; import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_MULTIPLE_VALUE_ACCESSOR = { export const SELECT_MULTIPLE_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor), useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true multi: true
@ -121,8 +121,8 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
/** @internal */ /** @internal */
_getOptionValue(valueString: string): any { _getOptionValue(valueString: string): any {
const opt = this._optionMap.get(_extractId(valueString)); const id: string = _extractId(valueString);
return opt ? opt._value : valueString; return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString;
} }
} }
@ -180,12 +180,10 @@ export class NgSelectMultipleOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected); this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this._select) { if (this._select) {
this._select._optionMap.delete(this.id); this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value); this._select.writeValue(this._select.value);
} }
} }
} }
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];

View File

@ -5,10 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core'; import {Directive, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AbstractControl} from '../model'; import {AbstractControl} from '../model';
import {NG_VALIDATORS, Validators} from '../validators'; import {NG_VALIDATORS, Validators} from '../validators';
@ -57,17 +54,17 @@ export const REQUIRED_VALIDATOR: any = {
@Directive({ @Directive({
selector: '[required][formControlName],[required][formControl],[required][ngModel]', selector: '[required][formControlName],[required][formControl],[required][ngModel]',
providers: [REQUIRED_VALIDATOR], providers: [REQUIRED_VALIDATOR],
host: {'[attr.required]': 'required? "" : null'} host: {'[attr.required]': 'required ? "" : null'}
}) })
export class RequiredValidator implements Validator { export class RequiredValidator implements Validator {
private _required: boolean; private _required: boolean;
private _onChange: () => void; private _onChange: () => void;
@Input() @Input()
get required(): boolean { return this._required; } get required(): boolean /*| string*/ { return this._required; }
set required(value: boolean) { set required(value: boolean) {
this._required = isPresent(value) && `${value}` !== 'false'; this._required = value != null && value !== false && `${value}` !== 'false';
if (this._onChange) this._onChange(); if (this._onChange) this._onChange();
} }
@ -75,7 +72,7 @@ export class RequiredValidator implements Validator {
return this.required ? Validators.required(c) : null; return this.required ? Validators.required(c) : null;
} }
registerOnValidatorChange(fn: () => void) { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
} }
/** /**
@ -112,7 +109,7 @@ export const MIN_LENGTH_VALIDATOR: any = {
@Directive({ @Directive({
selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]', selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]',
providers: [MIN_LENGTH_VALIDATOR], providers: [MIN_LENGTH_VALIDATOR],
host: {'[attr.minlength]': 'minlength? minlength : null'} host: {'[attr.minlength]': 'minlength ? minlength : null'}
}) })
export class MinLengthValidator implements Validator, export class MinLengthValidator implements Validator,
OnChanges { OnChanges {
@ -121,12 +118,8 @@ export class MinLengthValidator implements Validator,
@Input() minlength: string; @Input() minlength: string;
private _createValidator() { ngOnChanges(changes: SimpleChanges): void {
this._validator = Validators.minLength(parseInt(this.minlength, 10)); if ('minlength' in changes) {
}
ngOnChanges(changes: SimpleChanges) {
if (changes['minlength']) {
this._createValidator(); this._createValidator();
if (this._onChange) this._onChange(); if (this._onChange) this._onChange();
} }
@ -136,7 +129,11 @@ export class MinLengthValidator implements Validator,
return this.minlength == null ? null : this._validator(c); return this.minlength == null ? null : this._validator(c);
} }
registerOnValidatorChange(fn: () => void) { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void {
this._validator = Validators.minLength(parseInt(this.minlength, 10));
}
} }
/** /**
@ -162,7 +159,7 @@ export const MAX_LENGTH_VALIDATOR: any = {
@Directive({ @Directive({
selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]', selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]',
providers: [MAX_LENGTH_VALIDATOR], providers: [MAX_LENGTH_VALIDATOR],
host: {'[attr.maxlength]': 'maxlength? maxlength : null'} host: {'[attr.maxlength]': 'maxlength ? maxlength : null'}
}) })
export class MaxLengthValidator implements Validator, export class MaxLengthValidator implements Validator,
OnChanges { OnChanges {
@ -171,22 +168,22 @@ export class MaxLengthValidator implements Validator,
@Input() maxlength: string; @Input() maxlength: string;
private _createValidator() { ngOnChanges(changes: SimpleChanges): void {
this._validator = Validators.maxLength(parseInt(this.maxlength, 10)); if ('maxlength' in changes) {
}
ngOnChanges(changes: SimpleChanges) {
if (changes['maxlength']) {
this._createValidator(); this._createValidator();
if (this._onChange) this._onChange(); if (this._onChange) this._onChange();
} }
} }
validate(c: AbstractControl): {[key: string]: any} { validate(c: AbstractControl): {[key: string]: any} {
return isPresent(this.maxlength) ? this._validator(c) : null; return this.maxlength != null ? this._validator(c) : null;
} }
registerOnValidatorChange(fn: () => void) { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void {
this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
}
} }
@ -220,20 +217,18 @@ export class PatternValidator implements Validator,
private _validator: ValidatorFn; private _validator: ValidatorFn;
private _onChange: () => void; private _onChange: () => void;
@Input() pattern: string; @Input() pattern: string /*|RegExp*/;
private _createValidator() { this._validator = Validators.pattern(this.pattern); } ngOnChanges(changes: SimpleChanges): void {
if ('pattern' in changes) {
ngOnChanges(changes: SimpleChanges) {
if (changes['pattern']) {
this._createValidator(); this._createValidator();
if (this._onChange) this._onChange(); if (this._onChange) this._onChange();
} }
} }
validate(c: AbstractControl): {[key: string]: any} { validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
return this.pattern ? this._validator(c) : null;
}
registerOnValidatorChange(fn: () => void) { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void { this._validator = Validators.pattern(this.pattern); }
} }

View File

@ -23,7 +23,7 @@ export function main() {
NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName, NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper, NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator, NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
NgModelAsyncValidation NgModelAsyncValidation, NgModelSelectWithNullForm
], ],
imports: [FormsModule] imports: [FormsModule]
}); });
@ -699,6 +699,28 @@ export function main() {
expect(select.nativeElement.value).toEqual('2: Object'); expect(select.nativeElement.value).toEqual('2: Object');
expect(secondNYC.nativeElement.selected).toBe(true); expect(secondNYC.nativeElement.selected).toBe(true);
})); }));
it('should work with null option', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null;
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity['name']).toEqual('NYC');
select.nativeElement.value = '0: null';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity).toEqual(null);
}));
}); });
describe('custom value accessors', () => { describe('custom value accessors', () => {
@ -771,7 +793,7 @@ export function main() {
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
})); }));
it('should support optional fields with pattern validator', fakeAsync(() => { it('should support optional fields with string pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators); const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false; fixture.componentInstance.required = false;
fixture.componentInstance.pattern = '[a-z]+'; fixture.componentInstance.pattern = '[a-z]+';
@ -793,6 +815,28 @@ export function main() {
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy(); expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
})); }));
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = /^[a-z]+$/;
fixture.detectChanges();
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
fixture.detectChanges();
expect(form.valid).toBeTruthy();
input.nativeElement.value = '1';
dispatchEvent(input.nativeElement, 'input');
fixture.detectChanges();
expect(form.valid).toBeFalsy();
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
}));
it('should support optional fields with minlength validator', fakeAsync(() => { it('should support optional fields with minlength validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators); const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false; fixture.componentInstance.required = false;
@ -1078,6 +1122,20 @@ class NgModelSelectForm {
cities: any[] = []; cities: any[] = [];
} }
@Component({
selector: 'ng-model-select-null-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
<option [ngValue]="null">Unspecified</option>
</select>
`
})
class NgModelSelectWithNullForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
@Component({ @Component({
selector: 'ng-model-custom-comp', selector: 'ng-model-custom-comp',
template: ` template: `
@ -1141,7 +1199,7 @@ class NgModelValidationBindings {
class NgModelMultipleValidators { class NgModelMultipleValidators {
required: boolean; required: boolean;
minLen: number; minLen: number;
pattern: string; pattern: string|RegExp;
} }
@Directive({ @Directive({

View File

@ -7,15 +7,15 @@
*/ */
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {global} from '../facade/lang';
let _nextRequestId = 0; let _nextRequestId = 0;
export const JSONP_HOME = '__ng_jsonp__'; export const JSONP_HOME = '__ng_jsonp__';
let _jsonpConnections: {[key: string]: any} = null; let _jsonpConnections: {[key: string]: any} = null;
function _getJsonpConnections(): {[key: string]: any} { function _getJsonpConnections(): {[key: string]: any} {
const w: {[key: string]: any} = typeof window == 'object' ? window : {};
if (_jsonpConnections === null) { if (_jsonpConnections === null) {
_jsonpConnections = (<{[key: string]: any}>global)[JSONP_HOME] = {}; _jsonpConnections = w[JSONP_HOME] = {};
} }
return _jsonpConnections; return _jsonpConnections;
} }

View File

@ -12,7 +12,6 @@ import {Observer} from 'rxjs/Observer';
import {ResponseOptions} from '../base_response_options'; import {ResponseOptions} from '../base_response_options';
import {ReadyState, RequestMethod, ResponseType} from '../enums'; import {ReadyState, RequestMethod, ResponseType} from '../enums';
import {isPresent} from '../facade/lang';
import {Connection, ConnectionBackend} from '../interfaces'; import {Connection, ConnectionBackend} from '../interfaces';
import {Request} from '../static_request'; import {Request} from '../static_request';
import {Response} from '../static_response'; import {Response} from '../static_response';
@ -89,7 +88,7 @@ export class JSONPConnection_ extends JSONPConnection {
if (!this._finished) { if (!this._finished) {
let responseOptions = let responseOptions =
new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseType.Error, url}); new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseType.Error, url});
if (isPresent(baseResponseOptions)) { if (baseResponseOptions) {
responseOptions = baseResponseOptions.merge(responseOptions); responseOptions = baseResponseOptions.merge(responseOptions);
} }
responseObserver.error(new Response(responseOptions)); responseObserver.error(new Response(responseOptions));
@ -97,7 +96,7 @@ export class JSONPConnection_ extends JSONPConnection {
} }
let responseOptions = new ResponseOptions({body: this._responseData, url}); let responseOptions = new ResponseOptions({body: this._responseData, url});
if (isPresent(this.baseResponseOptions)) { if (this.baseResponseOptions) {
responseOptions = this.baseResponseOptions.merge(responseOptions); responseOptions = this.baseResponseOptions.merge(responseOptions);
} }
@ -110,7 +109,7 @@ export class JSONPConnection_ extends JSONPConnection {
this.readyState = ReadyState.Done; this.readyState = ReadyState.Done;
_dom.cleanup(script); _dom.cleanup(script);
let responseOptions = new ResponseOptions({body: error.message, type: ResponseType.Error}); let responseOptions = new ResponseOptions({body: error.message, type: ResponseType.Error});
if (isPresent(baseResponseOptions)) { if (baseResponseOptions) {
responseOptions = baseResponseOptions.merge(responseOptions); responseOptions = baseResponseOptions.merge(responseOptions);
} }
responseObserver.error(new Response(responseOptions)); responseObserver.error(new Response(responseOptions));
@ -125,10 +124,7 @@ export class JSONPConnection_ extends JSONPConnection {
this.readyState = ReadyState.Cancelled; this.readyState = ReadyState.Cancelled;
script.removeEventListener('load', onLoad); script.removeEventListener('load', onLoad);
script.removeEventListener('error', onError); script.removeEventListener('error', onError);
if (isPresent(script)) {
this._dom.cleanup(script); this._dom.cleanup(script);
}
}; };
}); });
} }

View File

@ -10,16 +10,13 @@ import {Injectable} from '@angular/core';
import {__platform_browser_private__} from '@angular/platform-browser'; import {__platform_browser_private__} from '@angular/platform-browser';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer'; import {Observer} from 'rxjs/Observer';
import {ResponseOptions} from '../base_response_options'; import {ResponseOptions} from '../base_response_options';
import {ContentType, ReadyState, RequestMethod, ResponseContentType, ResponseType} from '../enums'; import {ContentType, ReadyState, RequestMethod, ResponseContentType, ResponseType} from '../enums';
import {isPresent} from '../facade/lang';
import {Headers} from '../headers'; import {Headers} from '../headers';
import {getResponseURL, isSuccess} from '../http_utils'; import {getResponseURL, isSuccess} from '../http_utils';
import {Connection, ConnectionBackend, XSRFStrategy} from '../interfaces'; import {Connection, ConnectionBackend, XSRFStrategy} from '../interfaces';
import {Request} from '../static_request'; import {Request} from '../static_request';
import {Response} from '../static_response'; import {Response} from '../static_response';
import {BrowserXhr} from './browser_xhr'; import {BrowserXhr} from './browser_xhr';
const XSSI_PREFIX = /^\)\]\}',?\n/; const XSSI_PREFIX = /^\)\]\}',?\n/;
@ -47,24 +44,29 @@ export class XHRConnection implements Connection {
this.response = new Observable<Response>((responseObserver: Observer<Response>) => { this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
const _xhr: XMLHttpRequest = browserXHR.build(); const _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url); _xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
if (isPresent(req.withCredentials)) { if (req.withCredentials != null) {
_xhr.withCredentials = req.withCredentials; _xhr.withCredentials = req.withCredentials;
} }
// load event handler // load event handler
const onLoad = () => { const onLoad = () => {
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in ResourceLoader Level2 spec (supported
// by IE10)
let body = _xhr.response === undefined ? _xhr.responseText : _xhr.response;
// Implicitly strip a potential XSSI prefix.
if (typeof body === 'string') body = body.replace(XSSI_PREFIX, '');
const headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
const url = getResponseURL(_xhr);
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450) // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
let status: number = _xhr.status === 1223 ? 204 : _xhr.status; let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let body: any = null;
// HTTP 204 means no content
if (status !== 204) {
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in ResourceLoader Level2 spec
// (supported by IE10)
body = _xhr.response == null ? _xhr.responseText : _xhr.response;
// Implicitly strip a potential XSSI prefix.
if (typeof body === 'string') {
body = body.replace(XSSI_PREFIX, '');
}
}
// fix status code when it is 0 (0 status is undocumented). // fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser // Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache. // while retrieving files from application cache.
@ -72,10 +74,13 @@ export class XHRConnection implements Connection {
status = body ? 200 : 0; status = body ? 200 : 0;
} }
const statusText = _xhr.statusText || 'OK'; const headers: Headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
// IE 9 does not provide the way to get URL of response
const url = getResponseURL(_xhr) || req.url;
const statusText: string = _xhr.statusText || 'OK';
let responseOptions = new ResponseOptions({body, status, headers, statusText, url}); let responseOptions = new ResponseOptions({body, status, headers, statusText, url});
if (isPresent(baseResponseOptions)) { if (baseResponseOptions != null) {
responseOptions = baseResponseOptions.merge(responseOptions); responseOptions = baseResponseOptions.merge(responseOptions);
} }
const response = new Response(responseOptions); const response = new Response(responseOptions);
@ -89,14 +94,14 @@ export class XHRConnection implements Connection {
responseObserver.error(response); responseObserver.error(response);
}; };
// error event handler // error event handler
const onError = (err: any) => { const onError = (err: ErrorEvent) => {
let responseOptions = new ResponseOptions({ let responseOptions = new ResponseOptions({
body: err, body: err,
type: ResponseType.Error, type: ResponseType.Error,
status: _xhr.status, status: _xhr.status,
statusText: _xhr.statusText, statusText: _xhr.statusText,
}); });
if (isPresent(baseResponseOptions)) { if (baseResponseOptions != null) {
responseOptions = baseResponseOptions.merge(responseOptions); responseOptions = baseResponseOptions.merge(responseOptions);
} }
responseObserver.error(new Response(responseOptions)); responseObserver.error(new Response(responseOptions));
@ -104,12 +109,12 @@ export class XHRConnection implements Connection {
this.setDetectedContentType(req, _xhr); this.setDetectedContentType(req, _xhr);
if (isPresent(req.headers)) { if (req.headers != null) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(','))); req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
} }
// Select the correct buffer type to store the response // Select the correct buffer type to store the response
if (isPresent(req.responseType) && isPresent(_xhr.responseType)) { if (req.responseType != null && _xhr.responseType != null) {
switch (req.responseType) { switch (req.responseType) {
case ResponseContentType.ArrayBuffer: case ResponseContentType.ArrayBuffer:
_xhr.responseType = 'arraybuffer'; _xhr.responseType = 'arraybuffer';
@ -141,9 +146,9 @@ export class XHRConnection implements Connection {
}); });
} }
setDetectedContentType(req: any /** TODO #9100 */, _xhr: any /** TODO #9100 */) { setDetectedContentType(req: any /** TODO Request */, _xhr: any /** XMLHttpRequest */) {
// Skip if a custom Content-Type header is provided // Skip if a custom Content-Type header is provided
if (isPresent(req.headers) && isPresent(req.headers.get('Content-Type'))) { if (req.headers != null && req.headers.get('Content-Type') != null) {
return; return;
} }
@ -161,7 +166,7 @@ export class XHRConnection implements Connection {
_xhr.setRequestHeader('content-type', 'text/plain'); _xhr.setRequestHeader('content-type', 'text/plain');
break; break;
case ContentType.BLOB: case ContentType.BLOB:
let blob = req.blob(); const blob = req.blob();
if (blob.type) { if (blob.type) {
_xhr.setRequestHeader('content-type', blob.type); _xhr.setRequestHeader('content-type', blob.type);
} }
@ -185,7 +190,7 @@ export class CookieXSRFStrategy implements XSRFStrategy {
constructor( constructor(
private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {} private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {}
configureRequest(req: Request) { configureRequest(req: Request): void {
const xsrfToken = __platform_browser_private__.getDOM().getCookie(this._cookieName); const xsrfToken = __platform_browser_private__.getDOM().getCookie(this._cookieName);
if (xsrfToken) { if (xsrfToken) {
req.headers.set(this._headerName, xsrfToken); req.headers.set(this._headerName, xsrfToken);

View File

@ -8,8 +8,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {isPresent} from '../src/facade/lang';
import {RequestMethod, ResponseContentType} from './enums'; import {RequestMethod, ResponseContentType} from './enums';
import {Headers} from './headers'; import {Headers} from './headers';
import {normalizeMethodName} from './http_utils'; import {normalizeMethodName} from './http_utils';
@ -77,16 +75,14 @@ export class RequestOptions {
constructor( constructor(
{method, headers, body, url, search, withCredentials, {method, headers, body, url, search, withCredentials,
responseType}: RequestOptionsArgs = {}) { responseType}: RequestOptionsArgs = {}) {
this.method = isPresent(method) ? normalizeMethodName(method) : null; this.method = method != null ? normalizeMethodName(method) : null;
this.headers = isPresent(headers) ? headers : null; this.headers = headers != null ? headers : null;
this.body = isPresent(body) ? body : null; this.body = body != null ? body : null;
this.url = isPresent(url) ? url : null; this.url = url != null ? url : null;
this.search = isPresent(search) ? this.search =
(typeof search === 'string' ? new URLSearchParams(<string>(search)) : search != null ? (typeof search === 'string' ? new URLSearchParams(search) : search) : null;
<URLSearchParams>(search)) : this.withCredentials = withCredentials != null ? withCredentials : null;
null; this.responseType = responseType != null ? responseType : null;
this.withCredentials = isPresent(withCredentials) ? withCredentials : null;
this.responseType = isPresent(responseType) ? responseType : null;
} }
/** /**
@ -116,17 +112,17 @@ export class RequestOptions {
*/ */
merge(options?: RequestOptionsArgs): RequestOptions { merge(options?: RequestOptionsArgs): RequestOptions {
return new RequestOptions({ return new RequestOptions({
method: options && isPresent(options.method) ? options.method : this.method, method: options && options.method != null ? options.method : this.method,
headers: options && isPresent(options.headers) ? options.headers : this.headers, headers: options && options.headers != null ? options.headers : this.headers,
body: options && isPresent(options.body) ? options.body : this.body, body: options && options.body != null ? options.body : this.body,
url: options && isPresent(options.url) ? options.url : this.url, url: options && options.url != null ? options.url : this.url,
search: options && isPresent(options.search) ? search: options && options.search != null ?
(typeof options.search === 'string' ? new URLSearchParams(options.search) : (typeof options.search === 'string' ? new URLSearchParams(options.search) :
(<URLSearchParams>(options.search)).clone()) : options.search.clone()) :
this.search, this.search,
withCredentials: options && isPresent(options.withCredentials) ? options.withCredentials : withCredentials: options && options.withCredentials != null ? options.withCredentials :
this.withCredentials, this.withCredentials,
responseType: options && isPresent(options.responseType) ? options.responseType : responseType: options && options.responseType != null ? options.responseType :
this.responseType this.responseType
}); });
} }

View File

@ -8,8 +8,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {isPresent} from '../src/facade/lang';
import {ResponseType} from './enums'; import {ResponseType} from './enums';
import {Headers} from './headers'; import {Headers} from './headers';
import {ResponseOptionsArgs} from './interfaces'; import {ResponseOptionsArgs} from './interfaces';
@ -68,12 +66,12 @@ export class ResponseOptions {
type: ResponseType; type: ResponseType;
url: string; url: string;
constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) { constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) {
this.body = isPresent(body) ? body : null; this.body = body != null ? body : null;
this.status = isPresent(status) ? status : null; this.status = status != null ? status : null;
this.headers = isPresent(headers) ? headers : null; this.headers = headers != null ? headers : null;
this.statusText = isPresent(statusText) ? statusText : null; this.statusText = statusText != null ? statusText : null;
this.type = isPresent(type) ? type : null; this.type = type != null ? type : null;
this.url = isPresent(url) ? url : null; this.url = url != null ? url : null;
} }
/** /**
@ -103,13 +101,12 @@ export class ResponseOptions {
*/ */
merge(options?: ResponseOptionsArgs): ResponseOptions { merge(options?: ResponseOptionsArgs): ResponseOptions {
return new ResponseOptions({ return new ResponseOptions({
body: isPresent(options) && isPresent(options.body) ? options.body : this.body, body: options && options.body != null ? options.body : this.body,
status: isPresent(options) && isPresent(options.status) ? options.status : this.status, status: options && options.status != null ? options.status : this.status,
headers: isPresent(options) && isPresent(options.headers) ? options.headers : this.headers, headers: options && options.headers != null ? options.headers : this.headers,
statusText: isPresent(options) && isPresent(options.statusText) ? options.statusText : statusText: options && options.statusText != null ? options.statusText : this.statusText,
this.statusText, type: options && options.type != null ? options.type : this.type,
type: isPresent(options) && isPresent(options.type) ? options.type : this.type, url: options && options.url != null ? options.url : this.url,
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
}); });
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isJsObject, stringToArrayBuffer} from './http_utils'; import {stringToArrayBuffer} from './http_utils';
import {URLSearchParams} from './url_search_params'; import {URLSearchParams} from './url_search_params';
@ -51,7 +51,7 @@ export abstract class Body {
return ''; return '';
} }
if (isJsObject(this._body)) { if (typeof this._body === 'object') {
return JSON.stringify(this._body, null, 2); return JSON.stringify(this._body, null, 2);
} }

View File

@ -1 +0,0 @@
../../facade/src

View File

@ -8,7 +8,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {isPresent} from '../src/facade/lang';
import {BaseRequestOptions, RequestOptions} from './base_request_options'; import {BaseRequestOptions, RequestOptions} from './base_request_options';
import {RequestMethod} from './enums'; import {RequestMethod} from './enums';
import {ConnectionBackend, RequestOptionsArgs} from './interfaces'; import {ConnectionBackend, RequestOptionsArgs} from './interfaces';
@ -23,7 +22,7 @@ function mergeOptions(
defaultOpts: BaseRequestOptions, providedOpts: RequestOptionsArgs, method: RequestMethod, defaultOpts: BaseRequestOptions, providedOpts: RequestOptionsArgs, method: RequestMethod,
url: string): RequestOptions { url: string): RequestOptions {
const newOptions = defaultOpts; const newOptions = defaultOpts;
if (isPresent(providedOpts)) { if (providedOpts) {
// Hack so Dart can used named parameters // Hack so Dart can used named parameters
return newOptions.merge(new RequestOptions({ return newOptions.merge(new RequestOptions({
method: providedOpts.method || method, method: providedOpts.method || method,
@ -35,11 +34,8 @@ function mergeOptions(
responseType: providedOpts.responseType responseType: providedOpts.responseType
})); }));
} }
if (isPresent(method)) {
return newOptions.merge(new RequestOptions({method: method, url: url})); return newOptions.merge(new RequestOptions({method, url}));
} else {
return newOptions.merge(new RequestOptions({url: url}));
}
} }
/** /**

View File

@ -49,5 +49,3 @@ export function stringToArrayBuffer(input: String): ArrayBuffer {
} }
return view.buffer; return view.buffer;
} }
export {isJsObject} from '../src/facade/lang';

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isPresent} from '../src/facade/lang';
import {Body} from './body'; import {Body} from './body';
import {ContentType, RequestMethod, ResponseContentType} from './enums'; import {ContentType, RequestMethod, ResponseContentType} from './enums';
import {Headers} from './headers'; import {Headers} from './headers';
@ -78,7 +76,7 @@ export class Request extends Body {
// TODO: assert that url is present // TODO: assert that url is present
const url = requestOptions.url; const url = requestOptions.url;
this.url = requestOptions.url; this.url = requestOptions.url;
if (isPresent(requestOptions.search)) { if (requestOptions.search) {
const search = requestOptions.search.toString(); const search = requestOptions.search.toString();
if (search.length > 0) { if (search.length > 0) {
let prefix = '?'; let prefix = '?';
@ -93,7 +91,6 @@ export class Request extends Body {
this.method = normalizeMethodName(requestOptions.method); this.method = normalizeMethodName(requestOptions.method);
// TODO(jeffbcross): implement behavior // TODO(jeffbcross): implement behavior
// Defaults to 'omit', consistent with browser // Defaults to 'omit', consistent with browser
// TODO(jeffbcross): implement behavior
this.headers = new Headers(requestOptions.headers); this.headers = new Headers(requestOptions.headers);
this.contentType = this.detectContentType(); this.contentType = this.detectContentType();
this.withCredentials = requestOptions.withCredentials; this.withCredentials = requestOptions.withCredentials;

View File

@ -14,7 +14,6 @@ import {JSONPBackend, JSONPBackend_, JSONPConnection, JSONPConnection_} from '..
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options'; import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
import {BaseResponseOptions, ResponseOptions} from '../../src/base_response_options'; import {BaseResponseOptions, ResponseOptions} from '../../src/base_response_options';
import {ReadyState, RequestMethod, ResponseType} from '../../src/enums'; import {ReadyState, RequestMethod, ResponseType} from '../../src/enums';
import {isPresent} from '../../src/facade/lang';
import {Request} from '../../src/static_request'; import {Request} from '../../src/static_request';
let existingScripts: MockBrowserJsonp[] = []; let existingScripts: MockBrowserJsonp[] = [];
@ -22,18 +21,14 @@ let existingScripts: MockBrowserJsonp[] = [];
class MockBrowserJsonp extends BrowserJsonp { class MockBrowserJsonp extends BrowserJsonp {
src: string; src: string;
callbacks = new Map<string, (data: any) => any>(); callbacks = new Map<string, (data: any) => any>();
constructor() { super(); }
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); } addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); } removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string, argument?: any) { dispatchEvent(type: string, argument: any = {}) {
if (!isPresent(argument)) {
argument = {};
}
const cb = this.callbacks.get(type); const cb = this.callbacks.get(type);
if (isPresent(cb)) { if (cb) {
cb(argument); cb(argument);
} }
} }

View File

@ -9,7 +9,6 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {AsyncTestCompleter, SpyObject, afterEach, beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, SpyObject, afterEach, beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {__platform_browser_private__} from '@angular/platform-browser'; import {__platform_browser_private__} from '@angular/platform-browser';
import {BrowserXhr} from '../../src/backends/browser_xhr'; import {BrowserXhr} from '../../src/backends/browser_xhr';
import {CookieXSRFStrategy, XHRBackend, XHRConnection} from '../../src/backends/xhr_backend'; import {CookieXSRFStrategy, XHRBackend, XHRConnection} from '../../src/backends/xhr_backend';
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options'; import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
@ -486,6 +485,7 @@ export function main() {
existingXHRs[0].setStatusCode(statusCode); existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
it('should normalize IE\'s 1223 status code into 204', it('should normalize IE\'s 1223 status code into 204',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 1223; const statusCode = 1223;
@ -502,6 +502,22 @@ export function main() {
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
it('should ignore response body for 204 status code',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 204;
const connection = new XHRConnection(
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.text()).toBe('');
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].setResponseText('Doge');
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize responseText and response', it('should normalize responseText and response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const responseBody = 'Doge'; const responseBody = 'Doge';
@ -623,6 +639,21 @@ Connection: keep-alive`;
existingXHRs[0].dispatchEvent('load'); existingXHRs[0].dispatchEvent('load');
})); }));
it('should return request url if it cannot be retrieved from response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 200;
const connection = new XHRConnection(
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.url).toEqual('https://google.com');
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should set the status text property from the XMLHttpRequest instance if present', it('should set the status text property from the XMLHttpRequest instance if present',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusText = 'test'; const statusText = 'test';

View File

@ -37,5 +37,7 @@ export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
* @experimental All debugging apis are currently experimental. * @experimental All debugging apis are currently experimental.
*/ */
export function disableDebugTools(): void { export function disableDebugTools(): void {
if (context.ng) {
delete context.ng.profiler; delete context.ng.profiler;
}
} }

View File

@ -13,7 +13,8 @@ import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../privat
class _NoOpAnimationDriver implements AnimationDriver { class _NoOpAnimationDriver implements AnimationDriver {
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return new NoOpAnimationPlayer(); return new NoOpAnimationPlayer();
} }
} }
@ -25,5 +26,6 @@ export abstract class AnimationDriver {
static NOOP: AnimationDriver = new _NoOpAnimationDriver(); static NOOP: AnimationDriver = new _NoOpAnimationDriver();
abstract animate( abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer; duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
} }

View File

@ -260,9 +260,10 @@ export class DomRenderer implements Renderer {
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate( return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing); element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
} }
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationPlayer} from '@angular/core';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {AnimationKeyframe, AnimationStyles} from '../private_import_core'; import {AnimationKeyframe, AnimationStyles} from '../private_import_core';
@ -15,17 +16,16 @@ import {WebAnimationsPlayer} from './web_animations_player';
export class WebAnimationsDriver implements AnimationDriver { export class WebAnimationsDriver implements AnimationDriver {
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): WebAnimationsPlayer { duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
let formattedSteps: {[key: string]: string | number}[] = []; let formattedSteps: {[key: string]: string | number}[] = [];
let startingStyleLookup: {[key: string]: string | number} = {}; let startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) { if (isPresent(startingStyles)) {
startingStyleLookup = _populateStyles(element, startingStyles, {}); startingStyleLookup = _populateStyles(startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
} }
keyframes.forEach((keyframe: AnimationKeyframe) => { keyframes.forEach((keyframe: AnimationKeyframe) => {
const data = _populateStyles(element, keyframe.styles, startingStyleLookup); const data = _populateStyles(keyframe.styles, startingStyleLookup);
data['offset'] = keyframe.offset; data['offset'] = keyframe.offset;
formattedSteps.push(data); formattedSteps.push(data);
}); });
@ -52,13 +52,16 @@ export class WebAnimationsDriver implements AnimationDriver {
playerOptions['easing'] = easing; playerOptions['easing'] = easing;
} }
return new WebAnimationsPlayer(element, formattedSteps, playerOptions); // there may be a chance a NoOp player is returned depending
// on when the previous animation was cancelled
previousPlayers = previousPlayers.filter(filterWebAnimationPlayerFn);
return new WebAnimationsPlayer(
element, formattedSteps, playerOptions, <WebAnimationsPlayer[]>previousPlayers);
} }
} }
function _populateStyles( function _populateStyles(styles: AnimationStyles, defaultStyles: {[key: string]: string | number}):
element: any, styles: AnimationStyles, {[key: string]: string | number} {
defaultStyles: {[key: string]: string | number}): {[key: string]: string | number} {
const data: {[key: string]: string | number} = {}; const data: {[key: string]: string | number} = {};
styles.styles.forEach( styles.styles.forEach(
(entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); }); (entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); });
@ -69,3 +72,7 @@ function _populateStyles(
}); });
return data; return data;
} }
function filterWebAnimationPlayerFn(player: AnimationPlayer) {
return player instanceof WebAnimationsPlayer;
}

View File

@ -7,6 +7,8 @@
*/ */
import {AUTO_STYLE} from '@angular/core'; import {AUTO_STYLE} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AnimationPlayer} from '../private_import_core'; import {AnimationPlayer} from '../private_import_core';
import {getDOM} from './dom_adapter'; import {getDOM} from './dom_adapter';
@ -21,13 +23,22 @@ export class WebAnimationsPlayer implements AnimationPlayer {
private _finished = false; private _finished = false;
private _started = false; private _started = false;
private _destroyed = false; private _destroyed = false;
private _finalKeyframe: {[key: string]: string | number};
public parentPlayer: AnimationPlayer = null; public parentPlayer: AnimationPlayer = null;
public previousStyles: {[styleName: string]: string | number};
constructor( constructor(
public element: any, public keyframes: {[key: string]: string | number}[], public element: any, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) { public options: {[key: string]: string | number},
previousPlayers: WebAnimationsPlayer[] = []) {
this._duration = <number>options['duration']; this._duration = <number>options['duration'];
this.previousStyles = {};
previousPlayers.forEach(player => {
let styles = player._captureStyles();
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
});
} }
private _onFinish() { private _onFinish() {
@ -44,14 +55,39 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const keyframes = this.keyframes.map(styles => { const keyframes = this.keyframes.map(styles => {
const formattedKeyframe: {[key: string]: string | number} = {}; const formattedKeyframe: {[key: string]: string | number} = {};
Object.keys(styles).forEach(prop => { Object.keys(styles).forEach((prop, index) => {
const value = styles[prop]; let value = styles[prop];
formattedKeyframe[prop] = value == AUTO_STYLE ? _computeStyle(this.element, prop) : value; if (value == AUTO_STYLE) {
value = _computeStyle(this.element, prop);
}
if (value != undefined) {
formattedKeyframe[prop] = value;
}
}); });
return formattedKeyframe; return formattedKeyframe;
}); });
const previousStyleProps = Object.keys(this.previousStyles);
if (previousStyleProps.length) {
let startingKeyframe = keyframes[0];
let missingStyleProps: string[] = [];
previousStyleProps.forEach(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); this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
this._finalKeyframe = _copyKeyframeStyles(keyframes[keyframes.length - 1]);
// this is required so that the player doesn't start to animate right away // this is required so that the player doesn't start to animate right away
this._resetDomPlayerState(); this._resetDomPlayerState();
@ -119,8 +155,33 @@ export class WebAnimationsPlayer implements AnimationPlayer {
setPosition(p: number): void { this._player.currentTime = p * this.totalTime; } setPosition(p: number): void { this._player.currentTime = p * this.totalTime; }
getPosition(): number { return this._player.currentTime / this.totalTime; } getPosition(): number { return this._player.currentTime / this.totalTime; }
private _captureStyles(): {[prop: string]: string | number} {
const styles: {[key: string]: string | number} = {};
if (this.hasStarted()) {
Object.keys(this._finalKeyframe).forEach(prop => {
if (prop != 'offset') {
styles[prop] =
this._finished ? this._finalKeyframe[prop] : _computeStyle(this.element, prop);
}
});
}
return styles;
}
} }
function _computeStyle(element: any, prop: string): string { function _computeStyle(element: any, prop: string): string {
return getDOM().getComputedStyle(element)[prop]; return getDOM().getComputedStyle(element)[prop];
} }
function _copyKeyframeStyles(styles: {[style: string]: string | number}):
{[style: string]: string | number} {
const newStyles: {[style: string]: string | number} = {};
Object.keys(styles).forEach(prop => {
if (prop != 'offset') {
newStyles[prop] = styles[prop];
}
});
return newStyles;
}

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {AnimationPlayer} from '@angular/core';
import {el} from '@angular/platform-browser/testing/browser_util'; import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player'; import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver'; import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player'; import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
import {AnimationKeyframe, AnimationStyles} from '../../src/private_import_core'; import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player'; import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
class ExtendedWebAnimationsDriver extends WebAnimationsDriver { class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
@ -48,8 +48,7 @@ export function main() {
it('should use a fill mode of `both`', () => { it('should use a fill mode of `both`', () => {
const startingStyles = _makeStyles({}); const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})]; const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear', []);
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
const details = _formatOptions(player); const details = _formatOptions(player);
const options = details['options']; const options = details['options'];
expect(options['fill']).toEqual('both'); expect(options['fill']).toEqual('both');
@ -58,8 +57,7 @@ export function main() {
it('should apply the provided easing', () => { it('should apply the provided easing', () => {
const startingStyles = _makeStyles({}); const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})]; const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out', []);
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
const details = _formatOptions(player); const details = _formatOptions(player);
const options = details['options']; const options = details['options'];
expect(options['easing']).toEqual('ease-out'); expect(options['easing']).toEqual('ease-out');
@ -68,14 +66,30 @@ export function main() {
it('should only apply the provided easing if present', () => { it('should only apply the provided easing if present', () => {
const startingStyles = _makeStyles({}); const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})]; const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null, []);
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null);
const details = _formatOptions(player); const details = _formatOptions(player);
const options = details['options']; const options = details['options'];
const keys = Object.keys(options); const keys = Object.keys(options);
expect(keys.indexOf('easing')).toEqual(-1); expect(keys.indexOf('easing')).toEqual(-1);
}); });
it('should only apply the provided easing if present', () => {
const previousPlayers = [
new NoOpAnimationPlayerWithStyles(),
new NoOpAnimationPlayerWithStyles(),
new NoOpAnimationPlayerWithStyles(),
];
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {}), _makeKeyframe(1, {})];
const player = driver.animate(
elm, startingStyles, styles, 1000, 1000, null, <AnimationPlayer[]>previousPlayers);
expect(player.previousStyles).toEqual({});
}); });
});
}
class NoOpAnimationPlayerWithStyles extends NoOpAnimationPlayer {
private _captureStyles() { return {color: 'red'}; }
} }
function _formatOptions(player: WebAnimationsPlayer): {[key: string]: any} { function _formatOptions(player: WebAnimationsPlayer): {[key: string]: any} {

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {MockAnimationPlayer, beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {el} from '@angular/platform-browser/testing/browser_util'; import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player'; import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
@ -18,14 +20,16 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
constructor( constructor(
public element: HTMLElement, public keyframes: {[key: string]: string | number}[], public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) { public options: {[key: string]: string | number},
super(element, keyframes, options); public previousPlayers: WebAnimationsPlayer[] = []) {
super(element, keyframes, options, previousPlayers);
} }
get domPlayer() { return this._overriddenDomPlayer; } get domPlayer() { return this._overriddenDomPlayer; }
/** @internal */ /** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer { _triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
this._overriddenDomPlayer._capture('trigger', {elm, keyframes, options});
return this._overriddenDomPlayer; return this._overriddenDomPlayer;
} }
} }
@ -33,7 +37,7 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
export function main() { export function main() {
function makePlayer(): {[key: string]: any} { function makePlayer(): {[key: string]: any} {
const someElm = el('<div></div>'); const someElm = el('<div></div>');
const player = new ExtendedWebAnimationsPlayer(someElm, [], {}); const player = new ExtendedWebAnimationsPlayer(someElm, [{}, {}], {}, []);
player.init(); player.init();
return {'captures': player.domPlayer.captures, 'player': player}; return {'captures': player.domPlayer.captures, 'player': player};
} }
@ -156,5 +160,98 @@ export function main() {
player.destroy(); player.destroy();
expect(captures['cancel'].length).toBe(1); expect(captures['cancel'].length).toBe(1);
}); });
it('should resolve auto styles based on what is computed from the provided element', () => {
const elm = el('<div></div>');
document.body.appendChild(elm); // required for getComputedStyle() to work
elm.style.opacity = '0.5';
const player = new ExtendedWebAnimationsPlayer(
elm, [{opacity: AUTO_STYLE}, {opacity: '1'}], {duration: 1000}, []);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]);
});
describe('previousStyle', () => {
it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation',
() => {
const elm = el('<div></div>');
const previousStyles = {width: '100px', height: '666px'};
const previousPlayer =
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
previousPlayer.play();
previousPlayer.finish();
const player = new ExtendedWebAnimationsPlayer(
elm,
[
{width: '0px', height: '0px', opacity: 0, offset: 0},
{width: '0px', height: '0px', opacity: 1, offset: 1}
],
{duration: 1000}, [previousPlayer]);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([
{width: '100px', height: '666px', opacity: 0, offset: 0},
{width: '0px', height: '0px', opacity: 1, offset: 1}
]);
});
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;
const elm = el('<div></div>');
document.body.appendChild(elm);
const fromStyles = {width: '100px', height: '666px'};
const toStyles = {width: '50px', height: '333px'};
const previousPlayer =
new WebAnimationsPlayer(elm, [fromStyles, toStyles], {duration: 1000}, []);
previousPlayer.play();
previousPlayer.setPosition(0.5);
previousPlayer.pause();
const newStyles = {width: '0px', height: '0px'};
const player = new WebAnimationsPlayer(
elm, [newStyles, newStyles], {duration: 1000}, [previousPlayer]);
player.init();
const data = player.previousStyles;
expect(player.previousStyles).toEqual({width: '75px', height: '499.5px'});
});
});
}); });
} }

View File

@ -9,19 +9,29 @@
import {AnimationPlayer} from '@angular/core'; import {AnimationPlayer} from '@angular/core';
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal'; import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
import {AnimationDriver} from '@angular/platform-browser'; import {AnimationDriver} from '@angular/platform-browser';
import {ListWrapper} from './facade/collection';
import {AnimationKeyframe, AnimationStyles} from './private_import_core'; import {AnimationKeyframe, AnimationStyles} from './private_import_core';
export class MockAnimationDriver extends AnimationDriver { export class MockAnimationDriver extends AnimationDriver {
public log: {[key: string]: any}[] = []; public log: {[key: string]: any}[] = [];
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
const player = new MockAnimationPlayer(); previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const mockPlayers = <MockAnimationPlayer[]>previousPlayers.filter(
player => player instanceof MockAnimationPlayer);
const normalizedStartingStyles = _serializeStyles(startingStyles);
const normalizedKeyframes = _serializeKeyframes(keyframes);
const player =
new MockAnimationPlayer(normalizedStartingStyles, normalizedKeyframes, previousPlayers);
this.log.push({ this.log.push({
'element': element, 'element': element,
'startingStyles': _serializeStyles(startingStyles), 'startingStyles': normalizedStartingStyles,
'previousStyles': player.previousStyles,
'keyframes': keyframes, 'keyframes': keyframes,
'keyframeLookup': _serializeKeyframes(keyframes), 'keyframeLookup': normalizedKeyframes,
'duration': duration, 'duration': duration,
'delay': delay, 'delay': delay,
'easing': easing, 'easing': easing,

View File

@ -206,9 +206,10 @@ export class ServerRenderer implements Renderer {
animate( animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate( return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing); element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
} }
} }

View File

@ -89,7 +89,7 @@ export class MessageBasedRenderer {
'animate', 'animate',
[ [
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE, RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
PRIMITIVE, PRIMITIVE PRIMITIVE, PRIMITIVE, PRIMITIVE
], ],
this._animate.bind(this)); this._animate.bind(this));
@ -248,8 +248,14 @@ export class MessageBasedRenderer {
private _animate( private _animate(
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number, renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
delay: number, easing: string, playerId: any) { delay: number, easing: string, previousPlayers: number[], playerId: any) {
const player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing); let normalizedPreviousPlayers: AnimationPlayer[];
if (previousPlayers && previousPlayers.length) {
normalizedPreviousPlayers =
previousPlayers.map(playerId => this._renderStore.deserialize(playerId));
}
const player = renderer.animate(
element, startingStyles, keyframes, duration, delay, easing, normalizedPreviousPlayers);
this._renderStore.store(player, playerId); this._renderStore.store(player, playerId);
} }

View File

@ -16,6 +16,7 @@ import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api'; import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store'; import {RenderStore} from '../shared/render_store';
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer'; import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
import {deserializeGenericEvent} from './event_deserializer'; import {deserializeGenericEvent} from './event_deserializer';
@Injectable() @Injectable()
@ -239,13 +240,16 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
animate( animate(
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer { duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const playerId = this._rootRenderer.allocateId(); const playerId = this._rootRenderer.allocateId();
const previousPlayerIds: number[] =
previousPlayers.map(player => this._rootRenderer.renderStore.serialize(player));
this._runOnService('animate', [ this._runOnService('animate', [
new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null), new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null),
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null), new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
new FnArg(easing, null), new FnArg(playerId, null) new FnArg(easing, null), new FnArg(previousPlayerIds, null), new FnArg(playerId, null)
]); ]);
const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement); const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
@ -325,7 +329,7 @@ export class WebWorkerRenderNode {
animationPlayerEvents = new AnimationPlayerEmitter(); animationPlayerEvents = new AnimationPlayerEmitter();
} }
class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject { class _AnimationWorkerRendererPlayer implements RenderStoreObject {
public parentPlayer: AnimationPlayer = null; public parentPlayer: AnimationPlayer = null;
private _destroyed: boolean = false; private _destroyed: boolean = false;

View File

@ -289,6 +289,30 @@ export function main() {
expect(player.log.indexOf('destroy') >= 0).toBe(true); expect(player.log.indexOf('destroy') >= 0).toBe(true);
})); }));
it('should properly transition to the next animation if the current one is cancelled',
fakeAsync(() => {
const fixture = TestBed.createComponent(AnimationCmp);
const cmp = fixture.componentInstance;
cmp.state = 'on';
fixture.detectChanges();
flushMicrotasks();
let player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
player.finish();
player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
player.setPosition(0.5);
uiDriver.log = [];
cmp.state = 'off';
fixture.detectChanges();
flushMicrotasks();
const step = uiDriver.log.shift();
expect(step['previousStyles']).toEqual({opacity: AUTO_STYLE, fontSize: AUTO_STYLE});
}));
}); });
} }

View File

@ -0,0 +1,5 @@
/**
* @license Angular v0.0.0-ROUTERPLACEHOLDER
* (c) 2010-2016 Google, Inc. https://angular.io/
* License: MIT
*/

View File

@ -1,6 +1,6 @@
{ {
"name": "@angular/router", "name": "@angular/router",
"version": "3.0.0-rc.1", "version": "0.0.0-ROUTERPLACEHOLDER",
"description": "Angular - the routing library", "description": "Angular - the routing library",
"main": "bundles/router.umd.js", "main": "bundles/router.umd.js",
"module": "index.js", "module": "index.js",
@ -24,7 +24,6 @@
"@angular/core": "0.0.0-PLACEHOLDER", "@angular/core": "0.0.0-PLACEHOLDER",
"@angular/common": "0.0.0-PLACEHOLDER", "@angular/common": "0.0.0-PLACEHOLDER",
"@angular/platform-browser": "0.0.0-PLACEHOLDER", "@angular/platform-browser": "0.0.0-PLACEHOLDER",
"@angular/upgrade": "0.0.0-PLACEHOLDER",
"rxjs": "5.0.0-beta.12" "rxjs": "5.0.0-beta.12"
} }
} }

Some files were not shown because too many files have changed in this diff Show More