Compare commits
81 Commits
2.4.4
...
4.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
fa9e21e83c | |||
b6078f5887 | |||
c65b4fa9dc | |||
169ed82900 | |||
fd8e15b15d | |||
aa40366a92 | |||
40d8d9c3e3 | |||
ee2ac025ef | |||
aa3769ba69 | |||
d4ddb6004e | |||
84400bcc86 | |||
42d9998cbb | |||
c18d2fe5e3 | |||
d91a86aac6 | |||
d6e5e9283c | |||
eab7e490c9 | |||
3e90605db9 | |||
79671a6f12 | |||
a659259962 | |||
b56474d067 | |||
8395f0e138 | |||
dd0519abad | |||
f238c8ac7a | |||
8c27c62fab | |||
5031adc7a3 | |||
821b8f09d6 | |||
2bf1bbc071 | |||
7b0a86718c | |||
3edca4d37e | |||
a0a05041ac | |||
7256d0ede5 | |||
d62d89319e | |||
f5f1d5f65c | |||
a8d237581d | |||
d036165a19 | |||
d17e690eb4 | |||
714f2af0dd | |||
2b90cd532f | |||
3a64ad895a | |||
9ec0a4e105 | |||
4b3d135193 | |||
1d0ed6f75f | |||
6f330a5fc9 | |||
e23076f767 | |||
7295a5e7f2 | |||
20bed46737 | |||
2a5012d515 | |||
fb38fba8f9 | |||
4c35be3e07 | |||
e9f307f948 | |||
2e500cc85b | |||
56dce0e26d | |||
8a8c53250e | |||
08ff2e5249 | |||
a006c1418a | |||
90c223591f | |||
aaf6e05f56 | |||
3bee521aa4 | |||
95f48292b1 | |||
04cfa1ebdf | |||
4022173d1e | |||
c8baf51f4f | |||
b4db73d0bf | |||
e15a3f273f | |||
213c713409 | |||
9a8423da36 | |||
f0b0762f4a | |||
b5c4bf1c59 | |||
56c361ff6a | |||
562f7a2f8b | |||
6dd5201765 | |||
72361fb68f | |||
5c6ec20c7e | |||
440ef02f29 | |||
4e3d58a792 | |||
61d7c1e0b3 | |||
bf93389615 | |||
4398056146 | |||
1b547886d0 | |||
9591a08dfb | |||
65965c27a8 |
@ -1,32 +0,0 @@
|
|||||||
# Configuration for pullapprove.com
|
|
||||||
# See ownership spreadsheet:
|
|
||||||
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
group_defaults:
|
|
||||||
required: 1
|
|
||||||
reset_on_reopened:
|
|
||||||
enabled: true
|
|
||||||
approve_by_comment:
|
|
||||||
enabled: true
|
|
||||||
approve_regex: '^(Approved|:\+1:|LGTM)'
|
|
||||||
|
|
||||||
groups:
|
|
||||||
config:
|
|
||||||
conditions:
|
|
||||||
files:
|
|
||||||
- "*.yml"
|
|
||||||
- "*.json"
|
|
||||||
teams:
|
|
||||||
- repoowners
|
|
||||||
|
|
||||||
compiler:
|
|
||||||
conditions:
|
|
||||||
files:
|
|
||||||
- "tools/@angular/tsc-wrapped/*"
|
|
||||||
- "modules/@angular/compiler/*"
|
|
||||||
- "modules/@angular/compiler-cli/*"
|
|
||||||
teams:
|
|
||||||
- compiler-owners
|
|
||||||
- repoowners
|
|
145
CHANGELOG.md
145
CHANGELOG.md
@ -1,148 +1,3 @@
|
|||||||
<a name="2.4.4"></a>
|
|
||||||
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
|
|
||||||
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
|
|
||||||
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
|
|
||||||
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
|
|
||||||
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
|
|
||||||
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
|
|
||||||
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.4.3"></a>
|
|
||||||
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
|
|
||||||
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
|
|
||||||
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
|
|
||||||
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
|
|
||||||
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
|
|
||||||
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
|
|
||||||
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
|
|
||||||
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.4.2"></a>
|
|
||||||
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
|
|
||||||
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
|
|
||||||
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
|
|
||||||
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
|
|
||||||
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
|
|
||||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
|
|
||||||
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
|
|
||||||
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
|
|
||||||
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
|
|
||||||
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
|
|
||||||
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
|
|
||||||
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
|
|
||||||
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
|
|
||||||
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
|
|
||||||
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
|
|
||||||
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.4.1"></a>
|
|
||||||
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **animations:** always quote string map key values in AOT code ([#13602](https://github.com/angular/angular/issues/13602)) ([6a5e46c](https://github.com/angular/angular/commit/6a5e46c))
|
|
||||||
* **animations:** always recover from a failed animation step ([#13604](https://github.com/angular/angular/issues/13604)) ([d788c67](https://github.com/angular/angular/commit/d788c67))
|
|
||||||
* **compiler:** ignore `@import` in comments ([#13368](https://github.com/angular/angular/issues/13368)) ([6316e5d](https://github.com/angular/angular/commit/6316e5d)), closes [#12196](https://github.com/angular/angular/issues/12196)
|
|
||||||
* **core:** improve error message when component factory cannot be found ([#13541](https://github.com/angular/angular/issues/13541)) ([b9e979e](https://github.com/angular/angular/commit/b9e979e)), closes [#12678](https://github.com/angular/angular/issues/12678)
|
|
||||||
* **router:** should reset location if a navigation by location is successful ([#13545](https://github.com/angular/angular/issues/13545)) ([a38f14b](https://github.com/angular/angular/commit/a38f14b)), closes [#13491](https://github.com/angular/angular/issues/13491)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.4.0"></a>
|
|
||||||
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **animations:** allow players to be destroyed before initialized ([#13346](https://github.com/angular/angular/issues/13346)) ([b36f4bc](https://github.com/angular/angular/commit/b36f4bc)), closes [#13293](https://github.com/angular/angular/issues/13293)
|
|
||||||
* **build:** use bash string comparison operator ([#13502](https://github.com/angular/angular/issues/13502)) ([50afbe0](https://github.com/angular/angular/commit/50afbe0))
|
|
||||||
* **compiler:** do not lex `}}` when interpolation is disabled ([#13531](https://github.com/angular/angular/issues/13531)) ([9b87bb6](https://github.com/angular/angular/commit/9b87bb6)), closes [#13525](https://github.com/angular/angular/issues/13525)
|
|
||||||
* **compiler-cli:** produce metadata for .d.ts files without metadata ([#13526](https://github.com/angular/angular/issues/13526)) ([debb0c9](https://github.com/angular/angular/commit/debb0c9)), closes [#13307](https://github.com/angular/angular/issues/13307) [#13473](https://github.com/angular/angular/issues/13473) [#13521](https://github.com/angular/angular/issues/13521)
|
|
||||||
* **i18n:** add a default example to xmb placeholders ([#13507](https://github.com/angular/angular/issues/13507)) ([3f17841](https://github.com/angular/angular/commit/3f17841))
|
|
||||||
* **upgrade:** fix `registerForNg1Tests` ([#13522](https://github.com/angular/angular/issues/13522)) ([c26c24c](https://github.com/angular/angular/commit/c26c24c))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<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 don’t 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)
|
|
||||||
* **core:** 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)
|
|
||||||
* **compiler**: better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
|
|
||||||
* **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)
|
|
||||||
* **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)
|
|
||||||
|
|
||||||
### Note ###
|
|
||||||
|
|
||||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
|
||||||
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
|
|
||||||
|
|
||||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
|
||||||
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
|
|
||||||
|
|
||||||
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
|
|
||||||
|
|
||||||
|
|
||||||
<a name="2.3.0"></a>
|
<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)
|
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
|
||||||
|
|
||||||
|
44
DEVELOPER.md
44
DEVELOPER.md
@ -1,6 +1,6 @@
|
|||||||
# Building and Testing Angular
|
# Building and Testing Angular 2 for JS
|
||||||
|
|
||||||
This document describes how to set up your development environment to build and test Angular.
|
This document describes how to set up your development environment to build and test Angular 2 JS version.
|
||||||
It also explains the basic mechanics of using `git`, `node`, and `npm`.
|
It also explains the basic mechanics of using `git`, `node`, and `npm`.
|
||||||
|
|
||||||
* [Prerequisite Software](#prerequisite-software)
|
* [Prerequisite Software](#prerequisite-software)
|
||||||
@ -74,15 +74,6 @@ use in these instructions.
|
|||||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||||
|
|
||||||
## Installing Bower Modules
|
|
||||||
|
|
||||||
Now run `bower` to install additional dependencies:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Install other Angular project dependencies (bower.json)
|
|
||||||
bower install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Windows only
|
## Windows only
|
||||||
|
|
||||||
In order to create the right symlinks, run **as administrator**:
|
In order to create the right symlinks, run **as administrator**:
|
||||||
@ -133,8 +124,7 @@ If you happen to modify the public API of Angular, API golden files must be upda
|
|||||||
$ gulp public-api:update
|
$ gulp public-api:update
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
|
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||||
the project before trying to verify after an API change.
|
|
||||||
|
|
||||||
## <a name="clang-format"></a> Formatting your source code
|
## <a name="clang-format"></a> Formatting your source code
|
||||||
|
|
||||||
@ -147,32 +137,4 @@ You can automatically format your code by running:
|
|||||||
$ gulp format
|
$ gulp format
|
||||||
```
|
```
|
||||||
|
|
||||||
## Linting/verifying your source code
|
|
||||||
|
|
||||||
You can check that your code is properly formatted and adheres to coding style by running:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ gulp lint
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publishing your own personal snapshot build
|
|
||||||
|
|
||||||
You may find that your un-merged change needs some validation from external participants.
|
|
||||||
Rather than requiring them to pull your Pull Request and build Angular locally, you can
|
|
||||||
publish the `*-builds` snapshots just like our Travis build does.
|
|
||||||
|
|
||||||
First time, you need to create the github repositories:
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ export TOKEN=[get one from https://github.com/settings/tokens]
|
|
||||||
$ CREATE_REPOS=1 ./scripts/publish/publish-build-artifacts.sh [github username]
|
|
||||||
```
|
|
||||||
|
|
||||||
For subsequent snapshots, just run
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ ./scripts/publish/publish-build-artifacts.sh [github username]
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will publish the build snapshot to a branch with the same name as your current branch,
|
|
||||||
and create it if it doesn't exist.
|
|
||||||
|
@ -24,25 +24,17 @@ module.exports = function(config) {
|
|||||||
'node_modules/core-js/client/core.js',
|
'node_modules/core-js/client/core.js',
|
||||||
// include Angular v1 for upgrade module testing
|
// include Angular v1 for upgrade module testing
|
||||||
'node_modules/angular/angular.js',
|
'node_modules/angular/angular.js',
|
||||||
'node_modules/angular-mocks/angular-mocks.js',
|
|
||||||
|
|
||||||
'node_modules/zone.js/dist/zone.js',
|
'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||||
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',
|
||||||
'node_modules/zone.js/dist/proxy.js',
|
'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js',
|
||||||
'node_modules/zone.js/dist/sync-test.js',
|
|
||||||
'node_modules/zone.js/dist/jasmine-patch.js',
|
|
||||||
'node_modules/zone.js/dist/async-test.js',
|
|
||||||
'node_modules/zone.js/dist/fake-async-test.js',
|
'node_modules/zone.js/dist/fake-async-test.js',
|
||||||
|
|
||||||
// Including systemjs because it defines `__eval`, which produces correct stack traces.
|
// Including systemjs because it defines `__eval`, which produces correct stack traces.
|
||||||
'shims_for_IE.js',
|
'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js',
|
||||||
'node_modules/systemjs/dist/system.src.js',
|
|
||||||
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
|
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
|
||||||
'node_modules/reflect-metadata/Reflect.js',
|
'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js',
|
||||||
'tools/build/file2modulename.js',
|
{pattern: 'dist/all/empty.*', included: false, watched: false}, {
|
||||||
'test-main.js',
|
|
||||||
{pattern: 'dist/all/empty.*', included: false, watched: false},
|
|
||||||
{
|
|
||||||
pattern: 'modules/@angular/platform-browser/test/static_assets/**',
|
pattern: 'modules/@angular/platform-browser/test/static_assets/**',
|
||||||
included: false,
|
included: false,
|
||||||
watched: false
|
watched: false
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/core": "^2.0.0-rc.7",
|
"@angular/core": "^2.0.0-rc.7",
|
||||||
"reflect-metadata": "^0.1.2",
|
"reflect-metadata": "^0.1.2",
|
||||||
"rxjs": "^5.0.1",
|
"rxjs": "5.0.0-rc.4",
|
||||||
"jpm": "1.1.4",
|
"jpm": "1.1.4",
|
||||||
"firefox-profile": "0.4.0",
|
"firefox-profile": "0.4.0",
|
||||||
"selenium-webdriver": "^2.53.3"
|
"selenium-webdriver": "^2.53.3"
|
||||||
|
@ -12,9 +12,9 @@
|
|||||||
* Entry point for all public APIs of the common package.
|
* Entry point for all public APIs of the common package.
|
||||||
*/
|
*/
|
||||||
export * from './location/index';
|
export * from './location/index';
|
||||||
export {NgLocalization} from './localization';
|
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||||
export {CommonModule} from './common_module';
|
export {CommonModule} from './common_module';
|
||||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
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 {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||||
export {VERSION} from './version';
|
export {VERSION} from './version';
|
||||||
export {Version} from '@angular/core';
|
export {Version} from '@angular/core';
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||||
|
|
||||||
import {isListLikeIterable} from '../facade/collection';
|
import {isListLikeIterable} from '../facade/collection';
|
||||||
import {stringify} from '../facade/lang';
|
import {isPresent, stringify} from '../facade/lang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
@ -25,8 +25,6 @@ import {stringify} from '../facade/lang';
|
|||||||
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
||||||
*
|
*
|
||||||
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
||||||
*
|
|
||||||
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
|
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
@ -134,7 +132,7 @@ export class NgClass implements DoCheck {
|
|||||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||||
} else {
|
} else {
|
||||||
Object.keys(rawClassVal).forEach(klass => {
|
Object.keys(rawClassVal).forEach(klass => {
|
||||||
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
|
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
|
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
import {getTypeNameForDebugging} from '../facade/lang';
|
import {getTypeNameForDebugging} from '../facade/lang';
|
||||||
|
|
||||||
@ -89,23 +89,9 @@ export class NgForRow {
|
|||||||
@Directive({selector: '[ngFor][ngForOf]'})
|
@Directive({selector: '[ngFor][ngForOf]'})
|
||||||
export class NgFor implements DoCheck, OnChanges {
|
export class NgFor implements DoCheck, OnChanges {
|
||||||
@Input() ngForOf: any;
|
@Input() ngForOf: any;
|
||||||
@Input()
|
@Input() ngForTrackBy: TrackByFn;
|
||||||
set ngForTrackBy(fn: TrackByFn) {
|
|
||||||
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
|
||||||
// TODO(vicb): use a log service once there is a public one available
|
|
||||||
if (<any>console && <any>console.warn) {
|
|
||||||
console.warn(
|
|
||||||
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
|
|
||||||
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._trackByFn = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
|
|
||||||
|
|
||||||
private _differ: IterableDiffer = null;
|
private _differ: IterableDiffer = null;
|
||||||
private _trackByFn: TrackByFn;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
||||||
@ -133,7 +119,7 @@ export class NgFor implements DoCheck, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck(): void {
|
ngDoCheck() {
|
||||||
if (this._differ) {
|
if (this._differ) {
|
||||||
const changes = this._differ.diff(this.ngForOf);
|
const changes = this._differ.diff(this.ngForOf);
|
||||||
if (changes) this._applyChanges(changes);
|
if (changes) this._applyChanges(changes);
|
||||||
|
@ -6,46 +6,152 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
* Conditionally includes a template based on the value of an `expression`.
|
||||||
*
|
*
|
||||||
* If the expression assigned to `ngIf` evaluates to a falsy value then the element
|
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
|
||||||
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
|
* when expression is thruthy or falsy respectively. Typically the:
|
||||||
|
* - `then` template is the inline template of `ngIf` unless bound to a different value.
|
||||||
|
* - `else` template is blank unless its bound.
|
||||||
*
|
*
|
||||||
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
|
* # Most common usage
|
||||||
|
*
|
||||||
|
* The most common usage of the `ngIf` is to conditionally show the inline template as seen in this
|
||||||
|
* example:
|
||||||
|
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
|
||||||
|
*
|
||||||
|
* # Showing an alternative template using `else`
|
||||||
|
*
|
||||||
|
* If it is necessary to display a template when the `expression` is falsy use the `else` template
|
||||||
|
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
|
||||||
|
* The template can be defined anywhere in the component view but is typically placed right after
|
||||||
|
* `ngIf` for readability.
|
||||||
|
*
|
||||||
|
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
|
||||||
|
*
|
||||||
|
* # Using non-inlined `then` template
|
||||||
|
*
|
||||||
|
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
|
||||||
|
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
|
||||||
|
* change at runtime as shown in thise example.
|
||||||
|
*
|
||||||
|
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
|
||||||
|
*
|
||||||
|
* # Storing conditional result in a variable
|
||||||
|
*
|
||||||
|
* A common patter is that we need to show a set of properties from the same object. if the
|
||||||
|
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
|
||||||
|
* dereferencing a `null` value. This is especially the case when waiting on async data such as
|
||||||
|
* when using the `async` pipe as shown in folowing example:
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* <div *ngIf="errorCount > 0" class="error">
|
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
|
||||||
* <!-- Error message displayed when the errorCount property in the current context is greater
|
|
||||||
* than 0. -->
|
|
||||||
* {{errorCount}} errors detected
|
|
||||||
* </div>
|
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* There are several inefficiencies in the above example.
|
||||||
|
* - We create multiple subscriptions on the `userStream`. One for each `async` pipe, or two
|
||||||
|
* as shown in the example above.
|
||||||
|
* - We can not display an alternative screen while waiting for the data to arrive asynchronously.
|
||||||
|
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
|
||||||
|
* - We have to place the `async` pipe in parenthesis.
|
||||||
|
*
|
||||||
|
* A better way to do this is to use `ngIf` and store the result of the condition in a local
|
||||||
|
* variable as shown in the the example below:
|
||||||
|
*
|
||||||
|
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
|
||||||
|
*
|
||||||
|
* Notice that:
|
||||||
|
* - We use only one `async` pipe and hence only one subscription gets created.
|
||||||
|
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
|
||||||
|
* - The local `user` can than be bound repeatedly in a more efficient way.
|
||||||
|
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
|
||||||
|
* display the data if `userStream` returns a value.
|
||||||
|
* - We can display an alternative template while waiting for the data.
|
||||||
|
*
|
||||||
* ### Syntax
|
* ### Syntax
|
||||||
*
|
*
|
||||||
|
* Simple form:
|
||||||
* - `<div *ngIf="condition">...</div>`
|
* - `<div *ngIf="condition">...</div>`
|
||||||
* - `<div template="ngIf condition">...</div>`
|
* - `<div template="ngIf condition">...</div>`
|
||||||
* - `<template [ngIf]="condition"><div>...</div></template>`
|
* - `<ng-container [ngIf]="condition"><div>...</div></ng-container>`
|
||||||
|
*
|
||||||
|
* Form with an else block:
|
||||||
|
* ```
|
||||||
|
* <div *ngIf="condition; else elseBlock">...</div>
|
||||||
|
* <template #elseBlock>...</template>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Form with a `then` and `else` block:
|
||||||
|
* ```
|
||||||
|
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
|
||||||
|
* <template #thenBlock>...</template>
|
||||||
|
* <template #elseBlock>...</template>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Form with storing the value locally:
|
||||||
|
* ```
|
||||||
|
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
|
||||||
|
* <template #elseBlock>...</template>
|
||||||
|
* ```
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[ngIf]'})
|
@Directive({selector: '[ngIf]'})
|
||||||
export class NgIf {
|
export class NgIf {
|
||||||
private _hasView = false;
|
private _context: NgIfContext = new NgIfContext();
|
||||||
|
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
|
||||||
|
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
|
||||||
|
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||||
|
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||||
|
|
||||||
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
|
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
|
||||||
|
this._thenTemplateRef = templateRef;
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set ngIf(condition: any) {
|
set ngIf(condition: any) {
|
||||||
if (condition && !this._hasView) {
|
this._context.$implicit = condition;
|
||||||
this._hasView = true;
|
this._updateView();
|
||||||
this._viewContainer.createEmbeddedView(this._template);
|
}
|
||||||
} else if (!condition && this._hasView) {
|
|
||||||
this._hasView = false;
|
@Input()
|
||||||
|
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
|
||||||
|
this._thenTemplateRef = templateRef;
|
||||||
|
this._thenViewRef = null; // clear previous view if any.
|
||||||
|
this._updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
|
||||||
|
this._elseTemplateRef = templateRef;
|
||||||
|
this._elseViewRef = null; // clear previous view if any.
|
||||||
|
this._updateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateView() {
|
||||||
|
if (this._context.$implicit) {
|
||||||
|
if (!this._thenViewRef) {
|
||||||
this._viewContainer.clear();
|
this._viewContainer.clear();
|
||||||
|
this._elseViewRef = null;
|
||||||
|
if (this._thenTemplateRef) {
|
||||||
|
this._thenViewRef =
|
||||||
|
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this._elseViewRef) {
|
||||||
|
this._viewContainer.clear();
|
||||||
|
this._thenViewRef = null;
|
||||||
|
if (this._elseTemplateRef) {
|
||||||
|
this._elseViewRef =
|
||||||
|
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NgIfContext { public $implicit: any = null; }
|
||||||
|
@ -21,9 +21,10 @@ import {SwitchView} from './ng_switch';
|
|||||||
* @howToUse
|
* @howToUse
|
||||||
* ```
|
* ```
|
||||||
* <some-element [ngPlural]="value">
|
* <some-element [ngPlural]="value">
|
||||||
* <template ngPluralCase="=0">there is nothing</template>
|
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
|
||||||
* <template ngPluralCase="=1">there is one</template>
|
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
|
||||||
* <template ngPluralCase="few">there are a few</template>
|
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
|
||||||
|
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
|
||||||
* </some-element>
|
* </some-element>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -89,8 +90,8 @@ export class NgPlural {
|
|||||||
* @howToUse
|
* @howToUse
|
||||||
* ```
|
* ```
|
||||||
* <some-element [ngPlural]="value">
|
* <some-element [ngPlural]="value">
|
||||||
* <template ngPluralCase="=0">...</template>
|
* <ng-container *ngPluralCase="'=0'">...</ng-container>
|
||||||
* <template ngPluralCase="other">...</template>
|
* <ng-container *ngPluralCase="'other'">...</ng-container>
|
||||||
* </some-element>
|
* </some-element>
|
||||||
*```
|
*```
|
||||||
*
|
*
|
||||||
@ -103,7 +104,6 @@ export class NgPluralCase {
|
|||||||
constructor(
|
constructor(
|
||||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||||
const isANumber: boolean = !isNaN(Number(value));
|
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||||
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,10 @@ export function getPluralCategory(
|
|||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NgLocaleLocalization extends NgLocalization {
|
export class NgLocaleLocalization extends NgLocalization {
|
||||||
constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
|
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
|
||||||
|
|
||||||
getPluralCategory(value: any): string {
|
getPluralCategory(value: any): string {
|
||||||
const plural = getPluralCase(this._locale, value);
|
const plural = getPluralCase(this.locale, value);
|
||||||
|
|
||||||
switch (plural) {
|
switch (plural) {
|
||||||
case Plural.Zero:
|
case Plural.Zero:
|
||||||
|
61
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
61
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms text to lowercase.
|
||||||
|
*
|
||||||
|
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe' }
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
@Pipe({name: 'lowercase'})
|
||||||
|
export class LowerCasePipe implements PipeTransform {
|
||||||
|
transform(value: string): string {
|
||||||
|
if (!value) return value;
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||||
|
}
|
||||||
|
return value.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms text to titlecase.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
@Pipe({name: 'titlecase'})
|
||||||
|
export class TitleCasePipe implements PipeTransform {
|
||||||
|
transform(value: string): string {
|
||||||
|
if (!value) return value;
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new InvalidPipeArgumentError(TitleCasePipe, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[0].toUpperCase() + value.substr(1).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms text to uppercase.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
@Pipe({name: 'uppercase'})
|
||||||
|
export class UpperCasePipe implements PipeTransform {
|
||||||
|
transform(value: string): string {
|
||||||
|
if (!value) return value;
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||||
|
}
|
||||||
|
return value.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {isBlank} from '../facade/lang';
|
||||||
import {NgLocalization, getPluralCategory} from '../localization';
|
import {NgLocalization, getPluralCategory} from '../localization';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ export class I18nPluralPipe implements PipeTransform {
|
|||||||
constructor(private _localization: NgLocalization) {}
|
constructor(private _localization: NgLocalization) {}
|
||||||
|
|
||||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||||
if (value == null) return '';
|
if (isBlank(value)) return '';
|
||||||
|
|
||||||
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
||||||
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
||||||
|
@ -12,14 +12,13 @@
|
|||||||
* This module provides a set of common Pipes.
|
* This module provides a set of common Pipes.
|
||||||
*/
|
*/
|
||||||
import {AsyncPipe} from './async_pipe';
|
import {AsyncPipe} from './async_pipe';
|
||||||
|
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
|
||||||
import {DatePipe} from './date_pipe';
|
import {DatePipe} from './date_pipe';
|
||||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||||
import {JsonPipe} from './json_pipe';
|
import {JsonPipe} from './json_pipe';
|
||||||
import {LowerCasePipe} from './lowercase_pipe';
|
|
||||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||||
import {SlicePipe} from './slice_pipe';
|
import {SlicePipe} from './slice_pipe';
|
||||||
import {UpperCasePipe} from './uppercase_pipe';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
@ -32,9 +31,11 @@ export {
|
|||||||
LowerCasePipe,
|
LowerCasePipe,
|
||||||
PercentPipe,
|
PercentPipe,
|
||||||
SlicePipe,
|
SlicePipe,
|
||||||
|
TitleCasePipe,
|
||||||
UpperCasePipe
|
UpperCasePipe
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of Angular pipes that are likely to be used in each and every application.
|
* A collection of Angular pipes that are likely to be used in each and every application.
|
||||||
*/
|
*/
|
||||||
@ -46,6 +47,7 @@ export const COMMON_PIPES = [
|
|||||||
SlicePipe,
|
SlicePipe,
|
||||||
DecimalPipe,
|
DecimalPipe,
|
||||||
PercentPipe,
|
PercentPipe,
|
||||||
|
TitleCasePipe,
|
||||||
CurrencyPipe,
|
CurrencyPipe,
|
||||||
DatePipe,
|
DatePipe,
|
||||||
I18nPluralPipe,
|
I18nPluralPipe,
|
||||||
|
@ -1,37 +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 {Pipe, PipeTransform} from '@angular/core';
|
|
||||||
import {isBlank} from '../facade/lang';
|
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngModule CommonModule
|
|
||||||
* @whatItDoes Transforms string to lowercase.
|
|
||||||
* @howToUse `expression | lowercase`
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Converts value into a lowercase string using `String.prototype.toLowerCase()`.
|
|
||||||
*
|
|
||||||
* ### Example
|
|
||||||
*
|
|
||||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
|
||||||
*
|
|
||||||
* @stable
|
|
||||||
*/
|
|
||||||
@Pipe({name: 'lowercase'})
|
|
||||||
export class LowerCasePipe implements PipeTransform {
|
|
||||||
transform(value: string): string {
|
|
||||||
if (isBlank(value)) return value;
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
|
||||||
}
|
|
||||||
return value.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||||
|
|
||||||
import {NumberWrapper} from '../facade/lang';
|
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
||||||
|
|
||||||
import {NumberFormatStyle, NumberFormatter} from './intl';
|
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
|||||||
function formatNumber(
|
function formatNumber(
|
||||||
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
||||||
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
||||||
if (value == null) return null;
|
if (isBlank(value)) return null;
|
||||||
|
|
||||||
// Convert strings to numbers
|
// Convert strings to numbers
|
||||||
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
||||||
@ -41,13 +41,13 @@ function formatNumber(
|
|||||||
if (parts === null) {
|
if (parts === null) {
|
||||||
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
||||||
}
|
}
|
||||||
if (parts[1] != null) { // min integer digits
|
if (isPresent(parts[1])) { // min integer digits
|
||||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||||
}
|
}
|
||||||
if (parts[3] != null) { // min fraction digits
|
if (isPresent(parts[3])) { // min fraction digits
|
||||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||||
}
|
}
|
||||||
if (parts[5] != null) { // max fraction digits
|
if (isPresent(parts[5])) { // max fraction digits
|
||||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {isBlank} from '../facade/lang';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +58,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|||||||
@Pipe({name: 'slice', pure: false})
|
@Pipe({name: 'slice', pure: false})
|
||||||
export class SlicePipe implements PipeTransform {
|
export class SlicePipe implements PipeTransform {
|
||||||
transform(value: any, start: number, end?: number): any {
|
transform(value: any, start: number, end?: number): any {
|
||||||
if (value == null) return value;
|
if (isBlank(value)) return value;
|
||||||
|
|
||||||
if (!this.supports(value)) {
|
if (!this.supports(value)) {
|
||||||
throw new InvalidPipeArgumentError(SlicePipe, value);
|
throw new InvalidPipeArgumentError(SlicePipe, value);
|
||||||
|
@ -1,36 +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 {Pipe, PipeTransform} from '@angular/core';
|
|
||||||
import {isBlank} from '../facade/lang';
|
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @ngModule CommonModule
|
|
||||||
* @whatItDoes Transforms string to uppercase.
|
|
||||||
* @howToUse `expression | uppercase`
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Converts value into an uppercase string using `String.prototype.toUpperCase()`.
|
|
||||||
*
|
|
||||||
* ### Example
|
|
||||||
*
|
|
||||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
|
||||||
*
|
|
||||||
* @stable
|
|
||||||
*/
|
|
||||||
@Pipe({name: 'uppercase'})
|
|
||||||
export class UpperCasePipe implements PipeTransform {
|
|
||||||
transform(value: string): string {
|
|
||||||
if (isBlank(value)) return value;
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
|
||||||
}
|
|
||||||
return value.toUpperCase();
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component} from '@angular/core';
|
import {Component, ContentChild, TemplateRef} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
@ -29,7 +29,10 @@ export function main() {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [TestComponent],
|
declarations: [
|
||||||
|
TestComponent,
|
||||||
|
ComponentUsingTestComponent,
|
||||||
|
],
|
||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -74,7 +77,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should iterate over an array of objects', async(() => {
|
it('should iterate over an array of objects', async(() => {
|
||||||
const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
|
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
// INIT
|
// INIT
|
||||||
@ -92,7 +95,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should gracefully handle nulls', async(() => {
|
it('should gracefully handle nulls', async(() => {
|
||||||
const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
|
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
detectChangesAndExpectText('');
|
detectChangesAndExpectText('');
|
||||||
@ -137,8 +140,12 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested arrays', async(() => {
|
it('should repeat over nested arrays', async(() => {
|
||||||
const template = '<div *ngFor="let item of items">' +
|
const template = '<div>' +
|
||||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
|
'<div template="ngFor let item of items">' +
|
||||||
|
'<div template="ngFor let subitem of item">' +
|
||||||
|
'{{subitem}}-{{item.length}};' +
|
||||||
|
'</div>|' +
|
||||||
|
'</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -150,9 +157,10 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested arrays with no intermediate element', async(() => {
|
it('should repeat over nested arrays with no intermediate element', async(() => {
|
||||||
const template = '<div *ngFor="let item of items">' +
|
const template = '<div><template ngFor let-item [ngForOf]="items">' +
|
||||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
|
'<div template="ngFor let subitem of item">' +
|
||||||
'</div>';
|
'{{subitem}}-{{item.length}};' +
|
||||||
|
'</div></template></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [['a', 'b'], ['c']];
|
getComponent().items = [['a', 'b'], ['c']];
|
||||||
@ -162,11 +170,10 @@ export function main() {
|
|||||||
detectChangesAndExpectText('e-1;f-2;g-2;');
|
detectChangesAndExpectText('e-1;f-2;g-2;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
|
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
|
||||||
const template = `<div *ngFor="let item of items; let i=index">` +
|
const template =
|
||||||
`<div>{{i}}|</div>` +
|
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
||||||
`<div *ngIf="i % 2 == 0">even|</div>` +
|
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
||||||
`</div>`;
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -182,7 +189,8 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display indices correctly', async(() => {
|
it('should display indices correctly', async(() => {
|
||||||
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
|
const template =
|
||||||
|
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
@ -194,7 +202,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display first item correctly', async(() => {
|
it('should display first item correctly', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
|
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -206,7 +214,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display last item correctly', async(() => {
|
it('should display last item correctly', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
|
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -218,7 +226,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display even items correctly', async(() => {
|
it('should display even items correctly', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
|
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -230,7 +238,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display odd items correctly', async(() => {
|
it('should display odd items correctly', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
|
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2, 3];
|
getComponent().items = [0, 1, 2, 3];
|
||||||
@ -241,57 +249,54 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow to use a custom template', async(() => {
|
it('should allow to use a custom template', async(() => {
|
||||||
const template =
|
const tcTemplate =
|
||||||
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
|
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>';
|
||||||
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
|
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}});
|
||||||
fixture = createTestComponent(template);
|
const cutTemplate =
|
||||||
getComponent().items = ['a', 'b', 'c'];
|
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||||
|
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||||
|
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||||
|
|
||||||
|
const testComponent = fixture.debugElement.children[0];
|
||||||
|
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should use a default template if a custom one is null', async(() => {
|
it('should use a default template if a custom one is null', async(() => {
|
||||||
const template =
|
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||||
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
|
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`;
|
||||||
fixture = createTestComponent(template);
|
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||||
getComponent().items = ['a', 'b', 'c'];
|
const cutTemplate =
|
||||||
|
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||||
|
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||||
|
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||||
|
|
||||||
|
const testComponent = fixture.debugElement.children[0];
|
||||||
|
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should use a custom template when both default and a custom one are present', async(() => {
|
it('should use a custom template when both default and a custom one are present', async(() => {
|
||||||
const template =
|
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||||
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
|
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`;
|
||||||
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
|
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||||
fixture = createTestComponent(template);
|
const cutTemplate =
|
||||||
getComponent().items = ['a', 'b', 'c'];
|
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||||
|
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||||
|
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||||
|
|
||||||
|
const testComponent = fixture.debugElement.children[0];
|
||||||
|
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('track by', () => {
|
describe('track by', () => {
|
||||||
it('should console.warn if trackBy is not a function', async(() => {
|
|
||||||
// TODO(vicb): expect a warning message when we have a proper log service
|
|
||||||
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
|
|
||||||
fixture = createTestComponent(template);
|
|
||||||
fixture.componentInstance.value = 0;
|
|
||||||
fixture.detectChanges();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
|
|
||||||
// TODO(vicb): expect no warning message when we have a proper log service
|
|
||||||
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
|
|
||||||
fixture = createTestComponent(template);
|
|
||||||
fixture.componentInstance.items = ['a', 'b', 'c'];
|
|
||||||
fixture.componentInstance.value = null;
|
|
||||||
detectChangesAndExpectText('abc');
|
|
||||||
fixture.componentInstance.value = undefined;
|
|
||||||
detectChangesAndExpectText('abc');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should set the context to the component instance', async(() => {
|
it('should set the context to the component instance', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
thisArg = null;
|
thisArg = null;
|
||||||
@ -301,7 +306,9 @@ export function main() {
|
|||||||
|
|
||||||
it('should not replace tracked items', async(() => {
|
it('should not replace tracked items', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
||||||
|
<p>{{items[i]}}</p>
|
||||||
|
</template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
const buildItemList = () => {
|
const buildItemList = () => {
|
||||||
@ -317,7 +324,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should update implicit local variable on view', async(() => {
|
it('should update implicit local variable on view', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
|
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
||||||
@ -326,10 +333,9 @@ export function main() {
|
|||||||
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
||||||
detectChangesAndExpectText('red');
|
detectChangesAndExpectText('red');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should move items around and keep them updated ', async(() => {
|
it('should move items around and keep them updated ', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
|
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
||||||
@ -340,7 +346,8 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle added and removed items properly when tracking by index', async(() => {
|
it('should handle added and removed items properly when tracking by index', async(() => {
|
||||||
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
|
const template =
|
||||||
|
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = ['a', 'b', 'c', 'd'];
|
getComponent().items = ['a', 'b', 'c', 'd'];
|
||||||
@ -360,14 +367,19 @@ class Foo {
|
|||||||
|
|
||||||
@Component({selector: 'test-cmp', template: ''})
|
@Component({selector: 'test-cmp', template: ''})
|
||||||
class TestComponent {
|
class TestComponent {
|
||||||
value: any;
|
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
|
||||||
items: any[] = [1, 2];
|
items: any[] = [1, 2];
|
||||||
trackById(index: number, item: any): string { return item['id']; }
|
trackById(index: number, item: any): string { return item['id']; }
|
||||||
trackByIndex(index: number, item: any): number { return index; }
|
trackByIndex(index: number, item: any): number { return index; }
|
||||||
trackByContext(): void { thisArg = this; }
|
trackByContext(): void { thisArg = this; }
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
|
@Component({selector: 'outer-cmp', template: ''})
|
||||||
|
class ComponentUsingTestComponent {
|
||||||
|
items: any = [1, 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
|
||||||
|
|
||||||
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
||||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
@ -29,114 +28,203 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work in a template attribute', async(() => {
|
it('should work in a template attribute', async(() => {
|
||||||
const template = '<span *ngIf="booleanCondition">hello</span>';
|
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work on a template element', async(() => {
|
it('should work in a template element', async(() => {
|
||||||
const template = '<template [ngIf]="booleanCondition">hello2</template>';
|
const template =
|
||||||
|
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('hello2');
|
expect(fixture.nativeElement).toHaveText('hello2');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should toggle node when condition changes', async(() => {
|
it('should toggle node when condition changes', async(() => {
|
||||||
const template = '<span *ngIf="booleanCondition">hello</span>';
|
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().booleanCondition = true;
|
getComponent().booleanCondition = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle nested if correctly', async(() => {
|
it('should handle nested if correctly', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
|
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().booleanCondition = true;
|
getComponent().booleanCondition = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().nestedBooleanCondition = false;
|
getComponent().nestedBooleanCondition = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().nestedBooleanCondition = true;
|
getComponent().nestedBooleanCondition = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||||
expect(fixture.nativeElement).toHaveText('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update several nodes with if', async(() => {
|
it('should update several nodes with if', async(() => {
|
||||||
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
|
const template = '<div>' +
|
||||||
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
|
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' +
|
||||||
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
|
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' +
|
||||||
|
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3);
|
||||||
expect(getDOM().getText(fixture.nativeElement))
|
expect(getDOM().getText(fixture.nativeElement))
|
||||||
.toEqual('helloNumberhelloStringhelloFunction');
|
.toEqual('helloNumberhelloStringhelloFunction');
|
||||||
|
|
||||||
getComponent().numberCondition = 0;
|
getComponent().numberCondition = 0;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('helloString');
|
expect(fixture.nativeElement).toHaveText('helloString');
|
||||||
|
|
||||||
getComponent().numberCondition = 1;
|
getComponent().numberCondition = 1;
|
||||||
getComponent().stringCondition = 'bar';
|
getComponent().stringCondition = 'bar';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(fixture.nativeElement).toHaveText('helloNumber');
|
expect(fixture.nativeElement).toHaveText('helloNumber');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
|
it('should not add the element twice if the condition goes from true to true (JS)',
|
||||||
const template = '<span *ngIf="numberCondition">hello</span>';
|
async(() => {
|
||||||
|
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
let els = fixture.debugElement.queryAll(By.css('span'));
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(els.length).toEqual(1);
|
|
||||||
getDOM().addClass(els[0].nativeElement, 'marker');
|
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().numberCondition = 2;
|
getComponent().numberCondition = 2;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
els = fixture.debugElement.queryAll(By.css('span'));
|
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||||
expect(els.length).toEqual(1);
|
|
||||||
expect(getDOM().hasClass(els[0].nativeElement, 'marker')).toBe(true);
|
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('hello');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
|
||||||
|
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
||||||
|
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
|
||||||
|
|
||||||
|
getComponent().numberCondition = 2;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
||||||
|
.toBe(true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('else', () => {
|
||||||
|
it('should support else', async(() => {
|
||||||
|
const template = '<div>' +
|
||||||
|
'<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
|
||||||
|
'<template #elseBlock>FALSE</template>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||||
|
|
||||||
|
getComponent().booleanCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('FALSE');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support then and else', async(() => {
|
||||||
|
const template = '<div>' +
|
||||||
|
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
|
||||||
|
'<template #thenBlock>THEN</template>' +
|
||||||
|
'<template #elseBlock>ELSE</template>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('THEN');
|
||||||
|
|
||||||
|
getComponent().booleanCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('ELSE');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support dynamic else', async(() => {
|
||||||
|
const template = '<div>' +
|
||||||
|
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
|
||||||
|
'<template #b1>FALSE1</template>' +
|
||||||
|
'<template #b2>FALSE2</template>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||||
|
|
||||||
|
getComponent().booleanCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('FALSE1');
|
||||||
|
|
||||||
|
getComponent().nestedBooleanCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('FALSE2');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support binding to variable', async(() => {
|
||||||
|
const template = '<div>' +
|
||||||
|
'<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
|
||||||
|
'<template #elseBlock let-v>{{v}}</template>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('true');
|
||||||
|
|
||||||
|
getComponent().booleanCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.nativeElement).toHaveText('false');
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
|||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('ngPlural', () => {
|
describe('switch', () => {
|
||||||
let fixture: ComponentFixture<any>;
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||||
@ -33,25 +33,10 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display the template according to the exact value', async(() => {
|
it('should display the template according to the exact value', async(() => {
|
||||||
const template = '<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
|
||||||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
|
||||||
'</ul>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
|
||||||
|
|
||||||
getComponent().switchValue = 0;
|
|
||||||
detectChangesAndExpectText('you have no messages.');
|
|
||||||
|
|
||||||
getComponent().switchValue = 1;
|
|
||||||
detectChangesAndExpectText('you have one message.');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should display the template according to the exact numeric value', async(() => {
|
|
||||||
const template = '<div>' +
|
const template = '<div>' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
'<ul [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
|
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
||||||
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
|
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
||||||
'</ul></div>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
@ -66,9 +51,10 @@ export function main() {
|
|||||||
// https://github.com/angular/angular/issues/9868
|
// https://github.com/angular/angular/issues/9868
|
||||||
// https://github.com/angular/angular/issues/9882
|
// https://github.com/angular/angular/issues/9882
|
||||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||||
const template = '<ul [ngPlural]="switchValue">' +
|
const template = '<div>' +
|
||||||
|
'<ul [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
||||||
'</ul>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -78,10 +64,11 @@ export function main() {
|
|||||||
|
|
||||||
|
|
||||||
it('should be applicable to <ng-container> elements', async(() => {
|
it('should be applicable to <ng-container> elements', async(() => {
|
||||||
const template = '<ng-container [ngPlural]="switchValue">' +
|
const template = '<div>' +
|
||||||
|
'<ng-container [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="=0">you have no messages.</template>' +
|
'<template ngPluralCase="=0">you have no messages.</template>' +
|
||||||
'<template ngPluralCase="=1">you have one message.</template>' +
|
'<template ngPluralCase="=1">you have one message.</template>' +
|
||||||
'</ng-container>';
|
'</ng-container></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -93,10 +80,11 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display the template according to the category', async(() => {
|
it('should display the template according to the category', async(() => {
|
||||||
const template = '<ul [ngPlural]="switchValue">' +
|
const template = '<div>' +
|
||||||
|
'<ul [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||||
'</ul>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -108,10 +96,11 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should default to other when no matches are found', async(() => {
|
it('should default to other when no matches are found', async(() => {
|
||||||
const template = '<ul [ngPlural]="switchValue">' +
|
const template = '<div>' +
|
||||||
|
'<ul [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||||
'</ul>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -120,10 +109,11 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should prioritize value matches over category matches', async(() => {
|
it('should prioritize value matches over category matches', async(() => {
|
||||||
const template = '<ul [ngPlural]="switchValue">' +
|
const template = '<div>' +
|
||||||
|
'<ul [ngPlural]="switchValue">' +
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||||
'</ul>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
@ -29,19 +29,22 @@ export function main() {
|
|||||||
it('should add styles specified in an object literal', async(() => {
|
it('should add styles specified in an object literal', async(() => {
|
||||||
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should add and change styles specified in an object expression', async(() => {
|
it('should add and change styles specified in an object expression', async(() => {
|
||||||
const template = `<div [ngStyle]="expr"></div>`;
|
const template = `<div [ngStyle]="expr"></div>`;
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
let expr: {[k: string]: string};
|
||||||
|
|
||||||
getComponent().expr = {'max-width': '40px'};
|
getComponent().expr = {'max-width': '40px'};
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||||
|
|
||||||
let expr = getComponent().expr;
|
expr = getComponent().expr;
|
||||||
expr['max-width'] = '30%';
|
expr['max-width'] = '30%';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
||||||
|
@ -33,10 +33,11 @@ export function main() {
|
|||||||
|
|
||||||
describe('switch value changes', () => {
|
describe('switch value changes', () => {
|
||||||
it('should switch amongst when values', () => {
|
it('should switch amongst when values', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchCase="\'b\'">when b</li>' +
|
'<template ngSwitchCase="a"><li>when a</li></template>' +
|
||||||
'</ul>';
|
'<template ngSwitchCase="b"><li>when b</li></template>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -50,10 +51,11 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should switch amongst when values with fallback to default', () => {
|
it('should switch amongst when values with fallback to default', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchDefault>when default</li>' +
|
'<li template="ngSwitchCase \'a\'">when a</li>' +
|
||||||
'</ul>';
|
'<li template="ngSwitchDefault">when default</li>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default');
|
detectChangesAndExpectText('when default');
|
||||||
@ -69,14 +71,15 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should support multiple whens with the same value', () => {
|
it('should support multiple whens with the same value', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||||
'<li *ngSwitchDefault>when default1;</li>' +
|
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||||
'<li *ngSwitchDefault>when default2;</li>' +
|
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||||
'</ul>';
|
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
@ -91,11 +94,12 @@ export function main() {
|
|||||||
|
|
||||||
describe('when values changes', () => {
|
describe('when values changes', () => {
|
||||||
it('should switch amongst when values', () => {
|
it('should switch amongst when values', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchCase="when1">when 1;</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchCase="when2">when 2;</li>' +
|
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
|
||||||
'<li *ngSwitchDefault>when default;</li>' +
|
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
|
||||||
'</ul>';
|
'<template ngSwitchDefault><li>when default;</li></template>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
getComponent().when1 = 'a';
|
getComponent().when1 = 'a';
|
||||||
@ -144,10 +148,11 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create the default case if there is no other case', () => {
|
it('should create the default case if there is no other case', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchDefault>when default1;</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchDefault>when default2;</li>' +
|
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||||
'</ul>';
|
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
@ -155,14 +160,15 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow defaults before cases', () => {
|
it('should allow defaults before cases', () => {
|
||||||
const template = '<ul [ngSwitch]="switchValue">' +
|
const template = '<div>' +
|
||||||
'<li *ngSwitchDefault>when default1;</li>' +
|
'<ul [ngSwitch]="switchValue">' +
|
||||||
'<li *ngSwitchDefault>when default2;</li>' +
|
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||||
'</ul>';
|
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||||
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
|
@ -34,22 +34,29 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should do nothing if templateRef is `null`', async(() => {
|
it('should do nothing if templateRef is null', async(() => {
|
||||||
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
|
const template = `<template [ngTemplateOutlet]="null"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
detectChangesAndExpectText('');
|
detectChangesAndExpectText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should insert content specified by TemplateRef', async(() => {
|
it('should insert content specified by TemplateRef', async(() => {
|
||||||
const template = `<template #tpl>foo</template>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
detectChangesAndExpectText('');
|
||||||
|
|
||||||
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
|
|
||||||
|
setTplRef(refs.tplRefs.first);
|
||||||
detectChangesAndExpectText('foo');
|
detectChangesAndExpectText('foo');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should clear content if TemplateRef becomes `null`', async(() => {
|
it('should clear content if TemplateRef becomes null', async(() => {
|
||||||
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
@ -63,8 +70,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should swap content if TemplateRef changes', async(() => {
|
it('should swap content if TemplateRef changes', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
|
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||||
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -77,47 +83,70 @@ export function main() {
|
|||||||
detectChangesAndExpectText('bar');
|
detectChangesAndExpectText('bar');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display template if context is `null`', async(() => {
|
it('should display template if context is null', async(() => {
|
||||||
const template = `<template #tpl>foo</template>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
detectChangesAndExpectText('');
|
||||||
|
|
||||||
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
|
|
||||||
|
setTplRef(refs.tplRefs.first);
|
||||||
detectChangesAndExpectText('foo');
|
detectChangesAndExpectText('foo');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect initial context and changes', async(() => {
|
it('should reflect initial context and changes', async(() => {
|
||||||
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
|
setTplRef(refs.tplRefs.first);
|
||||||
|
|
||||||
detectChangesAndExpectText('bar');
|
detectChangesAndExpectText('bar');
|
||||||
|
|
||||||
fixture.componentInstance.context.foo = 'alter-bar';
|
fixture.componentInstance.context.foo = 'alter-bar';
|
||||||
|
|
||||||
detectChangesAndExpectText('alter-bar');
|
detectChangesAndExpectText('alter-bar');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect user defined `$implicit` property in the context', async(() => {
|
it('should reflect user defined $implicit property in the context', async(() => {
|
||||||
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
|
|
||||||
detectChangesAndExpectText('bra');
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
|
setTplRef(refs.tplRefs.first);
|
||||||
|
|
||||||
|
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
|
||||||
|
detectChangesAndExpectText('bar');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect context re-binding', async(() => {
|
it('should reflect context re-binding', async(() => {
|
||||||
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
|
const template =
|
||||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
|
setTplRef(refs.tplRefs.first);
|
||||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||||
|
|
||||||
detectChangesAndExpectText('brooks');
|
detectChangesAndExpectText('brooks');
|
||||||
|
|
||||||
fixture.componentInstance.context = {shawshank: 'was here'};
|
fixture.componentInstance.context = {shawshank: 'was here'};
|
||||||
|
|
||||||
detectChangesAndExpectText('was here');
|
detectChangesAndExpectText('was here');
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
||||||
class CaptureTplRefs {
|
class CaptureTplRefs {
|
||||||
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* @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 {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('LowerCasePipe', () => {
|
||||||
|
let pipe: LowerCasePipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new LowerCasePipe(); });
|
||||||
|
|
||||||
|
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
|
||||||
|
|
||||||
|
it('should lowercase when there is a new value', () => {
|
||||||
|
expect(pipe.transform('FOO')).toEqual('foo');
|
||||||
|
expect(pipe.transform('BAr')).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not support other objects',
|
||||||
|
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TitleCasePipe', () => {
|
||||||
|
let pipe: TitleCasePipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new TitleCasePipe(); });
|
||||||
|
|
||||||
|
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
|
||||||
|
|
||||||
|
it('should titlecase when there is a new value', () => {
|
||||||
|
expect(pipe.transform('bar')).toEqual('Bar');
|
||||||
|
expect(pipe.transform('foo')).toEqual('Foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not support other objects',
|
||||||
|
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('UpperCasePipe', () => {
|
||||||
|
let pipe: UpperCasePipe;
|
||||||
|
|
||||||
|
beforeEach(() => { pipe = new UpperCasePipe(); });
|
||||||
|
|
||||||
|
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
|
||||||
|
|
||||||
|
it('should uppercase when there is a new value', () => {
|
||||||
|
expect(pipe.transform('foo')).toEqual('FOO');
|
||||||
|
expect(pipe.transform('bar')).toEqual('BAR');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not support other objects',
|
||||||
|
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||||
|
});
|
||||||
|
}
|
@ -1,42 +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 {LowerCasePipe} from '@angular/common';
|
|
||||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('LowerCasePipe', () => {
|
|
||||||
let upper: string;
|
|
||||||
let lower: string;
|
|
||||||
let pipe: LowerCasePipe;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
lower = 'something';
|
|
||||||
upper = 'SOMETHING';
|
|
||||||
pipe = new LowerCasePipe();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('transform', () => {
|
|
||||||
it('should return lowercase', () => {
|
|
||||||
const val = pipe.transform(upper);
|
|
||||||
expect(val).toEqual(lower);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should lowercase when there is a new value', () => {
|
|
||||||
const val = pipe.transform(upper);
|
|
||||||
expect(val).toEqual(lower);
|
|
||||||
const val2 = pipe.transform('WAT');
|
|
||||||
expect(val2).toEqual('wat');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not support other objects',
|
|
||||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,43 +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 {UpperCasePipe} from '@angular/common';
|
|
||||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('UpperCasePipe', () => {
|
|
||||||
let upper: string;
|
|
||||||
let lower: string;
|
|
||||||
let pipe: UpperCasePipe;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
lower = 'something';
|
|
||||||
upper = 'SOMETHING';
|
|
||||||
pipe = new UpperCasePipe();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('transform', () => {
|
|
||||||
|
|
||||||
it('should return uppercase', () => {
|
|
||||||
const val = pipe.transform(lower);
|
|
||||||
expect(val).toEqual(upper);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should uppercase when there is a new value', () => {
|
|
||||||
const val = pipe.transform(lower);
|
|
||||||
expect(val).toEqual(upper);
|
|
||||||
const val2 = pipe.transform('wat');
|
|
||||||
expect(val2).toEqual('WAT');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not support other objects',
|
|
||||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 i18n>hello world</h1>
|
<h1>hello world</h1>
|
||||||
<a [routerLink]="['lazy']">lazy</a>
|
<a [routerLink]="['lazy']">lazy</a>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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: 'lazy-feature-comp', template: 'lazy feature, nested!'})
|
|
||||||
export class LazyFeatureNestedComponent {
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild([
|
|
||||||
{path: '', component: LazyFeatureNestedComponent, pathMatch: 'full'},
|
|
||||||
])],
|
|
||||||
declarations: [LazyFeatureNestedComponent]
|
|
||||||
})
|
|
||||||
export class LazyFeatureNestedModule {
|
|
||||||
}
|
|
@ -16,10 +16,7 @@ export class LazyFeatureComponent {
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild([
|
imports: [RouterModule.forChild([
|
||||||
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
||||||
{path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
|
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
|
||||||
path: 'nested-feature',
|
|
||||||
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
|
|
||||||
}
|
|
||||||
])],
|
])],
|
||||||
declarations: [LazyFeatureComponent]
|
declarations: [LazyFeatureComponent]
|
||||||
})
|
})
|
||||||
|
@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
|||||||
<!ELEMENT ex (#PCDATA)>
|
<!ELEMENT ex (#PCDATA)>
|
||||||
]>
|
]>
|
||||||
<messagebundle>
|
<messagebundle>
|
||||||
|
<msg id="3772663375917578720">other-3rdP-component</msg>
|
||||||
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
|
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
|
||||||
<msg id="3492007542396725315">Welcome</msg>
|
<msg id="3492007542396725315">Welcome</msg>
|
||||||
<msg id="3772663375917578720">other-3rdP-component</msg>
|
|
||||||
</messagebundle>
|
</messagebundle>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -44,6 +44,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
|||||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||||
<body>
|
<body>
|
||||||
|
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
|
||||||
|
<source>other-3rdP-component</source>
|
||||||
|
<target/>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
|
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
|
||||||
<source>translate me</source>
|
<source>translate me</source>
|
||||||
<target/>
|
<target/>
|
||||||
@ -54,10 +58,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
|||||||
<source>Welcome</source>
|
<source>Welcome</source>
|
||||||
<target/>
|
<target/>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
|
|
||||||
<source>other-3rdP-component</source>
|
|
||||||
<target/>
|
|
||||||
</trans-unit>
|
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -19,6 +19,7 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler
|
|||||||
|
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method.
|
* Main method.
|
||||||
* Standalone program that executes codegen using the ngtools API and tests that files were
|
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||||
@ -29,7 +30,6 @@ function main() {
|
|||||||
|
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
.then(() => codeGenTest())
|
.then(() => codeGenTest())
|
||||||
.then(() => i18nTest())
|
|
||||||
.then(() => lazyRoutesTest())
|
.then(() => lazyRoutesTest())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('All done!');
|
console.log('All done!');
|
||||||
@ -42,6 +42,7 @@ function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function codeGenTest() {
|
function codeGenTest() {
|
||||||
const basePath = path.join(__dirname, '../ngtools_src');
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
const project = path.join(basePath, 'tsconfig-build.json');
|
const project = path.join(basePath, 'tsconfig-build.json');
|
||||||
@ -51,9 +52,12 @@ function codeGenTest() {
|
|||||||
const config = tsc.readConfiguration(project, basePath);
|
const config = tsc.readConfiguration(project, basePath);
|
||||||
const hostContext = new NodeCompilerHostContext();
|
const hostContext = new NodeCompilerHostContext();
|
||||||
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
||||||
const host: ts.CompilerHost = Object.assign(
|
const host: ts.CompilerHost = Object.assign({}, delegateHost, {
|
||||||
{}, delegateHost,
|
writeFile: (fileName: string, ...rest: any[]) => {
|
||||||
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
|
wroteFiles.push(fileName);
|
||||||
|
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
|
||||||
|
}
|
||||||
|
});
|
||||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||||
|
|
||||||
config.ngOptions.basePath = basePath;
|
config.ngOptions.basePath = basePath;
|
||||||
@ -108,67 +112,6 @@ function codeGenTest() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function i18nTest() {
|
|
||||||
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); }});
|
|
||||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
|
||||||
|
|
||||||
config.ngOptions.basePath = basePath;
|
|
||||||
|
|
||||||
console.log(`>>> running i18n extraction for ${project}`);
|
|
||||||
return __NGTOOLS_PRIVATE_API_2
|
|
||||||
.extractI18n({
|
|
||||||
basePath,
|
|
||||||
compilerOptions: config.parsed.options, program, host,
|
|
||||||
angularCompilerOptions: config.ngOptions,
|
|
||||||
i18nFormat: 'xlf',
|
|
||||||
readResource: (fileName: string) => {
|
|
||||||
readResources.push(fileName);
|
|
||||||
return hostContext.readResource(fileName);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log(`>>> i18n extraction done, asserting read and wrote files`);
|
|
||||||
|
|
||||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
|
||||||
|
|
||||||
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
|
|
||||||
`Expected the bundle file to be "message.xlf".`);
|
|
||||||
|
|
||||||
allFiles.forEach((fileName: string) => {
|
|
||||||
// Skip tsconfig.
|
|
||||||
if (fileName.match(/tsconfig-build.json$/)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that file was read.
|
|
||||||
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('Extraction failed');
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function lazyRoutesTest() {
|
function lazyRoutesTest() {
|
||||||
const basePath = path.join(__dirname, '../ngtools_src');
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
@ -191,8 +134,6 @@ function lazyRoutesTest() {
|
|||||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||||
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||||
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||||
'./feature.module#FeatureModule': 'feature/feature.module.ts',
|
|
||||||
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
|
|
||||||
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||||
'./default.module': 'feature2/default.module.ts',
|
'./default.module': 'feature2/default.module.ts',
|
||||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||||
|
@ -27,14 +27,9 @@ function main() {
|
|||||||
const basePath = path.resolve(__dirname, '..');
|
const basePath = path.resolve(__dirname, '..');
|
||||||
const project = path.resolve(basePath, 'tsconfig-build.json');
|
const project = path.resolve(basePath, 'tsconfig-build.json');
|
||||||
const readFiles: string[] = [];
|
const readFiles: string[] = [];
|
||||||
const writtenFiles: {fileName: string, content: string}[] = [];
|
|
||||||
|
|
||||||
class AssertingHostContext extends NodeCompilerHostContext {
|
class AssertingHostContext extends NodeCompilerHostContext {
|
||||||
readFile(fileName: string): string {
|
readFile(fileName: string): string {
|
||||||
if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName)) {
|
|
||||||
// Only allow to read summaries from node_modules
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
readFiles.push(path.relative(basePath, fileName));
|
readFiles.push(path.relative(basePath, fileName));
|
||||||
return super.readFile(fileName);
|
return super.readFile(fileName);
|
||||||
}
|
}
|
||||||
@ -50,29 +45,16 @@ function main() {
|
|||||||
config.ngOptions.generateCodeForLibraries = false;
|
config.ngOptions.generateCodeForLibraries = false;
|
||||||
|
|
||||||
console.log(`>>> running codegen for ${project}`);
|
console.log(`>>> running codegen for ${project}`);
|
||||||
codegen(
|
codegen(config, (host) => new AssertingHostContext())
|
||||||
config,
|
|
||||||
(host) => {
|
|
||||||
host.writeFile = (fileName: string, content: string) => {
|
|
||||||
fileName = path.relative(basePath, fileName);
|
|
||||||
writtenFiles.push({fileName, content});
|
|
||||||
};
|
|
||||||
return new AssertingHostContext();
|
|
||||||
})
|
|
||||||
.then((exitCode: any) => {
|
.then((exitCode: any) => {
|
||||||
console.log(`>>> codegen done, asserting read files`);
|
console.log(`>>> codegen done, asserting read files`);
|
||||||
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
|
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
|
||||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
|
|
||||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
|
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
|
||||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
|
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
|
||||||
|
|
||||||
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
|
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
|
||||||
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
|
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
|
||||||
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
|
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
|
||||||
|
|
||||||
console.log(`>>> asserting written files`);
|
|
||||||
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
|
|
||||||
|
|
||||||
console.log(`done, no errors.`);
|
console.log(`done, no errors.`);
|
||||||
process.exit(exitCode);
|
process.exit(exitCode);
|
||||||
})
|
})
|
||||||
@ -115,11 +97,4 @@ function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
|
|||||||
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
|
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function assertWrittenFile(
|
|
||||||
files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) {
|
|
||||||
assert(
|
|
||||||
files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)),
|
|
||||||
`Expected some written files for ${filePattern} and content ${contentPattern}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "../node_modules/third_party",
|
"outDir": "../node_modules/third_party"
|
||||||
// Prevent scanning up the directory tree for types
|
|
||||||
"typeRoots": ["node_modules/@types"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,9 +14,7 @@
|
|||||||
"rootDir": "",
|
"rootDir": "",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"baseUrl": ".",
|
"baseUrl": "."
|
||||||
// Prevent scanning up the directory tree for types
|
|
||||||
"typeRoots": ["node_modules/@types"]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"ng-xi18n": "./src/extract_i18n.js"
|
"ng-xi18n": "./src/extract_i18n.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/tsc-wrapped": "0.5.1",
|
"@angular/tsc-wrapped": "0.4.2",
|
||||||
"reflect-metadata": "^0.1.2",
|
"reflect-metadata": "^0.1.2",
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
* Intended to be used in a build step.
|
* Intended to be used in a build step.
|
||||||
*/
|
*/
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||||
import {readFileSync} from 'fs';
|
import {readFileSync} from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -18,8 +19,11 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
|
import {Console} from './private_import_core';
|
||||||
|
|
||||||
|
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||||
const GENERATED_META_FILES = /\.json$/;
|
const GENERATED_META_FILES = /\.json$/;
|
||||||
|
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||||
|
|
||||||
const PREAMBLE = `/**
|
const PREAMBLE = `/**
|
||||||
* @fileoverview This file is generated by the Angular 2 template compiler.
|
* @fileoverview This file is generated by the Angular 2 template compiler.
|
||||||
@ -36,6 +40,29 @@ export class CodeGenerator {
|
|||||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||||
private ngCompilerHost: CompilerHost) {}
|
private ngCompilerHost: CompilerHost) {}
|
||||||
|
|
||||||
|
// Write codegen in a directory structure matching the sources.
|
||||||
|
private calculateEmitPath(filePath: string): string {
|
||||||
|
let root = this.options.basePath;
|
||||||
|
for (const eachRootDir of this.options.rootDirs || []) {
|
||||||
|
if (this.options.trace) {
|
||||||
|
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||||
|
}
|
||||||
|
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||||
|
root = eachRootDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transplant the codegen path to be inside the `genDir`
|
||||||
|
let relativePath: string = path.relative(root, filePath);
|
||||||
|
while (relativePath.startsWith('..' + path.sep)) {
|
||||||
|
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||||
|
// into `genDir`.
|
||||||
|
relativePath = relativePath.substr(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(this.options.genDir, relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
codegen(): Promise<any> {
|
codegen(): Promise<any> {
|
||||||
return this.compiler
|
return this.compiler
|
||||||
.compileAll(this.program.getSourceFiles().map(
|
.compileAll(this.program.getSourceFiles().map(
|
||||||
@ -43,7 +70,7 @@ export class CodeGenerator {
|
|||||||
.then(generatedModules => {
|
.then(generatedModules => {
|
||||||
generatedModules.forEach(generatedModule => {
|
generatedModules.forEach(generatedModule => {
|
||||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||||
PREAMBLE + generatedModule.source;
|
PREAMBLE + generatedModule.source;
|
||||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||||
@ -75,8 +102,14 @@ export class CodeGenerator {
|
|||||||
debug: options.debug === true,
|
debug: options.debug === true,
|
||||||
translations: transContent,
|
translations: transContent,
|
||||||
i18nFormat: cliOptions.i18nFormat,
|
i18nFormat: cliOptions.i18nFormat,
|
||||||
locale: cliOptions.locale
|
locale: cliOptions.locale,
|
||||||
|
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
|
||||||
|
GENERATED_FILES
|
||||||
});
|
});
|
||||||
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
|
||||||
|
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||||
|
}
|
||||||
|
@ -16,8 +16,6 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
|||||||
const DTS = /\.d\.ts$/;
|
const DTS = /\.d\.ts$/;
|
||||||
const NODE_MODULES = '/node_modules/';
|
const NODE_MODULES = '/node_modules/';
|
||||||
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
|
||||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
|
||||||
|
|
||||||
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
||||||
readResource(fileName: string): Promise<string>;
|
readResource(fileName: string): Promise<string>;
|
||||||
@ -30,7 +28,6 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
protected basePath: string;
|
protected basePath: string;
|
||||||
private genDir: string;
|
private genDir: string;
|
||||||
private resolverCache = new Map<string, ModuleMetadata[]>();
|
private resolverCache = new Map<string, ModuleMetadata[]>();
|
||||||
protected resolveModuleNameHost: CompilerHostContext;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected program: ts.Program, protected options: AngularCompilerOptions,
|
protected program: ts.Program, protected options: AngularCompilerOptions,
|
||||||
@ -41,31 +38,12 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
|
|
||||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||||
this.resolveModuleNameHost = Object.create(this.context);
|
|
||||||
|
|
||||||
// When calling ts.resolveModuleName,
|
|
||||||
// additional allow checks for .d.ts files to be done based on
|
|
||||||
// checks for .ngsummary.json files,
|
|
||||||
// so that our codegen depends on fewer inputs and requires to be called
|
|
||||||
// less often.
|
|
||||||
// This is needed as we use ts.resolveModuleName in reflector_host
|
|
||||||
// and it should be able to resolve summary file names.
|
|
||||||
this.resolveModuleNameHost.fileExists = (fileName: string): boolean => {
|
|
||||||
if (this.context.fileExists(fileName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (DTS.test(fileName)) {
|
|
||||||
const base = fileName.substring(0, fileName.length - 5);
|
|
||||||
return this.context.fileExists(base + '.ngsummary.json');
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use absolute paths on disk as canonical.
|
// We use absolute paths on disk as canonical.
|
||||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||||
|
|
||||||
moduleNameToFileName(m: string, containingFile: string): string|null {
|
moduleNameToFileName(m: string, containingFile: string) {
|
||||||
if (!containingFile || !containingFile.length) {
|
if (!containingFile || !containingFile.length) {
|
||||||
if (m.indexOf('.') === 0) {
|
if (m.indexOf('.') === 0) {
|
||||||
throw new Error('Resolution of relative paths requires a containing file.');
|
throw new Error('Resolution of relative paths requires a containing file.');
|
||||||
@ -75,8 +53,7 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
}
|
}
|
||||||
m = m.replace(EXT, '');
|
m = m.replace(EXT, '');
|
||||||
const resolved =
|
const resolved =
|
||||||
ts.resolveModuleName(
|
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
|
||||||
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
|
|
||||||
.resolvedModule;
|
.resolvedModule;
|
||||||
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
|
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
|
||||||
};
|
};
|
||||||
@ -181,12 +158,6 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
||||||
if (this.context.fileExists(metadataPath)) {
|
if (this.context.fileExists(metadataPath)) {
|
||||||
return this.readMetadata(metadataPath, filePath);
|
return this.readMetadata(metadataPath, filePath);
|
||||||
} else {
|
|
||||||
// If there is a .d.ts file but no metadata file we need to produce a
|
|
||||||
// v3 metadata from the .d.ts file as v3 includes the exports we need
|
|
||||||
// to resolve symbols.
|
|
||||||
return [this.upgradeVersion1Metadata(
|
|
||||||
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const sf = this.getSourceFile(filePath);
|
const sf = this.getSourceFile(filePath);
|
||||||
@ -202,27 +173,16 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
|
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
|
||||||
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
|
const metadatas = metadataOrMetadatas ?
|
||||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||||
[];
|
[];
|
||||||
const v1Metadata = metadatas.find(m => m.version === 1);
|
const v1Metadata = metadatas.find((m: any) => m['version'] === 1);
|
||||||
let v3Metadata = metadatas.find(m => m.version === 3);
|
let v3Metadata = metadatas.find((m: any) => m['version'] === 3);
|
||||||
if (!v3Metadata && v1Metadata) {
|
if (!v3Metadata && v1Metadata) {
|
||||||
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
|
|
||||||
}
|
|
||||||
this.resolverCache.set(filePath, metadatas);
|
|
||||||
return metadatas;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to read JSON file ${filePath}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private upgradeVersion1Metadata(v1Metadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
|
|
||||||
// patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file
|
// 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
|
// as the only difference between the versions is whether all exports are contained in
|
||||||
// the metadata and the `extends` clause.
|
// the metadata and the `extends` clause.
|
||||||
let v3Metadata: ModuleMetadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
|
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
|
||||||
if (v1Metadata.exports) {
|
if (v1Metadata.exports) {
|
||||||
v3Metadata.exports = v1Metadata.exports;
|
v3Metadata.exports = v1Metadata.exports;
|
||||||
}
|
}
|
||||||
@ -241,49 +201,23 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
v3Metadata.exports = exports.exports;
|
v3Metadata.exports = exports.exports;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v3Metadata;
|
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); }
|
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
|
||||||
|
|
||||||
loadSummary(filePath: string): string|null {
|
loadSummary(filePath: string): string { return this.context.readFile(filePath); }
|
||||||
if (this.context.fileExists(filePath)) {
|
|
||||||
return this.context.readFile(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getOutputFileName(sourceFilePath: string): string {
|
getOutputFileName(sourceFilePath: string): string {
|
||||||
return sourceFilePath.replace(EXT, '') + '.d.ts';
|
return sourceFilePath.replace(EXT, '') + '.d.ts';
|
||||||
}
|
}
|
||||||
|
|
||||||
isSourceFile(filePath: string): boolean {
|
|
||||||
const excludeRegex =
|
|
||||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
|
||||||
return !excludeRegex.test(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateEmitPath(filePath: string): string {
|
|
||||||
// Write codegen in a directory structure matching the sources.
|
|
||||||
let root = this.options.basePath;
|
|
||||||
for (const eachRootDir of this.options.rootDirs || []) {
|
|
||||||
if (this.options.trace) {
|
|
||||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
|
||||||
}
|
|
||||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
|
||||||
root = eachRootDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transplant the codegen path to be inside the `genDir`
|
|
||||||
let relativePath: string = path.relative(root, filePath);
|
|
||||||
while (relativePath.startsWith('..' + path.sep)) {
|
|
||||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
|
||||||
// into `genDir`.
|
|
||||||
relativePath = relativePath.substr(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.join(this.options.genDir, relativePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompilerHostContextAdapter {
|
export class CompilerHostContextAdapter {
|
||||||
|
@ -14,15 +14,41 @@
|
|||||||
// Must be imported first, because angular2 decorators throws on load.
|
// Must be imported first, because angular2 decorators throws on load.
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
|
import * as compiler from '@angular/compiler';
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Extractor} from './extractor';
|
import {Extractor} from './extractor';
|
||||||
|
|
||||||
function extract(
|
function extract(
|
||||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||||
program: ts.Program, host: ts.CompilerHost): Promise<void> {
|
program: ts.Program, host: ts.CompilerHost) {
|
||||||
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
|
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
|
||||||
|
|
||||||
|
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
||||||
|
|
||||||
|
return (bundlePromise).then(messageBundle => {
|
||||||
|
let ext: string;
|
||||||
|
let serializer: compiler.Serializer;
|
||||||
|
const format = (cliOptions.i18nFormat || 'xlf').toLowerCase();
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case 'xmb':
|
||||||
|
ext = 'xmb';
|
||||||
|
serializer = new compiler.Xmb();
|
||||||
|
break;
|
||||||
|
case 'xliff':
|
||||||
|
case 'xlf':
|
||||||
|
default:
|
||||||
|
ext = 'xlf';
|
||||||
|
serializer = new compiler.Xliff();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
|
||||||
|
host.writeFile(dstPath, messageBundle.write(serializer), false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
|
@ -15,74 +15,29 @@ import 'reflect-metadata';
|
|||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
import * as path from 'path';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
import {excludeFilePattern} from './codegen';
|
||||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
|
|
||||||
export class Extractor {
|
export class Extractor {
|
||||||
constructor(
|
constructor(
|
||||||
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
|
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
|
||||||
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
|
|
||||||
private program: ts.Program) {}
|
private program: ts.Program) {}
|
||||||
|
|
||||||
extract(formatName: string): Promise<void> {
|
extract(): Promise<compiler.MessageBundle> {
|
||||||
// Checks the format and returns the extension
|
return this.ngExtractor.extract(this.program.getSourceFiles().map(
|
||||||
const ext = this.getExtension(formatName);
|
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
||||||
|
|
||||||
const promiseBundle = this.extractBundle();
|
|
||||||
|
|
||||||
return promiseBundle.then(bundle => {
|
|
||||||
const content = this.serialize(bundle, ext);
|
|
||||||
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
|
|
||||||
this.host.writeFile(dstPath, content, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
extractBundle(): Promise<compiler.MessageBundle> {
|
|
||||||
const files = this.program.getSourceFiles().map(
|
|
||||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
|
|
||||||
|
|
||||||
return this.ngExtractor.extract(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(bundle: compiler.MessageBundle, ext: string): string {
|
|
||||||
let serializer: compiler.Serializer;
|
|
||||||
|
|
||||||
switch (ext) {
|
|
||||||
case 'xmb':
|
|
||||||
serializer = new compiler.Xmb();
|
|
||||||
break;
|
|
||||||
case 'xlf':
|
|
||||||
default:
|
|
||||||
serializer = new compiler.Xliff();
|
|
||||||
}
|
|
||||||
|
|
||||||
return bundle.write(serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
getExtension(formatName: string): string {
|
|
||||||
const format = (formatName || 'xlf').toLowerCase();
|
|
||||||
|
|
||||||
if (format === 'xmb') return 'xmb';
|
|
||||||
if (format === 'xlf' || format === 'xlif') return 'xlf';
|
|
||||||
|
|
||||||
throw new Error('Unsupported format "${formatName}"');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
|
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||||
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
|
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
|
||||||
if (!ngCompilerHost) {
|
if (!ngCompilerHost)
|
||||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
ngCompilerHost =
|
||||||
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
|
||||||
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
const {extractor: ngExtractor} = compiler.Extractor.create(
|
||||||
new CompilerHost(program, options, context);
|
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
|
||||||
}
|
return new Extractor(ngExtractor, ngCompilerHost, program);
|
||||||
|
|
||||||
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost);
|
|
||||||
|
|
||||||
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import 'reflect-metadata';
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
|
|
||||||
import {SyntaxError} from '@angular/compiler';
|
|
||||||
import {CodeGenerator} from './codegen';
|
import {CodeGenerator} from './codegen';
|
||||||
|
|
||||||
function codegen(
|
function codegen(
|
||||||
@ -29,7 +28,7 @@ export function main(
|
|||||||
const cliOptions = new tsc.NgcCliOptions(args);
|
const cliOptions = new tsc.NgcCliOptions(args);
|
||||||
|
|
||||||
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
|
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
|
||||||
if (e instanceof tsc.UserError || e instanceof SyntaxError) {
|
if (e instanceof tsc.UserError) {
|
||||||
consoleError(e.message);
|
consoleError(e.message);
|
||||||
return Promise.resolve(1);
|
return Promise.resolve(1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,16 +13,16 @@
|
|||||||
* something else.
|
* something else.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
|
import {AotCompilerHost, StaticReflector} from '@angular/compiler';
|
||||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CodeGenerator} from './codegen';
|
import {CodeGenerator} from './codegen';
|
||||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
import {Extractor} from './extractor';
|
|
||||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
|
|
||||||
|
|
||||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||||
basePath: string;
|
basePath: string;
|
||||||
compilerOptions: ts.CompilerOptions;
|
compilerOptions: ts.CompilerOptions;
|
||||||
@ -50,18 +50,9 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
|||||||
// Every new property under this line should be optional.
|
// Every new property under this line should be optional.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
||||||
|
|
||||||
export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
|
|
||||||
basePath: string;
|
|
||||||
compilerOptions: ts.CompilerOptions;
|
|
||||||
program: ts.Program;
|
|
||||||
host: ts.CompilerHost;
|
|
||||||
angularCompilerOptions: AngularCompilerOptions;
|
|
||||||
i18nFormat: string;
|
|
||||||
readResource: (fileName: string) => Promise<string>;
|
|
||||||
// Every new property under this line should be optional.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||||
@ -103,6 +94,7 @@ export class NgTools_InternalApi_NG_2 {
|
|||||||
return codeGenerator.codegen();
|
return codeGenerator.codegen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
* @private
|
* @private
|
||||||
@ -119,10 +111,7 @@ export class NgTools_InternalApi_NG_2 {
|
|||||||
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
||||||
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
||||||
|
|
||||||
const symbolCache = new StaticSymbolCache();
|
const staticReflector = new StaticReflector(ngCompilerHost);
|
||||||
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
|
|
||||||
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
|
|
||||||
const staticReflector = new StaticReflector(symbolResolver);
|
|
||||||
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
||||||
|
|
||||||
return Object.keys(routeMap).reduce(
|
return Object.keys(routeMap).reduce(
|
||||||
@ -132,19 +121,4 @@ export class NgTools_InternalApi_NG_2 {
|
|||||||
},
|
},
|
||||||
{});
|
{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<void> {
|
|
||||||
const hostContext: CompilerHostContext =
|
|
||||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
|
||||||
|
|
||||||
// Create the i18n extractor.
|
|
||||||
const extractor = Extractor.create(
|
|
||||||
options.angularCompilerOptions, options.program, options.host, hostContext);
|
|
||||||
|
|
||||||
return extractor.extract(options.i18nFormat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// We cannot depend directly to @angular/router.
|
// We cannot depend directly to @angular/router.
|
||||||
type Route = any;
|
type Route = any;
|
||||||
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
||||||
@ -61,37 +63,29 @@ export function listLazyRoutesOfModule(
|
|||||||
const className = entryRouteDef.className;
|
const className = entryRouteDef.className;
|
||||||
|
|
||||||
// List loadChildren of this single module.
|
// List loadChildren of this single module.
|
||||||
const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
|
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
|
||||||
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
|
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
|
||||||
const lazyRoutes: LazyRoute[] =
|
const lazyRoutes: LazyRoute[] =
|
||||||
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
|
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
|
||||||
|
const routes: LazyRouteMap = {};
|
||||||
|
|
||||||
const allLazyRoutes = lazyRoutes.reduce(
|
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
||||||
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
|
|
||||||
LazyRouteMap {
|
|
||||||
const route: string = lazyRoute.routeDef.toString();
|
const route: string = lazyRoute.routeDef.toString();
|
||||||
_assertRoute(allRoutes, lazyRoute);
|
_assertRoute(routes, lazyRoute);
|
||||||
allRoutes[route] = lazyRoute;
|
routes[route] = lazyRoute;
|
||||||
|
|
||||||
// StaticReflector does not support discovering annotations like `NgModule` on default
|
|
||||||
// exports
|
|
||||||
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
|
|
||||||
// we cannot parse its routes to see if they have loadChildren or not.
|
|
||||||
if (!lazyRoute.routeDef.className) {
|
|
||||||
return allRoutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lazyModuleSymbol = reflector.findDeclaration(
|
const lazyModuleSymbol = reflector.findDeclaration(
|
||||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||||
|
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||||
|
|
||||||
const subRoutes =
|
// Populate the map using the routes we just found.
|
||||||
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
subRoutes.forEach(subRoute => {
|
||||||
|
_assertRoute(routes, subRoute);
|
||||||
|
routes[subRoute.routeDef.toString()] = subRoute;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
|
return routes;
|
||||||
},
|
|
||||||
{});
|
|
||||||
|
|
||||||
return allLazyRoutes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -198,7 +192,7 @@ function _collectRoutes(
|
|||||||
*/
|
*/
|
||||||
function _collectLoadChildren(routes: Route[]): string[] {
|
function _collectLoadChildren(routes: Route[]): string[] {
|
||||||
return routes.reduce((m, r) => {
|
return routes.reduce((m, r) => {
|
||||||
if (r.loadChildren && typeof r.loadChildren === 'string') {
|
if (r.loadChildren) {
|
||||||
return m.concat(r.loadChildren);
|
return m.concat(r.loadChildren);
|
||||||
} else if (Array.isArray(r)) {
|
} else if (Array.isArray(r)) {
|
||||||
return m.concat(_collectLoadChildren(r));
|
return m.concat(_collectLoadChildren(r));
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
import {__core_private__ as r} from '@angular/core';
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
export type ReflectorReader = typeof r._ReflectorReader;
|
export type ReflectorReader = typeof r._ReflectorReader;
|
||||||
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||||
|
|
||||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
|
|
||||||
export type Console = typeof r._Console;
|
export type Console = typeof r._Console;
|
||||||
export const Console: typeof r.Console = r.Console;
|
export var Console: typeof r.Console = r.Console;
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ModuleMetadata} from '@angular/tsc-wrapped';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {CompilerHost} from '../src/compiler_host';
|
import {CompilerHost} from '../src/compiler_host';
|
||||||
@ -151,14 +150,12 @@ describe('CompilerHost', () => {
|
|||||||
|
|
||||||
it('should be able to read a metadata file', () => {
|
it('should be able to read a metadata file', () => {
|
||||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
|
||||||
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}
|
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
||||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
|
||||||
dummyMetadata
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to read empty metadata ', () => {
|
it('should be able to read empty metadata ', () => {
|
||||||
@ -184,21 +181,10 @@ describe('CompilerHost', () => {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should upgrade a missing metadata file into v3', () => {
|
|
||||||
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([
|
|
||||||
{__symbolic: 'module', version: 3, metadata: {}, exports: [{from: './lib/utils'}]}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dummyModule = 'export let foo: any[];';
|
const dummyModule = 'export let foo: any[];';
|
||||||
const dummyMetadata: ModuleMetadata = {
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 3,
|
|
||||||
metadata:
|
|
||||||
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
|
|
||||||
};
|
|
||||||
const FILES: Entry = {
|
const FILES: Entry = {
|
||||||
'tmp': {
|
'tmp': {
|
||||||
'src': {
|
'src': {
|
||||||
@ -218,7 +204,7 @@ const FILES: Entry = {
|
|||||||
'@angular': {
|
'@angular': {
|
||||||
'core.d.ts': dummyModule,
|
'core.d.ts': dummyModule,
|
||||||
'core.metadata.json':
|
'core.metadata.json':
|
||||||
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||||
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
|
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
|
||||||
'unused.d.ts': dummyModule,
|
'unused.d.ts': dummyModule,
|
||||||
'empty.d.ts': 'export declare var a: string;',
|
'empty.d.ts': 'export declare var a: string;',
|
||||||
@ -239,9 +225,6 @@ const FILES: Entry = {
|
|||||||
`,
|
`,
|
||||||
'v1.metadata.json':
|
'v1.metadata.json':
|
||||||
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||||
'v1_empty.d.ts': `
|
|
||||||
export * from './lib/utils';
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,17 +29,13 @@ describe('compiler-cli', () => {
|
|||||||
"types": [],
|
"types": [],
|
||||||
"outDir": "built",
|
"outDir": "built",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"module": "es2015",
|
"module": "es2015"
|
||||||
"moduleResolution": "node"
|
|
||||||
},
|
},
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"annotateForClosureCompiler": true
|
"annotateForClosureCompiler": true
|
||||||
},
|
},
|
||||||
"files": ["test.ts"]
|
"files": ["test.ts"]
|
||||||
}`);
|
}`);
|
||||||
const nodeModulesPath = path.resolve(basePath, 'node_modules');
|
|
||||||
fs.mkdirSync(nodeModulesPath);
|
|
||||||
fs.symlinkSync(path.resolve(__dirname, '..', '..'), path.resolve(nodeModulesPath, '@angular'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||||
|
@ -122,9 +122,11 @@ export class MockCompilerHost implements ts.CompilerHost {
|
|||||||
return ts.getDefaultLibFileName(options);
|
return ts.getDefaultLibFileName(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
|
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); }
|
||||||
|
|
||||||
getCurrentDirectory(): string { return this.context.currentDirectory; }
|
getCurrentDirectory(): string {
|
||||||
|
return this.context.currentDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||||
|
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
import {__core_private__ as r} from '@angular/core';
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
export const reflector: typeof r.reflector = r.reflector;
|
export var reflector: typeof r.reflector = r.reflector;
|
||||||
|
@ -32,9 +32,7 @@ export * from './src/aot/compiler_host';
|
|||||||
export * from './src/aot/static_reflector';
|
export * from './src/aot/static_reflector';
|
||||||
export * from './src/aot/static_reflection_capabilities';
|
export * from './src/aot/static_reflection_capabilities';
|
||||||
export * from './src/aot/static_symbol';
|
export * from './src/aot/static_symbol';
|
||||||
export * from './src/aot/static_symbol_resolver';
|
|
||||||
export * from './src/aot/summary_resolver';
|
export * from './src/aot/summary_resolver';
|
||||||
export * from './src/summary_resolver';
|
|
||||||
export {JitCompiler} from './src/jit/compiler';
|
export {JitCompiler} from './src/jit/compiler';
|
||||||
export * from './src/jit/compiler_factory';
|
export * from './src/jit/compiler_factory';
|
||||||
export * from './src/url_resolver';
|
export * from './src/url_resolver';
|
||||||
@ -62,5 +60,4 @@ export * from './src/style_compiler';
|
|||||||
export * from './src/template_parser/template_parser';
|
export * from './src/template_parser/template_parser';
|
||||||
export {ViewCompiler} from './src/view_compiler/view_compiler';
|
export {ViewCompiler} from './src/view_compiler/view_compiler';
|
||||||
export {AnimationParser} from './src/animation/animation_parser';
|
export {AnimationParser} from './src/animation/animation_parser';
|
||||||
export {SyntaxError} from './src/util';
|
|
||||||
// This file only reexports content of the `src` folder. Keep it that way.
|
// This file only reexports content of the `src` folder. Keep it that way.
|
||||||
|
@ -66,7 +66,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
ast.styles.forEach(entry => {
|
ast.styles.forEach(entry => {
|
||||||
const entries =
|
const entries =
|
||||||
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
||||||
stylesArr.push(o.literalMap(entries, null, true));
|
stylesArr.push(o.literalMap(entries));
|
||||||
});
|
});
|
||||||
|
|
||||||
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||||
@ -322,13 +322,12 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
const styleMap: any[] = [];
|
const styleMap: any[] = [];
|
||||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||||
variableValue = o.literalMap(styleMap, null, true);
|
variableValue = o.literalMap(styleMap);
|
||||||
}
|
}
|
||||||
lookupMap.push([stateName, variableValue]);
|
lookupMap.push([stateName, variableValue]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const compiledStatesMapStmt =
|
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
||||||
this._statesMapVar.set(o.literalMap(lookupMap, null, true)).toDeclStmt();
|
|
||||||
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
||||||
|
|
||||||
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
||||||
|
@ -6,10 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
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, identifierName} from '../compile_metadata';
|
||||||
import {StringMapWrapper} from '../facade/collection';
|
import {StringMapWrapper} from '../facade/collection';
|
||||||
import {isBlank, isPresent} from '../facade/lang';
|
import {isBlank, isPresent} from '../facade/lang';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
||||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||||
@ -34,7 +35,7 @@ export class AnimationEntryParseResult {
|
|||||||
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class AnimationParser {
|
export class AnimationParser {
|
||||||
constructor(private _schema: ElementSchemaRegistry) {}
|
constructor(private _schema: ElementSchemaRegistry) {}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {SchemaMetadata} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||||
import {AnimationParser} from '../animation/animation_parser';
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
@ -18,34 +20,34 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
|||||||
import {OutputEmitter} from '../output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {SummaryResolver} from '../summary_resolver';
|
|
||||||
import {TemplateParser} from '../template_parser/template_parser';
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||||
|
|
||||||
import {AotCompilerHost} from './compiler_host';
|
import {AotCompilerOptions} from './compiler_options';
|
||||||
import {GeneratedFile} from './generated_file';
|
import {GeneratedFile} from './generated_file';
|
||||||
|
import {StaticReflector} from './static_reflector';
|
||||||
import {StaticSymbol} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
import {AotSummaryResolver} from './summary_resolver';
|
||||||
import {serializeSummaries, summaryFileName} from './summary_serializer';
|
import {filterFileByPatterns} from './utils';
|
||||||
|
|
||||||
export class AotCompiler {
|
export class AotCompiler {
|
||||||
private _animationCompiler = new AnimationCompiler();
|
private _animationCompiler = new AnimationCompiler();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver,
|
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||||
private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||||
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
|
private _summaryResolver: AotSummaryResolver, private _localeId: string,
|
||||||
private _translationFormat: string, private _animationParser: AnimationParser,
|
private _translationFormat: string, private _animationParser: AnimationParser,
|
||||||
private _symbolResolver: StaticSymbolResolver) {}
|
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {}
|
||||||
|
|
||||||
clearCache() { this._metadataResolver.clearCache(); }
|
clearCache() { this._metadataResolver.clearCache(); }
|
||||||
|
|
||||||
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
|
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
|
||||||
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
|
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
|
||||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||||
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
|
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
|
||||||
return Promise
|
return Promise
|
||||||
.all(ngModules.map(
|
.all(ngModules.map(
|
||||||
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||||
@ -54,21 +56,27 @@ export class AotCompiler {
|
|||||||
const sourceModules = files.map(
|
const sourceModules = files.map(
|
||||||
file => this._compileSrcFile(
|
file => this._compileSrcFile(
|
||||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
|
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
|
||||||
file.ngModules, file.injectables));
|
file.ngModules));
|
||||||
return ListWrapper.flatten(sourceModules);
|
return ListWrapper.flatten(sourceModules);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compileSrcFile(
|
private _compileSrcFile(
|
||||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
|
directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||||
injectables: StaticSymbol[]): GeneratedFile[] {
|
ngModules: StaticSymbol[]): GeneratedFile[] {
|
||||||
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
||||||
const statements: o.Statement[] = [];
|
const statements: o.Statement[] = [];
|
||||||
const exportedVars: string[] = [];
|
const exportedVars: string[] = [];
|
||||||
const generatedFiles: GeneratedFile[] = [];
|
const generatedFiles: GeneratedFile[] = [];
|
||||||
|
|
||||||
generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables));
|
// 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));
|
||||||
|
|
||||||
// compile all ng modules
|
// compile all ng modules
|
||||||
exportedVars.push(
|
exportedVars.push(
|
||||||
@ -113,22 +121,6 @@ export class AotCompiler {
|
|||||||
return generatedFiles;
|
return generatedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createSummary(
|
|
||||||
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
|
|
||||||
ngModules: StaticSymbol[], injectables: StaticSymbol[]): GeneratedFile {
|
|
||||||
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl)
|
|
||||||
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
|
|
||||||
const typeSummaries = [
|
|
||||||
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
|
|
||||||
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
|
|
||||||
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)),
|
|
||||||
...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref))
|
|
||||||
];
|
|
||||||
const json = serializeSummaries(
|
|
||||||
this._host, this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
|
|
||||||
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
|
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
|
||||||
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
|
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
|
||||||
const providers: CompileProviderMetadata[] = [];
|
const providers: CompileProviderMetadata[] = [];
|
||||||
@ -150,7 +142,7 @@ export class AotCompiler {
|
|||||||
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
||||||
|
|
||||||
appCompileResult.dependencies.forEach((dep) => {
|
appCompileResult.dependencies.forEach((dep) => {
|
||||||
dep.placeholder.reference = this._symbolResolver.getStaticSymbol(
|
dep.placeholder.reference = this._staticReflector.getStaticSymbol(
|
||||||
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
|
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,7 +163,7 @@ export class AotCompiler {
|
|||||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
||||||
targetStatements: o.Statement[]): string {
|
targetStatements: o.Statement[]): string {
|
||||||
const hostMeta = createHostComponentMeta(
|
const hostMeta = createHostComponentMeta(
|
||||||
this._symbolResolver.getStaticSymbol(
|
this._staticReflector.getStaticSymbol(
|
||||||
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
|
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
|
||||||
compMeta);
|
compMeta);
|
||||||
const hostViewFactoryVar = this._compileComponent(
|
const hostViewFactoryVar = this._compileComponent(
|
||||||
@ -214,16 +206,16 @@ export class AotCompiler {
|
|||||||
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
||||||
if (componentStyles) {
|
if (componentStyles) {
|
||||||
targetStatements.push(
|
targetStatements.push(
|
||||||
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
|
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix));
|
||||||
}
|
}
|
||||||
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
||||||
targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult));
|
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult));
|
||||||
return viewResult.viewClassVar;
|
return viewResult.viewClassVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _codgenStyles(
|
private _codgenStyles(
|
||||||
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
|
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
|
||||||
_resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
|
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix);
|
||||||
return this._codegenSourceModule(
|
return this._codegenSourceModule(
|
||||||
fileUrl, _stylesModuleUrl(
|
fileUrl, _stylesModuleUrl(
|
||||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||||
@ -240,7 +232,7 @@ export class AotCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _resolveViewStatements(
|
function _resolveViewStatements(
|
||||||
reflector: StaticSymbolResolver, compileResult: ViewCompileResult): o.Statement[] {
|
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] {
|
||||||
compileResult.dependencies.forEach((dep) => {
|
compileResult.dependencies.forEach((dep) => {
|
||||||
if (dep instanceof ViewClassDependency) {
|
if (dep instanceof ViewClassDependency) {
|
||||||
const vfd = <ViewClassDependency>dep;
|
const vfd = <ViewClassDependency>dep;
|
||||||
@ -261,7 +253,7 @@ function _resolveViewStatements(
|
|||||||
|
|
||||||
|
|
||||||
function _resolveStyleStatements(
|
function _resolveStyleStatements(
|
||||||
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
|
reflector: StaticReflector, compileResult: CompiledStylesheet,
|
||||||
fileSuffix: string): o.Statement[] {
|
fileSuffix: string): o.Statement[] {
|
||||||
compileResult.dependencies.forEach((dep) => {
|
compileResult.dependencies.forEach((dep) => {
|
||||||
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
|
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
|
||||||
@ -311,27 +303,26 @@ export interface NgAnalyzedModules {
|
|||||||
srcUrl: string,
|
srcUrl: string,
|
||||||
directives: StaticSymbol[],
|
directives: StaticSymbol[],
|
||||||
pipes: StaticSymbol[],
|
pipes: StaticSymbol[],
|
||||||
ngModules: StaticSymbol[],
|
ngModules: StaticSymbol[]
|
||||||
injectables: StaticSymbol[]
|
|
||||||
}>;
|
}>;
|
||||||
symbolsMissingModule?: StaticSymbol[];
|
symbolsMissingModule?: StaticSymbol[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
|
|
||||||
|
|
||||||
// Returns all the source files and a mapping from modules to directives
|
// Returns all the source files and a mapping from modules to directives
|
||||||
export function analyzeNgModules(
|
export function analyzeNgModules(
|
||||||
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
programStaticSymbols: StaticSymbol[],
|
||||||
|
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||||
const {ngModules, symbolsMissingModule} =
|
const {ngModules, symbolsMissingModule} =
|
||||||
_createNgModules(programStaticSymbols, host, metadataResolver);
|
_createNgModules(programStaticSymbols, options, metadataResolver);
|
||||||
return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
|
return _analyzeNgModules(ngModules, symbolsMissingModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function analyzeAndValidateNgModules(
|
export function analyzeAndValidateNgModules(
|
||||||
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
programStaticSymbols: StaticSymbol[],
|
||||||
|
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||||
const result = analyzeNgModules(programStaticSymbols, host, metadataResolver);
|
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
|
||||||
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
|
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
|
||||||
const messages = result.symbolsMissingModule.map(
|
const messages = result.symbolsMissingModule.map(
|
||||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||||
@ -341,27 +332,16 @@ export function analyzeAndValidateNgModules(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _analyzeNgModules(
|
function _analyzeNgModules(
|
||||||
programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[],
|
ngModuleMetas: CompileNgModuleMetadata[],
|
||||||
symbolsMissingModule: StaticSymbol[],
|
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
|
||||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
|
||||||
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
||||||
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
|
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
|
||||||
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||||
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
||||||
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
||||||
const ngPipesByFile = new Map<string, StaticSymbol[]>();
|
const ngPipesByFile = new Map<string, StaticSymbol[]>();
|
||||||
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
|
|
||||||
const filePaths = new Set<string>();
|
const filePaths = new Set<string>();
|
||||||
|
|
||||||
// Make sure we produce an analyzed file for each input file
|
|
||||||
programSymbols.forEach((symbol) => {
|
|
||||||
const filePath = symbol.filePath;
|
|
||||||
filePaths.add(filePath);
|
|
||||||
if (metadataResolver.isInjectable(symbol)) {
|
|
||||||
ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Looping over all modules to construct:
|
// Looping over all modules to construct:
|
||||||
// - a map from file to modules `ngModulesByFile`,
|
// - a map from file to modules `ngModulesByFile`,
|
||||||
// - a map from file to directives `ngDirectivesByFile`,
|
// - a map from file to directives `ngDirectivesByFile`,
|
||||||
@ -389,20 +369,17 @@ function _analyzeNgModules(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const files: {
|
const files:
|
||||||
srcUrl: string,
|
{srcUrl: string,
|
||||||
directives: StaticSymbol[],
|
directives: StaticSymbol[],
|
||||||
pipes: StaticSymbol[],
|
pipes: StaticSymbol[],
|
||||||
ngModules: StaticSymbol[],
|
ngModules: StaticSymbol[]}[] = [];
|
||||||
injectables: StaticSymbol[]
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
filePaths.forEach((srcUrl) => {
|
filePaths.forEach((srcUrl) => {
|
||||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||||
const pipes = ngPipesByFile.get(srcUrl) || [];
|
const pipes = ngPipesByFile.get(srcUrl) || [];
|
||||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||||
const injectables = ngInjectablesByFile.get(srcUrl) || [];
|
files.push({srcUrl, directives, pipes, ngModules});
|
||||||
files.push({srcUrl, directives, pipes, ngModules, injectables});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -415,21 +392,30 @@ function _analyzeNgModules(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function extractProgramSymbols(
|
export function extractProgramSymbols(
|
||||||
staticSymbolResolver: StaticSymbolResolver, files: string[],
|
staticReflector: StaticReflector, files: string[],
|
||||||
host: NgAnalyzeModulesHost): StaticSymbol[] {
|
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
|
||||||
const staticSymbols: StaticSymbol[] = [];
|
const staticSymbols: StaticSymbol[] = [];
|
||||||
files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
|
files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => {
|
||||||
staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
|
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
|
||||||
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
|
if (!moduleMetadata) {
|
||||||
const symbolMeta = resolvedSymbol.metadata;
|
console.error(`WARNING: no metadata found for ${sourceFile}`);
|
||||||
if (symbolMeta) {
|
return;
|
||||||
if (symbolMeta.__symbolic != 'error') {
|
}
|
||||||
|
|
||||||
|
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.
|
// Ignore symbols that are only included to record error information.
|
||||||
staticSymbols.push(resolvedSymbol.symbol);
|
continue;
|
||||||
}
|
}
|
||||||
|
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return staticSymbols;
|
return staticSymbols;
|
||||||
}
|
}
|
||||||
@ -438,7 +424,8 @@ export function extractProgramSymbols(
|
|||||||
// that all directives / pipes that are present in the program
|
// that all directives / pipes that are present in the program
|
||||||
// are also declared by a module.
|
// are also declared by a module.
|
||||||
function _createNgModules(
|
function _createNgModules(
|
||||||
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
programStaticSymbols: StaticSymbol[],
|
||||||
|
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||||
metadataResolver: CompileMetadataResolver):
|
metadataResolver: CompileMetadataResolver):
|
||||||
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
|
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
|
||||||
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
||||||
@ -446,7 +433,7 @@ function _createNgModules(
|
|||||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||||
|
|
||||||
const addNgModule = (staticSymbol: any) => {
|
const addNgModule = (staticSymbol: any) => {
|
||||||
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
|
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
||||||
|
@ -34,11 +34,8 @@ import {AotCompilerHost} from './compiler_host';
|
|||||||
import {AotCompilerOptions} from './compiler_options';
|
import {AotCompilerOptions} from './compiler_options';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||||
import {StaticReflector} from './static_reflector';
|
import {StaticReflector} from './static_reflector';
|
||||||
import {StaticSymbolCache} from './static_symbol';
|
|
||||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
|
||||||
import {AotSummaryResolver} from './summary_resolver';
|
import {AotSummaryResolver} from './summary_resolver';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new AotCompiler based on options and a host.
|
* Creates a new AotCompiler based on options and a host.
|
||||||
*/
|
*/
|
||||||
@ -47,10 +44,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
|||||||
let translations: string = options.translations || '';
|
let translations: string = options.translations || '';
|
||||||
|
|
||||||
const urlResolver = createOfflineCompileUrlResolver();
|
const urlResolver = createOfflineCompileUrlResolver();
|
||||||
const symbolCache = new StaticSymbolCache();
|
const staticReflector = new StaticReflector(compilerHost);
|
||||||
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
|
||||||
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
|
||||||
const staticReflector = new StaticReflector(symbolResolver);
|
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
|
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
|
||||||
const config = new CompilerConfig({
|
const config = new CompilerConfig({
|
||||||
@ -66,16 +60,17 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
|||||||
const console = new Console();
|
const console = new Console();
|
||||||
const tmplParser =
|
const tmplParser =
|
||||||
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||||
|
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
|
||||||
const resolver = new CompileMetadataResolver(
|
const resolver = new CompileMetadataResolver(
|
||||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||||
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
||||||
staticReflector);
|
staticReflector);
|
||||||
// TODO(vicb): do not pass options.i18nFormat here
|
// TODO(vicb): do not pass options.i18nFormat here
|
||||||
const compiler = new AotCompiler(
|
const compiler = new AotCompiler(
|
||||||
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
|
resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||||
new ViewCompiler(config, elementSchemaRegistry),
|
new ViewCompiler(config, elementSchemaRegistry),
|
||||||
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
||||||
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
|
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
|
||||||
options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver);
|
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
|
||||||
return {compiler, reflector: staticReflector};
|
return {compiler, reflector: staticReflector};
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,16 @@
|
|||||||
|
|
||||||
import {ImportResolver} from '../output/path_util';
|
import {ImportResolver} from '../output/path_util';
|
||||||
|
|
||||||
|
import {StaticReflectorHost} from './static_reflector';
|
||||||
import {StaticSymbol} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {StaticSymbolResolverHost} from './static_symbol_resolver';
|
|
||||||
import {AotSummaryResolverHost} from './summary_resolver';
|
import {AotSummaryResolverHost} from './summary_resolver';
|
||||||
import {AotSummarySerializerHost} from './summary_serializer';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
||||||
* services and from underlying file systems.
|
* services and from underlying file systems.
|
||||||
*/
|
*/
|
||||||
export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver,
|
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver,
|
||||||
AotSummaryResolverHost, AotSummarySerializerHost {
|
AotSummaryResolverHost {
|
||||||
/**
|
/**
|
||||||
* Loads a resource (e.g. html / css)
|
* Loads a resource (e.g. html / css)
|
||||||
*/
|
*/
|
||||||
|
@ -11,4 +11,6 @@ export interface AotCompilerOptions {
|
|||||||
locale?: string;
|
locale?: string;
|
||||||
i18nFormat?: string;
|
i18nFormat?: string;
|
||||||
translations?: string;
|
translations?: string;
|
||||||
|
includeFilePattern?: RegExp;
|
||||||
|
excludeFilePattern?: RegExp;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
|
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
|
||||||
import {StaticReflector} from './static_reflector';
|
import {StaticReflector} from './static_reflector';
|
||||||
import {StaticSymbol} from './static_symbol';
|
|
||||||
|
|
||||||
export class StaticAndDynamicReflectionCapabilities {
|
export class StaticAndDynamicReflectionCapabilities {
|
||||||
static install(staticDelegate: StaticReflector) {
|
static install(staticDelegate: StaticReflector) {
|
||||||
@ -43,7 +42,7 @@ export class StaticAndDynamicReflectionCapabilities {
|
|||||||
method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
|
method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
|
||||||
importUri(type: any): string { return this.staticDelegate.importUri(type); }
|
importUri(type: any): string { return this.staticDelegate.importUri(type); }
|
||||||
resolveIdentifier(name: string, moduleUrl: string, runtime: any) {
|
resolveIdentifier(name: string, moduleUrl: string, runtime: any) {
|
||||||
return this.staticDelegate.resolveIdentifier(name, moduleUrl);
|
return this.staticDelegate.resolveIdentifier(name, moduleUrl, runtime);
|
||||||
}
|
}
|
||||||
resolveEnum(enumIdentifier: any, name: string): any {
|
resolveEnum(enumIdentifier: any, name: string): any {
|
||||||
if (isStaticType(enumIdentifier)) {
|
if (isStaticType(enumIdentifier)) {
|
||||||
|
@ -7,12 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||||
|
|
||||||
import {ReflectorReader} from '../private_import_core';
|
import {ReflectorReader} from '../private_import_core';
|
||||||
|
|
||||||
import {StaticSymbol} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
|
||||||
|
|
||||||
|
const SUPPORTED_SCHEMA_VERSION = 3;
|
||||||
const ANGULAR_IMPORT_LOCATIONS = {
|
const ANGULAR_IMPORT_LOCATIONS = {
|
||||||
coreDecorators: '@angular/core/src/metadata',
|
coreDecorators: '@angular/core/src/metadata',
|
||||||
diDecorators: '@angular/core/src/di/metadata',
|
diDecorators: '@angular/core/src/di/metadata',
|
||||||
@ -24,20 +22,66 @@ const ANGULAR_IMPORT_LOCATIONS = {
|
|||||||
|
|
||||||
const HIDDEN_KEY = /^\$.*\$$/;
|
const HIDDEN_KEY = /^\$.*\$$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
|
||||||
|
* services and from underlying file systems.
|
||||||
|
*/
|
||||||
|
export interface StaticReflectorHost {
|
||||||
|
/**
|
||||||
|
* Return a ModuleMetadata for the given module.
|
||||||
|
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
|
||||||
|
* produced and the module has exported variables or classes with decorators. Module metadata can
|
||||||
|
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
|
||||||
|
*
|
||||||
|
* @param modulePath is a string identifier for a module as an absolute path.
|
||||||
|
* @returns the metadata for the given module.
|
||||||
|
*/
|
||||||
|
getMetadataFor(modulePath: string): {[key: string]: any}[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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'`.
|
||||||
|
*/
|
||||||
|
moduleNameToFileName(moduleName: string, containingFile: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cache of static symbol used by the StaticReflector to return the same symbol for the
|
||||||
|
* same symbol values.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A static reflector implements enough of the Reflector API that is necessary to compile
|
* A static reflector implements enough of the Reflector API that is necessary to compile
|
||||||
* templates statically.
|
* templates statically.
|
||||||
*/
|
*/
|
||||||
export class StaticReflector implements ReflectorReader {
|
export class StaticReflector implements ReflectorReader {
|
||||||
|
private declarationCache = new Map<string, StaticSymbol>();
|
||||||
private annotationCache = new Map<StaticSymbol, any[]>();
|
private annotationCache = new Map<StaticSymbol, any[]>();
|
||||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
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 conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||||
private opaqueToken: StaticSymbol;
|
private opaqueToken: StaticSymbol;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private symbolResolver: StaticSymbolResolver,
|
private host: StaticReflectorHost,
|
||||||
|
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
|
||||||
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||||
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
|
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
|
||||||
private errorRecorder?: (error: any, fileName: string) => void) {
|
private errorRecorder?: (error: any, fileName: string) => void) {
|
||||||
@ -50,26 +94,12 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
importUri(typeOrFunc: StaticSymbol): string {
|
importUri(typeOrFunc: StaticSymbol): string {
|
||||||
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
|
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
||||||
return staticSymbol ? staticSymbol.filePath : null;
|
return staticSymbol ? staticSymbol.filePath : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
|
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
|
||||||
return this.findDeclaration(moduleUrl, name);
|
return this.findDeclaration(moduleUrl, name, '');
|
||||||
}
|
|
||||||
|
|
||||||
findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
|
|
||||||
return this.findSymbolDeclaration(
|
|
||||||
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
|
|
||||||
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
|
|
||||||
if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
|
|
||||||
return this.findSymbolDeclaration(resolvedSymbol.metadata);
|
|
||||||
} else {
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveEnum(enumIdentifier: any, name: string): any {
|
resolveEnum(enumIdentifier: any, name: string): any {
|
||||||
@ -98,7 +128,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
|
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
|
||||||
let propMetadata = this.propertyCache.get(type);
|
let propMetadata = this.propertyCache.get(type);
|
||||||
if (!propMetadata) {
|
if (!propMetadata) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type) || {};
|
||||||
propMetadata = {};
|
propMetadata = {};
|
||||||
if (classMetadata['extends']) {
|
if (classMetadata['extends']) {
|
||||||
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
|
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
|
||||||
@ -173,7 +203,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
private _methodNames(type: any): {[key: string]: boolean} {
|
private _methodNames(type: any): {[key: string]: boolean} {
|
||||||
let methodNames = this.methodCache.get(type);
|
let methodNames = this.methodCache.get(type);
|
||||||
if (!methodNames) {
|
if (!methodNames) {
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
const classMetadata = this.getTypeMetadata(type) || {};
|
||||||
methodNames = {};
|
methodNames = {};
|
||||||
if (classMetadata['extends']) {
|
if (classMetadata['extends']) {
|
||||||
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
|
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
|
||||||
@ -276,7 +306,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
* @param name the name of the type.
|
* @param name the name of the type.
|
||||||
*/
|
*/
|
||||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
||||||
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
|
return this.staticSymbolCache.get(declarationFile, name, members);
|
||||||
}
|
}
|
||||||
|
|
||||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||||
@ -287,6 +317,96 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public simplify(context: StaticSymbol, value: any): any {
|
public simplify(context: StaticSymbol, value: any): any {
|
||||||
const self = this;
|
const self = this;
|
||||||
@ -294,12 +414,60 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
const calling = new Map<StaticSymbol, boolean>();
|
const calling = new Map<StaticSymbol, boolean>();
|
||||||
|
|
||||||
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
|
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
|
||||||
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
|
||||||
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
|
let staticSymbol: StaticSymbol;
|
||||||
return resolvedSymbol ? resolvedSymbol.metadata : null;
|
if (expression['module']) {
|
||||||
|
staticSymbol =
|
||||||
|
self.findDeclaration(expression['module'], expression['name'], context.filePath);
|
||||||
|
} else {
|
||||||
|
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
|
||||||
|
}
|
||||||
|
return staticSymbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
|
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
||||||
|
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
|
||||||
|
const declarationValue =
|
||||||
|
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
|
||||||
|
return declarationValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOpaqueToken(context: StaticSymbol, value: any): boolean {
|
||||||
|
if (value && value.__symbolic === 'new' && value.expression) {
|
||||||
|
const target = value.expression;
|
||||||
|
if (target.__symbolic == 'reference') {
|
||||||
|
return sameSymbol(resolveReference(context, target), self.opaqueToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simplifyCall(expression: any) {
|
||||||
|
let callContext: {[name: string]: string}|undefined = undefined;
|
||||||
|
if (expression['__symbolic'] == 'call') {
|
||||||
|
const target = expression['expression'];
|
||||||
|
let functionSymbol: StaticSymbol;
|
||||||
|
let targetFunction: any;
|
||||||
|
if (target) {
|
||||||
|
switch (target.__symbolic) {
|
||||||
|
case 'reference':
|
||||||
|
// Find the function to call.
|
||||||
|
callContext = {name: target.name};
|
||||||
|
functionSymbol = resolveReference(context, target);
|
||||||
|
targetFunction = resolveReferenceValue(functionSymbol);
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
// Find the static method to call
|
||||||
|
if (target.expression.__symbolic == 'reference') {
|
||||||
|
functionSymbol = resolveReference(context, target.expression);
|
||||||
|
const classData = resolveReferenceValue(functionSymbol);
|
||||||
|
if (classData && classData.statics) {
|
||||||
|
targetFunction = classData.statics[target.member];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (targetFunction && targetFunction['__symbolic'] == 'function') {
|
if (targetFunction && targetFunction['__symbolic'] == 'function') {
|
||||||
if (calling.get(functionSymbol)) {
|
if (calling.get(functionSymbol)) {
|
||||||
throw new Error('Recursion not supported');
|
throw new Error('Recursion not supported');
|
||||||
@ -308,9 +476,11 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
try {
|
try {
|
||||||
const value = targetFunction['value'];
|
const value = targetFunction['value'];
|
||||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||||
|
// Determine the arguments
|
||||||
|
const args: any[] =
|
||||||
|
(expression['arguments'] || []).map((arg: any) => simplify(arg));
|
||||||
const parameters: string[] = targetFunction['parameters'];
|
const parameters: string[] = targetFunction['parameters'];
|
||||||
const defaults: any[] = targetFunction.defaults;
|
const defaults: any[] = targetFunction.defaults;
|
||||||
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
|
|
||||||
if (defaults && defaults.length > args.length) {
|
if (defaults && defaults.length > args.length) {
|
||||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||||
}
|
}
|
||||||
@ -332,6 +502,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
calling.delete(functionSymbol);
|
calling.delete(functionSymbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (depth === 0) {
|
if (depth === 0) {
|
||||||
// If depth is 0 we are evaluating the top level expression that is describing element
|
// If depth is 0 we are evaluating the top level expression that is describing element
|
||||||
@ -340,7 +511,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
return {__symbolic: 'ignore'};
|
return {__symbolic: 'ignore'};
|
||||||
}
|
}
|
||||||
return simplify(
|
return simplify(
|
||||||
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
|
{__symbolic: 'error', message: 'Function call not supported', context: callContext});
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplify(expression: any): any {
|
function simplify(expression: any): any {
|
||||||
@ -369,18 +540,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (expression instanceof StaticSymbol) {
|
if (expression instanceof StaticSymbol) {
|
||||||
// Stop simplification at builtin symbols
|
|
||||||
if (expression === self.opaqueToken || self.conversionMap.has(expression)) {
|
|
||||||
return expression;
|
return expression;
|
||||||
} else {
|
|
||||||
const staticSymbol = expression;
|
|
||||||
const declarationValue = resolveReferenceValue(staticSymbol);
|
|
||||||
if (declarationValue) {
|
|
||||||
return simplifyInContext(staticSymbol, declarationValue, depth + 1);
|
|
||||||
} else {
|
|
||||||
return staticSymbol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (expression) {
|
if (expression) {
|
||||||
if (expression['__symbolic']) {
|
if (expression['__symbolic']) {
|
||||||
@ -458,33 +618,50 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
||||||
return null;
|
return null;
|
||||||
case 'select':
|
case 'select':
|
||||||
const member = expression['member'];
|
|
||||||
let selectContext = context;
|
let selectContext = context;
|
||||||
let selectTarget = simplify(expression['expression']);
|
let selectTarget = simplify(expression['expression']);
|
||||||
if (selectTarget instanceof StaticSymbol) {
|
if (selectTarget instanceof StaticSymbol) {
|
||||||
const members = selectTarget.members.concat(member);
|
// 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 =
|
selectContext =
|
||||||
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||||
const declarationValue = resolveReferenceValue(selectContext);
|
if (declarationValue && declarationValue.statics) {
|
||||||
if (declarationValue) {
|
selectTarget = declarationValue.statics;
|
||||||
return simplifyInContext(selectContext, declarationValue, depth + 1);
|
|
||||||
} else {
|
} else {
|
||||||
return selectContext;
|
return selectContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
|
||||||
if (selectTarget && isPrimitive(member))
|
if (selectTarget && isPrimitive(member))
|
||||||
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
|
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
|
||||||
return null;
|
return null;
|
||||||
case 'reference':
|
case 'reference':
|
||||||
// Note: This only has to deal with variable references,
|
if (!expression['name']) {
|
||||||
// as symbol references have been converted into StaticSymbols already
|
return context;
|
||||||
// in the StaticSymbolResolver!
|
}
|
||||||
|
if (!expression.module) {
|
||||||
const name: string = expression['name'];
|
const name: string = expression['name'];
|
||||||
const localValue = scope.resolve(name);
|
const localValue = scope.resolve(name);
|
||||||
if (localValue != BindingScope.missing) {
|
if (localValue != BindingScope.missing) {
|
||||||
return localValue;
|
return localValue;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
staticSymbol = resolveReference(context, expression);
|
||||||
|
let result: any = staticSymbol;
|
||||||
|
let declarationValue = resolveReferenceValue(result);
|
||||||
|
if (declarationValue) {
|
||||||
|
if (isOpaqueToken(staticSymbol, declarationValue)) {
|
||||||
|
// If the referenced symbol is initalized by a new OpaqueToken we can keep the
|
||||||
|
// reference to the symbol.
|
||||||
|
return staticSymbol;
|
||||||
|
}
|
||||||
|
result = simplifyInContext(staticSymbol, declarationValue, depth + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
case 'class':
|
case 'class':
|
||||||
return context;
|
return context;
|
||||||
case 'function':
|
case 'function':
|
||||||
@ -492,26 +669,26 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
case 'new':
|
case 'new':
|
||||||
case 'call':
|
case 'call':
|
||||||
// Determine if the function is a built-in conversion
|
// Determine if the function is a built-in conversion
|
||||||
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
|
let target = expression['expression'];
|
||||||
if (staticSymbol instanceof StaticSymbol) {
|
if (target['module']) {
|
||||||
if (staticSymbol === self.opaqueToken) {
|
staticSymbol =
|
||||||
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
|
self.findDeclaration(target['module'], target['name'], context.filePath);
|
||||||
// but rather return the symbol to which the OpaqueToken is assigned to.
|
} else {
|
||||||
return context;
|
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
|
||||||
}
|
}
|
||||||
const argExpressions: any[] = expression['arguments'] || [];
|
|
||||||
let converter = self.conversionMap.get(staticSymbol);
|
let converter = self.conversionMap.get(staticSymbol);
|
||||||
if (converter) {
|
if (converter) {
|
||||||
const args =
|
let args: any[] = expression['arguments'];
|
||||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
if (!args) {
|
||||||
return converter(context, args);
|
args = [];
|
||||||
} else {
|
}
|
||||||
|
return converter(
|
||||||
|
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if the function is one we can simplify.
|
// Determine if the function is one we can simplify.
|
||||||
const targetFunction = resolveReferenceValue(staticSymbol);
|
return simplifyCall(expression);
|
||||||
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'error':
|
case 'error':
|
||||||
let message = produceErrorMessage(expression);
|
let message = produceErrorMessage(expression);
|
||||||
if (expression['line']) {
|
if (expression['line']) {
|
||||||
@ -532,9 +709,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
try {
|
try {
|
||||||
return simplify(value);
|
return simplify(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const members = context.members.length ? `.${context.members.join('.')}` : '';
|
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`;
|
||||||
const message =
|
|
||||||
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
|
|
||||||
if (e.fileName) {
|
if (e.fileName) {
|
||||||
throw positionalError(message, e.fileName, e.line, e.column);
|
throw positionalError(message, e.fileName, e.line, e.column);
|
||||||
}
|
}
|
||||||
@ -558,10 +733,40 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param module an absolute path to a module file.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
this.metadataCache.set(module, moduleMetadata);
|
||||||
|
}
|
||||||
|
return moduleMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
|
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
|
||||||
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
|
const moduleMetadata = this.getModuleMetadata(type.filePath);
|
||||||
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
|
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
|
||||||
{__symbolic: 'class'};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -652,6 +857,10 @@ class PopulatedScope extends BindingScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
|
||||||
|
return a === b || (a.name == b.name && a.filePath == b.filePath);
|
||||||
|
}
|
||||||
|
|
||||||
function shouldIgnore(value: any): boolean {
|
function shouldIgnore(value: any): boolean {
|
||||||
return value && value.__symbolic == 'ignore';
|
return value && value.__symbolic == 'ignore';
|
||||||
}
|
}
|
||||||
|
@ -14,23 +14,3 @@
|
|||||||
export class StaticSymbol {
|
export class StaticSymbol {
|
||||||
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache of static symbol used by the StaticReflector to return the same symbol for the
|
|
||||||
* same symbol values.
|
|
||||||
*/
|
|
||||||
export class StaticSymbolCache {
|
|
||||||
private cache = new Map<string, StaticSymbol>();
|
|
||||||
|
|
||||||
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
|
||||||
members = members || [];
|
|
||||||
const memberSuffix = members.length ? `.${ 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,289 +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 {SummaryResolver} from '../summary_resolver';
|
|
||||||
import {ValueTransformer, visitValue} from '../util';
|
|
||||||
|
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
|
||||||
|
|
||||||
export class ResolvedStaticSymbol {
|
|
||||||
constructor(public symbol: StaticSymbol, public metadata: any) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The host of the SymbolResolverHost disconnects the implementation from TypeScript / other
|
|
||||||
* language
|
|
||||||
* services and from underlying file systems.
|
|
||||||
*/
|
|
||||||
export interface StaticSymbolResolverHost {
|
|
||||||
/**
|
|
||||||
* 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}[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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'`.
|
|
||||||
*/
|
|
||||||
moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SUPPORTED_SCHEMA_VERSION = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is responsible for loading metadata per symbol,
|
|
||||||
* and normalizing references between symbols.
|
|
||||||
*/
|
|
||||||
export class StaticSymbolResolver {
|
|
||||||
private metadataCache = new Map<string, {[key: string]: any}>();
|
|
||||||
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
|
|
||||||
private resolvedFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
|
|
||||||
private summaryResolver: SummaryResolver<StaticSymbol>,
|
|
||||||
private errorRecorder?: (error: any, fileName: string) => void) {}
|
|
||||||
|
|
||||||
resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
|
|
||||||
if (staticSymbol.members.length > 0) {
|
|
||||||
return this._resolveSymbolMembers(staticSymbol);
|
|
||||||
}
|
|
||||||
let result = this._resolveSymbolFromSummary(staticSymbol);
|
|
||||||
if (!result) {
|
|
||||||
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
|
|
||||||
// have summaries, only .d.ts files. So we always need to check both, the summary
|
|
||||||
// and metadata.
|
|
||||||
this._createSymbolsOf(staticSymbol.filePath);
|
|
||||||
result = this.resolvedSymbols.get(staticSymbol);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
|
|
||||||
const members = staticSymbol.members;
|
|
||||||
const baseResolvedSymbol =
|
|
||||||
this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name));
|
|
||||||
if (!baseResolvedSymbol) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const baseMetadata = baseResolvedSymbol.metadata;
|
|
||||||
if (baseMetadata instanceof StaticSymbol) {
|
|
||||||
return new ResolvedStaticSymbol(
|
|
||||||
staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
|
|
||||||
} else if (baseMetadata && baseMetadata.__symbolic === 'class') {
|
|
||||||
if (baseMetadata.statics && members.length === 1) {
|
|
||||||
return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let value = baseMetadata;
|
|
||||||
for (let i = 0; i < members.length && value; i++) {
|
|
||||||
value = value[members[i]];
|
|
||||||
}
|
|
||||||
return new ResolvedStaticSymbol(staticSymbol, value);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
|
|
||||||
const summary = this.summaryResolver.resolveSummary(staticSymbol);
|
|
||||||
return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSymbolsOf(filePath: string): StaticSymbol[] {
|
|
||||||
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
|
|
||||||
// have summaries, only .d.ts files. So we always need to check both, the summary
|
|
||||||
// and metadata.
|
|
||||||
let symbols = new Set<StaticSymbol>(this.summaryResolver.getSymbolsOf(filePath));
|
|
||||||
this._createSymbolsOf(filePath);
|
|
||||||
this.resolvedSymbols.forEach((resolvedSymbol) => {
|
|
||||||
if (resolvedSymbol.symbol.filePath === filePath) {
|
|
||||||
symbols.add(resolvedSymbol.symbol);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Array.from(symbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createSymbolsOf(filePath: string) {
|
|
||||||
if (this.resolvedFilePaths.has(filePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.resolvedFilePaths.add(filePath);
|
|
||||||
const resolvedSymbols: ResolvedStaticSymbol[] = [];
|
|
||||||
const metadata = this.getModuleMetadata(filePath);
|
|
||||||
if (metadata['metadata']) {
|
|
||||||
// handle direct declarations of the symbol
|
|
||||||
Object.keys(metadata['metadata']).forEach((symbolName) => {
|
|
||||||
const symbolMeta = metadata['metadata'][symbolName];
|
|
||||||
resolvedSymbols.push(
|
|
||||||
this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle the symbols in one of the re-export location
|
|
||||||
if (metadata['exports']) {
|
|
||||||
for (const moduleExport of metadata['exports']) {
|
|
||||||
// handle the symbols in the list of explicitly re-exported symbols.
|
|
||||||
if (moduleExport.export) {
|
|
||||||
moduleExport.export.forEach((exportSymbol: any) => {
|
|
||||||
let symbolName: string;
|
|
||||||
if (typeof exportSymbol === 'string') {
|
|
||||||
symbolName = exportSymbol;
|
|
||||||
} else {
|
|
||||||
symbolName = exportSymbol.as;
|
|
||||||
}
|
|
||||||
let symName = symbolName;
|
|
||||||
if (typeof exportSymbol !== 'string') {
|
|
||||||
symName = exportSymbol.name;
|
|
||||||
}
|
|
||||||
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
|
|
||||||
if (resolvedModule) {
|
|
||||||
const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
|
|
||||||
const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
|
|
||||||
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// handle the symbols via export * directives.
|
|
||||||
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
|
|
||||||
if (resolvedModule) {
|
|
||||||
const nestedExports = this.getSymbolsOf(resolvedModule);
|
|
||||||
nestedExports.forEach((targetSymbol) => {
|
|
||||||
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
|
|
||||||
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolvedSymbols.forEach(
|
|
||||||
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
|
|
||||||
}
|
|
||||||
|
|
||||||
private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
class ReferenceTransformer extends ValueTransformer {
|
|
||||||
visitStringMap(map: {[key: string]: any}, functionParams: string[]): any {
|
|
||||||
const symbolic = map['__symbolic'];
|
|
||||||
if (symbolic === 'function') {
|
|
||||||
const oldLen = functionParams.length;
|
|
||||||
functionParams.push(...(map['parameters'] || []));
|
|
||||||
const result = super.visitStringMap(map, functionParams);
|
|
||||||
functionParams.length = oldLen;
|
|
||||||
return result;
|
|
||||||
} else if (symbolic === 'reference') {
|
|
||||||
const module = map['module'];
|
|
||||||
const name = map['name'];
|
|
||||||
if (!name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let filePath: string;
|
|
||||||
if (module) {
|
|
||||||
filePath = self.resolveModule(module, sourceSymbol.filePath);
|
|
||||||
if (!filePath) {
|
|
||||||
return {
|
|
||||||
__symbolic: 'error',
|
|
||||||
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const isFunctionParam = functionParams.indexOf(name) >= 0;
|
|
||||||
if (!isFunctionParam) {
|
|
||||||
filePath = sourceSymbol.filePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filePath) {
|
|
||||||
return self.getStaticSymbol(filePath, name);
|
|
||||||
} else {
|
|
||||||
// reference to a function parameter
|
|
||||||
return {__symbolic: 'reference', name: name};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return super.visitStringMap(map, functionParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
|
|
||||||
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
|
||||||
if (this.errorRecorder) {
|
|
||||||
this.errorRecorder(error, (context && context.filePath) || path);
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param module an absolute path to a module file.
|
|
||||||
*/
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
this.metadataCache.set(module, moduleMetadata);
|
|
||||||
}
|
|
||||||
return moduleMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
|
|
||||||
const filePath = this.resolveModule(module, containingFile);
|
|
||||||
if (!filePath) {
|
|
||||||
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
|
|
||||||
}
|
|
||||||
return this.getStaticSymbol(filePath, symbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveModule(module: string, containingFile: string): string {
|
|
||||||
try {
|
|
||||||
return this.host.moduleNameToFileName(module, containingFile);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Could not resolve module '${module}' relative to file ${containingFile}`);
|
|
||||||
this.reportError(new e, null, containingFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,12 +5,13 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
|
|
||||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
import {GeneratedFile} from './generated_file';
|
||||||
|
import {StaticReflector} from './static_reflector';
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {ResolvedStaticSymbol} from './static_symbol_resolver';
|
import {filterFileByPatterns} from './utils';
|
||||||
import {deserializeSummaries, summaryFileName} from './summary_serializer';
|
|
||||||
|
|
||||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
|
|
||||||
@ -18,60 +19,106 @@ export interface AotSummaryResolverHost {
|
|||||||
/**
|
/**
|
||||||
* Loads an NgModule/Directive/Pipe summary file
|
* Loads an NgModule/Directive/Pipe summary file
|
||||||
*/
|
*/
|
||||||
loadSummary(filePath: string): string /*|null*/;
|
loadSummary(filePath: string): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a file is a source file or not.
|
* Returns the output file path of a source file.
|
||||||
|
* E.g.
|
||||||
|
* `some_file.ts` -> `some_file.d.ts`
|
||||||
*/
|
*/
|
||||||
isSourceFile(sourceFilePath: string): boolean;
|
getOutputFileName(sourceFilePath: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
|
export interface AotSummaryResolverOptions {
|
||||||
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
|
includeFilePattern?: RegExp;
|
||||||
private loadedFilePaths = new Set<string>();
|
excludeFilePattern?: RegExp;
|
||||||
|
|
||||||
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
|
|
||||||
|
|
||||||
private _assertNoMembers(symbol: StaticSymbol) {
|
|
||||||
if (symbol.members.length) {
|
|
||||||
throw new Error(
|
|
||||||
`Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
|
export class AotSummaryResolver implements SummaryResolver {
|
||||||
this._assertNoMembers(staticSymbol);
|
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
|
||||||
let summary = this.summaryCache.get(staticSymbol);
|
|
||||||
if (!summary) {
|
constructor(
|
||||||
this._loadSummaryFile(staticSymbol.filePath);
|
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
|
||||||
summary = this.summaryCache.get(staticSymbol);
|
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 summary;
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
getSymbolsOf(filePath: string): StaticSymbol[] {
|
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
|
||||||
this._loadSummaryFile(filePath);
|
|
||||||
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadSummaryFile(filePath: string) {
|
resolveSummary(staticSymbol: StaticSymbol): any {
|
||||||
if (this.loadedFilePaths.has(filePath)) {
|
const filePath = staticSymbol.filePath;
|
||||||
return;
|
const name = staticSymbol.name;
|
||||||
}
|
const cacheKey = this._cacheKey(staticSymbol);
|
||||||
this.loadedFilePaths.add(filePath);
|
if (!filterFileByPatterns(filePath, this.options)) {
|
||||||
if (!this.host.isSourceFile(filePath)) {
|
let summary = this.summaryCache[cacheKey];
|
||||||
const summaryFilePath = summaryFileName(filePath);
|
const summaryFilePath = summaryFileName(filePath);
|
||||||
let json: string;
|
if (!summary) {
|
||||||
try {
|
try {
|
||||||
json = this.host.loadSummary(summaryFilePath);
|
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) {
|
} catch (e) {
|
||||||
console.error(`Error loading summary file ${summaryFilePath}`);
|
console.error(`Error loading summary file ${summaryFilePath}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
if (json) {
|
}
|
||||||
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
|
if (!summary) {
|
||||||
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, 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`;
|
||||||
}
|
}
|
||||||
|
@ -1,183 +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 {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
|
||||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
|
||||||
import {ValueTransformer, visitValue} from '../util';
|
|
||||||
|
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
|
||||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
|
||||||
|
|
||||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
|
||||||
|
|
||||||
|
|
||||||
export interface AotSummarySerializerHost {
|
|
||||||
/**
|
|
||||||
* Returns the output file path of a source file.
|
|
||||||
* E.g.
|
|
||||||
* `some_file.ts` -> `some_file.d.ts`
|
|
||||||
*/
|
|
||||||
getOutputFileName(sourceFilePath: string): string;
|
|
||||||
/**
|
|
||||||
* Returns whether a file is a source file or not.
|
|
||||||
*/
|
|
||||||
isSourceFile(sourceFilePath: string): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function serializeSummaries(
|
|
||||||
host: AotSummarySerializerHost, summaryResolver: SummaryResolver<StaticSymbol>,
|
|
||||||
symbolResolver: StaticSymbolResolver,
|
|
||||||
|
|
||||||
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
|
|
||||||
const serializer = new Serializer(host);
|
|
||||||
|
|
||||||
// for symbols, we use everything except for the class metadata itself
|
|
||||||
// (we keep the statics though), as the class metadata is contained in the
|
|
||||||
// CompileTypeSummary.
|
|
||||||
symbols.forEach(
|
|
||||||
(resolvedSymbol) => serializer.addOrMergeSummary(
|
|
||||||
{symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}));
|
|
||||||
// Add summaries that are referenced by the given symbols (transitively)
|
|
||||||
// Note: the serializer.symbols array might be growing while
|
|
||||||
// we execute the loop!
|
|
||||||
for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) {
|
|
||||||
const symbol = serializer.symbols[processedIndex];
|
|
||||||
if (!host.isSourceFile(symbol.filePath)) {
|
|
||||||
let summary = summaryResolver.resolveSummary(symbol);
|
|
||||||
if (!summary) {
|
|
||||||
// some symbols might originate from a plain typescript library
|
|
||||||
// that just exported .d.ts and .metadata.json files, i.e. where no summary
|
|
||||||
// files were created.
|
|
||||||
const resolvedSymbol = symbolResolver.resolveSymbol(symbol);
|
|
||||||
if (resolvedSymbol) {
|
|
||||||
summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (summary) {
|
|
||||||
serializer.addOrMergeSummary(summary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add type summaries.
|
|
||||||
// Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols,
|
|
||||||
// as the type summaries already contain the transitive data that they require
|
|
||||||
// (in a minimal way).
|
|
||||||
types.forEach((typeSummary) => {
|
|
||||||
serializer.addOrMergeSummary(
|
|
||||||
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
|
|
||||||
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
|
|
||||||
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
|
|
||||||
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
|
||||||
const symbol: StaticSymbol = id.reference;
|
|
||||||
if (!host.isSourceFile(symbol.filePath)) {
|
|
||||||
serializer.addOrMergeSummary(summaryResolver.resolveSummary(symbol));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return serializer.serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deserializeSummaries(
|
|
||||||
symbolCache: StaticSymbolCache, json: string): Summary<StaticSymbol>[] {
|
|
||||||
const deserializer = new Deserializer(symbolCache);
|
|
||||||
return deserializer.deserialize(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function summaryFileName(fileName: string): string {
|
|
||||||
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
|
|
||||||
return `${fileNameWithoutSuffix}.ngsummary.json`;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Serializer extends ValueTransformer {
|
|
||||||
symbols: StaticSymbol[] = [];
|
|
||||||
private indexBySymbol = new Map<StaticSymbol, number>();
|
|
||||||
// This now contains a `__symbol: number` in the place of
|
|
||||||
// StaticSymbols, but otherwise has the same shape as the original objects.
|
|
||||||
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
|
|
||||||
private processedSummaries: any[] = [];
|
|
||||||
|
|
||||||
constructor(private host: AotSummarySerializerHost) { super(); }
|
|
||||||
|
|
||||||
addOrMergeSummary(summary: Summary<StaticSymbol>) {
|
|
||||||
let symbolMeta = summary.metadata;
|
|
||||||
if (symbolMeta && symbolMeta.__symbolic === 'class') {
|
|
||||||
// For classes, we only keep their statics, but not the metadata
|
|
||||||
// of the class itself as that has been captured already via other summaries
|
|
||||||
// (e.g. DirectiveSummary, ...).
|
|
||||||
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics};
|
|
||||||
}
|
|
||||||
|
|
||||||
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
|
|
||||||
if (!processedSummary) {
|
|
||||||
processedSummary = this.processValue({symbol: summary.symbol});
|
|
||||||
this.processedSummaries.push(processedSummary);
|
|
||||||
this.processedSummaryBySymbol.set(summary.symbol, processedSummary);
|
|
||||||
}
|
|
||||||
// Note: == by purpose to compare with undefined!
|
|
||||||
if (processedSummary.metadata == null && symbolMeta != null) {
|
|
||||||
processedSummary.metadata = this.processValue(symbolMeta);
|
|
||||||
}
|
|
||||||
// Note: == by purpose to compare with undefined!
|
|
||||||
if (processedSummary.type == null && summary.type != null) {
|
|
||||||
processedSummary.type = this.processValue(summary.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(): string {
|
|
||||||
return JSON.stringify({
|
|
||||||
summaries: this.processedSummaries,
|
|
||||||
symbols: this.symbols.map((symbol, index) => {
|
|
||||||
return {
|
|
||||||
__symbol: index,
|
|
||||||
name: symbol.name,
|
|
||||||
// We convert the source filenames tinto output filenames,
|
|
||||||
// as the generated summary file will be used when teh current
|
|
||||||
// compilation unit is used as a library
|
|
||||||
filePath: this.host.getOutputFileName(symbol.filePath)
|
|
||||||
};
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private processValue(value: any): any { return visitValue(value, this, null); }
|
|
||||||
|
|
||||||
visitOther(value: any, context: any): any {
|
|
||||||
if (value instanceof StaticSymbol) {
|
|
||||||
let index = this.indexBySymbol.get(value);
|
|
||||||
// Note: == by purpose to compare with undefined!
|
|
||||||
if (index == null) {
|
|
||||||
index = this.indexBySymbol.size;
|
|
||||||
this.indexBySymbol.set(value, index);
|
|
||||||
this.symbols.push(value);
|
|
||||||
}
|
|
||||||
return {__symbol: index};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Deserializer extends ValueTransformer {
|
|
||||||
private symbols: StaticSymbol[];
|
|
||||||
|
|
||||||
constructor(private symbolCache: StaticSymbolCache) { super(); }
|
|
||||||
|
|
||||||
deserialize(json: string): Summary<StaticSymbol>[] {
|
|
||||||
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
|
|
||||||
this.symbols = data.symbols.map(
|
|
||||||
serializedSymbol => this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name));
|
|
||||||
return visitValue(data.summaries, this, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
visitStringMap(map: {[key: string]: any}, context: any): any {
|
|
||||||
if ('__symbol' in map) {
|
|
||||||
return this.symbols[map['__symbol']];
|
|
||||||
} else {
|
|
||||||
return super.visitStringMap(map, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
19
modules/@angular/compiler/src/aot/utils.ts
Normal file
19
modules/@angular/compiler/src/aot/utils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
@ -115,10 +115,10 @@ export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata
|
|||||||
export interface CompileIdentifierMetadata { reference: any; }
|
export interface CompileIdentifierMetadata { reference: any; }
|
||||||
|
|
||||||
export enum CompileSummaryKind {
|
export enum CompileSummaryKind {
|
||||||
|
Template,
|
||||||
Pipe,
|
Pipe,
|
||||||
Directive,
|
Directive,
|
||||||
NgModule,
|
NgModule
|
||||||
Injectable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,10 +126,9 @@ export enum CompileSummaryKind {
|
|||||||
* in other modules / components. However, this data is not enough to compile
|
* in other modules / components. However, this data is not enough to compile
|
||||||
* the directive / module itself.
|
* the directive / module itself.
|
||||||
*/
|
*/
|
||||||
export interface CompileTypeSummary {
|
export interface CompileSummary { summaryKind: CompileSummaryKind; }
|
||||||
summaryKind: CompileSummaryKind;
|
|
||||||
type: CompileTypeMetadata;
|
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; }
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompileDiDependencyMetadata {
|
export interface CompileDiDependencyMetadata {
|
||||||
isAttribute?: boolean;
|
isAttribute?: boolean;
|
||||||
@ -211,7 +210,7 @@ export class CompileStylesheetMetadata {
|
|||||||
/**
|
/**
|
||||||
* Summary Metadata regarding compilation of a template.
|
* Summary Metadata regarding compilation of a template.
|
||||||
*/
|
*/
|
||||||
export interface CompileTemplateSummary {
|
export interface CompileTemplateSummary extends CompileSummary {
|
||||||
animations: string[];
|
animations: string[];
|
||||||
ngContentSelectors: string[];
|
ngContentSelectors: string[];
|
||||||
encapsulation: ViewEncapsulation;
|
encapsulation: ViewEncapsulation;
|
||||||
@ -259,6 +258,7 @@ export class CompileTemplateMetadata {
|
|||||||
|
|
||||||
toSummary(): CompileTemplateSummary {
|
toSummary(): CompileTemplateSummary {
|
||||||
return {
|
return {
|
||||||
|
summaryKind: CompileSummaryKind.Template,
|
||||||
animations: this.animations.map(anim => anim.name),
|
animations: this.animations.map(anim => anim.name),
|
||||||
ngContentSelectors: this.ngContentSelectors,
|
ngContentSelectors: this.ngContentSelectors,
|
||||||
encapsulation: this.encapsulation
|
encapsulation: this.encapsulation
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import {ClassBuilder} from '../output/class_builder';
|
import {ClassBuilder} from '../output/class_builder';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import * as cdAst from '../expression_parser/ast';
|
import * as cdAst from '../expression_parser/ast';
|
||||||
import {isBlank} from '../facade/lang';
|
import {isBlank, isPresent} from '../facade/lang';
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import {ClassBuilder} from '../output/class_builder';
|
import {ClassBuilder} from '../output/class_builder';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
|||||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||||
if (receiver === this._implicitReceiver) {
|
if (receiver === this._implicitReceiver) {
|
||||||
const varExpr = this._getLocal(ast.name);
|
const varExpr = this._getLocal(ast.name);
|
||||||
if (varExpr) {
|
if (isPresent(varExpr)) {
|
||||||
result = varExpr.callFn(args);
|
result = varExpr.callFn(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
|||||||
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||||
if (receiver === this._implicitReceiver) {
|
if (receiver === this._implicitReceiver) {
|
||||||
const varExpr = this._getLocal(ast.name);
|
const varExpr = this._getLocal(ast.name);
|
||||||
if (varExpr) {
|
if (isPresent(varExpr)) {
|
||||||
throw new Error('Cannot assign to a reference or variable!');
|
throw new Error('Cannot assign to a reference or variable!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, ViewEncapsulation} from '@angular/core';
|
import {Component, Injectable, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
|
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||||
import {CompilerConfig} from './config';
|
import {CompilerConfig} from './config';
|
||||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import * as html from './ml_parser/ast';
|
import * as html from './ml_parser/ast';
|
||||||
import {HtmlParser} from './ml_parser/html_parser';
|
import {HtmlParser} from './ml_parser/html_parser';
|
||||||
import {InterpolationConfig} from './ml_parser/interpolation_config';
|
import {InterpolationConfig} from './ml_parser/interpolation_config';
|
||||||
@ -19,7 +18,7 @@ import {ResourceLoader} from './resource_loader';
|
|||||||
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
|
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
|
||||||
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
|
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
|
||||||
import {UrlResolver} from './url_resolver';
|
import {UrlResolver} from './url_resolver';
|
||||||
import {SyncAsyncResult, SyntaxError} from './util';
|
import {SyncAsyncResult} from './util';
|
||||||
|
|
||||||
export interface PrenormalizedTemplateMetadata {
|
export interface PrenormalizedTemplateMetadata {
|
||||||
componentType: any;
|
componentType: any;
|
||||||
@ -33,7 +32,7 @@ export interface PrenormalizedTemplateMetadata {
|
|||||||
animations?: CompileAnimationEntryMetadata[];
|
animations?: CompileAnimationEntryMetadata[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class DirectiveNormalizer {
|
export class DirectiveNormalizer {
|
||||||
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
||||||
|
|
||||||
@ -71,7 +70,7 @@ export class DirectiveNormalizer {
|
|||||||
} else if (prenormData.templateUrl) {
|
} else if (prenormData.templateUrl) {
|
||||||
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
||||||
} else {
|
} else {
|
||||||
throw new SyntaxError(
|
throw new Error(
|
||||||
`No template specified for component ${stringify(prenormData.componentType)}`);
|
`No template specified for component ${stringify(prenormData.componentType)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +104,7 @@ export class DirectiveNormalizer {
|
|||||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
template, stringify(prenomData.componentType), false, interpolationConfig);
|
||||||
if (rootNodesAndErrors.errors.length > 0) {
|
if (rootNodesAndErrors.errors.length > 0) {
|
||||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||||
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
throw new Error(`Template parse errors:\n${errorString}`);
|
||||||
}
|
}
|
||||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||||
styles: prenomData.styles,
|
styles: prenomData.styles,
|
||||||
|
@ -6,14 +6,14 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||||
import {stringify} from './facade/lang';
|
import {stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
import {splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolve a `Type` for {@link Directive}.
|
* Resolve a `Type` for {@link Directive}.
|
||||||
*
|
*
|
||||||
@ -21,7 +21,7 @@ import {splitAtColon} from './util';
|
|||||||
*
|
*
|
||||||
* See {@link Compiler}
|
* See {@link Compiler}
|
||||||
*/
|
*/
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class DirectiveResolver {
|
export class DirectiveResolver {
|
||||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||||
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
||||||
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
||||||
@ -13,12 +15,11 @@ import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
|
|||||||
import {CompilerConfig} from './config';
|
import {CompilerConfig} from './config';
|
||||||
import {Parser} from './expression_parser/parser';
|
import {Parser} from './expression_parser/parser';
|
||||||
import {Identifiers, createIdentifier} from './identifiers';
|
import {Identifiers, createIdentifier} from './identifiers';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
||||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||||
import {Console, LifecycleHooks} from './private_import_core';
|
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
||||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||||
import {BindingParser} from './template_parser/binding_parser';
|
import {BindingParser} from './template_parser/binding_parser';
|
||||||
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
||||||
@ -50,7 +51,7 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
|
|||||||
*
|
*
|
||||||
* So far, only `@Input` and the lifecycle hooks have been implemented.
|
* So far, only `@Input` and the lifecycle hooks have been implemented.
|
||||||
*/
|
*/
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class DirectiveWrapperCompiler {
|
export class DirectiveWrapperCompiler {
|
||||||
static dirWrapperClassName(id: CompileIdentifierMetadata) {
|
static dirWrapperClassName(id: CompileIdentifierMetadata) {
|
||||||
return `Wrapper_${identifierName(id)}`;
|
return `Wrapper_${identifierName(id)}`;
|
||||||
@ -128,6 +129,7 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||||||
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const fields: o.ClassField[] = [
|
const fields: o.ClassField[] = [
|
||||||
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
||||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
import * as chars from '../chars';
|
import * as chars from '../chars';
|
||||||
import {NumberWrapper} from '../facade/lang';
|
import {NumberWrapper, isPresent} from '../facade/lang';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
|
|
||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
Character,
|
Character,
|
||||||
@ -22,7 +22,7 @@ export enum TokenType {
|
|||||||
|
|
||||||
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
|
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class Lexer {
|
export class Lexer {
|
||||||
tokenize(text: string): Token[] {
|
tokenize(text: string): Token[] {
|
||||||
const scanner = new _Scanner(text);
|
const scanner = new _Scanner(text);
|
||||||
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
|
|||||||
return new Token(index, TokenType.Error, 0, message);
|
return new Token(index, TokenType.Error, 0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||||
|
|
||||||
class _Scanner {
|
class _Scanner {
|
||||||
length: number;
|
length: number;
|
||||||
@ -241,7 +241,7 @@ class _Scanner {
|
|||||||
this.advance();
|
this.advance();
|
||||||
str += two;
|
str += two;
|
||||||
}
|
}
|
||||||
if (threeCode != null && this.peek == threeCode) {
|
if (isPresent(threeCode) && this.peek == threeCode) {
|
||||||
this.advance();
|
this.advance();
|
||||||
str += three;
|
str += three;
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
import * as chars from '../chars';
|
import * as chars from '../chars';
|
||||||
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
|
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
|
|
||||||
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
|
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
|
||||||
@ -30,7 +31,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
|
|||||||
return new RegExp(pattern, 'g');
|
return new RegExp(pattern, 'g');
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class Parser {
|
export class Parser {
|
||||||
private errors: ParserError[] = [];
|
private errors: ParserError[] = [];
|
||||||
|
|
||||||
|
@ -9,13 +9,13 @@
|
|||||||
import * as i18n from './i18n_ast';
|
import * as i18n from './i18n_ast';
|
||||||
|
|
||||||
export function digest(message: i18n.Message): string {
|
export function digest(message: i18n.Message): string {
|
||||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
return message.id || sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decimalDigest(message: i18n.Message): string {
|
export function decimalDigest(message: i18n.Message): string {
|
||||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||||
return computeMsgId(parts.join(''), message.meaning);
|
return message.id || computeMsgId(parts.join(''), message.meaning);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,9 +14,7 @@ import {ViewEncapsulation} from '@angular/core';
|
|||||||
|
|
||||||
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
|
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
|
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
|
||||||
import {StaticReflector} from '../aot/static_reflector';
|
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
|
||||||
import {StaticSymbolCache} from '../aot/static_symbol';
|
|
||||||
import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver';
|
|
||||||
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
|
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
|
||||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
@ -28,17 +26,23 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
|||||||
import {NgModuleResolver} from '../ng_module_resolver';
|
import {NgModuleResolver} from '../ng_module_resolver';
|
||||||
import {ParseError} from '../parse_util';
|
import {ParseError} from '../parse_util';
|
||||||
import {PipeResolver} from '../pipe_resolver';
|
import {PipeResolver} from '../pipe_resolver';
|
||||||
|
import {Console} from '../private_import_core';
|
||||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||||
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
||||||
|
|
||||||
import {I18NHtmlParser} from './i18n_html_parser';
|
import {I18NHtmlParser} from './i18n_html_parser';
|
||||||
import {MessageBundle} from './message_bundle';
|
import {MessageBundle} from './message_bundle';
|
||||||
|
|
||||||
|
export interface ExtractorOptions {
|
||||||
|
includeFilePattern?: RegExp;
|
||||||
|
excludeFilePattern?: RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The host of the Extractor disconnects the implementation from TypeScript / other language
|
* The host of the Extractor disconnects the implementation from TypeScript / other language
|
||||||
* services and from underlying file systems.
|
* services and from underlying file systems.
|
||||||
*/
|
*/
|
||||||
export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
|
export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost {
|
||||||
/**
|
/**
|
||||||
* Loads a resource (e.g. html / css)
|
* Loads a resource (e.g. html / css)
|
||||||
*/
|
*/
|
||||||
@ -47,13 +51,14 @@ export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResol
|
|||||||
|
|
||||||
export class Extractor {
|
export class Extractor {
|
||||||
constructor(
|
constructor(
|
||||||
public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver,
|
private options: ExtractorOptions, public host: ExtractorHost,
|
||||||
private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {}
|
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
|
||||||
|
private metadataResolver: CompileMetadataResolver) {}
|
||||||
|
|
||||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options);
|
||||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
|
||||||
return Promise
|
return Promise
|
||||||
.all(ngModules.map(
|
.all(ngModules.map(
|
||||||
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||||
@ -86,14 +91,12 @@ export class Extractor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} {
|
static create(host: ExtractorHost, options: ExtractorOptions):
|
||||||
|
{extractor: Extractor, staticReflector: StaticReflector} {
|
||||||
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
||||||
|
|
||||||
const urlResolver = createOfflineCompileUrlResolver();
|
const urlResolver = createOfflineCompileUrlResolver();
|
||||||
const symbolCache = new StaticSymbolCache();
|
const staticReflector = new StaticReflector(host);
|
||||||
const summaryResolver = new AotSummaryResolver(host, symbolCache);
|
|
||||||
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
|
|
||||||
const staticReflector = new StaticReflector(staticSymbolResolver);
|
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
|
|
||||||
const config = new CompilerConfig({
|
const config = new CompilerConfig({
|
||||||
@ -108,13 +111,13 @@ export class Extractor {
|
|||||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
const resolver = new CompileMetadataResolver(
|
const resolver = new CompileMetadataResolver(
|
||||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||||
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options),
|
||||||
staticReflector);
|
elementSchemaRegistry, normalizer, staticReflector);
|
||||||
|
|
||||||
// TODO(vicb): implicit tags & attributes
|
// TODO(vicb): implicit tags & attributes
|
||||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||||
|
|
||||||
const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver);
|
const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver);
|
||||||
return {extractor, staticReflector};
|
return {extractor, staticReflector};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
|
|||||||
const _I18N_ATTR = 'i18n';
|
const _I18N_ATTR = 'i18n';
|
||||||
const _I18N_ATTR_PREFIX = 'i18n-';
|
const _I18N_ATTR_PREFIX = 'i18n-';
|
||||||
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
|
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
|
||||||
|
const MEANING_SEPARATOR = '|';
|
||||||
|
const ID_SEPARATOR = '@@';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract translatable messages from an html AST
|
* Extract translatable messages from an html AST
|
||||||
@ -53,22 +55,20 @@ enum _VisitorMode {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class _Visitor implements html.Visitor {
|
class _Visitor implements html.Visitor {
|
||||||
private _depth: number;
|
|
||||||
|
|
||||||
// <el i18n>...</el>
|
// <el i18n>...</el>
|
||||||
private _inI18nNode: boolean;
|
private _inI18nNode: boolean;
|
||||||
|
private _depth: number;
|
||||||
private _inImplicitNode: boolean;
|
private _inImplicitNode: boolean;
|
||||||
|
|
||||||
// <!--i18n-->...<!--/i18n-->
|
// <!--i18n-->...<!--/i18n-->
|
||||||
private _inI18nBlock: boolean;
|
|
||||||
private _blockMeaningAndDesc: string;
|
private _blockMeaningAndDesc: string;
|
||||||
private _blockChildren: html.Node[];
|
private _blockChildren: html.Node[];
|
||||||
private _blockStartDepth: number;
|
private _blockStartDepth: number;
|
||||||
|
private _inI18nBlock: boolean;
|
||||||
|
|
||||||
// {<icu message>}
|
// {<icu message>}
|
||||||
private _inIcu: boolean;
|
private _inIcu: boolean;
|
||||||
|
|
||||||
// set to void 0 when not in a section
|
|
||||||
private _msgCountAtSectionStart: number;
|
private _msgCountAtSectionStart: number;
|
||||||
private _errors: I18nError[];
|
private _errors: I18nError[];
|
||||||
private _mode: _VisitorMode;
|
private _mode: _VisitorMode;
|
||||||
@ -79,7 +79,7 @@ class _Visitor implements html.Visitor {
|
|||||||
// _VisitorMode.Merge only
|
// _VisitorMode.Merge only
|
||||||
private _translations: TranslationBundle;
|
private _translations: TranslationBundle;
|
||||||
private _createI18nMessage:
|
private _createI18nMessage:
|
||||||
(msg: html.Node[], meaning: string, description: string) => i18n.Message;
|
(msg: html.Node[], meaning: string, description: string, id: string) => i18n.Message;
|
||||||
|
|
||||||
|
|
||||||
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
|
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
|
||||||
@ -210,31 +210,50 @@ class _Visitor implements html.Visitor {
|
|||||||
this._depth++;
|
this._depth++;
|
||||||
const wasInI18nNode = this._inI18nNode;
|
const wasInI18nNode = this._inI18nNode;
|
||||||
const wasInImplicitNode = this._inImplicitNode;
|
const wasInImplicitNode = this._inImplicitNode;
|
||||||
let childNodes: html.Node[] = [];
|
let childNodes: html.Node[];
|
||||||
let translatedChildNodes: html.Node[];
|
|
||||||
|
|
||||||
// Extract:
|
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
|
||||||
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
|
// message
|
||||||
// - ICU messages
|
|
||||||
const i18nAttr = _getI18nAttr(el);
|
const i18nAttr = _getI18nAttr(el);
|
||||||
const i18nMeta = i18nAttr ? i18nAttr.value : '';
|
|
||||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||||
!this._isInTranslatableSection;
|
!this._isInTranslatableSection;
|
||||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||||
this._inImplicitNode = wasInImplicitNode || isImplicit;
|
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
||||||
|
|
||||||
if (!this._isInTranslatableSection && !this._inIcu) {
|
if (!this._isInTranslatableSection && !this._inIcu) {
|
||||||
if (i18nAttr || isTopLevelImplicit) {
|
if (i18nAttr) {
|
||||||
|
// explicit translation
|
||||||
this._inI18nNode = true;
|
this._inI18nNode = true;
|
||||||
const message = this._addMessage(el.children, i18nMeta);
|
const message = this._addMessage(el.children, i18nAttr.value);
|
||||||
translatedChildNodes = this._translateMessage(el, message);
|
childNodes = this._translateMessage(el, message);
|
||||||
|
} else if (isTopLevelImplicit) {
|
||||||
|
// implicit translation
|
||||||
|
this._inI18nNode = true;
|
||||||
|
const message = this._addMessage(el.children);
|
||||||
|
childNodes = this._translateMessage(el, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._mode == _VisitorMode.Extract) {
|
if (this._mode == _VisitorMode.Extract) {
|
||||||
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
||||||
if (isTranslatable) this._openTranslatableSection(el);
|
if (isTranslatable) {
|
||||||
|
this._openTranslatableSection(el);
|
||||||
|
}
|
||||||
html.visitAll(this, el.children);
|
html.visitAll(this, el.children);
|
||||||
if (isTranslatable) this._closeTranslatableSection(el, el.children);
|
if (isTranslatable) {
|
||||||
|
this._closeTranslatableSection(el, el.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
|
||||||
|
childNodes = [];
|
||||||
|
el.children.forEach(child => {
|
||||||
|
const visited = child.visit(this, context);
|
||||||
|
if (visited && !this._isInTranslatableSection) {
|
||||||
|
// Do not add the children from translatable sections (= i18n blocks here)
|
||||||
|
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
||||||
|
childNodes = childNodes.concat(visited);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i18nAttr || isTopLevelImplicit) {
|
if (i18nAttr || isTopLevelImplicit) {
|
||||||
@ -246,19 +265,20 @@ class _Visitor implements html.Visitor {
|
|||||||
// Descend into child nodes for extraction
|
// Descend into child nodes for extraction
|
||||||
html.visitAll(this, el.children);
|
html.visitAll(this, el.children);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this._mode === _VisitorMode.Merge) {
|
if (this._mode == _VisitorMode.Merge) {
|
||||||
const visitNodes = translatedChildNodes || el.children;
|
// Translate attributes in ICU messages
|
||||||
visitNodes.forEach(child => {
|
childNodes = [];
|
||||||
|
el.children.forEach(child => {
|
||||||
const visited = child.visit(this, context);
|
const visited = child.visit(this, context);
|
||||||
if (visited && !this._isInTranslatableSection) {
|
if (visited && !this._isInTranslatableSection) {
|
||||||
// Do not add the children from translatable sections (= i18n blocks here)
|
// Do not add the children from translatable sections (= i18n blocks here)
|
||||||
// They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
|
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
||||||
childNodes = childNodes.concat(visited);
|
childNodes = childNodes.concat(visited);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._visitAttributesOf(el);
|
this._visitAttributesOf(el);
|
||||||
|
|
||||||
@ -267,6 +287,7 @@ class _Visitor implements html.Visitor {
|
|||||||
this._inImplicitNode = wasInImplicitNode;
|
this._inImplicitNode = wasInImplicitNode;
|
||||||
|
|
||||||
if (this._mode === _VisitorMode.Merge) {
|
if (this._mode === _VisitorMode.Merge) {
|
||||||
|
// There are no childNodes in translatable sections - those nodes will be replace anyway
|
||||||
const translatedAttrs = this._translateAttributes(el);
|
const translatedAttrs = this._translateAttributes(el);
|
||||||
return new html.Element(
|
return new html.Element(
|
||||||
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
||||||
@ -311,15 +332,15 @@ class _Visitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add a translatable message
|
// add a translatable message
|
||||||
private _addMessage(ast: html.Node[], meaningAndDesc?: string): i18n.Message {
|
private _addMessage(ast: html.Node[], msgMeta?: string): i18n.Message {
|
||||||
if (ast.length == 0 ||
|
if (ast.length == 0 ||
|
||||||
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
|
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
|
||||||
// Do not create empty messages
|
// Do not create empty messages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [meaning, description] = _splitMeaningAndDesc(meaningAndDesc);
|
const {meaning, description, id} = _parseMessageMeta(msgMeta);
|
||||||
const message = this._createI18nMessage(ast, meaning, description);
|
const message = this._createI18nMessage(ast, meaning, description, id);
|
||||||
this._messages.push(message);
|
this._messages.push(message);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -349,7 +370,7 @@ class _Visitor implements html.Visitor {
|
|||||||
attributes.forEach(attr => {
|
attributes.forEach(attr => {
|
||||||
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
|
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
|
||||||
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
|
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
|
||||||
_splitMeaningAndDesc(attr.value)[0];
|
_parseMessageMeta(attr.value).meaning;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -363,7 +384,7 @@ class _Visitor implements html.Visitor {
|
|||||||
|
|
||||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||||
const meaning = i18nAttributeMeanings[attr.name];
|
const meaning = i18nAttributeMeanings[attr.name];
|
||||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
const message: i18n.Message = this._createI18nMessage([attr], meaning, '', '');
|
||||||
const nodes = this._translations.get(message);
|
const nodes = this._translations.get(message);
|
||||||
if (nodes) {
|
if (nodes) {
|
||||||
if (nodes[0] instanceof html.Text) {
|
if (nodes[0] instanceof html.Text) {
|
||||||
@ -413,7 +434,7 @@ class _Visitor implements html.Visitor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A translatable section could be:
|
* A translatable section could be:
|
||||||
* - the content of translatable element,
|
* - a translatable element,
|
||||||
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
||||||
*/
|
*/
|
||||||
private get _isInTranslatableSection(): boolean {
|
private get _isInTranslatableSection(): boolean {
|
||||||
@ -477,8 +498,16 @@ function _getI18nAttr(p: html.Element): html.Attribute {
|
|||||||
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
|
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _splitMeaningAndDesc(i18n: string): [string, string] {
|
function _parseMessageMeta(i18n: string): {meaning: string, description: string, id: string} {
|
||||||
if (!i18n) return ['', ''];
|
if (!i18n) return {meaning: '', description: '', id: ''};
|
||||||
const pipeIndex = i18n.indexOf('|');
|
|
||||||
return pipeIndex == -1 ? ['', i18n] : [i18n.slice(0, pipeIndex), i18n.slice(pipeIndex + 1)];
|
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
||||||
|
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
||||||
|
const [meaningAndDesc, id] =
|
||||||
|
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
||||||
|
const [meaning, description] = (descIndex > -1) ?
|
||||||
|
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
||||||
|
['', meaningAndDesc];
|
||||||
|
|
||||||
|
return {meaning, description, id};
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,12 @@ export class Message {
|
|||||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||||
* @param meaning
|
* @param meaning
|
||||||
* @param description
|
* @param description
|
||||||
|
* @param id
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||||
public description: string) {}
|
public description: string, public id: string) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Node {
|
export interface Node {
|
||||||
|
@ -22,11 +22,11 @@ const _expParser = new ExpressionParser(new ExpressionLexer());
|
|||||||
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
||||||
*/
|
*/
|
||||||
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
|
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
|
||||||
nodes: html.Node[], meaning: string, description: string) => i18n.Message {
|
nodes: html.Node[], meaning: string, description: string, id: string) => i18n.Message {
|
||||||
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
||||||
|
|
||||||
return (nodes: html.Node[], meaning: string, description: string) =>
|
return (nodes: html.Node[], meaning: string, description: string, id: string) =>
|
||||||
visitor.toI18nMessage(nodes, meaning, description);
|
visitor.toI18nMessage(nodes, meaning, description, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _I18nVisitor implements html.Visitor {
|
class _I18nVisitor implements html.Visitor {
|
||||||
@ -40,7 +40,8 @@ class _I18nVisitor implements html.Visitor {
|
|||||||
private _expressionParser: ExpressionParser,
|
private _expressionParser: ExpressionParser,
|
||||||
private _interpolationConfig: InterpolationConfig) {}
|
private _interpolationConfig: InterpolationConfig) {}
|
||||||
|
|
||||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string): i18n.Message {
|
public toI18nMessage(nodes: html.Node[], meaning: string, description: string, id: string):
|
||||||
|
i18n.Message {
|
||||||
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
|
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
|
||||||
this._icuDepth = 0;
|
this._icuDepth = 0;
|
||||||
this._placeholderRegistry = new PlaceholderRegistry();
|
this._placeholderRegistry = new PlaceholderRegistry();
|
||||||
@ -50,7 +51,7 @@ class _I18nVisitor implements html.Visitor {
|
|||||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||||
|
|
||||||
return new i18n.Message(
|
return new i18n.Message(
|
||||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitElement(el: html.Element, context: any): i18n.Node {
|
visitElement(el: html.Element, context: any): i18n.Node {
|
||||||
@ -115,7 +116,7 @@ class _I18nVisitor implements html.Visitor {
|
|||||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
// 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 phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '', '');
|
||||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {Extractor, ExtractorHost} from './extractor';
|
export {Extractor, ExtractorHost, ExtractorOptions} from './extractor';
|
||||||
export {I18NHtmlParser} from './i18n_html_parser';
|
export {I18NHtmlParser} from './i18n_html_parser';
|
||||||
export {MessageBundle} from './message_bundle';
|
export {MessageBundle} from './message_bundle';
|
||||||
export {Serializer} from './serializers/serializer';
|
export {Serializer} from './serializers/serializer';
|
||||||
|
@ -39,7 +39,6 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
|||||||
|
|
||||||
export class Xmb implements Serializer {
|
export class Xmb implements Serializer {
|
||||||
write(messages: i18n.Message[]): string {
|
write(messages: i18n.Message[]): string {
|
||||||
const exampleVisitor = new ExampleVisitor();
|
|
||||||
const visitor = new _Visitor();
|
const visitor = new _Visitor();
|
||||||
const visited: {[id: string]: boolean} = {};
|
const visited: {[id: string]: boolean} = {};
|
||||||
let rootNode = new xml.Tag(_MESSAGES_TAG);
|
let rootNode = new xml.Tag(_MESSAGES_TAG);
|
||||||
@ -72,7 +71,7 @@ export class Xmb implements Serializer {
|
|||||||
new xml.CR(),
|
new xml.CR(),
|
||||||
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
|
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
|
||||||
new xml.CR(),
|
new xml.CR(),
|
||||||
exampleVisitor.addDefaultExamples(rootNode),
|
rootNode,
|
||||||
new xml.CR(),
|
new xml.CR(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -135,26 +134,3 @@ class _Visitor implements i18n.Visitor {
|
|||||||
export function digest(message: i18n.Message): string {
|
export function digest(message: i18n.Message): string {
|
||||||
return decimalDigest(message);
|
return decimalDigest(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TC requires at least one non-empty example on placeholders
|
|
||||||
class ExampleVisitor implements xml.IVisitor {
|
|
||||||
addDefaultExamples(node: xml.Node): xml.Node {
|
|
||||||
node.visit(this);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitTag(tag: xml.Tag): void {
|
|
||||||
if (tag.name === _PLACEHOLDER_TAG) {
|
|
||||||
if (!tag.children || tag.children.length == 0) {
|
|
||||||
const exText = new xml.Text(tag.attrs['name'] || '...');
|
|
||||||
tag.children = [new xml.Tag(_EXEMPLE_TAG, {}, [exText])];
|
|
||||||
}
|
|
||||||
} else if (tag.children) {
|
|
||||||
tag.children.forEach(node => node.visit(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visitText(text: xml.Text): void {}
|
|
||||||
visitDeclaration(decl: xml.Declaration): void {}
|
|
||||||
visitDoctype(doctype: xml.Doctype): void {}
|
|
||||||
}
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {StaticSymbol} from './aot/static_symbol';
|
import {StaticSymbol} from './aot/static_symbol';
|
||||||
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||||
|
@ -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 replacement for @Injectable to be used in the compiler, so that
|
|
||||||
* we don't try to evaluate the metadata in the compiler during AoT.
|
|
||||||
* This decorator is enough to make the compiler work with the ReflectiveInjector though.
|
|
||||||
*/
|
|
||||||
export function CompilerInjectable(): (data: any) => any {
|
|
||||||
return (x) => x;
|
|
||||||
}
|
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
|
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||||
import {AnimationParser} from '../animation/animation_parser';
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
@ -15,7 +15,6 @@ import {CompilerConfig} from '../config';
|
|||||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||||
import {stringify} from '../facade/lang';
|
import {stringify} from '../facade/lang';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import * as ir from '../output/output_ast';
|
import * as ir from '../output/output_ast';
|
||||||
@ -37,7 +36,7 @@ import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDepende
|
|||||||
* from a trusted source. Attacker-controlled data introduced by a template could expose your
|
* from a trusted source. Attacker-controlled data introduced by a template could expose your
|
||||||
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
|
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
|
||||||
*/
|
*/
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class JitCompiler implements Compiler {
|
export class JitCompiler implements Compiler {
|
||||||
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationParser} from '../animation/animation_parser';
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
@ -16,7 +16,6 @@ import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
|||||||
import {Lexer} from '../expression_parser/lexer';
|
import {Lexer} from '../expression_parser/lexer';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
import * as i18n from '../i18n/index';
|
import * as i18n from '../i18n/index';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
import {HtmlParser} from '../ml_parser/html_parser';
|
import {HtmlParser} from '../ml_parser/html_parser';
|
||||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
@ -84,7 +83,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class JitCompilerFactory implements CompilerFactory {
|
export class JitCompilerFactory implements CompilerFactory {
|
||||||
private _defaultOptions: CompilerOptions[];
|
private _defaultOptions: CompilerOptions[];
|
||||||
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
|
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
|
||||||
|
@ -16,7 +16,6 @@ import {DirectiveResolver} from './directive_resolver';
|
|||||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {hasLifecycleHook} from './lifecycle_reflector';
|
import {hasLifecycleHook} from './lifecycle_reflector';
|
||||||
import {NgModuleResolver} from './ng_module_resolver';
|
import {NgModuleResolver} from './ng_module_resolver';
|
||||||
import {PipeResolver} from './pipe_resolver';
|
import {PipeResolver} from './pipe_resolver';
|
||||||
@ -24,7 +23,7 @@ import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, ref
|
|||||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||||
import {SummaryResolver} from './summary_resolver';
|
import {SummaryResolver} from './summary_resolver';
|
||||||
import {getUrlScheme} from './url_resolver';
|
import {getUrlScheme} from './url_resolver';
|
||||||
import {MODULE_SUFFIX, SyncAsyncResult, SyntaxError, ValueTransformer, visitValue} from './util';
|
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, visitValue} from './util';
|
||||||
|
|
||||||
export type ErrorCollector = (error: any, type?: any) => void;
|
export type ErrorCollector = (error: any, type?: any) => void;
|
||||||
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
||||||
@ -36,7 +35,7 @@ export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
|||||||
// But we want to report errors even when the async work is
|
// But we want to report errors even when the async work is
|
||||||
// not required to check that the user would have been able
|
// not required to check that the user would have been able
|
||||||
// to wait correctly.
|
// to wait correctly.
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class CompileMetadataResolver {
|
export class CompileMetadataResolver {
|
||||||
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
|
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
|
||||||
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
|
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
|
||||||
@ -46,7 +45,7 @@ export class CompileMetadataResolver {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
||||||
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
|
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver,
|
||||||
private _schemaRegistry: ElementSchemaRegistry,
|
private _schemaRegistry: ElementSchemaRegistry,
|
||||||
private _directiveNormalizer: DirectiveNormalizer,
|
private _directiveNormalizer: DirectiveNormalizer,
|
||||||
private _reflector: ReflectorReader = reflector,
|
private _reflector: ReflectorReader = reflector,
|
||||||
@ -129,13 +128,12 @@ export class CompileMetadataResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
|
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
|
||||||
let typeSummary = this._summaryCache.get(type);
|
let summary = this._summaryCache.get(type);
|
||||||
if (!typeSummary) {
|
if (!summary) {
|
||||||
const summary = this._summaryResolver.resolveSummary(type);
|
summary = this._summaryResolver.resolveSummary(type);
|
||||||
typeSummary = summary ? summary.type : null;
|
this._summaryCache.set(type, summary);
|
||||||
this._summaryCache.set(type, typeSummary);
|
|
||||||
}
|
}
|
||||||
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
|
return summary && summary.summaryKind === kind ? summary : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
|
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
|
||||||
@ -239,7 +237,7 @@ export class CompileMetadataResolver {
|
|||||||
if (dirMeta.viewProviders) {
|
if (dirMeta.viewProviders) {
|
||||||
viewProviders = this._getProvidersMetadata(
|
viewProviders = this._getProvidersMetadata(
|
||||||
dirMeta.viewProviders, entryComponentMetadata,
|
dirMeta.viewProviders, entryComponentMetadata,
|
||||||
`viewProviders for "${stringifyType(directiveType)}"`, [], directiveType);
|
`viewProviders for "${stringify(directiveType)}"`, [], directiveType);
|
||||||
}
|
}
|
||||||
if (dirMeta.entryComponents) {
|
if (dirMeta.entryComponents) {
|
||||||
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
|
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
|
||||||
@ -253,8 +251,7 @@ export class CompileMetadataResolver {
|
|||||||
// Directive
|
// Directive
|
||||||
if (!selector) {
|
if (!selector) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`),
|
||||||
`Directive ${stringifyType(directiveType)} has no selector, please add it!`),
|
|
||||||
directiveType);
|
directiveType);
|
||||||
selector = 'error';
|
selector = 'error';
|
||||||
}
|
}
|
||||||
@ -263,8 +260,8 @@ export class CompileMetadataResolver {
|
|||||||
let providers: cpl.CompileProviderMetadata[] = [];
|
let providers: cpl.CompileProviderMetadata[] = [];
|
||||||
if (isPresent(dirMeta.providers)) {
|
if (isPresent(dirMeta.providers)) {
|
||||||
providers = this._getProvidersMetadata(
|
providers = this._getProvidersMetadata(
|
||||||
dirMeta.providers, entryComponentMetadata,
|
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`,
|
||||||
`providers for "${stringifyType(directiveType)}"`, [], directiveType);
|
[], directiveType);
|
||||||
}
|
}
|
||||||
let queries: cpl.CompileQueryMetadata[] = [];
|
let queries: cpl.CompileQueryMetadata[] = [];
|
||||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||||
@ -300,8 +297,8 @@ export class CompileMetadataResolver {
|
|||||||
const dirMeta = this._directiveCache.get(directiveType);
|
const dirMeta = this._directiveCache.get(directiveType);
|
||||||
if (!dirMeta) {
|
if (!dirMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
|
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`),
|
||||||
directiveType);
|
directiveType);
|
||||||
}
|
}
|
||||||
return dirMeta;
|
return dirMeta;
|
||||||
@ -312,8 +309,8 @@ export class CompileMetadataResolver {
|
|||||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||||
if (!dirSummary) {
|
if (!dirSummary) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`),
|
`Illegal state: Could not load the summary for directive ${stringify(dirType)}.`),
|
||||||
dirType);
|
dirType);
|
||||||
}
|
}
|
||||||
return dirSummary;
|
return dirSummary;
|
||||||
@ -386,8 +383,7 @@ export class CompileMetadataResolver {
|
|||||||
if (moduleWithProviders.providers) {
|
if (moduleWithProviders.providers) {
|
||||||
providers.push(...this._getProvidersMetadata(
|
providers.push(...this._getProvidersMetadata(
|
||||||
moduleWithProviders.providers, entryComponents,
|
moduleWithProviders.providers, entryComponents,
|
||||||
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
|
`provider for the NgModule '${stringify(importedModuleType)}'`, [], importedType));
|
||||||
importedType));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,16 +391,16 @@ export class CompileMetadataResolver {
|
|||||||
const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
|
const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
|
||||||
if (!importedModuleSummary) {
|
if (!importedModuleSummary) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
|
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
importedModules.push(importedModuleSummary);
|
importedModules.push(importedModuleSummary);
|
||||||
} else {
|
} else {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
|
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -415,8 +411,8 @@ export class CompileMetadataResolver {
|
|||||||
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
|
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
|
||||||
if (!isValidType(exportedType)) {
|
if (!isValidType(exportedType)) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`),
|
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -436,8 +432,8 @@ export class CompileMetadataResolver {
|
|||||||
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
|
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
|
||||||
if (!isValidType(declaredType)) {
|
if (!isValidType(declaredType)) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
|
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -453,8 +449,8 @@ export class CompileMetadataResolver {
|
|||||||
this._addTypeToModule(declaredType, moduleType);
|
this._addTypeToModule(declaredType, moduleType);
|
||||||
} else {
|
} else {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
|
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -472,8 +468,8 @@ export class CompileMetadataResolver {
|
|||||||
transitiveModule.addExportedPipe(exportedId);
|
transitiveModule.addExportedPipe(exportedId);
|
||||||
} else {
|
} else {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`),
|
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`),
|
||||||
moduleType);
|
moduleType);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -482,25 +478,25 @@ export class CompileMetadataResolver {
|
|||||||
// so that they overwrite any other provider we already added.
|
// so that they overwrite any other provider we already added.
|
||||||
if (meta.providers) {
|
if (meta.providers) {
|
||||||
providers.push(...this._getProvidersMetadata(
|
providers.push(...this._getProvidersMetadata(
|
||||||
meta.providers, entryComponents,
|
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`,
|
||||||
`provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType));
|
[], moduleType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.entryComponents) {
|
if (meta.entryComponents) {
|
||||||
entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
|
entryComponents.push(
|
||||||
.map(type => this._getIdentifierMetadata(type)));
|
...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.bootstrap) {
|
if (meta.bootstrap) {
|
||||||
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
|
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
|
||||||
if (!isValidType(type)) {
|
if (!isValidType(type)) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`),
|
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`),
|
||||||
moduleType);
|
moduleType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bootstrapComponents.push(this._getIdentifierMetadata(type));
|
bootstrapComponents.push(this._getTypeMetadata(type));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -558,10 +554,10 @@ export class CompileMetadataResolver {
|
|||||||
const oldModule = this._ngModuleOfTypes.get(type);
|
const oldModule = this._ngModuleOfTypes.get(type);
|
||||||
if (oldModule && oldModule !== moduleType) {
|
if (oldModule && oldModule !== moduleType) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` +
|
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
|
||||||
`Please consider moving ${stringifyType(type)} to a higher module that imports ${stringifyType(oldModule)} and ${stringifyType(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 ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(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);
|
moduleType);
|
||||||
}
|
}
|
||||||
this._ngModuleOfTypes.set(type, moduleType);
|
this._ngModuleOfTypes.set(type, moduleType);
|
||||||
@ -610,26 +606,6 @@ export class CompileMetadataResolver {
|
|||||||
return {reference: type};
|
return {reference: type};
|
||||||
}
|
}
|
||||||
|
|
||||||
isInjectable(type: any): boolean {
|
|
||||||
const annotations = this._reflector.annotations(type);
|
|
||||||
// Note: We need an exact check here as @Component / @Directive / ... inherit
|
|
||||||
// from @CompilerInjectable!
|
|
||||||
return annotations.some(ann => ann.constructor === Injectable);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInjectableSummary(type: any): cpl.CompileTypeSummary {
|
|
||||||
return {summaryKind: cpl.CompileSummaryKind.Injectable, type: this._getTypeMetadata(type)};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getInjectableMetadata(type: Type<any>, dependencies: any[] = null):
|
|
||||||
cpl.CompileTypeMetadata {
|
|
||||||
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
|
|
||||||
if (typeSummary) {
|
|
||||||
return typeSummary.type;
|
|
||||||
}
|
|
||||||
return this._getTypeMetadata(type, dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata {
|
private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata {
|
||||||
const identifier = this._getIdentifierMetadata(type);
|
const identifier = this._getIdentifierMetadata(type);
|
||||||
return {
|
return {
|
||||||
@ -654,8 +630,8 @@ export class CompileMetadataResolver {
|
|||||||
const pipeMeta = this._pipeCache.get(pipeType);
|
const pipeMeta = this._pipeCache.get(pipeType);
|
||||||
if (!pipeMeta) {
|
if (!pipeMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
|
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`),
|
||||||
pipeType);
|
pipeType);
|
||||||
}
|
}
|
||||||
return pipeMeta;
|
return pipeMeta;
|
||||||
@ -666,8 +642,7 @@ export class CompileMetadataResolver {
|
|||||||
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
|
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
|
||||||
if (!pipeSummary) {
|
if (!pipeSummary) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(`Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`),
|
||||||
`Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`),
|
|
||||||
pipeType);
|
pipeType);
|
||||||
}
|
}
|
||||||
return pipeSummary;
|
return pipeSummary;
|
||||||
@ -747,10 +722,9 @@ export class CompileMetadataResolver {
|
|||||||
|
|
||||||
if (hasUnknownDeps) {
|
if (hasUnknownDeps) {
|
||||||
const depsTokens =
|
const depsTokens =
|
||||||
dependenciesMetadata.map((dep) => dep ? stringifyType(dep.token) : '?').join(', ');
|
dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', ');
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`),
|
||||||
`Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`),
|
|
||||||
typeOrFunc);
|
typeOrFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -787,9 +761,9 @@ export class CompileMetadataResolver {
|
|||||||
(<string[]>providers.reduce(
|
(<string[]>providers.reduce(
|
||||||
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
||||||
if (seenProviderIdx < providerIdx) {
|
if (seenProviderIdx < providerIdx) {
|
||||||
soFar.push(`${stringifyType(seenProvider)}`);
|
soFar.push(`${stringify(seenProvider)}`);
|
||||||
} else if (seenProviderIdx == providerIdx) {
|
} else if (seenProviderIdx == providerIdx) {
|
||||||
soFar.push(`?${stringifyType(seenProvider)}?`);
|
soFar.push(`?${stringify(seenProvider)}?`);
|
||||||
} else if (seenProviderIdx == providerIdx + 1) {
|
} else if (seenProviderIdx == providerIdx + 1) {
|
||||||
soFar.push('...');
|
soFar.push('...');
|
||||||
}
|
}
|
||||||
@ -798,7 +772,7 @@ export class CompileMetadataResolver {
|
|||||||
[]))
|
[]))
|
||||||
.join(', ');
|
.join(', ');
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
|
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
|
||||||
type);
|
type);
|
||||||
}
|
}
|
||||||
@ -819,21 +793,19 @@ export class CompileMetadataResolver {
|
|||||||
|
|
||||||
if (provider.useFactory || provider.useExisting || provider.useClass) {
|
if (provider.useFactory || provider.useExisting || provider.useClass) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
|
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!provider.multi) {
|
if (!provider.multi) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`),
|
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), type);
|
||||||
type);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
extractIdentifiers(provider.useValue, collectedIdentifiers);
|
extractIdentifiers(provider.useValue, collectedIdentifiers);
|
||||||
collectedIdentifiers.forEach((identifier) => {
|
collectedIdentifiers.forEach((identifier) => {
|
||||||
if (this._directiveResolver.isDirective(identifier.reference) ||
|
if (this._directiveResolver.isDirective(identifier.reference)) {
|
||||||
this._loadSummary(identifier.reference, cpl.CompileSummaryKind.Directive)) {
|
|
||||||
components.push(identifier);
|
components.push(identifier);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -847,7 +819,7 @@ export class CompileMetadataResolver {
|
|||||||
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
|
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
|
||||||
|
|
||||||
if (provider.useClass) {
|
if (provider.useClass) {
|
||||||
compileTypeMetadata = this._getInjectableMetadata(provider.useClass, provider.dependencies);
|
compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies);
|
||||||
compileDeps = compileTypeMetadata.diDeps;
|
compileDeps = compileTypeMetadata.diDeps;
|
||||||
if (provider.token === provider.useClass) {
|
if (provider.token === provider.useClass) {
|
||||||
// use the compileTypeMetadata as it contains information about lifecycleHooks...
|
// use the compileTypeMetadata as it contains information about lifecycleHooks...
|
||||||
@ -895,8 +867,8 @@ export class CompileMetadataResolver {
|
|||||||
} else {
|
} else {
|
||||||
if (!q.selector) {
|
if (!q.selector) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
new Error(
|
||||||
`Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`),
|
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`),
|
||||||
typeOrFunc);
|
typeOrFunc);
|
||||||
}
|
}
|
||||||
selectors = [this._getTokenMetadata(q.selector)];
|
selectors = [this._getTokenMetadata(q.selector)];
|
||||||
@ -963,8 +935,8 @@ export function componentModuleUrl(
|
|||||||
const scheme = getUrlScheme(moduleId);
|
const scheme = getUrlScheme(moduleId);
|
||||||
return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`;
|
return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`;
|
||||||
} else if (moduleId !== null && moduleId !== void 0) {
|
} else if (moduleId !== null && moduleId !== void 0) {
|
||||||
throw new SyntaxError(
|
throw new Error(
|
||||||
`moduleId should be a string in "${stringifyType(type)}". See https://goo.gl/wIDDiL for more information.\n` +
|
`moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` +
|
||||||
`If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`);
|
`If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -980,11 +952,3 @@ class _CompileValueConverter extends ValueTransformer {
|
|||||||
targetIdentifiers.push({reference: value});
|
targetIdentifiers.push({reference: value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyType(type: any): string {
|
|
||||||
if (type instanceof StaticSymbol) {
|
|
||||||
return `${type.name} in ${type.filePath}`;
|
|
||||||
} else {
|
|
||||||
return stringify(type);
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CompilerInjectable} from '../injectable';
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {getHtmlTagDefinition} from './html_tags';
|
import {getHtmlTagDefinition} from './html_tags';
|
||||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
|
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
|
||||||
@ -14,7 +14,7 @@ import {ParseTreeResult, Parser} from './parser';
|
|||||||
|
|
||||||
export {ParseTreeResult, TreeError} from './parser';
|
export {ParseTreeResult, TreeError} from './parser';
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class HtmlParser extends Parser {
|
export class HtmlParser extends Parser {
|
||||||
constructor() { super(getHtmlTagDefinition); }
|
constructor() { super(getHtmlTagDefinition); }
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ const PLURAL_CASES: string[] = ['zero', 'one', 'two', 'few', 'many', 'other'];
|
|||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* <ng-container [ngPlural]="messages.length">
|
* <ng-container [ngPlural]="messages.length">
|
||||||
* <template ngPluralCase="=0">zero</template>
|
* <template ngPluralCase="=0">zero</ng-container>
|
||||||
* <template ngPluralCase="=1">one</template>
|
* <template ngPluralCase="=1">one</ng-container>
|
||||||
* <template ngPluralCase="other">more than one</template>
|
* <template ngPluralCase="other">more than one</ng-container>
|
||||||
* </ng-container>
|
* </ng-container>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
@ -133,7 +133,7 @@ class _Tokenizer {
|
|||||||
} else {
|
} else {
|
||||||
this._consumeTagOpen(start);
|
this._consumeTagOpen(start);
|
||||||
}
|
}
|
||||||
} else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
|
} else if (!this._tokenizeIcu || !this._tokenizeExpansionForm()) {
|
||||||
this._consumeText();
|
this._consumeText();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -586,8 +586,8 @@ class _Tokenizer {
|
|||||||
parts.push(this._interpolationConfig.start);
|
parts.push(this._interpolationConfig.start);
|
||||||
this._inInterpolation = true;
|
this._inInterpolation = true;
|
||||||
} else if (
|
} else if (
|
||||||
this._interpolationConfig && this._inInterpolation &&
|
this._interpolationConfig && this._attemptStr(this._interpolationConfig.end) &&
|
||||||
this._attemptStr(this._interpolationConfig.end)) {
|
this._inInterpolation) {
|
||||||
parts.push(this._interpolationConfig.end);
|
parts.push(this._interpolationConfig.end);
|
||||||
this._inInterpolation = false;
|
this._inInterpolation = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -283,7 +283,7 @@ class _TreeBuilder {
|
|||||||
const tagDef = this.getTagDefinition(el.name);
|
const tagDef = this.getTagDefinition(el.name);
|
||||||
const {parent, container} = this._getParentElementSkippingContainers();
|
const {parent, container} = this._getParentElementSkippingContainers();
|
||||||
|
|
||||||
if (parent && tagDef.requireExtraParent(parent.name)) {
|
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) {
|
||||||
const newParent = new html.Element(
|
const newParent = new html.Element(
|
||||||
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
||||||
this._insertBeforeContainer(parent, container, newParent);
|
this._insertBeforeContainer(parent, container, newParent);
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
||||||
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
||||||
import {isPresent} from './facade/lang';
|
import {isPresent} from './facade/lang';
|
||||||
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
|
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {convertValueToOutputAst} from './output/value_util';
|
import {convertValueToOutputAst} from './output/value_util';
|
||||||
@ -30,7 +31,7 @@ export class NgModuleCompileResult {
|
|||||||
public dependencies: ComponentFactoryDependency[]) {}
|
public dependencies: ComponentFactoryDependency[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class NgModuleCompiler {
|
export class NgModuleCompiler {
|
||||||
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
||||||
NgModuleCompileResult {
|
NgModuleCompileResult {
|
||||||
|
@ -6,11 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NgModule, Type} from '@angular/core';
|
import {Injectable, NgModule, Type} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from './facade/collection';
|
||||||
import {stringify} from './facade/lang';
|
import {isPresent, stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
function _isNgModuleMetadata(obj: any): obj is NgModule {
|
function _isNgModuleMetadata(obj: any): obj is NgModule {
|
||||||
@ -20,7 +19,7 @@ function _isNgModuleMetadata(obj: any): obj is NgModule {
|
|||||||
/**
|
/**
|
||||||
* Resolves types to {@link NgModule}.
|
* Resolves types to {@link NgModule}.
|
||||||
*/
|
*/
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class NgModuleResolver {
|
export class NgModuleResolver {
|
||||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ export class NgModuleResolver {
|
|||||||
const ngModuleMeta: NgModule =
|
const ngModuleMeta: NgModule =
|
||||||
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||||
|
|
||||||
if (ngModuleMeta) {
|
if (isPresent(ngModuleMeta)) {
|
||||||
return ngModuleMeta;
|
return ngModuleMeta;
|
||||||
} else {
|
} else {
|
||||||
if (throwIfNotFound) {
|
if (throwIfNotFound) {
|
||||||
|
@ -68,13 +68,13 @@ export class MapType extends Type {
|
|||||||
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||||
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||||
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||||
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||||
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||||
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||||
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||||
|
|
||||||
export interface TypeVisitor {
|
export interface TypeVisitor {
|
||||||
visitBuiltintType(type: BuiltinType, context: any): any;
|
visitBuiltintType(type: BuiltinType, context: any): any;
|
||||||
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
|
|||||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||||
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||||
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||||
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||||
export const NULL_EXPR = new LiteralExpr(null, null);
|
export var NULL_EXPR = new LiteralExpr(null, null);
|
||||||
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||||
|
|
||||||
//// Statements
|
//// Statements
|
||||||
export enum StmtModifier {
|
export enum StmtModifier {
|
||||||
@ -894,10 +894,8 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
|||||||
return new LiteralArrayExpr(values, type);
|
return new LiteralArrayExpr(values, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function literalMap(
|
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
||||||
values: [string, Expression][], type: MapType = null, quoted: boolean = false): LiteralMapExpr {
|
return new LiteralMapExpr(values.map(entry => new LiteralMapEntry(entry[0], entry[1])), type);
|
||||||
return new LiteralMapExpr(
|
|
||||||
values.map(entry => new LiteralMapEntry(entry[0], entry[1], quoted)), type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function not(expr: Expression): NotExpr {
|
export function not(expr: Expression): NotExpr {
|
||||||
|
@ -14,6 +14,5 @@ export abstract class ImportResolver {
|
|||||||
* Converts a file path to a module name that can be used as an `import.
|
* Converts a file path to a module name that can be used as an `import.
|
||||||
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
|
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
|
||||||
*/
|
*/
|
||||||
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
|
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
|
||||||
/*|null*/;
|
|
||||||
}
|
}
|
||||||
|
@ -335,7 +335,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
|||||||
}
|
}
|
||||||
ctx.print(`${prefix}.`);
|
ctx.print(`${prefix}.`);
|
||||||
}
|
}
|
||||||
if (value.reference && value.reference.members && value.reference.members.length) {
|
if (value.reference && value.reference.members) {
|
||||||
ctx.print(value.reference.name);
|
ctx.print(value.reference.name);
|
||||||
ctx.print('.');
|
ctx.print('.');
|
||||||
ctx.print(value.reference.members.join('.'));
|
ctx.print(value.reference.members.join('.'));
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||||
import {ValueTransformer, visitValue} from '../util';
|
import {ValueTransformer, visitValue} from '../util';
|
||||||
|
|
||||||
import * as o from './output_ast';
|
import * as o from './output_ast';
|
||||||
|
@ -6,11 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Pipe, Type, resolveForwardRef} from '@angular/core';
|
import {Injectable, Pipe, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from './facade/collection';
|
||||||
import {stringify} from './facade/lang';
|
import {isPresent, stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
function _isPipeMetadata(type: any): boolean {
|
function _isPipeMetadata(type: any): boolean {
|
||||||
@ -24,7 +23,7 @@ function _isPipeMetadata(type: any): boolean {
|
|||||||
*
|
*
|
||||||
* See {@link Compiler}
|
* See {@link Compiler}
|
||||||
*/
|
*/
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class PipeResolver {
|
export class PipeResolver {
|
||||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||||
|
|
||||||
@ -38,9 +37,9 @@ export class PipeResolver {
|
|||||||
*/
|
*/
|
||||||
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
||||||
const metas = this._reflector.annotations(resolveForwardRef(type));
|
const metas = this._reflector.annotations(resolveForwardRef(type));
|
||||||
if (metas) {
|
if (isPresent(metas)) {
|
||||||
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
||||||
if (annotation) {
|
if (isPresent(annotation)) {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
||||||
import {isBlank, isPresent} from './facade/lang';
|
import {isBlank, isPresent} from './facade/lang';
|
||||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ export class ProviderElementContext {
|
|||||||
let queries: CompileQueryMetadata[];
|
let queries: CompileQueryMetadata[];
|
||||||
while (currentEl !== null) {
|
while (currentEl !== null) {
|
||||||
queries = currentEl._contentQueries.get(tokenReference(token));
|
queries = currentEl._contentQueries.get(tokenReference(token));
|
||||||
if (queries) {
|
if (isPresent(queries)) {
|
||||||
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
||||||
}
|
}
|
||||||
if (currentEl._directiveAsts.length > 0) {
|
if (currentEl._directiveAsts.length > 0) {
|
||||||
@ -123,7 +123,7 @@ export class ProviderElementContext {
|
|||||||
currentEl = currentEl._parent;
|
currentEl = currentEl._parent;
|
||||||
}
|
}
|
||||||
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
||||||
if (queries) {
|
if (isPresent(queries)) {
|
||||||
result.push(...queries);
|
result.push(...queries);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -143,7 +143,7 @@ export class ProviderElementContext {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||||
if (transformedProviderAst) {
|
if (isPresent(transformedProviderAst)) {
|
||||||
return transformedProviderAst;
|
return transformedProviderAst;
|
||||||
}
|
}
|
||||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||||
@ -165,11 +165,11 @@ export class ProviderElementContext {
|
|||||||
transformedUseExisting = null;
|
transformedUseExisting = null;
|
||||||
transformedUseValue = existingDiDep.value;
|
transformedUseValue = existingDiDep.value;
|
||||||
}
|
}
|
||||||
} else if (provider.useFactory) {
|
} else if (isPresent(provider.useFactory)) {
|
||||||
const deps = provider.deps || provider.useFactory.diDeps;
|
const deps = provider.deps || provider.useFactory.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||||
} else if (provider.useClass) {
|
} else if (isPresent(provider.useClass)) {
|
||||||
const deps = provider.deps || provider.useClass.diDeps;
|
const deps = provider.deps || provider.useClass.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||||
@ -235,7 +235,7 @@ export class ProviderElementContext {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check parent elements
|
// check parent elements
|
||||||
while (!result && currElement._parent) {
|
while (!result && isPresent(currElement._parent)) {
|
||||||
const prevElement = currElement;
|
const prevElement = currElement;
|
||||||
currElement = currElement._parent;
|
currElement = currElement._parent;
|
||||||
if (prevElement._isViewRoot) {
|
if (prevElement._isViewRoot) {
|
||||||
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||||
if (transformedProviderAst) {
|
if (isPresent(transformedProviderAst)) {
|
||||||
return transformedProviderAst;
|
return transformedProviderAst;
|
||||||
}
|
}
|
||||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||||
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
|
|||||||
transformedUseExisting = null;
|
transformedUseExisting = null;
|
||||||
transformedUseValue = existingDiDep.value;
|
transformedUseValue = existingDiDep.value;
|
||||||
}
|
}
|
||||||
} else if (provider.useFactory) {
|
} else if (isPresent(provider.useFactory)) {
|
||||||
const deps = provider.deps || provider.useFactory.diDeps;
|
const deps = provider.deps || provider.useFactory.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||||
} else if (provider.useClass) {
|
} else if (isPresent(provider.useClass)) {
|
||||||
const deps = provider.deps || provider.useClass.diDeps;
|
const deps = provider.deps || provider.useClass.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||||
@ -454,7 +454,7 @@ function _resolveProviders(
|
|||||||
|
|
||||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
||||||
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
||||||
if (component.viewQueries) {
|
if (isPresent(component.viewQueries)) {
|
||||||
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
||||||
}
|
}
|
||||||
return viewQueries;
|
return viewQueries;
|
||||||
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
|
|||||||
Map<any, CompileQueryMetadata[]> {
|
Map<any, CompileQueryMetadata[]> {
|
||||||
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
||||||
directives.forEach(directive => {
|
directives.forEach(directive => {
|
||||||
if (directive.queries) {
|
if (isPresent(directive.queries)) {
|
||||||
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
|
import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
|
||||||
import {CompilerInjectable} from '../injectable';
|
|
||||||
|
|
||||||
import {dashCaseToCamelCase} from '../util';
|
import {dashCaseToCamelCase} from '../util';
|
||||||
|
|
||||||
@ -239,7 +238,7 @@ const _ATTR_TO_PROP: {[name: string]: string} = {
|
|||||||
'tabindex': 'tabIndex',
|
'tabindex': 'tabIndex',
|
||||||
};
|
};
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||||
private _schema: {[element: string]: {[property: string]: string}} = {};
|
private _schema: {[element: string]: {[property: string]: string}} = {};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const _SELECTOR_REGEXP = new RegExp(
|
|||||||
'(\\:not\\()|' + //":not("
|
'(\\:not\\()|' + //":not("
|
||||||
'([-\\w]+)|' + // "tag"
|
'([-\\w]+)|' + // "tag"
|
||||||
'(?:\\.([-\\w]+))|' + // ".class"
|
'(?:\\.([-\\w]+))|' + // ".class"
|
||||||
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||||
'(\\))|' + // ")"
|
'(\\))|' + // ")"
|
||||||
'(\\s*,\\s*)', // ","
|
'(\\s*,\\s*)', // ","
|
||||||
'g');
|
'g');
|
||||||
|
@ -6,10 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
import {Injectable, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {ShadowCss} from './shadow_css';
|
import {ShadowCss} from './shadow_css';
|
||||||
import {UrlResolver} from './url_resolver';
|
import {UrlResolver} from './url_resolver';
|
||||||
@ -37,7 +36,7 @@ export class CompiledStylesheet {
|
|||||||
public meta: CompileStylesheetMetadata) {}
|
public meta: CompileStylesheetMetadata) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerInjectable()
|
@Injectable()
|
||||||
export class StyleCompiler {
|
export class StyleCompiler {
|
||||||
private _shadowCss: ShadowCss = new ShadowCss();
|
private _shadowCss: ShadowCss = new ShadowCss();
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
// Some of the code comes from WebComponents.JS
|
// Some of the code comes from WebComponents.JS
|
||||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||||
|
|
||||||
|
import {isBlank, isPresent} from './facade/lang';
|
||||||
|
|
||||||
import {UrlResolver} from './url_resolver';
|
import {UrlResolver} from './url_resolver';
|
||||||
|
|
||||||
export class StyleWithImports {
|
export class StyleWithImports {
|
||||||
@ -16,8 +18,8 @@ export class StyleWithImports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isStyleUrlResolvable(url: string): boolean {
|
export function isStyleUrlResolvable(url: string): boolean {
|
||||||
if (url == null || url.length === 0 || url[0] == '/') return false;
|
if (isBlank(url) || url.length === 0 || url[0] == '/') return false;
|
||||||
const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
|
const schemeMatch = url.match(_urlWithSchemaRe);
|
||||||
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,9 +30,7 @@ export function isStyleUrlResolvable(url: string): boolean {
|
|||||||
export function extractStyleUrls(
|
export function extractStyleUrls(
|
||||||
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
||||||
const foundUrls: string[] = [];
|
const foundUrls: string[] = [];
|
||||||
|
const modifiedCssText = cssText.replace(_cssImportRe, function(...m: string[]) {
|
||||||
const modifiedCssText =
|
|
||||||
cssText.replace(CSS_COMMENT_REGEXP, '').replace(CSS_IMPORT_REGEXP, (...m: string[]) => {
|
|
||||||
const url = m[1] || m[2];
|
const url = m[1] || m[2];
|
||||||
if (!isStyleUrlResolvable(url)) {
|
if (!isStyleUrlResolvable(url)) {
|
||||||
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
||||||
@ -42,6 +42,5 @@ export function extractStyleUrls(
|
|||||||
return new StyleWithImports(modifiedCssText, foundUrls);
|
return new StyleWithImports(modifiedCssText, foundUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
const _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
||||||
const CSS_COMMENT_REGEXP = /\/\*.+?\*\//g;
|
const _urlWithSchemaRe = /^([^:/?#]+):/;
|
||||||
const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
|
|
||||||
|
@ -5,17 +5,10 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
import {CompileTypeSummary} from './compile_metadata';
|
import {CompileTypeSummary} from './compile_metadata';
|
||||||
import {CompilerInjectable} from './injectable';
|
|
||||||
|
|
||||||
export interface Summary<T> {
|
@Injectable()
|
||||||
symbol: T;
|
export class SummaryResolver {
|
||||||
metadata: any;
|
resolveSummary(reference: any): CompileTypeSummary { return null; }
|
||||||
type?: CompileTypeSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
@CompilerInjectable()
|
|
||||||
export class SummaryResolver<T> {
|
|
||||||
resolveSummary(reference: T): Summary<T> { return null; };
|
|
||||||
getSymbolsOf(filePath: string): T[] { return []; }
|
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {SecurityContext} from '@angular/core';
|
|||||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
|
import {isPresent} from '../facade/lang';
|
||||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
import {mergeNsAndName} from '../ml_parser/tags';
|
import {mergeNsAndName} from '../ml_parser/tags';
|
||||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||||
@ -110,14 +111,14 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseInlineTemplateBinding(
|
parseInlineTemplateBinding(
|
||||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||||
for (let i = 0; i < bindings.length; i++) {
|
for (let i = 0; i < bindings.length; i++) {
|
||||||
const binding = bindings[i];
|
const binding = bindings[i];
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||||
} else if (binding.expression) {
|
} else if (isPresent(binding.expression)) {
|
||||||
this._parsePropertyAst(
|
this._parsePropertyAst(
|
||||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||||
} else {
|
} else {
|
||||||
@ -135,7 +136,7 @@ export class BindingParser {
|
|||||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||||
bindingsResult.templateBindings.forEach((binding) => {
|
bindingsResult.templateBindings.forEach((binding) => {
|
||||||
if (binding.expression) {
|
if (isPresent(binding.expression)) {
|
||||||
this._checkPipes(binding.expression, sourceSpan);
|
this._checkPipes(binding.expression, sourceSpan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -192,7 +193,7 @@ export class BindingParser {
|
|||||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||||
targetProps: BoundProperty[]): boolean {
|
targetProps: BoundProperty[]): boolean {
|
||||||
const expr = this.parseInterpolation(value, sourceSpan);
|
const expr = this.parseInterpolation(value, sourceSpan);
|
||||||
if (expr) {
|
if (isPresent(expr)) {
|
||||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -373,7 +374,7 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||||
if (ast) {
|
if (isPresent(ast)) {
|
||||||
const collector = new PipeCollector();
|
const collector = new PipeCollector();
|
||||||
ast.visit(collector);
|
ast.visit(collector);
|
||||||
collector.pipes.forEach((ast, pipeName) => {
|
collector.pipes.forEach((ast, pipeName) => {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user