Compare commits

..

86 Commits
2.3.x ... 2.2.4

Author SHA1 Message Date
9a9a7ac7b5 docs(changelog): add changelog for 2.2.4 2016-11-30 15:18:38 -08:00
423dd2898a chore(release): cut the 2.2.4 release 2016-11-30 15:16:41 -08:00
ee2d6e572a fix(compiler): fix performance regression caused by 5b0f9e2
Fixes #13146
2016-11-30 15:13:39 -08:00
ba8645c529 Fix(http): invalidStateError response body
Check on null value failed with last version of mozilla.
Check on undefined type instead.
2016-11-30 15:13:39 -08:00
eba53fd16c fix(common): update DatePipe to allow closure compilation
Quote the date formats to prevent closure renaming.  These are specified as strings in templates using DatePipes and also need to be quoted here.
2016-11-30 15:13:39 -08:00
c0698de2ea doc(common): fix a typo in async pipe 2016-11-30 15:13:38 -08:00
dc6728e7ad refactor(core): remove unused import
APP_ID  was removed after 2.2.x
2016-11-30 15:13:38 -08:00
eb173bcd30 fix(compiler-cli): fix paths in source maps to be relative
The change looks bigger than it really is because I reordered the properties to match other tsconfigs we have.

The only real change is removal of sourceRoot property.

Fixes #13040
2016-11-30 15:13:38 -08:00
e39e16ae6a Keep console.log that are not called during compilation. 2016-11-30 15:13:38 -08:00
ff56da554f Check if console.error is defined 2016-11-30 15:13:38 -08:00
d160591453 refactor(lint): Don't allow console.log
Enable tslint check for `console.log` as a follow-up to
https://github.com/angular/angular/issues/13018
2016-11-30 15:13:38 -08:00
380377139b docs(changelog): add changelog for 2.2.3 2016-11-23 13:00:37 -08:00
9c7680ef69 chore(release): cut the 2.2.3 release 2016-11-23 12:53:13 -08:00
69572ac2f1 Revert "refactor(compiler): move static_reflector into @angular/compiler and rename files"
This reverts commit 8f295287a2.
2016-11-23 12:09:10 -08:00
76f53f929c Revert "refactor(compiler): move findDeclaration into the StaticReflector"
This reverts commit e7025c9423.
2016-11-23 12:09:09 -08:00
ba52f2f252 Revert "refactor(compiler): remove asset: urls"
This reverts commit 0c98f45105.
2016-11-23 12:09:08 -08:00
e122f6bf0f Revert "refactor(compiler): add createAotCompiler factory"
This reverts commit 170525a225.
2016-11-23 12:09:07 -08:00
453c758d1a Revert "refactor(compiler): move symbol extraction to AotCompiler"
This reverts commit b5afe51b26.
2016-11-23 12:09:06 -08:00
015ca47336 Revert "fix(compiler): fix versions of @angular/tsc-wrapped"
This reverts commit 2fe6fb1163.
2016-11-23 12:09:04 -08:00
f32e287812 Revert "refactor(tsc-wrapped): collect all exported functions and classes and bump metadata version from 1 to 2"
This reverts commit 39a71eb0ec.
2016-11-23 12:08:46 -08:00
9946ac5cc7 Revert "refactor(compiler): renames"
This reverts commit 38be2b81c6.
2016-11-23 12:08:45 -08:00
593e05dc97 Revert "refactor(comiler): various cleanups"
This reverts commit ef38676091.
2016-11-23 12:08:44 -08:00
da77b580c9 Revert "refactor(compiler): Reintroduce ReflectorHost and move Extractor into @angular/compiler"
This reverts commit 64bd672e3a.
2016-11-23 12:08:42 -08:00
1733ea09bd Revert "refactor(compiler): further minor fixes"
This reverts commit 3d407fc010.
2016-11-23 12:08:41 -08:00
1f4fa28fac Revert "refactor(compiler): allow control of StaticSymbol lifetime (#12986)"
This reverts commit 2ca67e1674.
2016-11-23 12:08:41 -08:00
c12e56ec0c Revert "fix(animations): blend in all previously transitioned styles into next animation if interrupted (#13014)"
This reverts commit ea4fc9b421.
2016-11-23 12:08:40 -08:00
4a5c8bd25f Revert "test(upgrade): remove setTimeout from lifecycle hook tests (#13027)"
This reverts commit a4ab14bf74.
2016-11-23 12:08:38 -08:00
9c954740d1 chore(release): cut tsc-wrapped 0.4.1 release 2016-11-22 14:51:47 -08:00
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
356 changed files with 6960 additions and 19523 deletions

View File

@ -1,127 +1,3 @@
<a name="2.3.1"></a>
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
### Bug Fixes
* **animations:** always cleanup players after they have finished internally ([#13334](https://github.com/angular/angular/issues/13334)) ([a26e054](https://github.com/angular/angular/commit/a26e054)), closes [#13333](https://github.com/angular/angular/issues/13333)
* **animations:** throw errors and normalize offset beyond the range of [0,1] ([6557bc3](https://github.com/angular/angular/commit/6557bc3)), closes [#13348](https://github.com/angular/angular/issues/13348) [#13440](https://github.com/angular/angular/issues/13440)
* **compiler:** emit quoted object literal keys if the source is quoted ([ecfad46](https://github.com/angular/angular/commit/ecfad46)), closes [#13249](https://github.com/angular/angular/issues/13249) [#13356](https://github.com/angular/angular/issues/13356)
* **compiler:** fix merge error in compiler_host ([69b52eb](https://github.com/angular/angular/commit/69b52eb))
* **compiler:** fix PR 13322 ([#13331](https://github.com/angular/angular/issues/13331)) ([79728b4](https://github.com/angular/angular/commit/79728b4))
* **compiler:** fix simplify a reference without a name ([1c279b3](https://github.com/angular/angular/commit/1c279b3)), closes [#13470](https://github.com/angular/angular/issues/13470)
* **compiler:** fix transpiled ES5 code ([#13322](https://github.com/angular/angular/issues/13322)) ([6c1d790](https://github.com/angular/angular/commit/6c1d790)), closes [#13301](https://github.com/angular/angular/issues/13301)
* **compiler:** generated CSS files suffixed with ngstyle. ([#13353](https://github.com/angular/angular/issues/13353)) ([c8a9b70](https://github.com/angular/angular/commit/c8a9b70)), closes [#13141](https://github.com/angular/angular/issues/13141)
* **compiler:** make sure provider values with `name` property dont break. ([efa2d80](https://github.com/angular/angular/commit/efa2d80)), closes [#13394](https://github.com/angular/angular/issues/13394) [#13445](https://github.com/angular/angular/issues/13445)
* **compiler:** narrow the span reported for invalid pipes ([307d305](https://github.com/angular/angular/commit/307d305)), closes [#13326](https://github.com/angular/angular/issues/13326) [#13411](https://github.com/angular/angular/issues/13411)
* **compiler:** propagate exports when upgrading metadata to v2 ([f6ef7d6](https://github.com/angular/angular/commit/f6ef7d6))
* **compiler:** resolver should merge host bindings and listeners ([#13474](https://github.com/angular/angular/issues/13474)) ([6aeaca3](https://github.com/angular/angular/commit/6aeaca3)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
* **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
* **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
* **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
* **dom_adapter:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
* **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
* **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
* **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364)
* **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18))
* **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980)
* **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414)
* Better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
* Better instructions on running examples and their tests ([203cc7e](https://github.com/angular/angular/commit/203cc7e))
* **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403)
* **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373)
* **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155))
* **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac))
* **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675)
### Features
* **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675)
### Performance Improvements
* **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440)
<a name="2.3.0"></a>
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
### Bug Fixes
* **common:** make sure the plural category exists ([#13169](https://github.com/angular/angular/issues/13169)) ([82c81cd](https://github.com/angular/angular/commit/82c81cd)), closes [#12379](https://github.com/angular/angular/issues/12379)
* **compiler:** include the summaries of reexported modules / directives / pipes ([#13196](https://github.com/angular/angular/issues/13196)) ([75d1617](https://github.com/angular/angular/commit/75d1617))
* **compiler:** serialize any `StaticSymbol` correctly, not matter in which context ([5614c4f](https://github.com/angular/angular/commit/5614c4f))
* **compiler:** short-circut expressions with an index ([#13263](https://github.com/angular/angular/issues/13263)) ([f31c947](https://github.com/angular/angular/commit/f31c947)), closes [#13254](https://github.com/angular/angular/issues/13254)
* **core:** display framework version on bootstrapped component ([#13252](https://github.com/angular/angular/issues/13252)) ([16efb13](https://github.com/angular/angular/commit/16efb13))
* **facade:** cache original format string ([#12764](https://github.com/angular/angular/issues/12764)) ([a132287](https://github.com/angular/angular/commit/a132287))
* **http:** set the default Accept header ([#12989](https://github.com/angular/angular/issues/12989)) ([986abbe](https://github.com/angular/angular/commit/986abbe)), closes [#6354](https://github.com/angular/angular/issues/6354)
* **language-service:** avoid throwing for invalid class declarations ([#13257](https://github.com/angular/angular/issues/13257)) ([93556a5](https://github.com/angular/angular/commit/93556a5)), closes [#13253](https://github.com/angular/angular/issues/13253)
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))
* **router:** runs guards every time when unsuccessfully navigating to the same url over and over again ([#13209](https://github.com/angular/angular/issues/13209)) ([d46b8de](https://github.com/angular/angular/commit/d46b8de))
* **router:** throw a better error message when angular 1 is not bootstraped ([c767df0](https://github.com/angular/angular/commit/c767df0))
* **router:** validate nested routes ([#13224](https://github.com/angular/angular/issues/13224)) ([2893c2c](https://github.com/angular/angular/commit/2893c2c)), closes [#12827](https://github.com/angular/angular/issues/12827)
* **tsc-wrapped:** have UserError display the actual error ([393c100](https://github.com/angular/angular/commit/393c100))
### Features
* **compiler:** read and write `.ngsummary.json` files ([614a35d](https://github.com/angular/angular/commit/614a35d)), closes [#12787](https://github.com/angular/angular/issues/12787)
<a name="2.3.0-rc.0"></a>
# [2.3.0-rc.0](https://github.com/angular/angular/compare/2.3.0-beta.0...2.3.0-rc.0) (2016-11-30)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ef96763](https://github.com/angular/angular/commit/ef96763)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([fc5ac1e](https://github.com/angular/angular/commit/fc5ac1e))
* **build:** update versions of umd bundles ([#13038](https://github.com/angular/angular/issues/13038)) ([86ffa88](https://github.com/angular/angular/commit/86ffa88)), closes [#13037](https://github.com/angular/angular/issues/13037)
* **changelog:** replace beta.1 with beta.0 ([#12961](https://github.com/angular/angular/issues/12961)) ([07a986d](https://github.com/angular/angular/commit/07a986d))
* **ci:** pin version of npm on CircleCI ([#12954](https://github.com/angular/angular/issues/12954)) ([a3884db](https://github.com/angular/angular/commit/a3884db))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([7dcca30](https://github.com/angular/angular/commit/7dcca30))
* **common:** update DatePipe to allow closure compilation ([b2b7219](https://github.com/angular/angular/commit/b2b7219))
* **compiler:** correctly evaluate references to static functions ([#13133](https://github.com/angular/angular/issues/13133)) ([627282d](https://github.com/angular/angular/commit/627282d))
* **compiler:** fix performance regression caused by 5b0f9e2 ([43c0e9a](https://github.com/angular/angular/commit/43c0e9a)), closes [#13146](https://github.com/angular/angular/issues/13146)
* **compiler:** fix versions of `@angular/tsc-wrapped` ([bccf0e6](https://github.com/angular/angular/commit/bccf0e6))
* **compiler-cli:** fix paths in source maps to be relative ([2a3ca7b](https://github.com/angular/angular/commit/2a3ca7b)), closes [#13040](https://github.com/angular/angular/issues/13040)
* **compiler-cli:** pin the version of `tsc-wrapped` ([966bcba](https://github.com/angular/angular/commit/966bcba))
* **language-service:** harden against partial normalization of directives ([2975d89](https://github.com/angular/angular/commit/2975d89))
* **core:** shrinkwrap was out of date with packages. ([e45b7ff](https://github.com/angular/angular/commit/e45b7ff))
* **language-service:** make link check pass ([7194fc2](https://github.com/angular/angular/commit/7194fc2))
* **router:** guards restore an incorrect url when used with skipLocationChange ([ad20d7d](https://github.com/angular/angular/commit/ad20d7d)), closes [#12825](https://github.com/angular/angular/issues/12825)
* **router:** support redirects to named outlets ([602522b](https://github.com/angular/angular/commit/602522b)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **tsc-wrapped:** set correct version number ([897555c](https://github.com/angular/angular/commit/897555c))
* **tsc-wrapped:** still emit version 1 metadata to allow use of new components in old setups ([bc69c74](https://github.com/angular/angular/commit/bc69c74))
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([1ef4696](https://github.com/angular/angular/commit/1ef4696))
* **version:** take all of version string after patch version ([f275f36](https://github.com/angular/angular/commit/f275f36))
### Features
* **core:** update RxJS peer dependency to 5.0.0-rc.4 Please see [this gist](https://gist.github.com/robwormald/19dea0c70a6e01aadced6731aed4f9f7) if you depend on the `cache` operator ([2d6a003](https://github.com/angular/angular/commit/2d6a003)), closes [#13125](https://github.com/angular/angular/issues/13125)
* **core:** upgrade zone.js to v0.7.1 ([c4bbafc](https://github.com/angular/angular/commit/c4bbafc))
* **build:** record angular version in the dom ([#13164](https://github.com/angular/angular/issues/13164)) ([e628b66](https://github.com/angular/angular/commit/e628b66))
* **core:** expose destroy() method on ViewRef ([808275a](https://github.com/angular/angular/commit/808275a))
* **core:** properly support inheritance ([f5c8e09](https://github.com/angular/angular/commit/f5c8e09)), closes [#11606](https://github.com/angular/angular/issues/11606) [#12892](https://github.com/angular/angular/issues/12892)
* **language-service:** add services to support editors ([#12987](https://github.com/angular/angular/issues/12987)) ([519a324](https://github.com/angular/angular/commit/519a324))
* **router:** add support for custom route reuse strategies ([42cf06f](https://github.com/angular/angular/commit/42cf06f))
* **tools:** allow disabling annotation lowering ([c1a62e2](https://github.com/angular/angular/commit/c1a62e2))
<a name="2.2.4"></a>
## [2.2.4](https://github.com/angular/angular/compare/2.2.3...2.2.4) (2016-11-30)
@ -143,7 +19,6 @@
* **animations:** Revert: blend in all previously transitioned styles into next animation if interrupted ([c12e56e](https://github.com/angular/angular/commit/c12e56e))
<a name="2.2.2"></a>
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
@ -185,8 +60,6 @@
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)
@ -1008,7 +881,7 @@ prefix using `animate-` must now be preixed using `bind-animate-`.
* core:
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
created during bootstrap instead by calling `NgModuleRef.destroy`.
- `ApplicationRef.registerDisposeListener` is deprecated.
- `AplicationRef.registerDisposeListener` is deprecated.
Use the `ngOnDestroy` lifecycle hook for providers or
`NgModuleRef.onDestroy` instead.
- `disposePlatform` is deprecated. Use `destroyPlatform` instead.

View File

@ -57,7 +57,7 @@ We want to fix all the issues as soon as possible, but before fixing a bug we ne
- 3rd-party libraries and their versions
- and most importantly - a use-case that fails
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demostrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demostrating the problem.
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
@ -244,7 +244,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
[github]: https://github.com/angular/angular
[gitter]: https://gitter.im/angular/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net
[plunker]: http://plnkr.co/edit
[runnable]: http://runnable.com

View File

@ -126,7 +126,7 @@ $ gulp public-api:update
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
## <a name="clang-format"></a> Formatting your source code
## Formatting your source code
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
is not properly formatted, the CI will fail and the PR can not be merged.

View File

@ -24,9 +24,8 @@ with it.
* `comp: forms`: `@kara`
* `comp: http`: `@jeffbcross`
* `comp: i18n`: `@vicb`
* `comp: language service`: `@chuckjaz`
* `comp: metadata-extractor`: `@chuckjaz`
* `comp: router`: `@vicb`
* `comp: router`: `@vsavkin`
* `comp: testing`: `@juliemr`
* `comp: upgrade`: `@mhevery`
* `comp: web-worker`: `@vicb`

View File

@ -36,7 +36,7 @@ var CIconfiguration = {
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
@ -44,10 +44,10 @@ var customLaunchers = {
'DartiumWithWebPlatform':
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'},
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '50'},
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
@ -96,7 +96,7 @@ var customLaunchers = {
'BS_IE10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.1',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},

View File

@ -17,7 +17,6 @@ PACKAGES=(core
upgrade
router
compiler-cli
language-service
benchpress)
BUILD_ALL=true
BUNDLE=true
@ -145,11 +144,6 @@ do
$TSC -p ${SRCDIR}/tsconfig-testing.json
fi
if [[ -e ${SRCDIR}/tsconfig-2015.json ]]; then
echo "====== COMPILING ESM: ${TSC} -p ${SRCDIR}/tsconfig-2015.json"
${TSC} -p ${SRCDIR}/tsconfig-2015.json
fi
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
if [ "$(uname)" == "Darwin" ]; then
@ -181,6 +175,7 @@ do
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
if [[ -e rollup-testing.config.js ]]; then
echo "====== Rollup ${PACKAGE} testing"
../../../node_modules/.bin/rollup -c rollup-testing.config.js

View File

@ -48,15 +48,11 @@ module.exports = function(config) {
exclude: [
'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/**/*node_only_spec.js',
'dist/all/@angular/benchpress/**',
'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'dist/all/@angular/examples/**/e2e_test/*',
'dist/all/@angular/language-service/**',
'dist/all/@angular/router/**',
'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/examples/**/e2e_test/**',
],

View File

@ -7,7 +7,7 @@
"dependencies": {
"@angular/core": "^2.0.0-rc.7",
"reflect-metadata": "^0.1.2",
"rxjs": "5.0.0-rc.4",
"rxjs": "5.0.0-beta.12",
"jpm": "1.1.4",
"firefox-profile": "0.4.0",
"selenium-webdriver": "^2.53.3"

View File

@ -11,6 +11,9 @@
* @description
* Entry point for all public APIs of the common package.
*/
export * from './src/common';
export * from './src/location';
export {NgLocalization} from './src/localization';
export {CommonModule} from './src/common_module';
// This file only reexports content of the `src` folder. Keep it that way.
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './src/directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './src/pipes/index';

View File

@ -1,20 +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
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
export * from './location/index';
export {NgLocalization} from './localization';
export {CommonModule} from './common_module';
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
export {VERSION} from './version';
export {Version} from '@angular/core';

View File

@ -23,23 +23,9 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
*/
export function getPluralCategory(
value: number, cases: string[], ngLocalization: NgLocalization): string {
let key = `=${value}`;
const nbCase = `=${value}`;
if (cases.indexOf(key) > -1) {
return key;
}
key = ngLocalization.getPluralCategory(value);
if (cases.indexOf(key) > -1) {
return key;
}
if (cases.indexOf('other') > -1) {
return 'other';
}
throw new Error(`No plural message found for value "${value}"`);
return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
}
/**

View File

@ -0,0 +1,13 @@
/**
* @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 * from './location/platform_location';
export * from './location/location_strategy';
export * from './location/hash_location_strategy';
export * from './location/path_location_strategy';
export * from './location/location';

View File

@ -17,8 +17,6 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/**
* @whatItDoes Use URL hash for storing application location data.
* @description
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
@ -29,7 +27,18 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
*
* ### Example
*
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
* ```
* import {Component, NgModule} from '@angular/core';
* import {
* LocationStrategy,
* HashLocationStrategy
* } from '@angular/common';
*
* @NgModule({
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
* })
* class AppModule {}
* ```
*
* @stable
*/

View File

@ -1,13 +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
*/
export * from './platform_location';
export * from './location_strategy';
export * from './hash_location_strategy';
export * from './path_location_strategy';
export * from './location';

View File

@ -12,8 +12,7 @@ import {LocationStrategy} from './location_strategy';
/**
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
* @description
* `Location` is a service that applications can use to interact with a browser's URL.
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
* to the URL's path or the URL's hash segment.
*
@ -29,7 +28,19 @@ import {LocationStrategy} from './location_strategy';
* - `/my/app/user/123/` **is not** normalized
*
* ### Example
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
*
* ```
* import {Component} from '@angular/core';
* import {Location} from '@angular/common';
*
* @Component({selector: 'app-component'})
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
* ```
*
* @stable
*/
@Injectable()
@ -85,7 +96,7 @@ export class Location {
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*/
prepareExternalUrl(url: string): string {
if (url && url[0] !== '/') {
if (url.length > 0 && !url.startsWith('/')) {
url = '/' + url;
}
return this._platformStrategy.prepareExternalUrl(url);
@ -132,7 +143,7 @@ export class Location {
* is.
*/
public static normalizeQueryParams(params: string): string {
return params && params[0] !== '?' ? '?' + params : params;
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
}
/**
@ -164,13 +175,25 @@ export class Location {
/**
* If url has a trailing slash, remove it, otherwise return url as is.
*/
public static stripTrailingSlash(url: string): string { return url.replace(/\/$/, ''); }
public static stripTrailingSlash(url: string): string {
if (/\/$/g.test(url)) {
url = url.substring(0, url.length - 1);
}
return url;
}
}
function _stripBaseHref(baseHref: string, url: string): string {
return baseHref && url.startsWith(baseHref) ? url.substring(baseHref.length) : url;
if (baseHref.length > 0 && url.startsWith(baseHref)) {
return url.substring(baseHref.length);
}
return url;
}
function _stripIndexHtml(url: string): string {
return url.replace(/\/index.html$/, '');
if (/\/index.html$/g.test(url)) {
// '/index.html'.length == 11
return url.substring(0, url.length - 11);
}
return url;
}

View File

@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
/**
* `LocationStrategy` is responsible for representing and reading route state
* from the browser's URL. Angular provides two strategies:
* {@link HashLocationStrategy} and {@link PathLocationStrategy}.
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
*
* This is used under the hood of the {@link Location} service.
*

View File

@ -17,13 +17,14 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
/**
* @whatItDoes Use URL for storing application location data.
* @description
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
* {@link Location} service to represent its state in the
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
* browser's URL.
*
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link ROUTER_PROVIDERS}.
*
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
* or add a base element to the document. This URL prefix that will be preserved
* when generating and recognizing URLs.
@ -36,10 +37,6 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* ### Example
*
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
*
* @stable
*/
@Injectable()

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
export class GeneratedFile {
constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {}
}
/**
* @module
* @description
* This module provides a set of common Pipes.
*/

View File

@ -7,14 +7,11 @@
*/
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {DateFormatter} from '../facade/intl';
import {NumberWrapper, isDate} from '../facade/lang';
import {DateFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules.

View File

@ -8,9 +8,9 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
import {NumberFormatStyle, NumberFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;

View File

@ -1,19 +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
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -7,10 +7,13 @@
*/
import {LOCALE_ID} from '@angular/core';
import {TestBed, inject} from '@angular/core/testing';
import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers';
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
export function main() {
describe('l10n', () => {
@ -138,23 +141,6 @@ export function main() {
expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5');
expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other');
});
it('should fallback to other when the case is not present', () => {
const l10n = new NgLocaleLocalization('ro');
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
// 2 -> 'few'
expect(getPluralCategory(2, ['one', 'other'], l10n)).toEqual('other');
});
describe('errors', () => {
it('should report an error when the "other" category is not present', () => {
expect(() => {
const l10n = new NgLocaleLocalization('ro');
// 2 -> 'few'
getPluralCategory(2, ['one'], l10n);
}).toThrowError('No plural message found for value "2"');
});
});
});
});
}

View File

@ -22,7 +22,6 @@
"../../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true
}
}

View File

@ -5,13 +5,10 @@
* 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 {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
export {Extractor} from './src/extractor';
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
export * from '@angular/tsc-wrapped';
export {VERSION} from './src/version';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'

View File

@ -1,3 +0,0 @@
:host {
background-color: blue;
}

View File

@ -1,5 +0,0 @@
<div>
<h1>hello world</h1>
<a [routerLink]="['lazy']">lazy</a>
<router-outlet></router-outlet>
</div>

View File

@ -1,19 +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 {Component, ViewEncapsulation} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
}

View File

@ -1,32 +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 {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouterModule} from '@angular/router';
import {AppComponent} from './app.component';
@Component({selector: 'home-view', template: 'home!'})
export class HomeView {
}
@NgModule({
declarations: [AppComponent, HomeView],
imports: [
BrowserModule, RouterModule.forRoot([
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
{path: 'feature2', loadChildren: 'feature2/feature2.module#Feature2Module'},
{path: '', component: HomeView}
])
],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -1,21 +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 {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'feature-component', template: 'foo.html'})
export class FeatureComponent {
}
@NgModule({
declarations: [FeatureComponent],
imports: [RouterModule.forChild([{path: '', component: FeatureComponent}])]
})
export class FeatureModule {
}

View File

@ -1,24 +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 {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'lazy-feature-comp', template: 'lazy feature!'})
export class LazyFeatureComponent {
}
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
])],
declarations: [LazyFeatureComponent]
})
export class LazyFeatureModule {
}

View File

@ -1,23 +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 {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'feature-component', template: 'foo.html'})
export class FeatureComponent {
}
@NgModule({
declarations: [FeatureComponent],
imports: [RouterModule.forChild([
{path: '', component: FeatureComponent},
])]
})
export default class DefaultModule {
}

View File

@ -1,26 +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 {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'feature-component', template: 'foo.html'})
export class FeatureComponent {
}
@NgModule({
declarations: [FeatureComponent],
imports: [RouterModule.forChild([
{path: '', component: FeatureComponent}, {path: 'd', loadChildren: './default.module'} {
path: 'e',
loadChildren: 'feature/feature.module#FeatureModule'
}
])]
})
export class Feature2Module {
}

View File

@ -1,27 +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 {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'lazy-comp', template: 'lazy!'})
export class LazyComponent {
}
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature/feature.module#FeatureModule'},
{path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'}
])],
declarations: [LazyComponent]
})
export class LazyModule {
}
export class SecondModule {}

View File

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

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
<!ATTLIST translationbundle lang CDATA #REQUIRED>
<!ELEMENT translation (#PCDATA|ph)*>
<!ATTLIST translation id CDATA #REQUIRED>
<!ELEMENT ph EMPTY>
<!ATTLIST ph name CDATA #REQUIRED>
]>
<translationbundle>
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
</translationbundle>

View File

@ -11,9 +11,7 @@ import {FormsModule} from '@angular/forms';
import {ServerModule} from '@angular/platform-server';
import {MdButtonModule} from '@angular2-material/button';
// Note: don't refer to third_party_src as we want to test that
// we can compile components from node_modules!
import {ThirdpartyModule} from 'third_party/module';
import {ThirdpartyModule} from '../third_party_src/module';
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
import {AnimateCmp} from './animate';
@ -21,7 +19,7 @@ import {BasicComp} from './basic';
import {ComponentUsingThirdParty} from './comp_using_3rdp';
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomeLibModule, SomePipeInRootModule, SomeService} from './module_fixtures';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
import {CompWithNgContent, ProjectingComp} from './projection';
import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, DirectiveForQuery} from './queries';
@ -54,7 +52,7 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
FormsModule,
MdButtonModule,
ModuleUsingCustomElements,
SomeLibModule.withProviders(),
someLibModuleWithProviders(),
ThirdpartyModule,
],
providers: [SomeService],

View File

@ -63,13 +63,17 @@ export function provideValueWithEntryComponents(value: any) {
entryComponents: [CompUsingLibModuleDirectiveAndPipe],
})
export class SomeLibModule {
static withProviders() {
return {
ngModule: SomeLibModule,
providers: [
ServiceUsingLibModule, provideValueWithEntryComponents(
[{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
]
};
}
}
// TODO(tbosch): Make this a static method in `SomeLibModule` once
// our static reflector supports it.
// See https://github.com/angular/angular/issues/10266.
export function someLibModuleWithProviders(): ModuleWithProviders {
return {
ngModule: SomeLibModule,
providers: [
ServiceUsingLibModule,
provideValueWithEntryComponents([{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
]
};
}

View File

@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)>
]>
<messagebundle>
<msg id="3772663375917578720">other-3rdP-component</msg>
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
<msg id="3492007542396725315">Welcome</msg>
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle>
`;
@ -79,4 +79,5 @@ describe('template i18n extraction output', () => {
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
expect(xlf).toEqual(EXPECTED_XLIFF);
});
});

View File

@ -1,158 +0,0 @@
#!/usr/bin/env node
/**
* @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
*/
/* tslint:disable:no-console */
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
const glob = require('glob');
/**
* Main method.
* Standalone program that executes codegen using the ngtools API and tests that files were
* properly read and wrote.
*/
function main() {
console.log(`testing ngtools API...`);
Promise.resolve()
.then(() => codeGenTest())
.then(() => lazyRoutesTest())
.then(() => {
console.log('All done!');
process.exit(0);
})
.catch((err) => {
console.error(err.stack);
console.error('Test failed');
process.exit(1);
});
}
function codeGenTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const hostContext = new NodeCompilerHostContext();
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const host: ts.CompilerHost = Object.assign({}, delegateHost, {
writeFile: (fileName: string, ...rest: any[]) => {
wroteFiles.push(fileName);
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
}
});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
console.log(`>>> running codegen for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.codeGen({
basePath,
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
// i18n options.
i18nFormat: null,
i18nFile: null,
locale: null,
readResource: (fileName: string) => {
readResources.push(fileName);
return hostContext.readResource(fileName);
}
})
.then(() => {
console.log(`>>> codegen done, asserting read and wrote files`);
// Assert for each file that it has been read and each `ts` has a written file associated.
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.module\.ts$/)) {
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
throw e;
});
}
function lazyRoutesTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const config = tsc.readConfiguration(project, basePath);
const host = ts.createCompilerHost(config.parsed.options, true);
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
program,
host,
angularCompilerOptions: config.ngOptions,
entryModule: 'app.module#AppModule'
});
const expectations: {[route: string]: string} = {
'./lazy.module#LazyModule': 'lazy.module.ts',
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
'./default.module': 'feature2/default.module.ts',
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
};
Object.keys(lazyRoutes).forEach((route: string) => {
assert(route in expectations, `Found a route that was not expected: "${route}".`);
assert(
lazyRoutes[route] == path.join(basePath, expectations[route]),
`Route "${route}" does not point to the expected absolute path ` +
`"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`);
});
// Verify that all expectations were met.
assert.deepEqual(
Object.keys(lazyRoutes), Object.keys(expectations), `Expected routes listed to be: \n` +
` ${JSON.stringify(Object.keys(expectations))}\n` +
`Actual:\n` +
` ${JSON.stringify(Object.keys(lazyRoutes))}\n`);
}
main();

View File

@ -1,100 +0,0 @@
#!/usr/bin/env node
/**
* @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
*/
/* tslint:disable:no-console */
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
/**
* Main method.
* Standalone program that executes the real codegen and tests that
* ngsummary.json files are used for libraries.
*/
function main() {
console.log(`testing usage of ngsummary.json files in libraries...`);
const basePath = path.resolve(__dirname, '..');
const project = path.resolve(basePath, 'tsconfig-build.json');
const readFiles: string[] = [];
class AssertingHostContext extends NodeCompilerHostContext {
readFile(fileName: string): string {
readFiles.push(path.relative(basePath, fileName));
return super.readFile(fileName);
}
readResource(s: string): Promise<string> {
readFiles.push(path.relative(basePath, s));
return super.readResource(s);
}
}
const config = tsc.readConfiguration(project, basePath);
config.ngOptions.basePath = basePath;
// This flag tells ngc do not recompile libraries.
config.ngOptions.generateCodeForLibraries = false;
console.log(`>>> running codegen for ${project}`);
codegen(config, (host) => new AssertingHostContext())
.then((exitCode: any) => {
console.log(`>>> codegen done, asserting read files`);
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
console.log(`done, no errors.`);
process.exit(exitCode);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
}
/**
* Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext
*/
function codegen(
config: {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions},
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
const host = ts.createCompilerHost(config.parsed.options, true);
// HACK: patch the realpath to solve symlink issue here:
// https://github.com/Microsoft/TypeScript/issues/9552
// todo(misko): remove once facade symlinks are removed
host.realpath = (path) => path;
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
return CodeGenerator.create(config.ngOptions, {
} as any, program, host, hostContextFactory(host)).codegen();
}
function assertSomeFileMatch(fileNames: string[], pattern: RegExp) {
assert(
fileNames.some(fileName => pattern.test(fileName)),
`Expected some read files match ${pattern}`);
}
function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
const matches = fileNames.filter(fileName => pattern.test(fileName));
assert(
matches.length === 0,
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
}
main();

View File

@ -21,8 +21,6 @@
"src/module",
"src/bootstrap",
"test/all_spec",
"test/test_ngtools_api",
"test/test_summaries",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",

View File

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

View File

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

@ -1,278 +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 {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|ngstyle)$/;
export interface CompilerHostContext extends ts.ModuleResolutionHost {
readResource(fileName: string): Promise<string>;
assumeFileExists(fileName: string): void;
}
export class CompilerHost implements AotCompilerHost {
protected metadataCollector = new MetadataCollector();
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>();
constructor(
protected program: ts.Program, protected options: AngularCompilerOptions,
protected 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, '/');
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.context.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: any) => m['version'] === 1);
let v3Metadata = metadatas.find((m: any) => m['version'] === 3);
if (!v3Metadata && v1Metadata) {
// patch up v1 to v3 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 and the `extends` clause.
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
if (v1Metadata.exports) {
v3Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v3Metadata.metadata[prop]) {
v3Metadata.metadata[prop] = exports.metadata[prop];
}
}
if (exports.exports) {
v3Metadata.exports = exports.exports;
}
}
metadatas.push(v3Metadata);
}
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); }
loadSummary(filePath: string): string { return this.context.readFile(filePath); }
getOutputFileName(sourceFilePath: string): string {
return sourceFilePath.replace(EXT, '') + '.d.ts';
}
}
export class CompilerHostContextAdapter {
protected assumedExists: {[fileName: string]: boolean} = {};
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}
export class ModuleResolutionHostAdapter extends CompilerHostContextAdapter implements
CompilerHostContext {
public directoryExists: ((directoryName: string) => boolean)|undefined;
constructor(private host: ts.ModuleResolutionHost) {
super();
if (host.directoryExists) {
this.directoryExists = (directoryName: string) => host.directoryExists(directoryName);
}
}
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
readFile(fileName: string): string { return this.host.readFile(fileName); }
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));
}
}
export class NodeCompilerHostContext extends CompilerHostContextAdapter implements
CompilerHostContext {
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || fs.existsSync(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.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.readFile(s));
}
}

View File

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

View File

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

View File

@ -19,28 +19,19 @@ import {CodeGenerator} from './codegen';
function codegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
}
export function main(
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
const project = args.p || args.project || '.';
const cliOptions = new tsc.NgcCliOptions(args);
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
if (e instanceof tsc.UserError) {
consoleError(e.message);
return Promise.resolve(1);
} else {
consoleError(e.stack);
consoleError('Compilation failed');
return Promise.resolve(1);
}
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
transitiveModules: true
});
}
// CLI entry point
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2));
main(args).then((exitCode: number) => process.exit(exitCode));
const project = args.p || args.project || '.';
const cliOptions = new tsc.NgcCliOptions(args);
tsc.main(project, cliOptions, codegen).then(exitCode => process.exit(exitCode)).catch(e => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
}

View File

@ -1,124 +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
*/
/**
* This is a private API for the ngtools toolkit.
*
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
* something else.
*/
import {AotCompilerHost, StaticReflector} from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CodeGenerator} from './codegen';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string;
compilerOptions: ts.CompilerOptions;
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
// i18n options.
i18nFormat: string;
i18nFile: string;
locale: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.
}
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
entryModule: string;
// Every new property under this line should be optional.
}
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
/**
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
* passed in the interface.
*/
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
constructor(
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
super(host);
}
readResource(path: string) { return this._readResource(path); }
}
/**
* @internal
* @private
*/
export class NgTools_InternalApi_NG_2 {
/**
* @internal
* @private
*/
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<void> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
const cliOptions: NgcCliOptions = {
i18nFormat: options.i18nFormat,
i18nFile: options.i18nFile,
locale: options.locale,
basePath: options.basePath
};
// Create the Code Generator.
const codeGenerator = CodeGenerator.create(
options.angularCompilerOptions, cliOptions, options.program, options.host, hostContext);
return codeGenerator.codegen();
}
/**
* @internal
* @private
*/
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
NgTools_InternalApi_NG_2_LazyRouteMap {
const angularCompilerOptions = options.angularCompilerOptions;
const program = options.program;
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
const usePathMapping =
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
const ngCompilerHost: AotCompilerHost = usePathMapping ?
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
const staticReflector = new StaticReflector(ngCompilerHost);
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
return Object.keys(routeMap).reduce(
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
acc[route] = routeMap[route].absoluteFilePath;
return acc;
},
{});
}
}

View File

@ -1,205 +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
*/
/**
* This is a private API for the ngtools toolkit.
*
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
* something else.
*/
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
import {NgModule} from '@angular/core';
// We cannot depend directly to @angular/router.
type Route = any;
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
// LazyRoute information between the extractors.
export interface LazyRoute {
routeDef: RouteDef;
absoluteFilePath: string;
}
export type LazyRouteMap = {
[route: string]: LazyRoute
};
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
// the user, and this is a helper class to extract information from it.
export class RouteDef {
private constructor(public readonly path: string, public readonly className: string = null) {}
toString() {
return (this.className === null || this.className == 'default') ?
this.path :
`${this.path}#${this.className}`;
}
static fromString(entry: string): RouteDef {
const split = entry.split('#');
return new RouteDef(split[0], split[1] || null);
}
}
/**
*
* @returns {LazyRouteMap}
* @private
*/
export function listLazyRoutesOfModule(
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
const entryRouteDef = RouteDef.fromString(entryModule);
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
const className = entryRouteDef.className;
// List loadChildren of this single module.
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
const routes: LazyRouteMap = {};
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
const route: string = lazyRoute.routeDef.toString();
_assertRoute(routes, lazyRoute);
routes[route] = lazyRoute;
const lazyModuleSymbol = reflector.findDeclaration(
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
// Populate the map using the routes we just found.
subRoutes.forEach(subRoute => {
_assertRoute(routes, subRoute);
routes[subRoute.routeDef.toString()] = subRoute;
});
});
return routes;
}
/**
* Try to resolve a module, and returns its absolute path.
* @private
*/
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
const result = host.moduleNameToFileName(modulePath, containingFile);
if (!result) {
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
}
return result;
}
/**
* Throw an exception if a route is in a route map, but does not point to the same module.
* @private
*/
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
const r = route.routeDef.toString();
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
throw new Error(
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
'load the proper one.');
}
}
/**
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
* module and all statically referred modules.
* @private
*/
function _extractLazyRoutesFromStaticModule(
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
ROUTES: StaticSymbol): LazyRoute[] {
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
const allRoutes: any =
(moduleMetadata.imports || [])
.filter(i => 'providers' in i)
.reduce((mem: Route[], m: any) => {
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
const lazyRoutes: LazyRoute[] =
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
const routeDef = RouteDef.fromString(route);
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
acc.push({routeDef, absoluteFilePath});
return acc;
}, []);
const importedSymbols = ((moduleMetadata.imports || []) as any[])
.filter(i => i instanceof StaticSymbol) as StaticSymbol[];
return importedSymbols
.reduce(
(acc: LazyRoute[], i: StaticSymbol) => {
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
},
[])
.concat(lazyRoutes);
}
/**
* Get the NgModule Metadata of a symbol.
* @private
*/
function _getNgModuleMetadata(staticSymbol: StaticSymbol, reflector: StaticReflector): NgModule {
const ngModules = reflector.annotations(staticSymbol).filter((s: any) => s instanceof NgModule);
if (ngModules.length === 0) {
throw new Error(`${staticSymbol.name} is not an NgModule`);
}
return ngModules[0];
}
/**
* Return the routes from the provider list.
* @private
*/
function _collectRoutes(
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
return providers.reduce((routeList: Route[], p: any) => {
if (p.provide === ROUTES) {
return routeList.concat(p.useValue);
} else if (Array.isArray(p)) {
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
} else {
return routeList;
}
}, []);
}
/**
* Return the loadChildren values of a list of Route.
* @private
*/
function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => {
if (r.loadChildren) {
return m.concat(r.loadChildren);
} else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r));
} else if (r.children) {
return m.concat(_collectLoadChildren(r.children));
} else {
return m;
}
}, []);
}

View File

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

View File

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

View File

@ -0,0 +1,354 @@
/**
* @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

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

View File

@ -7,62 +7,54 @@
*/
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';
const SUPPORTED_SCHEMA_VERSION = 3;
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
import {ReflectorReader} from './private_import_core';
const HIDDEN_KEY = /^\$.*\$$/;
const SUPPORTED_SCHEMA_VERSION = 1;
/**
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
* The host of the static resolver is expected to be able to provide module metadata in the form of
* ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*/
export interface StaticReflectorHost {
/**
* Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any}[];
getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
* Resolve a symbol from an import statement form, to the file where it is declared.
* @param module the location imported from
* @param containingFile for relative imports, the path of the file containing the import
*/
moduleNameToFileName(moduleName: string, containingFile: string): string;
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol;
angularImportLocations(): {
coreDecorators: string,
diDecorators: string,
diMetadata: string,
diOpaqueToken: string,
animationMetadata: string,
provider: string
};
getCanonicalFileName(fileName: string): string;
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
* 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 StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {}
}
/**
@ -70,85 +62,55 @@ export class StaticSymbolCache {
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol;
constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) {
this.initializeConversionMap();
knownMetadataClasses.forEach(
(kc) => this._registerDecoratorOrConstructor(
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
knownMetadataFunctions.forEach(
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
}
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.findDeclaration(moduleUrl, name, '');
return this.host.findDeclaration(moduleUrl, name, '');
}
resolveEnum(enumIdentifier: any, name: string): any {
const staticSymbol: StaticSymbol = enumIdentifier;
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
}
public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type);
if (!annotations) {
annotations = [];
const classMetadata = this.getTypeMetadata(type);
if (classMetadata['extends']) {
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
annotations.push(...parentAnnotations);
}
if (classMetadata['decorators']) {
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
annotations.push(...ownAnnotations);
annotations = this.simplify(type, classMetadata['decorators']);
} else {
annotations = [];
}
this.annotationCache.set(type, annotations.filter(ann => !!ann));
}
return annotations;
}
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
public propMetadata(type: StaticSymbol): {[key: string]: any} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type) || {};
propMetadata = {};
if (classMetadata['extends']) {
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
Object.keys(parentPropMetadata).forEach((parentProp) => {
propMetadata[parentProp] = parentPropMetadata[parentProp];
});
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const classMetadata = this.getTypeMetadata(type);
const members = classMetadata ? classMetadata['members'] : {};
propMetadata = mapStringMap(members, (propData, propName) => {
const prop = (<any[]>propData)
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
const decorators: any[] = [];
if (propMetadata[propName]) {
decorators.push(...propMetadata[propName]);
}
propMetadata[propName] = decorators;
if (prop && prop['decorators']) {
decorators.push(...this.simplify(type, prop['decorators']));
return this.simplify(type, prop['decorators']);
} else {
return [];
}
});
this.propertyCache.set(type, propMetadata);
@ -158,10 +120,7 @@ export class StaticReflector implements ReflectorReader {
public parameters(type: StaticSymbol): any[] {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
return [];
throw new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`);
}
try {
let parameters = this.parameterCache.get(type);
@ -185,8 +144,6 @@ export class StaticReflector implements ReflectorReader {
}
parameters.push(nestedResult);
});
} else if (classMetadata['extends']) {
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
}
if (!parameters) {
parameters = [];
@ -200,216 +157,86 @@ export class StaticReflector implements ReflectorReader {
}
}
private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type) || {};
methodNames = {};
if (classMetadata['extends']) {
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
Object.keys(parentMethodNames).forEach((parentProp) => {
methodNames[parentProp] = parentMethodNames[parentProp];
});
}
const members = classMetadata['members'] || {};
Object.keys(members).forEach((propName) => {
const propData = members[propName];
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
methodNames[propName] = methodNames[propName] || isMethod;
});
this.methodCache.set(type, methodNames);
}
return methodNames;
}
hasLifecycleHook(type: any, lcProperty: string): boolean {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`),
type);
}
try {
return !!this._methodNames(type)[lcProperty];
} catch (e) {
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
throw new Error(
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
}
const classMetadata = this.getTypeMetadata(type);
const members = classMetadata ? classMetadata['members'] : null;
const member: any[] =
members && members.hasOwnProperty(lcProperty) ? members[lcProperty] : null;
return member ? member.some(a => a['__symbolic'] == 'method') : false;
}
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
}
private _registerFunction(type: StaticSymbol, fn: any): void {
private registerFunction(type: StaticSymbol, fn: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
}
private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
ANGULAR_IMPORT_LOCATIONS;
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.host.angularImportLocations();
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
this._registerDecoratorOrConstructor(
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this._registerDecoratorOrConstructor(
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.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Directive'), Directive);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Component'), Component);
this._registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diDecorators, 'Optional'), Optional);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Output'), Output);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule);
// Note: Some metadata classes can be used directly with Provider.deps.
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
this._registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
this._registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
this._registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
this._registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
this._registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
this._registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this._registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
this._registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(error, (context && context.filePath) || path);
} else {
throw error;
}
}
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
this.reportError(
new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`),
null, 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;
}
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
staticSymbol =
this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
break;
}
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
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;
}
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group);
}
/** @internal */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
const _this = this;
let scope = BindingScope.empty;
const calling = new Map<StaticSymbol, boolean>();
@ -417,16 +244,16 @@ export class StaticReflector implements ReflectorReader {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol;
if (expression['module']) {
staticSymbol =
self.findDeclaration(expression['module'], expression['name'], context.filePath);
staticSymbol = _this.host.findDeclaration(
expression['module'], expression['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
}
return staticSymbol;
}
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
const declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue;
@ -436,7 +263,7 @@ export class StaticReflector implements ReflectorReader {
if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression;
if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), self.opaqueToken);
return sameSymbol(resolveReference(context, target), _this.opaqueToken);
}
}
return false;
@ -618,31 +445,25 @@ export class StaticReflector implements ReflectorReader {
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case 'select':
let selectContext = context;
let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) {
// Access to a static instance variable
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
const declarationValue = resolveReferenceValue(selectTarget);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
if (declarationValue && declarationValue.statics) {
selectTarget = declarationValue.statics;
} else {
return selectContext;
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
return _this.host.getStaticSymbol(
selectTarget.filePath, selectTarget.name, members);
}
}
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
const member = simplify(expression['member']);
if (selectTarget && isPrimitive(member)) return simplify(selectTarget[member]);
return null;
case 'reference':
if (!expression['name']) {
return context;
}
if (!expression.module) {
const name: string = expression['name'];
const localValue = scope.resolve(name);
@ -671,12 +492,12 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion
let target = expression['expression'];
if (target['module']) {
staticSymbol =
self.findDeclaration(target['module'], target['name'], context.filePath);
staticSymbol = _this.host.findDeclaration(
target['module'], target['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
}
let converter = self.conversionMap.get(staticSymbol);
let converter = _this.conversionMap.get(staticSymbol);
if (converter) {
let args: any[] = expression['arguments'];
if (!args) {
@ -717,16 +538,7 @@ export class StaticReflector implements ReflectorReader {
}
}
const recordedSimplifyInContext = (context: StaticSymbol, value: any, depth: number) => {
try {
return simplifyInContext(context, value, depth);
} catch (e) {
this.reportError(e, context);
}
};
const result = this.errorRecorder ? recordedSimplifyInContext(context, value, 0) :
simplifyInContext(context, value, 0);
const result = simplifyInContext(context, value, 0);
if (shouldIgnore(result)) {
return undefined;
}
@ -739,25 +551,18 @@ export class StaticReflector implements ReflectorReader {
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
moduleMetadata = this.host.getMetadataFor(module);
if (Array.isArray(moduleMetadata)) {
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
moduleMetadata[0];
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
throw new Error(
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`);
}
this.metadataCache.set(module, moduleMetadata);
}
@ -795,7 +600,6 @@ function expandedMessage(error: any): string {
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
break;
}
return error.message;
}
@ -811,11 +615,7 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
Object.keys(input).forEach((key) => {
const value = transform(input[key], key);
if (!shouldIgnore(value)) {
if (HIDDEN_KEY.test(key)) {
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
} else {
result[key] = value;
}
result[key] = value;
}
});
return result;

View File

@ -1,19 +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
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -1,243 +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 * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
describe('CompilerHost', () => {
let context: MockAotContext;
let program: ts.Program;
let hostNestedGenDir: CompilerHost;
let hostSiblingGenDir: CompilerHost;
beforeEach(() => {
context = new MockAotContext('/tmp/src', clone(FILES));
const 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, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
hostSiblingGenDir = new CompilerHost(
program, {
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.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.shim.ngstyle');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.sass.ngstyle');
});
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');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/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');
});
});
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 v3 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: 3,
metadata: {
foo: {__symbolic: 'class'},
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
},
exports: [{from: './lib/utils2', export: ['Export']}],
}
]);
});
});
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': `
import {ReExport} from './lib/utils2';
export {ReExport};
export {Export} from './lib/utils2';
export declare class Bar {
ngOnInit() {}
}
export declare class BarChild extends 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

@ -1,177 +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 {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
import * as fs from 'fs';
import * as path from 'path';
import {main} from '../src/main';
import {ReflectionCapabilities, reflector} from './private_import_core';
describe('compiler-cli', () => {
let basePath: string;
let write: (fileName: string, content: string) => void;
beforeEach(() => {
basePath = makeTempDir();
write = (fileName: string, content: string) => {
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
};
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"module": "es2015"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
});
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
it('should compile without errors', (done) => {
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error).not.toHaveBeenCalled();
expect(exitCode).toEqual(0);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if user input file does not exist', (done) => {
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if user input file is malformed', (done) => {
write('test.ts', 'foo;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if cannot find the imported module', (done) => {
write('test.ts', `import {MyClass} from './not-exist-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
`:1:23: Cannot find module './not-exist-deps'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if cannot import', (done) => {
write('empty-deps.ts', 'export const A = 1;');
write('test.ts', `import {MyClass} from './empty-deps';`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if type mismatches', (done) => {
write('empty-deps.ts', 'export const A = "abc";');
write('test.ts', `
import {A} from './empty-deps';
A();
`);
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: basePath}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error)
.toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
':3:7: Cannot invoke an expression whose type lacks a call signature.');
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should print the stack trace on compiler internal errors', (done) => {
write('test.ts', 'export const A = 1;');
const mockConsole = {error: (s: string) => {}};
spyOn(mockConsole, 'error');
main({p: 'not-exist'}, mockConsole.error)
.then((exitCode) => {
expect(mockConsole.error).toHaveBeenCalled();
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
});

View File

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

View File

@ -1,13 +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 {__core_private__ as r} from '@angular/core';
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector;

View File

@ -0,0 +1,329 @@
/**
* @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

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler';
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
@ -21,21 +21,17 @@ describe('StaticReflector', () => {
let host: StaticReflectorHost;
let reflector: StaticReflector;
function init(
testData: {[key: string]: any} = DEFAULT_TEST_DATA,
decorators: {name: string, filePath: string, ctor: any}[] = []) {
host = new MockStaticReflectorHost(testData);
reflector = new StaticReflector(host, undefined, decorators);
}
beforeEach(() => init());
beforeEach(() => {
host = new MockReflectorHost();
reflector = new StaticReflector(host);
});
function simplify(context: StaticSymbol, value: any) {
return reflector.simplify(context, value);
}
it('should get annotations for NgFor', () => {
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1);
const annotation = annotations[0];
@ -44,15 +40,15 @@ describe('StaticReflector', () => {
});
it('should get constructor for NgFor', () => {
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
const ViewContainerRef = reflector.findDeclaration(
'@angular/core/src/linker/view_container_ref', 'ViewContainerRef');
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const ViewContainerRef =
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef');
const TemplateRef =
reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef');
const IterableDiffers = reflector.findDeclaration(
'@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = reflector.findDeclaration(
'@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef');
const IterableDiffers = host.findDeclaration(
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
const ChangeDetectorRef = host.findDeclaration(
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
const parameters = reflector.parameters(NgFor);
expect(parameters).toEqual([
@ -62,7 +58,7 @@ describe('StaticReflector', () => {
it('should get annotations for HeroDetailComponent', () => {
const HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1);
const annotation = annotations[0];
@ -77,46 +73,41 @@ describe('StaticReflector', () => {
])]);
});
it('should throw an exception for unsupported metadata versions', () => {
expect(() => reflector.findDeclaration('src/version-error', 'e'))
it('should throw and exception for unsupported metadata versions', () => {
const e = host.findDeclaration('src/version-error', 'e');
expect(() => reflector.annotations(e))
.toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3'));
});
it('should throw an exception for version 2 metadata', () => {
expect(() => reflector.findDeclaration('src/version-2-error', 'e'))
.toThrowError(
'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc');
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 1'));
});
it('should get and empty annotation list for an unknown class', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]);
});
it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy();
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
});
it('should get an empty object from propMetadata for an unknown class', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({});
});
it('should get empty parameters list for an unknown class ', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]);
});
it('should provide context for errors reported by the collector', () => {
const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass');
const SomeClass = host.findDeclaration('src/error-reporting', 'SomeClass');
expect(() => reflector.annotations(SomeClass))
.toThrow(new Error(
'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts'));
@ -313,24 +304,18 @@ describe('StaticReflector', () => {
.toEqual('s');
});
it('should not simplify a module reference without a name', () => {
const staticSymbol = new StaticSymbol('/src/cases', '');
expect(simplify(staticSymbol, ({__symbolic: 'reference', module: './extern', name: ''})))
.toEqual(staticSymbol);
});
it('should simplify a non existing reference as a static symbol', () => {
expect(simplify(
new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
.toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
.toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
});
it('should simplify a function reference as a static symbol', () => {
expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []})))
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction'));
.toEqual(host.getStaticSymbol('/src/cases', 'myFunction'));
});
it('should simplify values initialized with a function call', () => {
@ -358,11 +343,13 @@ describe('StaticReflector', () => {
try {
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts');
expect(metadata).toBeDefined();
const moduleMetadata: any = metadata[0]['metadata'];
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
if (!Array.isArray(metadata)) {
const moduleMetadata: any = metadata['metadata'];
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
}
} catch (e) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true;
@ -390,7 +377,7 @@ describe('StaticReflector', () => {
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
expect(metadata).toEqual({
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
Foo: {
__symbolic: 'class',
@ -419,35 +406,35 @@ describe('StaticReflector', () => {
it('should be able to get metadata for a class containing a custom decorator', () => {
const props = reflector.propMetadata(
reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
host.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
expect(props).toEqual({foo: []});
});
it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts';
const dep = reflector.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward'));
const dep = host.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(host.getStaticSymbol(src, 'Forward'));
expect(props).toEqual([[dep, new Inject(dep)]]);
});
it('should report an error for invalid function calls', () => {
expect(
() => reflector.annotations(
reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
() =>
reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
.toThrow(new Error(
`Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`));
});
it('should be able to get metadata for a class containing a static method call', () => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
});
it('should be able to get metadata for a class containing a static field reference', () => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
const annotations =
reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
});
@ -455,264 +442,60 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with a conditional expression',
() => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
]);
});
it('should be able to get metadata for a class with nested method calls', () => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyFactoryComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual({
provide: 'c',
useFactory:
reflector.getStaticSymbol('/tmp/src/static-method.ts', 'AnotherModule', ['someFactory'])
});
});
it('should be able to get the metadata for a class calling a method with default parameters',
() => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([['a', true, false]]);
});
it('should be able to get metadata with a reference to a static method', () => {
const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
host.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
expect(annotations.length).toBe(1);
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
});
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
const foo1 = reflector.getStaticSymbol('main.ts', 'foo');
const foo2 = reflector.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file',
() => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); });
it('should be able to trace a named export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Thirty');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
it('should cache tracing a named export', () => {
const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough();
const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough();
reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
moduleNameToFileNameSpy.calls.reset();
getMetadataForSpy.calls.reset();
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(moduleNameToFileNameSpy.calls.count()).toBe(1);
expect(getMetadataForSpy.calls.count()).toBe(0);
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
describe('inheritance', () => {
class ClassDecorator {
constructor(public value: any) {}
}
class ParamDecorator {
constructor(public value: any) {}
}
class PropDecorator {
constructor(public value: any) {}
}
function initWithDecorator(testData: {[key: string]: any}) {
testData['/tmp/src/decorator.ts'] = `
export function ClassDecorator(): any {}
export function ParamDecorator(): any {}
export function PropDecorator(): any {}
`;
init(testData, [
{filePath: '/tmp/src/decorator.ts', name: 'ClassDecorator', ctor: ClassDecorator},
{filePath: '/tmp/src/decorator.ts', name: 'ParamDecorator', ctor: ParamDecorator},
{filePath: '/tmp/src/decorator.ts', name: 'PropDecorator', ctor: PropDecorator}
]);
}
it('should inherit annotations', () => {
initWithDecorator({
'/tmp/src/main.ts': `
import {ClassDecorator} from './decorator';
@ClassDecorator('parent')
export class Parent {}
@ClassDecorator('child')
export class Child extends Parent {}
export class ChildNoDecorators extends Parent {}
`
});
// Check that metadata for Parent was not changed!
expect(reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
.toEqual([new ClassDecorator('parent')]);
expect(reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child')))
.toEqual([new ClassDecorator('parent'), new ClassDecorator('child')]);
expect(
reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildNoDecorators')))
.toEqual([new ClassDecorator('parent')]);
});
it('should inherit parameters', () => {
initWithDecorator({
'/tmp/src/main.ts': `
import {ParamDecorator} from './decorator';
export class A {}
export class B {}
export class C {}
export class Parent {
constructor(@ParamDecorator('a') a: A, @ParamDecorator('b') b: B) {}
}
export class Child extends Parent {}
export class ChildWithCtor extends Parent {
constructor(@ParamDecorator('c') c: C) {}
}
`
});
// Check that metadata for Parent was not changed!
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
.toEqual([
[reflector.getStaticSymbol('/tmp/src/main.ts', 'A'), new ParamDecorator('a')],
[reflector.getStaticSymbol('/tmp/src/main.ts', 'B'), new ParamDecorator('b')]
]);
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'))).toEqual([
[reflector.getStaticSymbol('/tmp/src/main.ts', 'A'), new ParamDecorator('a')],
[reflector.getStaticSymbol('/tmp/src/main.ts', 'B'), new ParamDecorator('b')]
]);
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildWithCtor')))
.toEqual([[reflector.getStaticSymbol('/tmp/src/main.ts', 'C'), new ParamDecorator('c')]]);
});
it('should inherit property metadata', () => {
initWithDecorator({
'/tmp/src/main.ts': `
import {PropDecorator} from './decorator';
export class A {}
export class B {}
export class C {}
export class Parent {
@PropDecorator('a')
a: A;
@PropDecorator('b1')
b: B;
}
export class Child extends Parent {
@PropDecorator('b2')
b: B;
@PropDecorator('c')
c: C;
}
`
});
// Check that metadata for Parent was not changed!
expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent')))
.toEqual({
'a': [new PropDecorator('a')],
'b': [new PropDecorator('b1')],
});
expect(reflector.propMetadata(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child')))
.toEqual({
'a': [new PropDecorator('a')],
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
'c': [new PropDecorator('c')]
});
});
it('should inherit lifecycle hooks', () => {
initWithDecorator({
'/tmp/src/main.ts': `
export class Parent {
hook1() {}
hook2() {}
}
export class Child extends Parent {
hook2() {}
hook3() {}
}
`
});
function hooks(symbol: StaticSymbol, names: string[]): boolean[] {
return names.map(name => reflector.hasLifecycleHook(symbol, name));
}
// Check that metadata for Parent was not changed!
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'Parent'), [
'hook1', 'hook2', 'hook3'
])).toEqual([true, true, false]);
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'), [
'hook1', 'hook2', 'hook3'
])).toEqual([true, true, true]);
});
});
});
export class MockStaticReflectorHost implements StaticReflectorHost {
class MockReflectorHost implements StaticReflectorHost {
private staticTypeCache = new Map<string, StaticSymbol>();
private collector = new MetadataCollector();
constructor(private data: {[key: string]: any}) {}
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
moduleNameToFileName(modulePath: string, containingFile?: string): string {
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string {
@ -746,55 +529,32 @@ export class MockStaticReflectorHost implements StaticReflectorHost {
if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts';
if (this._getMetadataFor(tsName)) {
return tsName;
if (this.getMetadataFor(tsName)) {
return this.getStaticSymbol(tsName, symbolName);
}
return baseName + '.d.ts';
return this.getStaticSymbol(baseName + '.d.ts', symbolName);
}
return '/tmp/' + modulePath + '.d.ts';
return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
}
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
private _getMetadataFor(moduleId: string): any {
if (this.data[moduleId] && moduleId.match(TS_EXT)) {
const text = this.data[moduleId];
if (typeof text === 'string') {
const sf = ts.createSourceFile(
moduleId, this.data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true);
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`);
}
return [this.collector.getMetadata(sf)];
}
}
const result = this.data[moduleId];
if (result) {
return Array.isArray(result) ? result : [result];
} else {
return null;
}
}
}
const DEFAULT_TEST_DATA: {[key: string]: any} = {
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
getMetadataFor(moduleId: string): any {
const data: {[key: string]: any} = {
'/tmp/angular2/src/common/forms-deprecated/directives.d.ts': [{
'__symbolic': 'module',
'version': 3,
'version': 1,
'metadata': {
'FORM_DIRECTIVES': [
{
'__symbolic': 'reference',
'name': 'NgFor',
'module': '@angular/common/src/directives/ng_for'
'module': 'angular2/src/common/directives/ng_for'
}
]
}
}],
'/tmp/@angular/common/src/directives/ng_for.d.ts': {
'/tmp/angular2/src/common/directives/ng_for.d.ts': {
'__symbolic': 'module',
'version': 3,
'version': 1,
'metadata': {
'NgFor': {
'__symbolic': 'class',
@ -804,7 +564,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'Directive',
'module': '@angular/core/src/metadata'
'module': '../../core/metadata'
},
'arguments': [
{
@ -821,22 +581,22 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'parameters': [
{
'__symbolic': 'reference',
'module': '@angular/core/src/linker/view_container_ref',
'module': '../../core/linker/view_container_ref',
'name': 'ViewContainerRef'
},
{
'__symbolic': 'reference',
'module': '@angular/core/src/linker/template_ref',
'module': '../../core/linker/template_ref',
'name': 'TemplateRef'
},
{
'__symbolic': 'reference',
'module': '@angular/core/src/change_detection/differs/iterable_differs',
'module': '../../core/change_detection/differs/iterable_differs',
'name': 'IterableDiffers'
},
{
'__symbolic': 'reference',
'module': '@angular/core/src/change_detection/change_detector_ref',
'module': '../../core/change_detection/change_detector_ref',
'name': 'ChangeDetectorRef'
}
]
@ -846,17 +606,17 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
}
},
'/tmp/@angular/core/src/linker/view_container_ref.d.ts':
{version: 3, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/linker/template_ref.d.ts':
{version: 3, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts':
{version: 3, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts':
{version: 3, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/linker/view_container_ref.d.ts':
{version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/linker/template_ref.d.ts':
{version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
{version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
{version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/src/app/hero-detail.component.d.ts': {
'__symbolic': 'module',
'version': 3,
'version': 1,
'metadata': {
'HeroDetailComponent': {
'__symbolic': 'class',
@ -866,7 +626,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'Component',
'module': '@angular/core/src/metadata'
'module': 'angular2/src/core/metadata'
},
'arguments': [
{
@ -878,7 +638,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'trigger',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments': [
'myAnimation',
@ -886,7 +646,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'state',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments': [
'state1',
@ -894,7 +654,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'style',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments': [
{ 'background':'white' }
@ -906,7 +666,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic':'reference',
'name':'transition',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments': [
'* => *',
@ -915,20 +675,20 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression':{
'__symbolic':'reference',
'name':'sequence',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[[{ '__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'group',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[[{
'__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'animate',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[
'1s 0.5s',
@ -936,13 +696,13 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic':'reference',
'name':'keyframes',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[[{ '__symbolic': 'call',
'expression': {
'__symbolic':'reference',
'name':'style',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[ { 'background': 'blue'} ]
}, {
@ -950,7 +710,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic':'reference',
'name':'style',
'module': '@angular/core/src/animation/metadata'
'module': 'angular2/src/core/animation/metadata'
},
'arguments':[ { 'background': 'red'} ]
}]]
@ -976,7 +736,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'expression': {
'__symbolic': 'reference',
'name': 'Input',
'module': '@angular/core/src/metadata'
'module': 'angular2/src/core/metadata'
}
}
]
@ -990,7 +750,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
'__symbolic': 'call',
'expression': {
'__symbolic': 'reference',
'module': '@angular/core/src/metadata',
'module': 'angular2/src/core/metadata',
'name': 'HostListener'
},
'arguments': [
@ -1007,12 +767,11 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
}
},
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}},
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 1, metadata: {s: 's'}},
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}},
'/tmp/src/error-reporting.d.ts': {
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
SomeClass: {
__symbolic: 'class',
@ -1022,7 +781,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
expression: {
__symbolic: 'reference',
name: 'Component',
module: '@angular/core/src/metadata'
module: 'angular2/src/core/metadata'
},
arguments: [
{
@ -1042,7 +801,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
},
'/tmp/src/error-references.d.ts': {
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
Link1: {
__symbolic: 'reference',
@ -1064,7 +823,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
},
'/tmp/src/function-declaration.d.ts': {
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
one: {
__symbolic: 'function',
@ -1093,7 +852,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
},
'/tmp/src/function-reference.ts': {
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
one: {
__symbolic: 'call',
@ -1135,7 +894,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
},
'/tmp/src/function-recursive.d.ts': {
__symbolic: 'modules',
version: 3,
version: 1,
metadata: {
recursive: {
__symbolic: 'function',
@ -1195,7 +954,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
},
'/tmp/src/spread.ts': {
__symbolic: 'module',
version: 3,
version: 1,
metadata: {
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
}
@ -1223,8 +982,8 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
`,
'/tmp/src/invalid-calls.ts': `
import {someFunction} from './nvalid-calll-definitions.ts';
import {Component} from '@angular/core/src/metadata';
import {NgIf} from '@angular/common';
import {Component} from 'angular2/src/core/metadata';
import {NgIf} from 'angular2/common';
@Component({
selector: 'my-component',
@ -1240,7 +999,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
export class MyOtherComponent { }
`,
'/tmp/src/static-method.ts': `
import {Component} from '@angular/core/src/metadata';
import {Component} from 'angular2/src/core/metadata';
@Component({
selector: 'stub'
@ -1255,19 +1014,10 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
static defaultsMethod(a, b = true, c = false) {
return [a, b, c];
}
static withFactory() {
return { provide: 'c', useFactory: AnotherModule.someFactory };
}
}
export class AnotherModule {
static someFactory() {
return 'e';
}
}
`,
'/tmp/src/static-method-call.ts': `
import {Component} from '@angular/core/src/metadata';
import {Component} from 'angular2/src/core/metadata';
import {MyModule} from './static-method';
@Component({
@ -1284,14 +1034,9 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
providers: [MyModule.defaultsMethod('a')]
})
export class MyDefaultsComponent { }
@Component({
providers: MyModule.withFactory()
})
export class MyFactoryComponent { }
`,
'/tmp/src/static-field.ts': `
import {Injectable} from '@angular/core';
import {Injectable} from 'angular2/core';
@Injectable()
export class MyModule {
@ -1299,7 +1044,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
`,
'/tmp/src/static-field-reference.ts': `
import {Component} from '@angular/core/src/metadata';
import {Component} from 'angular2/src/core/metadata';
import {MyModule} from './static-field';
@Component({
@ -1313,7 +1058,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
`,
'/tmp/src/static-method-ref.ts': `
import {Component} from '@angular/core/src/metadata';
import {Component} from 'angular2/src/core/metadata';
import {ClassWithStatics} from './static-method-def';
@Component({
@ -1324,7 +1069,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
`,
'/tmp/src/invalid-metadata.ts': `
import {Component} from '@angular/core/src/metadata';
import {Component} from 'angular2/src/core/metadata';
@Component({
providers: [ { provider: 'a', useValue: (() => 1)() }]
@ -1332,9 +1077,9 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
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';
import {forwardRef} from 'angular2/core';
import {Component} from 'angular2/src/core/metadata';
import {Inject} from 'angular2/src/core/di/metadata';
@Component({})
export class Forward {
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
@ -1342,48 +1087,22 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
export class Dep {
@Input f: Forward;
}
`,
'/tmp/src/reexport/reexport.d.ts': {
__symbolic: 'module',
version: 3,
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: 3,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin5.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Five: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin30.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Thirty: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/originNone.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
},
'/tmp/src/reexport/src/reexport2.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
}
`
};
if (data[moduleId] && moduleId.match(TS_EXT)) {
const text = data[moduleId];
if (typeof text === 'string') {
const sf = ts.createSourceFile(
moduleId, data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true);
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`);
}
return this.collector.getMetadata(sf);
}
}
return data[moduleId];
}
}

View File

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

View File

@ -21,22 +21,15 @@
* </p>
* </div>
*/
export {VERSION} from './src/version';
export * from './src/template_parser/template_ast';
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
export {CompilerConfig, RenderTypes} from './src/config';
export * from './src/compile_metadata';
export * from './src/aot/compiler_factory';
export * from './src/aot/compiler';
export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol';
export * from './src/aot/summary_resolver';
export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory';
export * from './src/offline_compiler';
export {RuntimeCompiler} from './src/runtime_compiler';
export * from './src/url_resolver';
export * from './src/resource_loader';
export * from './src/compiler';
export {DirectiveResolver} from './src/directive_resolver';
export {PipeResolver} from './src/pipe_resolver';
export {NgModuleResolver} from './src/ng_module_resolver';
@ -60,4 +53,5 @@ export * from './src/style_compiler';
export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler';
export {AnimationParser} from './src/animation/animation_parser';
// This file only reexports content of the `src` folder. Keep it that way.

View File

@ -8,7 +8,7 @@
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
@ -69,8 +69,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
stylesArr.push(o.literalMap(entries));
});
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
o.importExpr(createIdentifier(Identifiers.collectAndResolveStyles)).callFn([
return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([
o.importExpr(resolveIdentifier(Identifiers.collectAndResolveStyles)).callFn([
_ANIMATION_COLLECTED_STYLES, o.literalArr(stylesArr)
])
]);
@ -78,7 +78,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
visitAnimationKeyframe(ast: AnimationKeyframeAst, context: _AnimationBuilderContext):
o.Expression {
return o.importExpr(createIdentifier(Identifiers.AnimationKeyframe)).instantiate([
return o.importExpr(resolveIdentifier(Identifiers.AnimationKeyframe)).instantiate([
o.literal(ast.offset), ast.styles.visit(this, context)
]);
}
@ -100,7 +100,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
const startingStylesExpr = ast.startingStyles.visit(this, context);
const keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
const keyframesExpr =
o.importExpr(createIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
o.importExpr(resolveIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
_ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR,
o.literalArr(keyframeExpressions)
]);
@ -127,14 +127,14 @@ class _AnimationBuilder implements AnimationAstVisitor {
visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext):
o.Expression {
const playerExprs = ast.steps.map(step => step.visit(this, context));
return o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
return o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
o.literalArr(playerExprs)
]);
}
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
const playerExprs = ast.steps.map(step => step.visit(this, context));
return o.importExpr(createIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
return o.importExpr(resolveIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
o.literalArr(playerExprs)
]);
}
@ -199,9 +199,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
'getAnimationPlayers',
[
_ANIMATION_FACTORY_ELEMENT_VAR,
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
.conditional(o.NULL_EXPR, o.literal(this.animationName))
]))
.toDeclStmt());
@ -229,7 +228,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR),
[_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()]));
const RENDER_STYLES_FN = o.importExpr(createIdentifier(Identifiers.renderStyles));
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
@ -238,7 +237,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
statements.push(new o.IfStmt(
_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR),
[_ANIMATION_PLAYER_VAR
.set(o.importExpr(createIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
.set(o.importExpr(resolveIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
.toStmt()]));
// once complete we want to apply the styles on the element
@ -256,18 +255,17 @@ class _AnimationBuilder implements AnimationAstVisitor {
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(
createIdentifier(Identifiers.prepareFinalAnimationStyles))
.callFn(
[
_ANIMATION_START_STATE_STYLES_VAR,
_ANIMATION_END_STATE_STYLES_VAR
])
resolveIdentifier(Identifiers.prepareFinalAnimationStyles))
.callFn([
_ANIMATION_START_STATE_STYLES_VAR,
_ANIMATION_END_STATE_STYLES_VAR
])
])
.toStmt()
])])
.toStmt());
statements.push(o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer))
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
.callMethod('destroy', [])
.toStmt());
@ -278,7 +276,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(createIdentifier(Identifiers.clearStyles))
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
@ -293,7 +291,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
.toStmt());
statements.push(new o.ReturnStatement(
o.importExpr(createIdentifier(Identifiers.AnimationTransition)).instantiate([
o.importExpr(resolveIdentifier(Identifiers.AnimationTransition)).instantiate([
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR,
_ANIMATION_TIME_VAR
])));
@ -302,12 +300,12 @@ class _AnimationBuilder implements AnimationAstVisitor {
[
new o.FnParam(
_ANIMATION_FACTORY_VIEW_VAR.name,
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(_ANIMATION_FACTORY_ELEMENT_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(_ANIMATION_CURRENT_STATE_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(_ANIMATION_NEXT_STATE_VAR.name, o.DYNAMIC_TYPE)
],
statements, o.importType(createIdentifier(Identifiers.AnimationTransition)));
statements, o.importType(resolveIdentifier(Identifiers.AnimationTransition)));
}
build(ast: AnimationAst): AnimationEntryCompileResult {

View File

@ -8,7 +8,7 @@
import {Injectable} from '@angular/core';
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {ParseError} from '../parse_util';
@ -41,7 +41,7 @@ export class AnimationParser {
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
const errors: string[] = [];
const componentName = identifierName(component.type);
const componentName = component.type.name;
const animationTriggerNames = new Set<string>();
const asts = component.template.animations.map(entry => {
const result = this.parseEntry(entry);
@ -174,11 +174,6 @@ function _normalizeStyleMetadata(
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
schema: ElementSchemaRegistry, errors: AnimationParseError[],
permitStateReferences: boolean): {[key: string]: string | number}[] {
const offset = entry.offset;
if (offset > 1 || offset < 0) {
errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`));
}
const normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') {

View File

@ -1,76 +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 {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';
import {AotSummaryResolver} from './summary_resolver';
/**
* 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 summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, 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), summaryResolver, options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
return {compiler, reflector: staticReflector};
}

View File

@ -1,25 +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 {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {AotSummaryResolverHost} from './summary_resolver';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver,
AotSummaryResolverHost {
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
}

View File

@ -1,16 +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
*/
export interface AotCompilerOptions {
debug?: boolean;
locale?: string;
i18nFormat?: string;
translations?: string;
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}

View File

@ -1,16 +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
*/
/**
* 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

@ -1,124 +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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {filterFileByPatterns} from './utils';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummaryResolverHost {
/**
* Loads an NgModule/Directive/Pipe summary file
*/
loadSummary(filePath: string): string;
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
*/
getOutputFileName(sourceFilePath: string): string;
}
export interface AotSummaryResolverOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
export class AotSummaryResolver implements SummaryResolver {
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
constructor(
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
private options: AotSummaryResolverOptions) {}
serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile {
const jsonReplacer = (key: string, value: any) => {
if (value instanceof StaticSymbol) {
// We convert the source filenames into output filenames,
// as the generated summary file will be used when the current
// compilation unit is used as a library
return {
'__symbolic__': 'symbol',
'name': value.name,
'path': this.host.getOutputFileName(value.filePath),
'members': value.members
};
}
return value;
};
const allSummaries = summaries.slice();
summaries.forEach((summary) => {
if (summary.summaryKind === CompileSummaryKind.NgModule) {
const moduleMeta = <CompileNgModuleSummary>summary;
moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => {
if (!filterFileByPatterns(id.reference.filePath, this.options)) {
allSummaries.push(this.resolveSummary(id.reference));
}
});
}
});
return new GeneratedFile(
srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer));
}
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
resolveSummary(staticSymbol: StaticSymbol): any {
const filePath = staticSymbol.filePath;
const name = staticSymbol.name;
const cacheKey = this._cacheKey(staticSymbol);
if (!filterFileByPatterns(filePath, this.options)) {
let summary = this.summaryCache[cacheKey];
const summaryFilePath = summaryFileName(filePath);
if (!summary) {
try {
const jsonReviver = (key: string, value: any) => {
if (value && value['__symbolic__'] === 'symbol') {
// Note: We can't use staticReflector.findDeclaration here:
// Summary files can contain symbols of transitive compilation units
// (via the providers), and findDeclaration needs .metadata.json / .d.ts files,
// but we don't want to depend on these for transitive dependencies.
return this.staticReflector.getStaticSymbol(
value['path'], value['name'], value['members']);
} else {
return value;
}
};
const readSummaries: CompileTypeSummary[] =
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
readSummaries.forEach((summary) => {
const filePath = summary.type.reference.filePath;
this.summaryCache[this._cacheKey(summary.type.reference)] = summary;
});
summary = this.summaryCache[cacheKey];
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
}
if (!summary) {
throw new Error(
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
}
return summary;
} else {
return null;
}
}
}
function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}

View File

@ -1,19 +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
*/
export function filterFileByPatterns(
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
if (options.includeFilePattern) {
match = match && !!options.includeFilePattern.exec(fileName);
}
if (options.excludeFilePattern) {
match = match && !options.excludeFilePattern.exec(fileName);
}
return match;
}

View File

@ -8,12 +8,11 @@
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang';
import {LifecycleHooks, reflector} from './private_import_core';
import {isPresent} from './facade/lang';
import {LifecycleHooks} from './private_import_core';
import {CssSelector} from './selector';
import {splitAtColon} from './util';
import {sanitizeIdentifier, splitAtColon} from './util';
function unimplemented(): any {
throw new Error('unimplemented');
@ -25,6 +24,10 @@ function unimplemented(): any {
// group 3: "@trigger" from "@trigger"
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
export abstract class CompileMetadataWithIdentifier {
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}
export class CompileAnimationEntryMetadata {
constructor(
public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
@ -75,50 +78,24 @@ export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMeta
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
}
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
reference: any;
name: string;
prefix: string;
moduleUrl: string;
value: any;
function _sanitizeIdentifier(name: string): string {
return name.replace(/\W/g, '_');
}
let _anonymousTypeIndex = 0;
export function identifierName(compileIdentifier: CompileIdentifierMetadata): string {
if (!compileIdentifier || !compileIdentifier.reference) {
return null;
constructor(
{reference, name, moduleUrl, prefix, value}:
{reference?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
this.reference = reference;
this.name = name;
this.prefix = prefix;
this.moduleUrl = moduleUrl;
this.value = value;
}
const ref = compileIdentifier.reference;
if (ref instanceof StaticSymbol) {
return ref.name;
}
if (ref['__anonymousType']) {
return ref['__anonymousType'];
}
let identifier = stringify(ref);
if (identifier.indexOf('(') >= 0) {
// case: anonymous functions!
identifier = `anonymous_${_anonymousTypeIndex++}`;
ref['__anonymousType'] = identifier;
} else {
identifier = _sanitizeIdentifier(identifier);
}
return identifier;
}
export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata): string {
const ref = compileIdentifier.reference;
if (ref instanceof StaticSymbol) {
return ref.filePath;
}
return reflector.importUri(ref);
}
export interface CompileIdentifierMetadata { reference: any; }
export enum CompileSummaryKind {
Template,
Pipe,
Directive,
NgModule
get identifier(): CompileIdentifierMetadata { return this; }
}
/**
@ -126,69 +103,158 @@ export enum CompileSummaryKind {
* in other modules / components. However, this data is not enough to compile
* the directive / module itself.
*/
export interface CompileSummary { summaryKind: CompileSummaryKind; }
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; }
export interface CompileDiDependencyMetadata {
isAttribute?: boolean;
isSelf?: boolean;
isHost?: boolean;
isSkipSelf?: boolean;
isOptional?: boolean;
isValue?: boolean;
token?: CompileTokenMetadata;
value?: any;
export interface CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
}
export interface CompileProviderMetadata {
export class CompileDiDependencyMetadata {
isAttribute: boolean;
isSelf: boolean;
isHost: boolean;
isSkipSelf: boolean;
isOptional: boolean;
isValue: boolean;
token: CompileTokenMetadata;
useClass?: CompileTypeMetadata;
useValue?: any;
useExisting?: CompileTokenMetadata;
useFactory?: CompileFactoryMetadata;
deps?: CompileDiDependencyMetadata[];
multi?: boolean;
}
value: any;
export interface CompileFactoryMetadata extends CompileIdentifierMetadata {
diDeps: CompileDiDependencyMetadata[];
reference: any;
}
export function tokenName(token: CompileTokenMetadata) {
return isPresent(token.value) ? _sanitizeIdentifier(token.value) :
identifierName(token.identifier);
}
export function tokenReference(token: CompileTokenMetadata) {
if (isPresent(token.identifier)) {
return token.identifier.reference;
} else {
return token.value;
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, token, value}: {
isAttribute?: boolean,
isSelf?: boolean,
isHost?: boolean,
isSkipSelf?: boolean,
isOptional?: boolean,
isValue?: boolean,
query?: CompileQueryMetadata,
viewQuery?: CompileQueryMetadata,
token?: CompileTokenMetadata,
value?: any
} = {}) {
this.isAttribute = !!isAttribute;
this.isSelf = !!isSelf;
this.isHost = !!isHost;
this.isSkipSelf = !!isSkipSelf;
this.isOptional = !!isOptional;
this.isValue = !!isValue;
this.token = token;
this.value = value;
}
}
export interface CompileTokenMetadata {
value?: any;
identifier?: CompileIdentifierMetadata|CompileTypeMetadata;
export class CompileProviderMetadata {
token: CompileTokenMetadata;
useClass: CompileTypeMetadata;
useValue: any;
useExisting: CompileTokenMetadata;
useFactory: CompileFactoryMetadata;
deps: CompileDiDependencyMetadata[];
multi: boolean;
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
token?: CompileTokenMetadata,
useClass?: CompileTypeMetadata,
useValue?: any,
useExisting?: CompileTokenMetadata,
useFactory?: CompileFactoryMetadata,
deps?: CompileDiDependencyMetadata[],
multi?: boolean
}) {
this.token = token;
this.useClass = useClass;
this.useValue = useValue;
this.useExisting = useExisting;
this.useFactory = useFactory;
this.deps = deps || null;
this.multi = !!multi;
}
}
export class CompileFactoryMetadata extends CompileIdentifierMetadata {
diDeps: CompileDiDependencyMetadata[];
constructor({reference, name, moduleUrl, prefix, diDeps, value}: {
reference?: Function,
name?: string,
prefix?: string,
moduleUrl?: string,
value?: boolean,
diDeps?: CompileDiDependencyMetadata[]
}) {
super({reference: reference, name: name, prefix: prefix, moduleUrl: moduleUrl, value: value});
this.diDeps = _normalizeArray(diDeps);
}
}
export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
value: any;
identifier: CompileIdentifierMetadata;
identifierIsInstance: boolean;
constructor(
{value, identifier, identifierIsInstance}:
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
this.value = value;
this.identifier = identifier;
this.identifierIsInstance = !!identifierIsInstance;
}
get reference(): any {
if (isPresent(this.identifier)) {
return this.identifier.reference;
} else {
return this.value;
}
}
get name(): string {
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
}
}
/**
* Metadata regarding compilation of a type.
*/
export interface CompileTypeMetadata extends CompileIdentifierMetadata {
export class CompileTypeMetadata extends CompileIdentifierMetadata {
isHost: boolean;
diDeps: CompileDiDependencyMetadata[];
lifecycleHooks: LifecycleHooks[];
reference: any;
constructor({reference, name, moduleUrl, prefix, isHost, value, diDeps, lifecycleHooks}: {
reference?: Type<any>,
name?: string,
moduleUrl?: string,
prefix?: string,
isHost?: boolean,
value?: any,
diDeps?: CompileDiDependencyMetadata[],
lifecycleHooks?: LifecycleHooks[];
} = {}) {
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
this.isHost = !!isHost;
this.diDeps = _normalizeArray(diDeps);
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
}
}
export interface CompileQueryMetadata {
export class CompileQueryMetadata {
selectors: Array<CompileTokenMetadata>;
descendants: boolean;
first: boolean;
propertyName: string;
read: CompileTokenMetadata;
constructor({selectors, descendants, first, propertyName, read}: {
selectors?: Array<CompileTokenMetadata>,
descendants?: boolean,
first?: boolean,
propertyName?: string,
read?: CompileTokenMetadata
} = {}) {
this.selectors = selectors;
this.descendants = !!descendants;
this.first = !!first;
this.propertyName = propertyName;
this.read = read;
}
}
/**
@ -211,6 +277,7 @@ export class CompileStylesheetMetadata {
* Summary Metadata regarding compilation of a template.
*/
export interface CompileTemplateSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
animations: string[];
ngContentSelectors: string[];
encapsulation: ViewEncapsulation;
@ -258,7 +325,7 @@ export class CompileTemplateMetadata {
toSummary(): CompileTemplateSummary {
return {
summaryKind: CompileSummaryKind.Template,
isSummary: true,
animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation
@ -266,9 +333,8 @@ export class CompileTemplateMetadata {
}
}
// Note: This should only use interfaces as nested data types
// as we need to be able to serialize this from/to JSON!
export interface CompileDirectiveSummary extends CompileTypeSummary {
export interface CompileDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
@ -289,11 +355,10 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
/**
* Metadata regarding compilation of a directive.
*/
export class CompileDirectiveMetadata {
export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
static create(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
providers, viewProviders, queries, viewQueries, entryComponents, template}: {
isHost?: boolean,
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers,
viewProviders, queries, viewQueries, entryComponents, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
@ -302,8 +367,10 @@ export class CompileDirectiveMetadata {
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
providers?: CompileProviderMetadata[],
viewProviders?: CompileProviderMetadata[],
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[],
@ -345,7 +412,6 @@ export class CompileDirectiveMetadata {
}
return new CompileDirectiveMetadata({
isHost,
type,
isComponent: !!isComponent, selector, exportAs, changeDetection,
inputs: inputsMap,
@ -361,7 +427,6 @@ export class CompileDirectiveMetadata {
template,
});
}
isHost: boolean;
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
@ -381,10 +446,9 @@ export class CompileDirectiveMetadata {
template: CompileTemplateMetadata;
constructor(
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries,
viewQueries, entryComponents, template}: {
isHost?: boolean,
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries,
entryComponents, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
@ -395,14 +459,15 @@ export class CompileDirectiveMetadata {
hostListeners?: {[key: string]: string},
hostProperties?: {[key: string]: string},
hostAttributes?: {[key: string]: string},
providers?: CompileProviderMetadata[],
viewProviders?: CompileProviderMetadata[],
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[],
template?: CompileTemplateMetadata,
} = {}) {
this.isHost = !!isHost;
this.type = type;
this.isComponent = isComponent;
this.selector = selector;
@ -422,9 +487,11 @@ export class CompileDirectiveMetadata {
this.template = template;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileDirectiveSummary {
return {
summaryKind: CompileSummaryKind.Directive,
isSummary: true,
type: this.type,
isComponent: this.isComponent,
selector: this.selector,
@ -447,12 +514,16 @@ export class CompileDirectiveMetadata {
/**
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
*/
export function createHostComponentMeta(
typeReference: any, compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata {
export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
CompileDirectiveMetadata {
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({
isHost: true,
type: {reference: typeReference, diDeps: [], lifecycleHooks: []},
type: new CompileTypeMetadata({
reference: Object,
name: `${compMeta.type.name}_Host`,
moduleUrl: compMeta.type.moduleUrl,
isHost: true
}),
template: new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template: template,
@ -475,13 +546,14 @@ export function createHostComponentMeta(
});
}
export interface CompilePipeSummary extends CompileTypeSummary {
export interface CompilePipeSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
name: string;
pure: boolean;
}
export class CompilePipeMetadata {
export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
name: string;
pure: boolean;
@ -495,44 +567,42 @@ export class CompilePipeMetadata {
this.name = name;
this.pure = !!pure;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompilePipeSummary {
return {
summaryKind: CompileSummaryKind.Pipe,
type: this.type,
name: this.name,
pure: this.pure
};
return {isSummary: true, type: this.type, name: this.name, pure: this.pure};
}
}
// Note: This should only use interfaces as nested data types
// as we need to be able to serialize this from/to JSON!
export interface CompileNgModuleSummary extends CompileTypeSummary {
export interface CompileNgModuleInjectorSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
// Note: This is transitive over the exported modules.
exportedDirectives: CompileIdentifierMetadata[];
// Note: This is transitive over the exported modules.
exportedPipes: CompileIdentifierMetadata[];
// Note: This is transitive.
entryComponents: CompileIdentifierMetadata[];
// Note: This is transitive.
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[];
// Note: This is transitive.
modules: CompileTypeMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleInjectorSummary[];
exportedModules: CompileNgModuleInjectorSummary[];
}
export interface CompileNgModuleDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[];
directiveLoaders: (() => Promise<void>)[];
}
export type CompileNgModuleSummary =
CompileNgModuleInjectorSummary & CompileNgModuleDirectiveSummary;
/**
* Metadata regarding compilation of a module.
*/
export class CompileNgModuleMetadata {
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
declaredDirectives: CompileIdentifierMetadata[];
exportedDirectives: CompileIdentifierMetadata[];
declaredPipes: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
entryComponents: CompileIdentifierMetadata[];
bootstrapComponents: CompileIdentifierMetadata[];
@ -550,7 +620,8 @@ export class CompileNgModuleMetadata {
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
transitiveModule, id}: {
type?: CompileTypeMetadata,
providers?: CompileProviderMetadata[],
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileIdentifierMetadata[],
exportedDirectives?: CompileIdentifierMetadata[],
declaredPipes?: CompileIdentifierMetadata[],
@ -578,81 +649,84 @@ export class CompileNgModuleMetadata {
this.transitiveModule = transitiveModule;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileNgModuleSummary {
return {
summaryKind: CompileSummaryKind.NgModule,
isSummary: true,
type: this.type,
entryComponents: this.transitiveModule.entryComponents,
providers: this.transitiveModule.providers,
modules: this.transitiveModule.modules,
exportedDirectives: this.transitiveModule.exportedDirectives,
exportedPipes: this.transitiveModule.exportedPipes
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
toInjectorSummary(): CompileNgModuleInjectorSummary {
return {
isSummary: true,
type: this.type,
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules
};
}
toDirectiveSummary(): CompileNgModuleDirectiveSummary {
return {
isSummary: true,
type: this.type,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules,
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
}
export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<any>();
directives: CompileIdentifierMetadata[] = [];
exportedDirectivesSet = new Set<any>();
exportedDirectives: CompileIdentifierMetadata[] = [];
pipesSet = new Set<any>();
pipes: CompileIdentifierMetadata[] = [];
exportedPipesSet = new Set<any>();
exportedPipes: CompileIdentifierMetadata[] = [];
modulesSet = new Set<any>();
modules: CompileTypeMetadata[] = [];
entryComponentsSet = new Set<any>();
entryComponents: CompileIdentifierMetadata[] = [];
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = [];
constructor(
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public directiveLoaders: (() => Promise<void>)[]) {
directives.forEach(dir => this.directivesSet.add(dir.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
}
}
addProvider(provider: CompileProviderMetadata, module: CompileIdentifierMetadata) {
this.providers.push({provider: provider, module: module});
}
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
T[] {
const map = new Map<any, T>();
addDirective(id: CompileIdentifierMetadata) {
if (!this.directivesSet.has(id.reference)) {
this.directivesSet.add(id.reference);
this.directives.push(id);
items.forEach((item) => {
if (!map.get(item.identifier.reference)) {
map.set(item.identifier.reference, item);
}
}
addExportedDirective(id: CompileIdentifierMetadata) {
if (!this.exportedDirectivesSet.has(id.reference)) {
this.exportedDirectivesSet.add(id.reference);
this.exportedDirectives.push(id);
}
}
addPipe(id: CompileIdentifierMetadata) {
if (!this.pipesSet.has(id.reference)) {
this.pipesSet.add(id.reference);
this.pipes.push(id);
}
}
addExportedPipe(id: CompileIdentifierMetadata) {
if (!this.exportedPipesSet.has(id.reference)) {
this.exportedPipesSet.add(id.reference);
this.exportedPipes.push(id);
}
}
addModule(id: CompileTypeMetadata) {
if (!this.modulesSet.has(id.reference)) {
this.modulesSet.add(id.reference);
this.modules.push(id);
}
}
addEntryComponent(id: CompileIdentifierMetadata) {
if (!this.entryComponentsSet.has(id.reference)) {
this.entryComponentsSet.add(id.reference);
this.entryComponents.push(id);
}
}
});
return Array.from(map.values());
}
function _normalizeArray(obj: any[]): any[] {
return obj || [];
}
export function isStaticSymbol(value: any): value is StaticSymbol {
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
}
export interface StaticSymbol {
name: string;
filePath: string;
}
export class ProviderMeta {
token: any;
useClass: Type<any>;

View File

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

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Identifiers, createIdentifier} from '../identifiers';
import {Identifiers, resolveIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';
@ -22,7 +22,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
// private is fine here as no child view will reference the cached value...
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
return new CheckBindingField(fieldExpr, bindingId);
}
@ -30,7 +30,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
export function createCheckBindingStmt(
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
let condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
throwOnChangeVar, fieldExpr, evalResult.currValExpr
]);
if (evalResult.forceUpdate) {

View File

@ -9,7 +9,7 @@
import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import {Identifiers, resolveIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';
@ -116,7 +116,7 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
unwrapperStmts.push(
VAL_UNWRAPPER_VAR
.set(o.importExpr(createIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
return unwrapperStmts;
@ -277,20 +277,15 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
args.push(o.literal(ast.strings[ast.strings.length - 1]));
return ast.expressions.length <= 9 ?
o.importExpr(createIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
o.importExpr(createIdentifier(Identifiers.interpolate)).callFn([
o.importExpr(resolveIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
o.importExpr(resolveIdentifier(Identifiers.interpolate)).callFn([
args[0], o.literalArr(args.slice(1))
]);
}
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) {
return this.convertSafeAccess(ast, leftMostSafe, mode);
} else {
return convertToStatementIfNeeded(
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
}
return convertToStatementIfNeeded(
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
}
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
@ -583,7 +578,7 @@ function flattenStatements(arg: any, output: o.Statement[]) {
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
}
const proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
const proxyParams: o.FnParam[] = [];
@ -604,7 +599,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
function createCachedLiteralMap(
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
if (entries.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
}
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
const proxyParams: o.FnParam[] = [];

View File

@ -8,12 +8,15 @@
import {CompileTokenMetadata} from '../compile_metadata';
import {isPresent} from '../facade/lang';
import {IdentifierSpec, Identifiers, createEnumIdentifier, createIdentifier} from '../identifiers';
import {IdentifierSpec, Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
@ -21,13 +24,13 @@ export function createDiTokenExpression(token: CompileTokenMetadata): o.Expressi
export function createInlineArray(values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
}
const log2 = Math.log(values.length) / Math.log(2);
const index = Math.ceil(log2);
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
Identifiers.InlineArrayDynamic;
const identifier = createIdentifier(identifierSpec);
const identifier = resolveIdentifier(identifierSpec);
return o.importExpr(identifier).instantiate([
<o.Expression>o.literal(values.length)
].concat(values));
@ -43,7 +46,7 @@ export function createPureProxy(
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
}
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
.set(o.importExpr(createIdentifier(pureProxyId)).callFn([fn]))
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
.toStmt());
}
@ -53,5 +56,5 @@ export function createEnumExpression(enumType: IdentifierSpec, enumValue: any):
if (!enumName) {
throw new Error(`Unknown enum value ${enumValue} in ${enumType.name}`);
}
return o.importExpr(createEnumIdentifier(enumType, enumName));
return o.importExpr(resolveEnumIdentifier(resolveIdentifier(enumType), enumName));
}

View File

@ -8,7 +8,7 @@
import {SecurityContext} from '@angular/core';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
@ -26,7 +26,7 @@ export function writeToRenderer(
case PropertyBindingType.Property:
if (logBindingUpdate) {
updateStmts.push(
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
}
@ -91,8 +91,8 @@ function sanitizedValue(
export function triggerAnimation(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
renderValue: o.Expression, lastRenderValue: o.Expression) {
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
lastRenderValue: o.Expression) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
@ -104,7 +104,7 @@ export function triggerAnimation(
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
@ -121,32 +121,23 @@ export function triggerAnimation(
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
const registerStmts: o.Statement[] = [];
const animationStartMethodExists = boundOutputs.find(
event => event.isAnimation && event.name == animationName && event.phase == 'start');
if (animationStartMethodExists) {
registerStmts.push(
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt());
}
const registerStmts = [
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt(),
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt(),
const animationDoneMethodExists = boundOutputs.find(
event => event.isAnimation && event.name == animationName && event.phase == 'done');
if (animationDoneMethodExists) {
registerStmts.push(
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt());
}
];
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);

View File

@ -9,7 +9,7 @@
import {ViewEncapsulation, isDevMode} from '@angular/core';
import {CompileIdentifierMetadata} from './compile_metadata';
import {Identifiers, createIdentifier} from './identifiers';
import {Identifiers, resolveIdentifier} from './identifiers';
function unimplemented(): any {
throw new Error('unimplemented');
@ -61,7 +61,7 @@ export abstract class RenderTypes {
}
export class DefaultRenderTypes implements RenderTypes {
get renderer() { return createIdentifier(Identifiers.Renderer); };
get renderer() { return resolveIdentifier(Identifiers.Renderer); };
renderText: any = null;
renderElement: any = null;
renderComment: any = null;

View File

@ -8,12 +8,11 @@
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {StringMapWrapper} from './facade/collection';
import {stringify} from './facade/lang';
import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util';
/*
* Resolve a `Type` for {@link Directive}.
*
@ -36,7 +35,7 @@ export class DirectiveResolver {
resolve(type: Type<any>, throwIfNotFound = true): Directive {
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
if (typeMetadata) {
const metadata = ListWrapper.findLast(typeMetadata, isDirectiveMetadata);
const metadata = typeMetadata.find(isDirectiveMetadata);
if (metadata) {
const propertyMetadata = this._reflector.propMetadata(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
@ -59,74 +58,85 @@ export class DirectiveResolver {
const queries: {[key: string]: any} = {};
Object.keys(propertyMetadata).forEach((propName: string) => {
const input = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Input);
if (input) {
if (input.bindingPropertyName) {
inputs.push(`${propName}: ${input.bindingPropertyName}`);
} else {
inputs.push(propName);
}
}
const output = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Output);
if (output) {
if (output.bindingPropertyName) {
outputs.push(`${propName}: ${output.bindingPropertyName}`);
} else {
outputs.push(propName);
}
}
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
hostBindings.forEach(hostBinding => {
if (hostBinding.hostPropertyName) {
const startWith = hostBinding.hostPropertyName[0];
if (startWith === '(') {
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
} else if (startWith === '[') {
throw new Error(
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
propertyMetadata[propName].forEach(a => {
if (a instanceof Input) {
if (a.bindingPropertyName) {
inputs.push(`${propName}: ${a.bindingPropertyName}`);
} else {
inputs.push(propName);
}
host[`[${hostBinding.hostPropertyName}]`] = propName;
} else {
host[`[${propName}]`] = propName;
} else if (a instanceof Output) {
const output: Output = a;
if (output.bindingPropertyName) {
outputs.push(`${propName}: ${output.bindingPropertyName}`);
} else {
outputs.push(propName);
}
} else if (a instanceof HostBinding) {
const hostBinding: HostBinding = a;
if (hostBinding.hostPropertyName) {
const startWith = hostBinding.hostPropertyName[0];
if (startWith === '(') {
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
} else if (startWith === '[') {
throw new Error(
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
}
host[`[${hostBinding.hostPropertyName}]`] = propName;
} else {
host[`[${propName}]`] = propName;
}
} else if (a instanceof HostListener) {
const hostListener: HostListener = a;
const args = hostListener.args || [];
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
} else if (a instanceof Query) {
queries[propName] = a;
}
});
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
hostListeners.forEach(hostListener => {
const args = hostListener.args || [];
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
});
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
if (query) {
queries[propName] = query;
}
});
return this._merge(dm, inputs, outputs, host, queries, directiveType);
}
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
private _dedupeBindings(bindings: string[]): string[] {
const names = new Set<string>();
const reversedResult: string[] = [];
// go last to first to allow later entries to overwrite previous entries
for (let i = bindings.length - 1; i >= 0; i--) {
const binding = bindings[i];
const name = this._extractPublicName(binding);
if (!names.has(name)) {
names.add(name);
reversedResult.push(binding);
}
}
return reversedResult.reverse();
}
private _merge(
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
const mergedInputs =
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
const mergedOutputs =
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
const mergedInputs: string[] = inputs;
if (directive.inputs) {
const inputNames: string[] =
directive.inputs.map((def: string): string => this._extractPublicName(def));
inputs.forEach((inputDef: string) => {
const publicName = this._extractPublicName(inputDef);
if (inputNames.indexOf(publicName) > -1) {
throw new Error(
`Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
}
});
mergedInputs.unshift(...directive.inputs);
}
const mergedOutputs: string[] = outputs;
if (directive.outputs) {
const outputNames: string[] =
directive.outputs.map((def: string): string => this._extractPublicName(def));
outputs.forEach((outputDef: string) => {
const publicName = this._extractPublicName(outputDef);
if (outputNames.indexOf(publicName) > -1) {
throw new Error(
`Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
}
});
mergedOutputs.unshift(...directive.outputs);
}
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
const mergedQueries =
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;

View File

@ -8,13 +8,13 @@
import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser';
import {Identifiers, createIdentifier} from './identifiers';
import {Identifiers, resolveIdentifier} from './identifiers';
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
@ -53,9 +53,7 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
*/
@Injectable()
export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) {
return `Wrapper_${identifierName(id)}`;
}
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
constructor(
private compilerConfig: CompilerConfig, private _exprParser: Parser,
@ -70,7 +68,7 @@ export class DirectiveWrapperCompiler {
addCheckInputMethod(inputFieldName, builder);
});
addNgDoCheckMethod(builder);
addCheckHostMethod(hostParseResult.hostProps, hostParseResult.hostListeners, builder);
addCheckHostMethod(hostParseResult.hostProps, builder);
addHandleEventMethod(hostParseResult.hostListeners, builder);
addSubscribeMethod(dirMeta, builder);
@ -119,10 +117,10 @@ class DirectiveWrapperBuilder implements ClassBuilder {
[
new o.FnParam(
VIEW_VAR.name,
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
],
this.detachStmts),
@ -174,7 +172,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
}
if (builder.compilerConfig.logBindingUpdate) {
onChangesStmts.push(
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfoForChanges))
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
.callFn(
[VIEW_VAR.prop('renderer'), RENDER_EL_VAR, o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
.toStmt());
@ -200,7 +198,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
'ngDoCheck',
[
new o.FnParam(
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
],
@ -216,7 +214,7 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
if (builder.genChanges) {
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
.key(o.literal(input))
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
.instantiate([field.expression, CURR_VALUE_VAR]))
.toStmt());
}
@ -235,15 +233,14 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
}
function addCheckHostMethod(
hostProps: BoundElementPropertyAst[], hostEvents: BoundEventAst[],
builder: DirectiveWrapperBuilder) {
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
const stmts: o.Statement[] = [];
const methodParams: o.FnParam[] = [
new o.FnParam(
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
];
@ -258,14 +255,14 @@ function addCheckHostMethod(
if (hostProp.needsRuntimeSecurityContext) {
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
methodParams.push(new o.FnParam(
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
}
let checkBindingStmts: o.Statement[];
if (hostProp.isAnimation) {
const {updateStmts, detachStmts} = triggerAnimation(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
.or(o.importExpr(createIdentifier(Identifiers.noop))),
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
checkBindingStmts = updateStmts;
builder.detachStmts.push(...detachStmts);
@ -309,7 +306,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
const methodParams: o.FnParam[] = [
new o.FnParam(
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)
];
const stmts: o.Statement[] = [
@ -351,10 +348,9 @@ function parseHostBindings(
const errors: ParseError[] = [];
const parser =
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
const moduleUrl = identifierModuleUrl(dirMeta.type);
const sourceFileName = moduleUrl ?
`in Directive ${identifierName(dirMeta.type)} in ${moduleUrl}` :
`in Directive ${identifierName(dirMeta.type)}`;
const sourceFileName = dirMeta.type.moduleUrl ?
`in Directive ${dirMeta.type.name} in ${dirMeta.type.moduleUrl}` :
`in Directive ${dirMeta.type.name}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),

View File

@ -344,7 +344,7 @@ export class _ParseAST {
while (this.optionalCharacter(chars.$COLON)) {
args.push(this.parseExpression());
}
result = new BindingPipe(this.span(result.span.start), result, name, args);
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args);
} while (this.optionalOperator('|'));
}

View File

@ -8,16 +8,10 @@
import * as i18n from './i18n_ast';
export function digest(message: i18n.Message): string {
export function digestMessage(message: i18n.Message): string {
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
}
export function decimalDigest(message: i18n.Message): string {
const visitor = new _SerializerIgnoreIcuExpVisitor();
const parts = message.nodes.map(a => a.visit(visitor, null));
return computeMsgId(parts.join(''), message.meaning);
}
/**
* Serialize the i18n ast to something xml-like in order to generate an UID.
*
@ -45,7 +39,7 @@ class _SerializerVisitor implements i18n.Visitor {
}
visitPlaceholder(ph: i18n.Placeholder, context: any): any {
return ph.value ? `<ph name="${ph.name}">${ph.value}</ph>` : `<ph name="${ph.name}"/>`;
return `<ph name="${ph.name}">${ph.value}</ph>`;
}
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
@ -59,21 +53,6 @@ export function serializeNodes(nodes: i18n.Node[]): string[] {
return nodes.map(a => a.visit(serializerVisitor, null));
}
/**
* Serialize the i18n ast to something xml-like in order to generate an UID.
*
* Ignore the ICU expressions so that message IDs stays identical if only the expression changes.
*
* @internal
*/
class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
visitIcu(icu: i18n.Icu, context: any): any {
let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
// Do not take the expression into account
return `{${icu.type}, ${strCases.join(', ')}}`;
}
}
/**
* Compute the SHA1 of the given string
*
@ -84,7 +63,7 @@ class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
*/
export function sha1(str: string): string {
const utf8 = utf8Encode(str);
const words32 = stringToWords32(utf8, Endian.Big);
const words32 = stringToWords32(utf8);
const len = utf8.length * 8;
const w = new Array(80);
@ -111,99 +90,15 @@ export function sha1(str: string): string {
[a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];
}
return byteStringToHexString(words32ToByteString([a, b, c, d, e]));
}
const sha1 = words32ToString([a, b, c, d, e]);
function fk(index: number, b: number, c: number, d: number): [number, number] {
if (index < 20) {
return [(b & c) | (~b & d), 0x5a827999];
let hex: string = '';
for (let i = 0; i < sha1.length; i++) {
const b = sha1.charCodeAt(i);
hex += (b >>> 4 & 0x0f).toString(16) + (b & 0x0f).toString(16);
}
if (index < 40) {
return [b ^ c ^ d, 0x6ed9eba1];
}
if (index < 60) {
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
}
return [b ^ c ^ d, 0xca62c1d6];
}
/**
* Compute the fingerprint of the given string
*
* The output is 64 bit number encoded as a decimal string
*
* based on:
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
*/
export function fingerprint(str: string): [number, number] {
const utf8 = utf8Encode(str);
let [hi, lo] = [hash32(utf8, 0), hash32(utf8, 102072)];
if (hi == 0 && (lo == 0 || lo == 1)) {
hi = hi ^ 0x130f9bef;
lo = lo ^ -0x6b5f56d8;
}
return [hi, lo];
}
export function computeMsgId(msg: string, meaning: string): string {
let [hi, lo] = fingerprint(msg);
if (meaning) {
const [him, lom] = fingerprint(meaning);
[hi, lo] = add64(rol64([hi, lo], 1), [him, lom]);
}
return byteStringToDecString(words32ToByteString([hi & 0x7fffffff, lo]));
}
function hash32(str: string, c: number): number {
let [a, b] = [0x9e3779b9, 0x9e3779b9];
let i: number;
const len = str.length;
for (i = 0; i + 12 <= len; i += 12) {
a = add32(a, wordAt(str, i, Endian.Little));
b = add32(b, wordAt(str, i + 4, Endian.Little));
c = add32(c, wordAt(str, i + 8, Endian.Little));
[a, b, c] = mix([a, b, c]);
}
a = add32(a, wordAt(str, i, Endian.Little));
b = add32(b, wordAt(str, i + 4, Endian.Little));
// the first byte of c is reserved for the length
c = add32(c, len);
c = add32(c, wordAt(str, i + 8, Endian.Little) << 8);
return mix([a, b, c])[2];
}
// clang-format off
function mix([a, b, c]: [number, number, number]): [number, number, number] {
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 13;
b = sub32(b, c); b = sub32(b, a); b ^= a << 8;
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 13;
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 12;
b = sub32(b, c); b = sub32(b, a); b ^= a << 16;
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 5;
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 3;
b = sub32(b, c); b = sub32(b, a); b ^= a << 10;
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 15;
return [a, b, c];
}
// clang-format on
// Utils
enum Endian {
Little,
Big,
return hex.toLowerCase();
}
function utf8Encode(str: string): string {
@ -236,9 +131,10 @@ function decodeSurrogatePairs(str: string, index: number): number {
}
const high = str.charCodeAt(index);
let low: number;
if (high >= 0xd800 && high <= 0xdfff && str.length > index + 1) {
const low = byteAt(str, index + 1);
low = str.charCodeAt(index + 1);
if (low >= 0xdc00 && low <= 0xdfff) {
return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
}
@ -247,126 +143,50 @@ function decodeSurrogatePairs(str: string, index: number): number {
return high;
}
function add32(a: number, b: number): number {
return add32to64(a, b)[1];
}
function add32to64(a: number, b: number): [number, number] {
const low = (a & 0xffff) + (b & 0xffff);
const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
return [high >>> 16, (high << 16) | (low & 0xffff)];
}
function add64([ah, al]: [number, number], [bh, bl]: [number, number]): [number, number] {
const [carry, l] = add32to64(al, bl);
const h = add32(add32(ah, bh), carry);
return [h, l];
}
function sub32(a: number, b: number): number {
const low = (a & 0xffff) - (b & 0xffff);
const high = (a >> 16) - (b >> 16) + (low >> 16);
return (high << 16) | (low & 0xffff);
}
// Rotate a 32b number left `count` position
function rol32(a: number, count: number): number {
return (a << count) | (a >>> (32 - count));
}
// Rotate a 64b number left `count` position
function rol64([hi, lo]: [number, number], count: number): [number, number] {
const h = (hi << count) | (lo >>> (32 - count));
const l = (lo << count) | (hi >>> (32 - count));
return [h, l];
}
function stringToWords32(str: string, endian: Endian): number[] {
const words32 = Array((str.length + 3) >>> 2);
function stringToWords32(str: string): number[] {
const words32 = Array(str.length >>> 2);
for (let i = 0; i < words32.length; i++) {
words32[i] = wordAt(str, i * 4, endian);
words32[i] = 0;
}
for (let i = 0; i < str.length; i++) {
words32[i >>> 2] |= (str.charCodeAt(i) & 0xff) << 8 * (3 - i & 0x3);
}
return words32;
}
function byteAt(str: string, index: number): number {
return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;
}
function wordAt(str: string, index: number, endian: Endian): number {
let word = 0;
if (endian === Endian.Big) {
for (let i = 0; i < 4; i++) {
word += byteAt(str, index + i) << (24 - 8 * i);
}
} else {
for (let i = 0; i < 4; i++) {
word += byteAt(str, index + i) << 8 * i;
}
}
return word;
}
function words32ToByteString(words32: number[]): string {
return words32.reduce((str, word) => str + word32ToByteString(word), '');
}
function word32ToByteString(word: number): string {
function words32ToString(words32: number[]): string {
let str = '';
for (let i = 0; i < 4; i++) {
str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);
for (let i = 0; i < words32.length * 4; i++) {
str += String.fromCharCode((words32[i >>> 2] >>> 8 * (3 - i & 0x3)) & 0xff);
}
return str;
}
function byteStringToHexString(str: string): string {
let hex: string = '';
for (let i = 0; i < str.length; i++) {
const b = byteAt(str, i);
hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);
}
return hex.toLowerCase();
}
// based on http://www.danvk.org/hex2dec.html (JS can not handle more than 56b)
function byteStringToDecString(str: string): string {
let decimal = '';
let toThePower = '1';
for (let i = str.length - 1; i >= 0; i--) {
decimal = addBigInt(decimal, numberTimesBigInt(byteAt(str, i), toThePower));
toThePower = numberTimesBigInt(256, toThePower);
function fk(index: number, b: number, c: number, d: number): [number, number] {
if (index < 20) {
return [(b & c) | (~b & d), 0x5a827999];
}
return decimal.split('').reverse().join('');
}
// x and y decimal, lowest significant digit first
function addBigInt(x: string, y: string): string {
let sum = '';
const len = Math.max(x.length, y.length);
for (let i = 0, carry = 0; i < len || carry; i++) {
const tmpSum = carry + +(x[i] || 0) + +(y[i] || 0);
if (tmpSum >= 10) {
carry = 1;
sum += tmpSum - 10;
} else {
carry = 0;
sum += tmpSum;
}
if (index < 40) {
return [b ^ c ^ d, 0x6ed9eba1];
}
return sum;
if (index < 60) {
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
}
return [b ^ c ^ d, 0xca62c1d6];
}
function numberTimesBigInt(num: number, b: string): string {
let product = '';
let bToThePower = b;
for (; num !== 0; num = num >>> 1) {
if (num & 1) product = addBigInt(product, bToThePower);
bToThePower = addBigInt(bToThePower, bToThePower);
}
return product;
function add32(a: number, b: number): number {
const low = (a & 0xffff) + (b & 0xffff);
const high = (a >> 16) + (b >> 16) + (low >> 16);
return (high << 16) | (low & 0xffff);
}
function rol32(a: number, count: number): number {
return (a << count) | (a >>> (32 - count));
}

View File

@ -1,123 +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
*/
/**
* Extract i18n messages from source code
*/
import {ViewEncapsulation} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
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, AotSummaryResolverHost {
/**
* 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 Promise
.all(ngModules.map(
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false)))
.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), new AotSummaryResolver(host, staticReflector, options),
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

@ -10,6 +10,7 @@ import * as html from '../ml_parser/ast';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {ParseTreeResult} from '../ml_parser/parser';
import {digestMessage} from './digest';
import * as i18n from './i18n_ast';
import {createI18nMessageFactory} from './i18n_parser';
import {I18nError} from './parse_util';
@ -213,8 +214,8 @@ class _Visitor implements html.Visitor {
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
// message
const i18nAttr = _getI18nAttr(el);
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
!this._isInTranslatableSection;
const isImplicit = this._implicitTags.some((tag: string): boolean => el.name === tag) &&
!this._inIcu && !this._isInTranslatableSection;
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit;
@ -347,14 +348,14 @@ class _Visitor implements html.Visitor {
// no-op when called in extraction mode (returns [])
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
if (message && this._mode === _VisitorMode.Merge) {
const nodes = this._translations.get(message);
const id = digestMessage(message);
const nodes = this._translations.get(id);
if (nodes) {
return nodes;
}
this._reportError(
el, `Translation unavailable for message id="${this._translations.digest(message)}"`);
this._reportError(el, `Translation unavailable for message id="${id}"`);
}
return [];
@ -383,20 +384,19 @@ class _Visitor implements html.Visitor {
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
const meaning = i18nAttributeMeanings[attr.name];
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
const nodes = this._translations.get(message);
const id = digestMessage(message);
const nodes = this._translations.get(id);
if (nodes) {
if (nodes[0] instanceof html.Text) {
const value = (nodes[0] as html.Text).value;
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
} else {
this._reportError(
el,
`Unexpected translation for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
el, `Unexpected translation for attribute "${attr.name}" (id="${id}")`);
}
} else {
this._reportError(
el,
`Translation unavailable for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
el, `Translation unavailable for attribute "${attr.name}" (id="${id}")`);
}
} else {
translatedAttributes.push(attr);

View File

@ -12,20 +12,18 @@ export class Message {
/**
* @param nodes message AST
* @param placeholders maps placeholder names to static content
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
* @param placeholderToMsgIds maps placeholder names to translatable message IDs (used for ICU
* messages)
* @param meaning
* @param description
*/
constructor(
public nodes: Node[], public placeholders: {[phName: string]: string},
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
public nodes: Node[], public placeholders: {[name: string]: string},
public placeholderToMsgIds: {[name: string]: string}, public meaning: string,
public description: string) {}
}
export interface Node {
sourceSpan: ParseSourceSpan;
visit(visitor: Visitor, context?: any): any;
}
export interface Node { visit(visitor: Visitor, context?: any): any; }
export class Text implements Node {
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
@ -33,7 +31,6 @@ export class Text implements Node {
visit(visitor: Visitor, context?: any): any { return visitor.visitText(this, context); }
}
// TODO(vicb): do we really need this node (vs an array) ?
export class Container implements Node {
constructor(public children: Node[], public sourceSpan: ParseSourceSpan) {}
@ -41,7 +38,6 @@ export class Container implements Node {
}
export class Icu implements Node {
public expressionPlaceholder: string;
constructor(
public expression: string, public type: string, public cases: {[k: string]: Node},
public sourceSpan: ParseSourceSpan) {}
@ -59,13 +55,13 @@ export class TagPlaceholder implements Node {
}
export class Placeholder implements Node {
constructor(public value: string, public name: string, public sourceSpan: ParseSourceSpan) {}
constructor(public value: string, public name: string = '', public sourceSpan: ParseSourceSpan) {}
visit(visitor: Visitor, context?: any): any { return visitor.visitPlaceholder(this, context); }
}
export class IcuPlaceholder implements Node {
constructor(public value: Icu, public name: string, public sourceSpan: ParseSourceSpan) {}
constructor(public value: Icu, public name: string = '', public sourceSpan: ParseSourceSpan) {}
visit(visitor: Visitor, context?: any): any { return visitor.visitIcuPlaceholder(this, context); }
}

View File

@ -11,6 +11,7 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/in
import {ParseTreeResult} from '../ml_parser/parser';
import {mergeTranslations} from './extractor_merger';
import {MessageBundle} from './message_bundle';
import {Serializer} from './serializers/serializer';
import {Xliff} from './serializers/xliff';
import {Xmb} from './serializers/xmb';
@ -40,29 +41,32 @@ export class I18NHtmlParser implements HtmlParser {
}
// TODO(vicb): add support for implicit tags / attributes
const messageBundle = new MessageBundle(this._htmlParser, [], {});
const errors = messageBundle.updateFromTemplate(source, url, interpolationConfig);
if (parseResult.errors.length) {
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors);
if (errors && errors.length) {
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors.concat(errors));
}
const serializer = this._createSerializer();
const translationBundle = TranslationBundle.load(this._translations, url, serializer);
const serializer = this._createSerializer(interpolationConfig);
const translationBundle =
TranslationBundle.load(this._translations, url, messageBundle, serializer);
return mergeTranslations(parseResult.rootNodes, translationBundle, interpolationConfig, [], {});
}
private _createSerializer(): Serializer {
private _createSerializer(interpolationConfig: InterpolationConfig): Serializer {
const format = (this._translationsFormat || 'xlf').toLowerCase();
switch (format) {
case 'xmb':
return new Xmb();
case 'xtb':
return new Xtb();
return new Xtb(this._htmlParser, interpolationConfig);
case 'xliff':
case 'xlf':
default:
return new Xliff();
return new Xliff(this._htmlParser, interpolationConfig);
}
}
}

View File

@ -12,6 +12,7 @@ import * as html from '../ml_parser/ast';
import {getHtmlTagDefinition} from '../ml_parser/html_tags';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {ParseSourceSpan} from '../parse_util';
import {digestMessage} from './digest';
import * as i18n from './i18n_ast';
import {PlaceholderRegistry} from './serializers/placeholder';
@ -33,8 +34,8 @@ class _I18nVisitor implements html.Visitor {
private _isIcu: boolean;
private _icuDepth: number;
private _placeholderRegistry: PlaceholderRegistry;
private _placeholderToContent: {[phName: string]: string};
private _placeholderToMessage: {[phName: string]: i18n.Message};
private _placeholderToContent: {[name: string]: string};
private _placeholderToIds: {[name: string]: string};
constructor(
private _expressionParser: ExpressionParser,
@ -45,12 +46,12 @@ class _I18nVisitor implements html.Visitor {
this._icuDepth = 0;
this._placeholderRegistry = new PlaceholderRegistry();
this._placeholderToContent = {};
this._placeholderToMessage = {};
this._placeholderToIds = {};
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
return new i18n.Message(
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
i18nodes, this._placeholderToContent, this._placeholderToIds, meaning, description);
}
visitElement(el: html.Element, context: any): i18n.Node {
@ -98,13 +99,7 @@ class _I18nVisitor implements html.Visitor {
this._icuDepth--;
if (this._isIcu || this._icuDepth > 0) {
// Returns an ICU node when:
// - the message (vs a part of the message) is an ICU message, or
// - the ICU message is nested.
const expPh = this._placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
i18nIcu.expressionPlaceholder = expPh;
this._placeholderToContent[expPh] = icu.switchValue;
// If the message (vs a part of the message) is an ICU message returns it
return i18nIcu;
}
@ -115,7 +110,7 @@ class _I18nVisitor implements html.Visitor {
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
this._placeholderToIds[phName] = digestMessage(visitor.toI18nMessage([icu], '', ''));
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
}

View File

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

View File

@ -10,6 +10,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {ParseError} from '../parse_util';
import {digestMessage} from './digest';
import {extractMessages} from './extractor_merger';
import {Message} from './i18n_ast';
import {Serializer} from './serializers/serializer';
@ -18,7 +19,7 @@ import {Serializer} from './serializers/serializer';
* A container for message extracted from the templates.
*/
export class MessageBundle {
private _messages: Message[] = [];
private _messageMap: {[id: string]: Message} = {};
constructor(
private _htmlParser: HtmlParser, private _implicitTags: string[],
@ -39,10 +40,11 @@ export class MessageBundle {
return i18nParserResult.errors;
}
this._messages.push(...i18nParserResult.messages);
i18nParserResult.messages.forEach(
(message) => { this._messageMap[digestMessage(message)] = message; });
}
getMessages(): Message[] { return this._messages; }
getMessageMap(): {[id: string]: Message} { return this._messageMap; }
write(serializer: Serializer): string { return serializer.write(this._messages); }
write(serializer: Serializer): string { return serializer.write(this._messageMap); }
}

View File

@ -40,9 +40,7 @@ const TAG_TO_PLACEHOLDER_NAMES: {[k: string]: string} = {
};
/**
* Creates unique names for placeholder with different content.
*
* Returns the same placeholder name when the content is identical.
* Creates unique names for placeholder with different content
*
* @internal
*/
@ -95,10 +93,6 @@ export class PlaceholderRegistry {
return uniqueName;
}
getUniquePlaceholder(name: string): string {
return this._generateUniqueName(name.toUpperCase());
}
// Generate a hash for a tag - does not take attribute order into account
private _hashTag(tag: string, attrs: {[k: string]: string}, isVoid: boolean): string {
const start = `<${tag}`;
@ -111,8 +105,18 @@ export class PlaceholderRegistry {
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
private _generateUniqueName(base: string): string {
const next = this._placeHolderNameCounts[base];
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
return next ? `${base}_${next}` : base;
let name = base;
let next = this._placeHolderNameCounts[name];
if (!next) {
next = 1;
} else {
name += `_${next}`;
next++;
}
this._placeHolderNameCounts[base] = next;
return name;
}
}

View File

@ -6,12 +6,36 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as html from '../../ml_parser/ast';
import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle';
export interface Serializer {
write(messages: i18n.Message[]): string;
write(messageMap: {[id: string]: i18n.Message}): string;
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]};
}
digest(message: i18n.Message): string;
// Generate a map of placeholder to content indexed by message ids
export function extractPlaceholders(messageBundle: MessageBundle) {
const messageMap = messageBundle.getMessageMap();
const placeholders: {[id: string]: {[name: string]: string}} = {};
Object.keys(messageMap).forEach(msgId => {
placeholders[msgId] = messageMap[msgId].placeholders;
});
return placeholders;
}
// Generate a map of placeholder to message ids indexed by message ids
export function extractPlaceholderToIds(messageBundle: MessageBundle) {
const messageMap = messageBundle.getMessageMap();
const placeholderToIds: {[id: string]: {[name: string]: string}} = {};
Object.keys(messageMap).forEach(msgId => {
placeholderToIds[msgId] = messageMap[msgId].placeholderToMsgIds;
});
return placeholderToIds;
}

View File

@ -7,12 +7,15 @@
*/
import * as ml from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
import {XmlParser} from '../../ml_parser/xml_parser';
import {digest} from '../digest';
import {ParseError} from '../../parse_util';
import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle';
import {I18nError} from '../parse_util';
import {Serializer} from './serializer';
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
import * as xml from './xml_helper';
const _VERSION = '1.2';
@ -20,7 +23,6 @@ const _XMLNS = 'urn:oasis:names:tc:xliff:document:1.2';
// TODO(vicb): make this a param (s/_/-/)
const _SOURCE_LANG = 'en';
const _PLACEHOLDER_TAG = 'x';
const _SOURCE_TAG = 'source';
const _TARGET_TAG = 'target';
const _UNIT_TAG = 'trans-unit';
@ -28,19 +30,17 @@ const _UNIT_TAG = 'trans-unit';
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
export class Xliff implements Serializer {
write(messages: i18n.Message[]): string {
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
write(messageMap: {[id: string]: i18n.Message}): string {
const visitor = new _WriteVisitor();
const visited: {[id: string]: boolean} = {};
const transUnits: xml.Node[] = [];
messages.forEach(message => {
const id = this.digest(message);
Object.keys(messageMap).forEach((id) => {
const message = messageMap[id];
// deduplicate messages
if (visited[id]) return;
visited[id] = true;
const transUnit = new xml.Tag(_UNIT_TAG, {id, datatype: 'html'});
const transUnit = new xml.Tag(_UNIT_TAG, {id: id, datatype: 'html'});
transUnit.children.push(
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
new xml.CR(8), new xml.Tag(_TARGET_TAG));
@ -75,28 +75,38 @@ export class Xliff implements Serializer {
]);
}
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
// xliff to xml nodes
const xliffParser = new XliffParser();
const {mlNodesByMsgId, errors} = xliffParser.parse(content, url);
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
// Parse the xtb file into xml nodes
const result = new XmlParser().parse(content, url);
// xml nodes to i18n nodes
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
const converter = new XmlToI18n();
Object.keys(mlNodesByMsgId).forEach(msgId => {
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
errors.push(...e);
i18nNodesByMsgId[msgId] = i18nNodes;
});
if (errors.length) {
throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
if (result.errors.length) {
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
}
return i18nNodesByMsgId;
}
// Replace the placeholders, messages are now string
const {messages, errors} = new _LoadVisitor().parse(result.rootNodes, messageBundle);
digest(message: i18n.Message): string { return digest(message); }
if (errors.length) {
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
}
// Convert the string messages to html ast
// TODO(vicb): map error message back to the original message in xtb
const messageMap: {[id: string]: ml.Node[]} = {};
const parseErrors: ParseError[] = [];
Object.keys(messages).forEach((id) => {
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
parseErrors.push(...res.errors);
messageMap[id] = res.rootNodes;
});
if (parseErrors.length) {
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
}
return messageMap;
}
}
class _WriteVisitor implements i18n.Visitor {
@ -156,46 +166,75 @@ class _WriteVisitor implements i18n.Visitor {
}
// TODO(vicb): add error management (structure)
// Extract messages as xml nodes from the xliff file
class XliffParser implements ml.Visitor {
private _unitMlNodes: ml.Node[];
// TODO(vicb): factorize (xtb) ?
class _LoadVisitor implements ml.Visitor {
private _messageNodes: [string, ml.Node[]][];
private _translatedMessages: {[id: string]: string};
private _msgId: string;
private _target: ml.Node[];
private _errors: I18nError[];
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
private _placeholders: {[name: string]: string};
private _placeholderToIds: {[name: string]: string};
parse(xliff: string, url: string) {
this._unitMlNodes = [];
this._mlNodesByMsgId = {};
parse(nodes: ml.Node[], messageBundle: MessageBundle):
{messages: {[k: string]: string}, errors: I18nError[]} {
this._messageNodes = [];
this._translatedMessages = {};
this._msgId = '';
this._target = [];
this._errors = [];
const xml = new XmlParser().parse(xliff, url, false);
// Find all messages
ml.visitAll(this, nodes, null);
this._errors = xml.errors;
ml.visitAll(this, xml.rootNodes, null);
const messageMap = messageBundle.getMessageMap();
const placeholders = extractPlaceholders(messageBundle);
const placeholderToIds = extractPlaceholderToIds(messageBundle);
return {
mlNodesByMsgId: this._mlNodesByMsgId,
errors: this._errors,
};
this._messageNodes
.filter(message => {
// Remove any messages that is not present in the source message bundle.
return messageMap.hasOwnProperty(message[0]);
})
.sort((a, b) => {
// Because there could be no ICU placeholders inside an ICU message,
// we do not need to take into account the `placeholderToMsgIds` of the referenced
// messages, those would always be empty
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
return -1;
}
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
return 1;
}
return 0;
})
.forEach(message => {
const id = message[0];
this._placeholders = placeholders[id] || {};
this._placeholderToIds = placeholderToIds[id] || {};
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
});
return {messages: this._translatedMessages, errors: this._errors};
}
visitElement(element: ml.Element, context: any): any {
switch (element.name) {
case _UNIT_TAG:
this._unitMlNodes = null;
const idAttr = element.attrs.find((attr) => attr.name === 'id');
if (!idAttr) {
this._target = null;
const msgId = element.attrs.find((attr) => attr.name === 'id');
if (!msgId) {
this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
} else {
const id = idAttr.value;
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
this._addError(element, `Duplicated translations for msg ${id}`);
} else {
ml.visitAll(this, element.children, null);
if (this._unitMlNodes) {
this._mlNodesByMsgId[id] = this._unitMlNodes;
} else {
this._addError(element, `Message ${id} misses a translation`);
}
}
this._msgId = msgId.value;
}
ml.visitAll(this, element.children, null);
if (this._msgId !== null) {
this._messageNodes.push([this._msgId, this._target]);
}
break;
@ -204,65 +243,48 @@ class XliffParser implements ml.Visitor {
break;
case _TARGET_TAG:
this._unitMlNodes = element.children;
this._target = element.children;
break;
case _PLACEHOLDER_TAG:
const idAttr = element.attrs.find((attr) => attr.name === 'id');
if (!idAttr) {
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
} else {
const id = idAttr.value;
if (this._placeholders.hasOwnProperty(id)) {
return this._placeholders[id];
}
if (this._placeholderToIds.hasOwnProperty(id) &&
this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])) {
return this._translatedMessages[this._placeholderToIds[id]];
}
// TODO(vicb): better error message for when
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])
this._addError(element, `The placeholder "${id}" does not exists in the source message`);
}
break;
default:
// TODO(vicb): assert file structure, xliff version
// For now only recurse on unhandled nodes
ml.visitAll(this, element.children, null);
}
}
visitAttribute(attribute: ml.Attribute, context: any): any {}
visitText(text: ml.Text, context: any): any {}
visitComment(comment: ml.Comment, context: any): any {}
visitExpansion(expansion: ml.Expansion, context: any): any {}
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
private _addError(node: ml.Node, message: string): void {
this._errors.push(new I18nError(node.sourceSpan, message));
}
}
// Convert ml nodes (xliff syntax) to i18n nodes
class XmlToI18n implements ml.Visitor {
private _errors: I18nError[];
convert(nodes: ml.Node[]) {
this._errors = [];
return {
i18nNodes: ml.visitAll(this, nodes),
errors: this._errors,
};
visitAttribute(attribute: ml.Attribute, context: any): any {
throw new Error('unreachable code');
}
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
visitText(text: ml.Text, context: any): any { return text.value; }
visitElement(el: ml.Element, context: any): i18n.Placeholder {
if (el.name === _PLACEHOLDER_TAG) {
const nameAttr = el.attrs.find((attr) => attr.name === 'id');
if (nameAttr) {
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
}
visitComment(comment: ml.Comment, context: any): any { return ''; }
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
} else {
this._addError(el, `Unexpected tag`);
}
visitExpansion(expansion: ml.Expansion, context: any): any {
throw new Error('unreachable code');
}
visitExpansion(icu: ml.Expansion, context: any) {}
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {}
visitComment(comment: ml.Comment, context: any) {}
visitAttribute(attribute: ml.Attribute, context: any) {}
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
throw new Error('unreachable code');
}
private _addError(node: ml.Node, message: string): void {
this._errors.push(new I18nError(node.sourceSpan, message));

View File

@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {decimalDigest} from '../digest';
import * as html from '../../ml_parser/ast';
import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle';
import {Serializer} from './serializer';
import * as xml from './xml_helper';
@ -38,18 +39,12 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
<!ELEMENT ex (#PCDATA)>`;
export class Xmb implements Serializer {
write(messages: i18n.Message[]): string {
write(messageMap: {[k: string]: i18n.Message}): string {
const visitor = new _Visitor();
const visited: {[id: string]: boolean} = {};
let rootNode = new xml.Tag(_MESSAGES_TAG);
messages.forEach(message => {
const id = this.digest(message);
// deduplicate messages
if (visited[id]) return;
visited[id] = true;
const rootNode = new xml.Tag(_MESSAGES_TAG);
Object.keys(messageMap).forEach((id) => {
const message = messageMap[id];
const attrs: {[k: string]: string} = {id};
if (message.description) {
@ -76,11 +71,9 @@ export class Xmb implements Serializer {
]);
}
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]} {
throw new Error('Unsupported');
}
digest(message: i18n.Message): string { return digest(message); }
}
class _Visitor implements i18n.Visitor {
@ -93,7 +86,7 @@ class _Visitor implements i18n.Visitor {
}
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
const nodes = [new xml.Text(`{${icu.expression}, ${icu.type}, `)];
Object.keys(icu.cases).forEach((c: string) => {
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
@ -130,7 +123,3 @@ class _Visitor implements i18n.Visitor {
return [].concat(...nodes.map(node => node.visit(this)));
}
}
export function digest(message: i18n.Message): string {
return decimalDigest(message);
}

View File

@ -7,63 +7,112 @@
*/
import * as ml from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
import {XmlParser} from '../../ml_parser/xml_parser';
import {ParseError} from '../../parse_util';
import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle';
import {I18nError} from '../parse_util';
import {Serializer} from './serializer';
import {digest} from './xmb';
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
const _TRANSLATIONS_TAG = 'translationbundle';
const _TRANSLATION_TAG = 'translation';
const _PLACEHOLDER_TAG = 'ph';
export class Xtb implements Serializer {
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
// xtb to xml nodes
const xtbParser = new XtbParser();
const {mlNodesByMsgId, errors} = xtbParser.parse(content, url);
write(messageMap: {[id: string]: i18n.Message}): string { throw new Error('Unsupported'); }
// xml nodes to i18n nodes
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
const converter = new XmlToI18n();
Object.keys(mlNodesByMsgId).forEach(msgId => {
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
errors.push(...e);
i18nNodesByMsgId[msgId] = i18nNodes;
});
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
// Parse the xtb file into xml nodes
const result = new XmlParser().parse(content, url);
if (result.errors.length) {
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
}
// Replace the placeholders, messages are now string
const {messages, errors} = new _Visitor().parse(result.rootNodes, messageBundle);
if (errors.length) {
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
}
return i18nNodesByMsgId;
}
// Convert the string messages to html ast
// TODO(vicb): map error message back to the original message in xtb
const messageMap: {[id: string]: ml.Node[]} = {};
const parseErrors: ParseError[] = [];
digest(message: i18n.Message): string { return digest(message); }
Object.keys(messages).forEach((id) => {
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
parseErrors.push(...res.errors);
messageMap[id] = res.rootNodes;
});
if (parseErrors.length) {
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
}
return messageMap;
}
}
// Extract messages as xml nodes from the xtb file
class XtbParser implements ml.Visitor {
class _Visitor implements ml.Visitor {
private _messageNodes: [string, ml.Node[]][];
private _translatedMessages: {[id: string]: string};
private _bundleDepth: number;
private _translationDepth: number;
private _errors: I18nError[];
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
private _placeholders: {[name: string]: string};
private _placeholderToIds: {[name: string]: string};
parse(xtb: string, url: string) {
parse(nodes: ml.Node[], messageBundle: MessageBundle):
{messages: {[k: string]: string}, errors: I18nError[]} {
this._messageNodes = [];
this._translatedMessages = {};
this._bundleDepth = 0;
this._mlNodesByMsgId = {};
this._translationDepth = 0;
this._errors = [];
const xml = new XmlParser().parse(xtb, url, true);
// Find all messages
ml.visitAll(this, nodes, null);
this._errors = xml.errors;
ml.visitAll(this, xml.rootNodes);
const messageMap = messageBundle.getMessageMap();
const placeholders = extractPlaceholders(messageBundle);
const placeholderToIds = extractPlaceholderToIds(messageBundle);
return {
mlNodesByMsgId: this._mlNodesByMsgId,
errors: this._errors,
};
this._messageNodes
.filter(message => {
// Remove any messages that is not present in the source message bundle.
return messageMap.hasOwnProperty(message[0]);
})
.sort((a, b) => {
// Because there could be no ICU placeholders inside an ICU message,
// we do not need to take into account the `placeholderToMsgIds` of the referenced
// messages, those would always be empty
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
return -1;
}
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
return 1;
}
return 0;
})
.forEach(message => {
const id = message[0];
this._placeholders = placeholders[id] || {};
this._placeholderToIds = placeholderToIds[id] || {};
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
});
return {messages: this._translatedMessages, errors: this._errors};
}
visitElement(element: ml.Element, context: any): any {
@ -78,16 +127,40 @@ class XtbParser implements ml.Visitor {
break;
case _TRANSLATION_TAG:
this._translationDepth++;
if (this._translationDepth > 1) {
this._addError(element, `<${_TRANSLATION_TAG}> elements can not be nested`);
}
const idAttr = element.attrs.find((attr) => attr.name === 'id');
if (!idAttr) {
this._addError(element, `<${_TRANSLATION_TAG}> misses the "id" attribute`);
} else {
const id = idAttr.value;
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
this._addError(element, `Duplicated translations for msg ${id}`);
} else {
this._mlNodesByMsgId[id] = element.children;
// ICU placeholders are reference to other messages.
// The referenced message might not have been decoded yet.
// We need to have all messages available to make sure deps are decoded first.
// TODO(vicb): report an error on duplicate id
this._messageNodes.push([idAttr.value, element.children]);
}
this._translationDepth--;
break;
case _PLACEHOLDER_TAG:
const nameAttr = element.attrs.find((attr) => attr.name === 'name');
if (!nameAttr) {
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
} else {
const name = nameAttr.value;
if (this._placeholders.hasOwnProperty(name)) {
return this._placeholders[name];
}
if (this._placeholderToIds.hasOwnProperty(name) &&
this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])) {
return this._translatedMessages[this._placeholderToIds[name]];
}
// TODO(vicb): better error message for when
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])
this._addError(
element, `The placeholder "${name}" does not exists in the source message`);
}
break;
@ -96,68 +169,23 @@ class XtbParser implements ml.Visitor {
}
}
visitAttribute(attribute: ml.Attribute, context: any): any {}
visitAttribute(attribute: ml.Attribute, context: any): any {
throw new Error('unreachable code');
}
visitText(text: ml.Text, context: any): any {}
visitText(text: ml.Text, context: any): any { return text.value; }
visitComment(comment: ml.Comment, context: any): any {}
visitComment(comment: ml.Comment, context: any): any { return ''; }
visitExpansion(expansion: ml.Expansion, context: any): any {}
visitExpansion(expansion: ml.Expansion, context: any): any {
const strCases = expansion.cases.map(c => c.visit(this, null));
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
private _addError(node: ml.Node, message: string): void {
this._errors.push(new I18nError(node.sourceSpan, message));
}
}
// Convert ml nodes (xtb syntax) to i18n nodes
class XmlToI18n implements ml.Visitor {
private _errors: I18nError[];
convert(nodes: ml.Node[]) {
this._errors = [];
return {
i18nNodes: ml.visitAll(this, nodes),
errors: this._errors,
};
}
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
visitExpansion(icu: ml.Expansion, context: any) {
const caseMap: {[value: string]: i18n.Node} = {};
ml.visitAll(this, icu.cases).forEach(c => {
caseMap[c.value] = new i18n.Container(c.nodes, icu.sourceSpan);
});
return new i18n.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
}
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {
return {
value: icuCase.value,
nodes: ml.visitAll(this, icuCase.expression),
};
}
visitElement(el: ml.Element, context: any): i18n.Placeholder {
if (el.name === _PLACEHOLDER_TAG) {
const nameAttr = el.attrs.find((attr) => attr.name === 'name');
if (nameAttr) {
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
}
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
} else {
this._addError(el, `Unexpected tag`);
}
}
visitComment(comment: ml.Comment, context: any) {}
visitAttribute(attribute: ml.Attribute, context: any) {}
return `{${expansion.switchValue}, ${expansion.type}, strCases.join(' ')}`;
}
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
return `${expansionCase.value} {${ml.visitAll(this, expansionCase.expression, null)}}`;
}
private _addError(node: ml.Node, message: string): void {
this._errors.push(new I18nError(node.sourceSpan, message));

View File

@ -7,120 +7,22 @@
*/
import * as html from '../ml_parser/ast';
import {HtmlParser} from '../ml_parser/html_parser';
import * as i18n from './i18n_ast';
import {I18nError} from './parse_util';
import {MessageBundle} from './message_bundle';
import {Serializer} from './serializers/serializer';
/**
* A container for translated messages
*/
export class TranslationBundle {
private _i18nToHtml: I18nToHtmlVisitor;
constructor(private _messageMap: {[id: string]: html.Node[]} = {}) {}
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
public digest: (m: i18n.Message) => string) {
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
static load(content: string, url: string, messageBundle: MessageBundle, serializer: Serializer):
TranslationBundle {
return new TranslationBundle(serializer.load(content, url, messageBundle));
}
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
const i18nNodesByMsgId = serializer.load(content, url);
const digestFn = (m: i18n.Message) => serializer.digest(m);
return new TranslationBundle(i18nNodesByMsgId, digestFn);
}
get(id: string): html.Node[] { return this._messageMap[id]; }
get(srcMsg: i18n.Message): html.Node[] {
const html = this._i18nToHtml.convert(srcMsg);
if (html.errors.length) {
throw new Error(html.errors.join('\n'));
}
return html.nodes;
}
has(srcMsg: i18n.Message): boolean { return this.digest(srcMsg) in this._i18nNodesByMsgId; }
}
class I18nToHtmlVisitor implements i18n.Visitor {
private _srcMsg: i18n.Message;
private _srcMsgStack: i18n.Message[] = [];
private _errors: I18nError[] = [];
constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
private _digest: (m: i18n.Message) => string) {}
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
this._srcMsgStack.length = 0;
this._errors.length = 0;
// i18n to text
const text = this._convertToText(srcMsg);
// text to html
const url = srcMsg.nodes[0].sourceSpan.start.file.url;
const html = new HtmlParser().parse(text, url, true);
return {
nodes: html.rootNodes,
errors: [...this._errors, ...html.errors],
};
}
visitText(text: i18n.Text, context?: any): string { return text.value; }
visitContainer(container: i18n.Container, context?: any): any {
return container.children.map(n => n.visit(this)).join('');
}
visitIcu(icu: i18n.Icu, context?: any): any {
const cases = Object.keys(icu.cases).map(k => `${k} {${icu.cases[k].visit(this)}}`);
// TODO(vicb): Once all format switch to using expression placeholders
// we should throw when the placeholder is not in the source message
const exp = this._srcMsg.placeholders.hasOwnProperty(icu.expression) ?
this._srcMsg.placeholders[icu.expression] :
icu.expression;
return `{${exp}, ${icu.type}, ${cases.join(' ')}}`;
}
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
const phName = ph.name;
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
return this._srcMsg.placeholders[phName];
}
if (this._srcMsg.placeholderToMessage.hasOwnProperty(phName)) {
return this._convertToText(this._srcMsg.placeholderToMessage[phName]);
}
this._addError(ph, `Unknown placeholder`);
return '';
}
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): any { throw 'unreachable code'; }
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
private _convertToText(srcMsg: i18n.Message): string {
const digest = this._digest(srcMsg);
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
this._srcMsgStack.push(this._srcMsg);
this._srcMsg = srcMsg;
const nodes = this._i18nNodesByMsgId[digest];
const text = nodes.map(node => node.visit(this)).join('');
this._srcMsg = this._srcMsgStack.pop();
return text;
}
this._addError(srcMsg.nodes[0], `Missing translation for message ${digest}`);
return '';
}
private _addError(el: i18n.Node, msg: string) {
this._errors.push(new I18nError(el.sourceSpan, msg));
}
has(id: string): boolean { return id in this._messageMap; }
}

View File

@ -8,8 +8,7 @@
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol';
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} 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';
const APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
@ -340,32 +339,32 @@ export class Identifiers {
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `@angular/${pkg}/index`;
return `asset:@angular/lib/${pkg}/index`;
} else {
return `@angular/${pkg}/${type}/${path}`;
return `asset:@angular/lib/${pkg}/src/${path}`;
}
}
export function resolveIdentifier(identifier: IdentifierSpec) {
return reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
}
export function createIdentifier(identifier: IdentifierSpec): CompileIdentifierMetadata {
const reference =
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
return {reference: reference};
return new CompileIdentifierMetadata({
name: identifier.name,
moduleUrl: identifier.moduleUrl,
reference:
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime)
});
}
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
return {identifier: identifier};
return new CompileTokenMetadata({identifier: identifier});
}
export function createIdentifierToken(identifier: IdentifierSpec): CompileTokenMetadata {
return identifierToken(createIdentifier(identifier));
export function resolveIdentifierToken(identifier: IdentifierSpec): CompileTokenMetadata {
return identifierToken(resolveIdentifier(identifier));
}
export function createEnumIdentifier(
enumType: IdentifierSpec, name: string): CompileIdentifierMetadata {
const resolvedEnum = reflector.resolveEnum(resolveIdentifier(enumType), name);
return {reference: resolvedEnum};
export function resolveEnumIdentifier(
enumType: CompileIdentifierMetadata, name: string): CompileIdentifierMetadata {
const resolvedEnum = reflector.resolveEnum(enumType.reference, name);
return new CompileIdentifierMetadata(
{name: `${enumType.name}.${name}`, moduleUrl: enumType.moduleUrl, reference: resolvedEnum});
}

View File

@ -6,27 +6,24 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Directive, Host, Inject, Injectable, ModuleWithProviders, OpaqueToken, 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 {StaticSymbol} from './aot/static_symbol';
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
import {ListWrapper, StringMapWrapper} from './facade/collection';
import {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {Identifiers, resolveIdentifierToken} from './identifiers';
import {hasLifecycleHook} from './lifecycle_reflector';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {SummaryResolver} from './summary_resolver';
import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, visitValue} from './util';
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
// Design notes:
// - don't lazily create metadata:
@ -38,24 +35,40 @@ export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
@Injectable()
export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
private _directiveSummaryCache = new Map<Type<any>, cpl.CompileDirectiveSummary>();
private _pipeCache = new Map<Type<any>, cpl.CompilePipeMetadata>();
private _pipeSummaryCache = new Map<Type<any>, cpl.CompilePipeSummary>();
private _ngModuleCache = new Map<Type<any>, cpl.CompileNgModuleMetadata>();
private _ngModuleOfTypes = new Map<Type<any>, Type<any>>();
private _anonymousTypes = new Map<Object, number>();
private _anonymousTypeIndex = 0;
constructor(
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver,
private _schemaRegistry: ElementSchemaRegistry,
private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer,
private _reflector: ReflectorReader = reflector,
@Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {}
private _reflector: ReflectorReader = reflector) {}
private sanitizeTokenName(token: any): string {
let identifier = stringify(token);
if (identifier.indexOf('(') >= 0) {
// case: anonymous functions!
let found = this._anonymousTypes.get(token);
if (!found) {
this._anonymousTypes.set(token, this._anonymousTypeIndex++);
found = this._anonymousTypes.get(token);
}
identifier = `anonymous_token_${found}_`;
}
return sanitizeIdentifier(identifier);
}
clearCacheFor(type: Type<any>) {
const dirMeta = this._directiveCache.get(type);
this._directiveCache.delete(type);
this._summaryCache.delete(type);
this._directiveSummaryCache.delete(type);
this._pipeCache.delete(type);
this._pipeSummaryCache.delete(type);
this._ngModuleOfTypes.delete(type);
// Clear all of the NgModule as they contain transitive information!
this._ngModuleCache.clear();
@ -66,8 +79,9 @@ export class CompileMetadataResolver {
clearCache() {
this._directiveCache.clear();
this._summaryCache.clear();
this._directiveSummaryCache.clear();
this._pipeCache.clear();
this._pipeSummaryCache.clear();
this._ngModuleCache.clear();
this._ngModuleOfTypes.clear();
this._directiveNormalizer.clearCache();
@ -127,67 +141,57 @@ export class CompileMetadataResolver {
return null;
}
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
let summary = this._summaryCache.get(type);
if (!summary) {
summary = this._summaryResolver.resolveSummary(type);
this._summaryCache.set(type, summary);
}
return summary && summary.summaryKind === kind ? summary : null;
}
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): () => Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
directiveType = resolveForwardRef(directiveType);
const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType);
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
type: metadata.type,
isComponent: metadata.isComponent,
selector: metadata.selector,
exportAs: metadata.exportAs,
changeDetection: metadata.changeDetection,
inputs: metadata.inputs,
outputs: metadata.outputs,
hostListeners: metadata.hostListeners,
hostProperties: metadata.hostProperties,
hostAttributes: metadata.hostAttributes,
providers: metadata.providers,
viewProviders: metadata.viewProviders,
queries: metadata.queries,
viewQueries: metadata.viewQueries,
entryComponents: metadata.entryComponents,
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._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta;
};
if (metadata.isComponent) {
if (nonNormalizedMetadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
encapsulation: metadata.template.encapsulation,
template: metadata.template.template,
templateUrl: metadata.template.templateUrl,
styles: metadata.template.styles,
styleUrls: metadata.template.styleUrls,
animations: metadata.template.animations,
interpolation: metadata.template.interpolation
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) {
this._reportError(new ComponentStillLoadingError(directiveType), directiveType);
return null;
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
return () => templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
@ -196,17 +200,18 @@ export class CompileMetadataResolver {
}
}
getNonNormalizedDirectiveMetadata(directiveType: any):
{annotation: Directive, metadata: cpl.CompileDirectiveMetadata} {
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
directiveType = resolveForwardRef(directiveType);
const dirMeta = this._directiveResolver.resolve(directiveType);
if (!dirMeta) {
return null;
}
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);
@ -227,7 +232,7 @@ export class CompileMetadataResolver {
}
let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: cpl.CompileProviderMetadata[] = [];
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
let selector = dirMeta.selector;
@ -237,12 +242,13 @@ export class CompileMetadataResolver {
if (dirMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`, [], directiveType);
`viewProviders for "${stringify(directiveType)}"`);
}
if (dirMeta.entryComponents) {
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this._getIdentifierMetadata(type))
.concat(entryComponentMetadata);
entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata);
}
if (!selector) {
selector = this._schemaRegistry.getDefaultComponentElementName();
@ -250,18 +256,14 @@ export class CompileMetadataResolver {
} else {
// Directive
if (!selector) {
this._reportError(
new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`),
directiveType);
selector = 'error';
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
}
}
let providers: cpl.CompileProviderMetadata[] = [];
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`,
[], directiveType);
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
@ -270,11 +272,11 @@ export class CompileMetadataResolver {
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
const metadata = cpl.CompileDirectiveMetadata.create({
return cpl.CompileDirectiveMetadata.create({
selector: selector,
exportAs: dirMeta.exportAs,
isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType),
type: this._getTypeMetadata(directiveType, moduleUrl),
template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs,
@ -286,7 +288,6 @@ export class CompileMetadataResolver {
viewQueries: viewQueries,
entryComponents: entryComponentMetadata
});
return {metadata, annotation: dirMeta};
}
/**
@ -296,22 +297,17 @@ export class CompileMetadataResolver {
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) {
this._reportError(
new Error(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`),
directiveType);
throw new Error(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`);
}
return dirMeta;
}
getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
const dirSummary =
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
const dirSummary = this._directiveSummaryCache.get(dirType);
if (!dirSummary) {
this._reportError(
new Error(
`Illegal state: Could not load the summary for directive ${stringify(dirType)}.`),
dirType);
throw new Error(
`Illegal state: getDirectiveSummary can only be called after loadNgModuleMetadata for a module that imports it. Directive ${stringify(dirType)}.`);
}
return dirSummary;
}
@ -320,39 +316,51 @@ export class CompileMetadataResolver {
isPipe(type: any) { return this._pipeResolver.isPipe(type); }
getNgModuleSummary(moduleType: any): cpl.CompileNgModuleSummary {
let moduleSummary =
<cpl.CompileNgModuleSummary>this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule);
if (!moduleSummary) {
const moduleMeta = this.getNgModuleMetadata(moduleType, false);
moduleSummary = moduleMeta ? moduleMeta.toSummary() : null;
if (moduleSummary) {
this._summaryCache.set(moduleType, moduleSummary);
}
/**
* Gets the metadata for the given module.
* This assumes `loadNgModuleMetadata` has been called first.
*/
getNgModuleMetadata(moduleType: any): cpl.CompileNgModuleMetadata {
const modMeta = this._ngModuleCache.get(moduleType);
if (!modMeta) {
throw new Error(
`Illegal state: getNgModuleMetadata can only be called after loadNgModuleMetadata. Module ${stringify(moduleType)}.`);
}
return moduleSummary;
return modMeta;
}
private _loadNgModuleSummary(moduleType: any, isSync: boolean): cpl.CompileNgModuleSummary {
// TODO(tbosch): add logic to read summary files!
// - needs to add directive / pipe summaries to this._directiveSummaryCache /
// this._pipeSummaryCache as well!
const moduleMeta = this._loadNgModuleMetadata(moduleType, isSync, false);
return moduleMeta ? moduleMeta.toSummary() : null;
}
/**
* Loads the declared directives and pipes of an NgModule.
* Loads an NgModule and all of its directives. This includes loading the exported directives of
* imported modules,
* but not private directives of imported modules.
*/
loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
Promise<any> {
const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound);
const loading: Promise<any>[] = [];
if (ngModule) {
ngModule.declaredDirectives.forEach((id) => {
const promise = this._loadDirectiveMetadata(id.reference, isSync);
if (promise) {
loading.push(promise);
}
});
ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference));
}
return Promise.all(loading);
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
const loading = ngModule ?
Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
Promise.resolve(null);
return {ngModule, loading};
}
getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata {
/**
* 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):
cpl.CompileNgModuleMetadata {
moduleType = resolveForwardRef(moduleType);
let compileMeta = this._ngModuleCache.get(moduleType);
if (compileMeta) {
@ -367,7 +375,7 @@ export class CompileMetadataResolver {
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
const importedModules: cpl.CompileNgModuleSummary[] = [];
const exportedModules: cpl.CompileNgModuleSummary[] = [];
const providers: cpl.CompileProviderMetadata[] = [];
const providers: any[] = [];
const entryComponents: cpl.CompileIdentifierMetadata[] = [];
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
const schemas: SchemaMetadata[] = [];
@ -383,26 +391,20 @@ export class CompileMetadataResolver {
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringify(importedModuleType)}'`, [], importedType));
`provider for the NgModule '${stringify(importedModuleType)}'`));
}
}
if (importedModuleType) {
const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
const importedModuleSummary = this._loadNgModuleSummary(importedModuleType, isSync);
if (!importedModuleSummary) {
this._reportError(
new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
}
importedModules.push(importedModuleSummary);
} else {
this._reportError(
new Error(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
}
});
}
@ -410,17 +412,15 @@ export class CompileMetadataResolver {
if (meta.exports) {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) {
this._reportError(
new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
}
const exportedModuleSummary = this.getNgModuleSummary(exportedType);
const exportedModuleSummary = this._loadNgModuleSummary(exportedType, isSync);
if (exportedModuleSummary) {
exportedModules.push(exportedModuleSummary);
} else {
exportedNonModuleIdentifiers.push(this._getIdentifierMetadata(exportedType));
exportedNonModuleIdentifiers.push(
this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType)));
}
});
}
@ -431,28 +431,29 @@ export class CompileMetadataResolver {
if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) {
this._reportError(
new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
}
const declaredIdentifier = this._getIdentifierMetadata(declaredType);
const declaredIdentifier =
this._getIdentifierMetadata(declaredType, staticTypeModuleUrl(declaredType));
if (this._directiveResolver.isDirective(declaredType)) {
transitiveModule.addDirective(declaredIdentifier);
transitiveModule.directivesSet.add(declaredType);
transitiveModule.directives.push(declaredIdentifier);
declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
const loader = this._loadDirectiveMetadata(declaredType, isSync);
if (loader) {
transitiveModule.directiveLoaders.push(loader);
}
} else if (this._pipeResolver.isPipe(declaredType)) {
transitiveModule.addPipe(declaredIdentifier);
transitiveModule.pipesSet.add(declaredType);
transitiveModule.pipes.push(declaredIdentifier);
declaredPipes.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
this._loadPipeMetadata(declaredType);
} else {
this._reportError(
new Error(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
}
});
}
@ -462,15 +463,11 @@ export class CompileMetadataResolver {
exportedNonModuleIdentifiers.forEach((exportedId) => {
if (transitiveModule.directivesSet.has(exportedId.reference)) {
exportedDirectives.push(exportedId);
transitiveModule.addExportedDirective(exportedId);
} else if (transitiveModule.pipesSet.has(exportedId.reference)) {
exportedPipes.push(exportedId);
transitiveModule.addExportedPipe(exportedId);
} else {
this._reportError(
new Error(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`),
moduleType);
throw new Error(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`);
}
});
@ -478,26 +475,24 @@ export class CompileMetadataResolver {
// so that they overwrite any other provider we already added.
if (meta.providers) {
providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`,
[], moduleType));
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`));
}
if (meta.entryComponents) {
entryComponents.push(
...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type)));
...flattenAndDedupeArray(meta.entryComponents)
.map(type => this._getTypeMetadata(type, staticTypeModuleUrl(type))));
}
if (meta.bootstrap) {
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => {
if (!isValidType(type)) {
this._reportError(
new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`),
moduleType);
return;
throw new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
}
bootstrapComponents.push(this._getTypeMetadata(type));
return this._getTypeMetadata(type, staticTypeModuleUrl(type));
});
bootstrapComponents.push(...typeMetadata);
}
entryComponents.push(...bootstrapComponents);
@ -506,8 +501,11 @@ export class CompileMetadataResolver {
schemas.push(...flattenAndDedupeArray(meta.schemas));
}
transitiveModule.entryComponents.push(...entryComponents);
transitiveModule.providers.push(...providers);
compileMeta = new cpl.CompileNgModuleMetadata({
type: this._getTypeMetadata(moduleType),
type: this._getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
providers,
entryComponents,
bootstrapComponents,
@ -522,9 +520,7 @@ export class CompileMetadataResolver {
id: meta.id,
});
entryComponents.forEach((id) => transitiveModule.addEntryComponent(id));
providers.forEach((provider) => transitiveModule.addProvider(provider, compileMeta.type));
transitiveModule.addModule(compileMeta.type);
transitiveModule.modules.push(compileMeta.toInjectorSummary());
this._ngModuleCache.set(moduleType, compileMeta);
return compileMeta;
}
@ -553,12 +549,10 @@ export class CompileMetadataResolver {
private _addTypeToModule(type: Type<any>, moduleType: Type<any>) {
const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) {
this._reportError(
new Error(
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`),
moduleType);
throw new Error(
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`);
}
this._ngModuleOfTypes.set(type, moduleType);
}
@ -567,59 +561,50 @@ export class CompileMetadataResolver {
importedModules: cpl.CompileNgModuleSummary[],
exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata {
// collect `providers` / `entryComponents` from all imported and all exported modules
const result = new cpl.TransitiveCompileNgModuleMetadata();
const modulesByToken = new Map<any, Set<any>>();
importedModules.concat(exportedModules).forEach((modSummary) => {
modSummary.modules.forEach((mod) => result.addModule(mod));
modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp));
const addedTokens = new Set<any>();
modSummary.providers.forEach((entry) => {
const tokenRef = cpl.tokenReference(entry.provider.token);
let prevModules = modulesByToken.get(tokenRef);
if (!prevModules) {
prevModules = new Set<any>();
modulesByToken.set(tokenRef, prevModules);
}
const moduleRef = entry.module.reference;
// Note: the providers of one module may still contain multiple providers
// per token (e.g. for multi providers), and we need to preserve these.
if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) {
prevModules.add(moduleRef);
addedTokens.add(tokenRef);
result.addProvider(entry.provider, entry.module);
}
});
});
exportedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addExportedDirective(id));
modSummary.exportedPipes.forEach((id) => result.addExportedPipe(id));
});
importedModules.forEach((modSummary) => {
modSummary.exportedDirectives.forEach((id) => result.addDirective(id));
modSummary.exportedPipes.forEach((id) => result.addPipe(id));
});
return result;
const transitiveModules = getTransitiveImportedModules(importedModules.concat(exportedModules));
const providers = flattenArray(transitiveModules.map((ngModule) => ngModule.providers));
const entryComponents =
flattenArray(transitiveModules.map((ngModule) => ngModule.entryComponents));
const transitiveExportedModules = getTransitiveExportedModules(importedModules);
const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const directiveLoaders =
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
}
private _getIdentifierMetadata(type: Type<any>): cpl.CompileIdentifierMetadata {
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
cpl.CompileIdentifierMetadata {
type = resolveForwardRef(type);
return {reference: type};
return new cpl.CompileIdentifierMetadata(
{name: this.sanitizeTokenName(type), moduleUrl, reference: type});
}
private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type);
return {
private _getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type, moduleUrl);
return new cpl.CompileTypeMetadata({
name: identifier.name,
moduleUrl: identifier.moduleUrl,
reference: identifier.reference,
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies),
lifecycleHooks:
LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, identifier.reference)),
};
});
}
private _getFactoryMetadata(factory: Function, dependencies: any[] = null):
private _getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
cpl.CompileFactoryMetadata {
factory = resolveForwardRef(factory);
return {reference: factory, diDeps: this._getDependenciesMetadata(factory, dependencies)};
return new cpl.CompileFactoryMetadata({
name: this.sanitizeTokenName(factory),
moduleUrl,
reference: factory,
diDeps: this._getDependenciesMetadata(factory, dependencies)
});
}
/**
@ -629,21 +614,17 @@ export class CompileMetadataResolver {
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
this._reportError(
new Error(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`),
pipeType);
throw new Error(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`);
}
return pipeMeta;
}
getPipeSummary(pipeType: any): cpl.CompilePipeSummary {
const pipeSummary =
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
const pipeSummary = this._pipeSummaryCache.get(pipeType);
if (!pipeSummary) {
this._reportError(
new Error(`Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`),
pipeType);
throw new Error(
`Illegal state: getPipeSummary can only be called after loadNgModuleMetadata for a module that imports it. Pipe ${stringify(pipeType)}.`);
}
return pipeSummary;
}
@ -661,12 +642,12 @@ export class CompileMetadataResolver {
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType),
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeAnnotation.name,
pure: pipeAnnotation.pure
});
this._pipeCache.set(pipeType, pipeMeta);
this._summaryCache.set(pipeType, pipeMeta.toSummary());
this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
}
@ -709,23 +690,22 @@ export class CompileMetadataResolver {
return null;
}
return {
return new cpl.CompileDiDependencyMetadata({
isAttribute,
isHost,
isSelf,
isSkipSelf,
isOptional,
token: this._getTokenMetadata(token)
};
});
});
if (hasUnknownDeps) {
const depsTokens =
dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', ');
this._reportError(
new Error(`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`),
typeOrFunc);
throw new Error(
`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`);
}
return dependenciesMetadata;
@ -735,75 +715,81 @@ export class CompileMetadataResolver {
token = resolveForwardRef(token);
let compileToken: cpl.CompileTokenMetadata;
if (typeof token === 'string') {
compileToken = {value: token};
compileToken = new cpl.CompileTokenMetadata({value: token});
} else {
compileToken = {identifier: {reference: token}};
compileToken = new cpl.CompileTokenMetadata({
identifier: new cpl.CompileIdentifierMetadata({
reference: token,
name: this.sanitizeTokenName(token),
moduleUrl: staticTypeModuleUrl(token)
})
});
}
return compileToken;
}
private _getProvidersMetadata(
providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [],
type?: any): cpl.CompileProviderMetadata[] {
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
providers.forEach((provider: any, providerIdx: number) => {
provider = resolveForwardRef(provider);
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
provider = new cpl.ProviderMeta(provider.provide, provider);
}
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
if (Array.isArray(provider)) {
this._getProvidersMetadata(provider, targetEntryComponents, debugInfo, compileProviders);
compileProvider = this._getProvidersMetadata(provider, targetEntryComponents, debugInfo);
} else if (provider instanceof cpl.ProviderMeta) {
const tokenMeta = this._getTokenMetadata(provider.token);
if (tokenMeta.reference ===
resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider));
} else {
compileProvider = this.getProviderMetadata(provider);
}
} else if (isValidType(provider)) {
compileProvider = this._getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else {
provider = resolveForwardRef(provider);
let providerMeta: cpl.ProviderMeta;
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
providerMeta = new cpl.ProviderMeta(provider.provide, provider);
} else if (isValidType(provider)) {
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
} else {
const providersInfo =
(<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringify(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringify(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
return soFar;
},
[]))
.join(', ');
this._reportError(
new Error(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
type);
}
if (providerMeta.token === resolveIdentifier(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS)) {
targetEntryComponents.push(...this._getEntryComponentsFromProvider(providerMeta, type));
} else {
compileProviders.push(this.getProviderMetadata(providerMeta));
}
const providersInfo =
(<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringify(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringify(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
return soFar;
},
[]))
.join(', ');
throw new Error(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
}
if (compileProvider) {
compileProviders.push(compileProvider);
}
});
return compileProviders;
}
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any):
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta):
cpl.CompileIdentifierMetadata[] {
const components: cpl.CompileIdentifierMetadata[] = [];
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
if (provider.useFactory || provider.useExisting || provider.useClass) {
this._reportError(
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
return [];
throw new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`);
}
if (!provider.multi) {
this._reportError(
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), type);
return [];
throw new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`);
}
extractIdentifiers(provider.useValue, collectedIdentifiers);
convertToCompileValue(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => {
if (this._directiveResolver.isDirective(identifier.reference)) {
components.push(identifier);
@ -816,29 +802,26 @@ export class CompileMetadataResolver {
let compileDeps: cpl.CompileDiDependencyMetadata[];
let compileTypeMetadata: cpl.CompileTypeMetadata = null;
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null;
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
if (provider.useClass) {
compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies);
compileTypeMetadata = this._getTypeMetadata(
provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies);
compileDeps = compileTypeMetadata.diDeps;
if (provider.token === provider.useClass) {
// use the compileTypeMetadata as it contains information about lifecycleHooks...
token = {identifier: compileTypeMetadata};
}
} else if (provider.useFactory) {
compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies);
compileFactoryMetadata = this._getFactoryMetadata(
provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies);
compileDeps = compileFactoryMetadata.diDeps;
}
return {
token: token,
return new cpl.CompileProviderMetadata({
token: this._getTokenMetadata(provider.token),
useClass: compileTypeMetadata,
useValue: provider.useValue,
useValue: convertToCompileValue(provider.useValue, []),
useFactory: compileFactoryMetadata,
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : null,
deps: compileDeps,
multi: provider.multi
};
});
}
private _getQueriesMetadata(
@ -866,32 +849,52 @@ export class CompileMetadataResolver {
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
} else {
if (!q.selector) {
this._reportError(
new Error(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`),
typeOrFunc);
throw new Error(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`);
}
selectors = [this._getTokenMetadata(q.selector)];
}
return {
return new cpl.CompileQueryMetadata({
selectors,
first: q.first,
descendants: q.descendants, propertyName,
read: q.read ? this._getTokenMetadata(q.read) : null
};
});
}
}
private _reportError(error: any, type?: any, otherType?: any) {
if (this._errorCollector) {
this._errorCollector(error, type);
if (otherType) {
this._errorCollector(error, otherType);
}
} else {
throw error;
function getTransitiveExportedModules(
modules: cpl.CompileNgModuleDirectiveSummary[],
targetModules: cpl.CompileNgModuleDirectiveSummary[] = [],
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleDirectiveSummary[] {
modules.forEach((ngModule) => {
if (!visitedModules.has(ngModule.type.reference)) {
visitedModules.add(ngModule.type.reference);
getTransitiveExportedModules(ngModule.exportedModules, targetModules, visitedModules);
// Add after recursing so imported/exported modules are before the module itself.
// This is important for overwriting providers of imported modules!
targetModules.push(ngModule);
}
}
});
return targetModules;
}
function getTransitiveImportedModules(
modules: cpl.CompileNgModuleInjectorSummary[],
targetModules: cpl.CompileNgModuleInjectorSummary[] = [],
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleInjectorSummary[] {
modules.forEach((ngModule) => {
if (!visitedModules.has(ngModule.type.reference)) {
visitedModules.add(ngModule.type.reference);
const nestedModules = ngModule.importedModules.concat(ngModule.exportedModules);
getTransitiveImportedModules(nestedModules, targetModules, visitedModules);
// Add after recursing so imported/exported modules are before the module itself.
// This is important for overwriting providers of imported modules!
targetModules.push(ngModule);
}
});
return targetModules;
}
function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
@ -920,13 +923,17 @@ function flattenAndDedupeArray(tree: any[]): Array<any> {
}
function isValidType(value: any): boolean {
return (value instanceof StaticSymbol) || (value instanceof Type);
return cpl.isStaticSymbol(value) || (value instanceof Type);
}
export function componentModuleUrl(
function staticTypeModuleUrl(value: any): string {
return cpl.isStaticSymbol(value) ? value.filePath : null;
}
function componentModuleUrl(
reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string {
if (type instanceof StaticSymbol) {
return type.filePath;
if (cpl.isStaticSymbol(type)) {
return staticTypeModuleUrl(type);
}
const moduleId = cmpMetadata.moduleId;
@ -943,12 +950,21 @@ export function componentModuleUrl(
return reflector.importUri(type);
}
function extractIdentifiers(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]) {
visitValue(value, new _CompileValueConverter(), targetIdentifiers);
function convertToCompileValue(
value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
return visitValue(value, new _CompileValueConverter(), targetIdentifiers);
}
class _CompileValueConverter extends ValueTransformer {
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
targetIdentifiers.push({reference: value});
let identifier: cpl.CompileIdentifierMetadata;
if (cpl.isStaticSymbol(value)) {
identifier = new cpl.CompileIdentifierMetadata(
{name: value.name, moduleUrl: value.filePath, reference: value});
} else {
identifier = new cpl.CompileIdentifierMetadata({reference: value});
}
targetIdentifiers.push(identifier);
return identifier;
}
}

View File

@ -8,10 +8,10 @@
import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
@ -35,10 +35,9 @@ export class NgModuleCompileResult {
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
const sourceFileName = isPresent(moduleUrl) ?
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
`in NgModule ${identifierName(ngModuleMeta.type)}`;
const sourceFileName = isPresent(ngModuleMeta.type.moduleUrl) ?
`in NgModule ${ngModuleMeta.type.name} in ${ngModuleMeta.type.moduleUrl}` :
`in NgModule ${ngModuleMeta.type.name}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
@ -47,9 +46,8 @@ export class NgModuleCompiler {
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
const entryComponentFactories =
ngModuleMeta.transitiveModule.entryComponents.map((entryComponent) => {
const id: CompileIdentifierMetadata = {reference: null};
if (ngModuleMeta.bootstrapComponents.some(
(id) => id.reference === entryComponent.reference)) {
const id = new CompileIdentifierMetadata({name: entryComponent.name});
if (ngModuleMeta.bootstrapComponents.indexOf(entryComponent) > -1) {
bootstrapComponentFactories.push(id);
}
deps.push(new ComponentFactoryDependency(entryComponent, id));
@ -61,21 +59,21 @@ export class NgModuleCompiler {
const providerParser = new NgModuleProviderAnalyzer(ngModuleMeta, extraProviders, sourceSpan);
providerParser.parse().forEach((provider) => builder.addProvider(provider));
const injectorClass = builder.build();
const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`;
const ngModuleFactoryVar = `${ngModuleMeta.type.name}NgFactory`;
const ngModuleFactoryStmt =
o.variable(ngModuleFactoryVar)
.set(o.importExpr(createIdentifier(Identifiers.NgModuleFactory))
.set(o.importExpr(resolveIdentifier(Identifiers.NgModuleFactory))
.instantiate(
[o.variable(injectorClass.name), o.importExpr(ngModuleMeta.type)],
o.importType(
createIdentifier(Identifiers.NgModuleFactory),
resolveIdentifier(Identifiers.NgModuleFactory),
[o.importType(ngModuleMeta.type)], [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]);
const stmts: o.Statement[] = [injectorClass, ngModuleFactoryStmt];
if (ngModuleMeta.id) {
const registerFactoryStmt =
o.importExpr(createIdentifier(Identifiers.RegisterModuleFactoryFn))
o.importExpr(resolveIdentifier(Identifiers.RegisterModuleFactoryFn))
.callFn([o.literal(ngModuleMeta.id), o.variable(ngModuleFactoryVar)])
.toStmt();
stmts.push(registerFactoryStmt);
@ -104,7 +102,7 @@ class _InjectorBuilder implements ClassBuilder {
addProvider(resolvedProvider: ProviderAst) {
const providerValueExpressions =
resolvedProvider.providers.map((provider) => this._getProviderValue(provider));
const propName = `_${tokenName(resolvedProvider.token)}_${this._instances.size}`;
const propName = `_${resolvedProvider.token.name}_${this._instances.size}`;
const instance = this._createProviderProperty(
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager);
@ -112,12 +110,12 @@ class _InjectorBuilder implements ClassBuilder {
this._destroyStmts.push(instance.callMethod('ngOnDestroy', []).toStmt());
}
this._tokens.push(resolvedProvider.token);
this._instances.set(tokenReference(resolvedProvider.token), instance);
this._instances.set(resolvedProvider.token.reference, instance);
}
build(): o.ClassStmt {
const getMethodStmts: o.Statement[] = this._tokens.map((token) => {
const providerExpr = this._instances.get(tokenReference(token));
const providerExpr = this._instances.get(token.reference);
return new o.IfStmt(
InjectMethodVars.token.identical(createDiTokenExpression(token)),
[new o.ReturnStatement(providerExpr)]);
@ -145,13 +143,13 @@ class _InjectorBuilder implements ClassBuilder {
o.literalArr(this._bootstrapComponentFactories.map(
(componentFactory) => o.importExpr(componentFactory)))
];
const injClassName = `${identifierName(this._ngModuleMeta.type)}Injector`;
const injClassName = `${this._ngModuleMeta.type.name}Injector`;
return createClassStmt({
name: injClassName,
ctorParams: [new o.FnParam(
InjectorProps.parent.name, o.importType(createIdentifier(Identifiers.Injector)))],
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
parent: o.importExpr(
createIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
parentArgs: parentArgs,
builders: [{methods}, this]
});
@ -160,7 +158,7 @@ class _InjectorBuilder implements ClassBuilder {
private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
let result: o.Expression;
if (isPresent(provider.useExisting)) {
result = this._getDependency({token: provider.useExisting});
result = this._getDependency(new CompileDiDependencyMetadata({token: provider.useExisting}));
} else if (isPresent(provider.useFactory)) {
const deps = provider.deps || provider.useFactory.diDeps;
const depsExpr = deps.map((dep) => this._getDependency(dep));
@ -217,12 +215,13 @@ class _InjectorBuilder implements ClassBuilder {
}
if (!dep.isSkipSelf) {
if (dep.token &&
(tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector) ||
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver))) {
(dep.token.reference === resolveIdentifierToken(Identifiers.Injector).reference ||
dep.token.reference ===
resolveIdentifierToken(Identifiers.ComponentFactoryResolver).reference)) {
result = o.THIS_EXPR;
}
if (!result) {
result = this._instances.get(tokenReference(dep.token));
result = this._instances.get(dep.token.reference);
}
}
if (!result) {

View File

@ -8,7 +8,6 @@
import {Injectable, NgModule, Type} from '@angular/core';
import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang';
import {ReflectorReader, reflector} from './private_import_core';
@ -26,8 +25,7 @@ export class NgModuleResolver {
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
const ngModuleMeta: NgModule =
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata);
if (isPresent(ngModuleMeta)) {
return ngModuleMeta;

View File

@ -8,29 +8,114 @@
import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {ListWrapper} from '../facade/collection';
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AnimationCompiler} from './animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {ListWrapper} from './facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler';
import {OutputEmitter} from './output/abstract_emitter';
import * as o from './output/output_ast';
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
import {TemplateParser} from './template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler';
import {AotCompilerOptions} from './compiler_options';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {AotSummaryResolver} from './summary_resolver';
import {filterFileByPatterns} from './utils';
export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
}
export class AotCompiler {
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
symbolsMissingModule?: StaticSymbol[];
}
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return result;
}
// Wait for the directives in the given modules have been loaded
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
return Promise
.all(ListWrapper.flatten(ngModules.map(
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
.then(() => {});
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, ngModules});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
}
export class OfflineCompiler {
private _animationCompiler = new AnimationCompiler();
constructor(
@ -38,45 +123,30 @@ export class AotCompiler {
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: AotSummaryResolver, private _localeId: string,
private _translationFormat: string, private _animationParser: AnimationParser,
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {}
private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {}
clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> {
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false)))
.then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules));
return ListWrapper.flatten(sourceModules);
});
analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return ListWrapper.flatten(sourceModules);
});
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] {
directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = [];
const exportedVars: string[] = [];
const generatedFiles: GeneratedFile[] = [];
// write summary files
const summaries: CompileTypeSummary[] = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref))
];
generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries));
const outputSourceModules: SourceModule[] = [];
// compile all ng modules
exportedVars.push(
@ -95,7 +165,7 @@ export class AotCompiler {
const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) {
throw new Error(
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
}
_assertComponent(compMeta);
@ -103,7 +173,7 @@ export class AotCompiler {
// compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
generatedFiles.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
});
// compile components
@ -116,9 +186,9 @@ export class AotCompiler {
if (statements.length > 0) {
const srcModule = this._codegenSourceModule(
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
generatedFiles.unshift(srcModule);
outputSourceModules.unshift(srcModule);
}
return generatedFiles;
return outputSourceModules;
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
@ -126,24 +196,24 @@ export class AotCompiler {
const providers: CompileProviderMetadata[] = [];
if (this._localeId) {
providers.push({
token: createIdentifierToken(Identifiers.LOCALE_ID),
providers.push(new CompileProviderMetadata({
token: resolveIdentifierToken(Identifiers.LOCALE_ID),
useValue: this._localeId,
});
}));
}
if (this._translationFormat) {
providers.push({
token: createIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
providers.push(new CompileProviderMetadata({
token: resolveIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
useValue: this._translationFormat
});
}));
}
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
appCompileResult.dependencies.forEach((dep) => {
dep.placeholder.reference = this._staticReflector.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
dep.placeholder.name = _componentFactoryName(dep.comp);
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.moduleUrl);
});
targetStatements.push(...appCompileResult.statements);
@ -162,17 +232,14 @@ export class AotCompiler {
private _compileComponentFactory(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string {
const hostMeta = createHostComponentMeta(
this._staticReflector.getStaticSymbol(
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
compMeta);
const hostMeta = createHostComponentMeta(compMeta);
const hostViewFactoryVar = this._compileComponent(
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
const compFactoryVar = _componentFactoryName(compMeta.type);
targetStatements.push(
o.variable(compFactoryVar)
.set(o.importExpr(
createIdentifier(Identifiers.ComponentFactory), [o.importType(compMeta.type)])
.set(o.importExpr(resolveIdentifier(Identifiers.ComponentFactory), [o.importType(
compMeta.type)])
.instantiate(
[
o.literal(compMeta.selector),
@ -180,7 +247,7 @@ export class AotCompiler {
o.importExpr(compMeta.type),
],
o.importType(
createIdentifier(Identifiers.ComponentFactory),
resolveIdentifier(Identifiers.ComponentFactory),
[o.importType(compMeta.type)], [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]));
return compFactoryVar;
@ -198,24 +265,23 @@ export class AotCompiler {
const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type));
compMeta.type.name);
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations =
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
const viewResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
if (componentStyles) {
targetStatements.push(
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix));
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
}
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult));
targetStatements.push(..._resolveViewStatements(viewResult));
return viewResult.viewClassVar;
}
private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix);
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
_resolveStyleStatements(stylesCompileResult, fileSuffix);
return this._codegenSourceModule(
fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
@ -223,29 +289,26 @@ export class AotCompiler {
}
private _codegenSourceModule(
srcFileUrl: string, genFileUrl: string, statements: o.Statement[],
exportedVars: string[]): GeneratedFile {
return new GeneratedFile(
srcFileUrl, genFileUrl,
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
fileUrl: string, moduleUrl: string, statements: o.Statement[],
exportedVars: string[]): SourceModule {
return new SourceModule(
fileUrl, moduleUrl,
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
}
}
function _resolveViewStatements(
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] {
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
if (dep instanceof ViewClassDependency) {
const vfd = <ViewClassDependency>dep;
vfd.placeholder.reference =
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(vfd.comp)), dep.name);
vfd.placeholder.moduleUrl = _ngfactoryModuleUrl(vfd.comp.moduleUrl);
} else if (dep instanceof ComponentFactoryDependency) {
const cfd = <ComponentFactoryDependency>dep;
cfd.placeholder.reference = reflector.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(cfd.comp)), _componentFactoryName(cfd.comp));
cfd.placeholder.name = _componentFactoryName(cfd.comp);
cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp.moduleUrl);
} else if (dep instanceof DirectiveWrapperDependency) {
const dwd = <DirectiveWrapperDependency>dep;
dwd.placeholder.reference =
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(dwd.dir)), dwd.name);
dwd.placeholder.moduleUrl = _ngfactoryModuleUrl(dwd.dir.moduleUrl);
}
});
return compileResult.statements;
@ -253,11 +316,9 @@ function _resolveViewStatements(
function _resolveStyleStatements(
reflector: StaticReflector, compileResult: CompiledStylesheet,
fileSuffix: string): o.Statement[] {
compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] {
compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix), dep.name);
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix);
});
return compileResult.statements;
}
@ -268,17 +329,16 @@ function _ngfactoryModuleUrl(dirUrl: string): string {
}
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
return `${identifierName(comp)}NgFactory`;
return `${comp.name}NgFactory`;
}
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`;
}
function _assertComponent(meta: CompileDirectiveMetadata) {
if (!meta.isComponent) {
throw new Error(
`Could not compile '${identifierName(meta.type)}' because it is not a component.`);
throw new Error(`Could not compile '${meta.type.name}' because it is not a component.`);
}
}
@ -296,136 +356,11 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, ''];
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{
srcUrl: string,
directives: StaticSymbol[],
pipes: 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;
}
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 ngPipesByFile = 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 file to pipes `ngPipesByFile`,
// - 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);
ngPipesByFile.set(
fileUrl, (ngPipesByFile.get(fileUrl) || []).concat(pipeIdentifier.reference));
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
const files:
{srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const pipes = ngPipesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, 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.error(`WARNING: no metadata found for ${sourceFile}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
}
});
return staticSymbols;
}
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -433,16 +368,18 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) {
if (ngModules.has(staticSymbol)) {
return false;
}
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
// For every input module add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.reference));
if (options.transitiveModules) {
// For every input modules add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
}
}
return !!ngModule;
};

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