Compare commits
264 Commits
2.2.4
...
4.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
f114e40212 | |||
952471e25d | |||
c65e428778 | |||
842f52e841 | |||
eb2ceff4ba | |||
f49ab56160 | |||
c0f750af4e | |||
bcd37f52fb | |||
e69c1fb36c | |||
9da4c259a5 | |||
fcd116fdc0 | |||
383adc9ad9 | |||
9b8488f007 | |||
1817ddb57b | |||
1ee574c51e | |||
171a9bdc85 | |||
896916af29 | |||
e49c7fae22 | |||
6b65fc1286 | |||
0e3981afc1 | |||
e78508507d | |||
a23fa94ca8 | |||
4568d5ddac | |||
c6e893953f | |||
55dfa1b69d | |||
0fe3cd9a4c | |||
0c19898694 | |||
5b6e8ea3ec | |||
732f446ad2 | |||
f0e092515c | |||
14e785f5b7 | |||
01d1624884 | |||
33910ddfc9 | |||
01ca2db6ae | |||
6cefccb314 | |||
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 | |||
13b41bd631 | |||
f3524af68f | |||
0a56f4ea82 | |||
cf52284ac3 | |||
4a09c81724 | |||
16efb13dd1 | |||
986abbe0b2 | |||
25c2141991 | |||
2893c2c0a2 | |||
393c1007a8 | |||
66b6fc010d | |||
f31c9470fa | |||
4bd8f58552 | |||
93556a5720 | |||
5614c4ff0f | |||
c3065aac7a | |||
c767df0e4e | |||
25e5b2fdf0 | |||
307c4693dc | |||
349ad75de3 | |||
f562cbf86c | |||
804943c9b1 | |||
dea59165de | |||
a1322873c8 | |||
b8c839bd51 | |||
d2e5198b93 | |||
6cf7a1bf84 | |||
d46b8deeea | |||
bbb7a39414 | |||
d7d8fab211 | |||
51b06924bd | |||
aa4bd14b3f | |||
3ff6554cbc | |||
dfd8140084 | |||
6ea3ab7e14 | |||
9761db5ac2 | |||
75d1617b63 | |||
614a35d539 | |||
9ab401f4d3 | |||
82c81cd0d2 | |||
12959f444c | |||
25a6da244c | |||
5908b66ae9 | |||
480ef20eb1 | |||
c066281bad | |||
1b9493f725 | |||
ae26504e84 | |||
d420080b3b | |||
2975d8933c | |||
43c0e9a6bb | |||
f275f36081 | |||
e628b66cca | |||
3e73bea3e7 | |||
42cf06fa12 | |||
c4bbafc291 | |||
2d6a003dba | |||
e45b7ffcd9 | |||
627282d2c8 | |||
2f7492c986 | |||
2452cd14e0 | |||
bc69c74be0 | |||
897555ca78 | |||
966bcbad5a | |||
94b8612e4e | |||
b2b72190f8 | |||
f5c8e0989d | |||
4a09251921 | |||
36caaaa8e4 | |||
808275a9d5 | |||
be3784c957 | |||
555301ce3a | |||
7194fc2b9e | |||
2a3ca7bfcf | |||
4cbf8ccf05 | |||
a6c4490fce | |||
2c02d34c05 | |||
6c2d931744 | |||
86ffa884b7 | |||
3e548de99d | |||
909268036b | |||
519a324454 | |||
ef96763fa4 | |||
7dcca307d9 | |||
491d5a22a9 | |||
44572f114f | |||
1ef4696cb7 | |||
07a986d330 | |||
59d2b4c831 | |||
2a5bd2f345 | |||
3c06a5dc25 | |||
adeea5d86a | |||
dddbb1c1cb | |||
bccf0e69dc | |||
b15039d228 | |||
2235048432 | |||
484119e59f | |||
24099bdbd2 | |||
912ca44979 | |||
664a6273e1 | |||
c1a62e2154 | |||
aac37bedc0 | |||
a3884db87c | |||
fc5ac1ebc4 | |||
ad20d7d260 | |||
602522beb2 | |||
4e047302f2 | |||
419a812f04 | |||
f340e1a414 | |||
481c9b3258 | |||
8b2dfb2eca | |||
824ea8406c | |||
1f96a93f59 | |||
009d545787 | |||
53c25210a6 | |||
927aa69726 | |||
ce89039036 | |||
42198cd7d5 | |||
d6ba092a27 | |||
773b31de8f | |||
f79b320fc4 | |||
6a212fd561 | |||
be010a292a | |||
7c36e7f956 | |||
13ba2f90b9 | |||
75277cd94b | |||
46d150266b | |||
1b5384ee54 | |||
9f7d32a326 | |||
9de76ebfa5 | |||
46023e4792 | |||
b55aaf094f | |||
d90b622fa4 | |||
79e2bb9291 | |||
efbbefd353 | |||
c2fae72bc6 | |||
7908679c4b | |||
9ed9ff40b3 | |||
2f14415836 | |||
76e4911e8b | |||
ed5e98d0df | |||
146af1fed9 | |||
c60ba7a72f | |||
05beffe0d0 | |||
08c038ebd9 | |||
582550a90d | |||
1d53a870dd | |||
a0c58a6b5c | |||
d3eff6c483 |
277
CHANGELOG.md
277
CHANGELOG.md
@ -1,3 +1,278 @@
|
|||||||
|
<a name="4.0.0-beta.1"></a>
|
||||||
|
# [4.0.0-beta.1](https://github.com/angular/angular/compare/2.4.0-marker...4.0.0-beta.1) (2016-12-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **upgrade:** support the `$doCheck()` lifecycle hook in `UpgradeComponent` ([#13015](https://github.com/angular/angular/issues/13015)) ([9da4c25](https://github.com/angular/angular/commit/9da4c25))
|
||||||
|
|
||||||
|
Note: 4.0.0-beta.1 release also contains all the changes present in the 2.4.0 and the 2.4.1 releases.
|
||||||
|
|
||||||
|
<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="4.0.0-beta.0"></a>
|
||||||
|
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
|
||||||
|
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
|
||||||
|
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
|
||||||
|
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
|
||||||
|
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
|
||||||
|
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
|
||||||
|
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
|
||||||
|
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
|
||||||
|
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
|
||||||
|
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
|
||||||
|
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
|
||||||
|
|
||||||
|
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
|
||||||
|
|
||||||
|
<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>
|
||||||
|
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **common:** make sure the plural category exists ([#13169](https://github.com/angular/angular/issues/13169)) ([82c81cd](https://github.com/angular/angular/commit/82c81cd)), closes [#12379](https://github.com/angular/angular/issues/12379)
|
||||||
|
* **compiler:** include the summaries of reexported modules / directives / pipes ([#13196](https://github.com/angular/angular/issues/13196)) ([75d1617](https://github.com/angular/angular/commit/75d1617))
|
||||||
|
* **compiler:** serialize any `StaticSymbol` correctly, not matter in which context ([5614c4f](https://github.com/angular/angular/commit/5614c4f))
|
||||||
|
* **compiler:** short-circut expressions with an index ([#13263](https://github.com/angular/angular/issues/13263)) ([f31c947](https://github.com/angular/angular/commit/f31c947)), closes [#13254](https://github.com/angular/angular/issues/13254)
|
||||||
|
* **core:** display framework version on bootstrapped component ([#13252](https://github.com/angular/angular/issues/13252)) ([16efb13](https://github.com/angular/angular/commit/16efb13))
|
||||||
|
* **facade:** cache original format string ([#12764](https://github.com/angular/angular/issues/12764)) ([a132287](https://github.com/angular/angular/commit/a132287))
|
||||||
|
* **http:** set the default Accept header ([#12989](https://github.com/angular/angular/issues/12989)) ([986abbe](https://github.com/angular/angular/commit/986abbe)), closes [#6354](https://github.com/angular/angular/issues/6354)
|
||||||
|
* **language-service:** avoid throwing for invalid class declarations ([#13257](https://github.com/angular/angular/issues/13257)) ([93556a5](https://github.com/angular/angular/commit/93556a5)), closes [#13253](https://github.com/angular/angular/issues/13253)
|
||||||
|
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
|
||||||
|
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
|
||||||
|
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
|
||||||
|
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
|
||||||
|
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
|
||||||
|
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
|
||||||
|
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))
|
||||||
|
* **router:** runs guards every time when unsuccessfully navigating to the same url over and over again ([#13209](https://github.com/angular/angular/issues/13209)) ([d46b8de](https://github.com/angular/angular/commit/d46b8de))
|
||||||
|
* **router:** throw a better error message when angular 1 is not bootstraped ([c767df0](https://github.com/angular/angular/commit/c767df0))
|
||||||
|
* **router:** validate nested routes ([#13224](https://github.com/angular/angular/issues/13224)) ([2893c2c](https://github.com/angular/angular/commit/2893c2c)), closes [#12827](https://github.com/angular/angular/issues/12827)
|
||||||
|
* **tsc-wrapped:** have UserError display the actual error ([393c100](https://github.com/angular/angular/commit/393c100))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler:** read and write `.ngsummary.json` files ([614a35d](https://github.com/angular/angular/commit/614a35d)), closes [#12787](https://github.com/angular/angular/issues/12787)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.3.0-rc.0"></a>
|
||||||
|
# [2.3.0-rc.0](https://github.com/angular/angular/compare/2.3.0-beta.0...2.3.0-rc.0) (2016-11-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ef96763](https://github.com/angular/angular/commit/ef96763)), closes [#13013](https://github.com/angular/angular/issues/13013)
|
||||||
|
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([fc5ac1e](https://github.com/angular/angular/commit/fc5ac1e))
|
||||||
|
* **build:** update versions of umd bundles ([#13038](https://github.com/angular/angular/issues/13038)) ([86ffa88](https://github.com/angular/angular/commit/86ffa88)), closes [#13037](https://github.com/angular/angular/issues/13037)
|
||||||
|
* **changelog:** replace beta.1 with beta.0 ([#12961](https://github.com/angular/angular/issues/12961)) ([07a986d](https://github.com/angular/angular/commit/07a986d))
|
||||||
|
* **ci:** pin version of npm on CircleCI ([#12954](https://github.com/angular/angular/issues/12954)) ([a3884db](https://github.com/angular/angular/commit/a3884db))
|
||||||
|
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([7dcca30](https://github.com/angular/angular/commit/7dcca30))
|
||||||
|
* **common:** update DatePipe to allow closure compilation ([b2b7219](https://github.com/angular/angular/commit/b2b7219))
|
||||||
|
* **compiler:** correctly evaluate references to static functions ([#13133](https://github.com/angular/angular/issues/13133)) ([627282d](https://github.com/angular/angular/commit/627282d))
|
||||||
|
* **compiler:** fix performance regression caused by 5b0f9e2 ([43c0e9a](https://github.com/angular/angular/commit/43c0e9a)), closes [#13146](https://github.com/angular/angular/issues/13146)
|
||||||
|
* **compiler:** fix versions of `@angular/tsc-wrapped` ([bccf0e6](https://github.com/angular/angular/commit/bccf0e6))
|
||||||
|
* **compiler-cli:** fix paths in source maps to be relative ([2a3ca7b](https://github.com/angular/angular/commit/2a3ca7b)), closes [#13040](https://github.com/angular/angular/issues/13040)
|
||||||
|
* **compiler-cli:** pin the version of `tsc-wrapped` ([966bcba](https://github.com/angular/angular/commit/966bcba))
|
||||||
|
* **language-service:** harden against partial normalization of directives ([2975d89](https://github.com/angular/angular/commit/2975d89))
|
||||||
|
* **core:** shrinkwrap was out of date with packages. ([e45b7ff](https://github.com/angular/angular/commit/e45b7ff))
|
||||||
|
* **language-service:** make link check pass ([7194fc2](https://github.com/angular/angular/commit/7194fc2))
|
||||||
|
* **router:** guards restore an incorrect url when used with skipLocationChange ([ad20d7d](https://github.com/angular/angular/commit/ad20d7d)), closes [#12825](https://github.com/angular/angular/issues/12825)
|
||||||
|
* **router:** support redirects to named outlets ([602522b](https://github.com/angular/angular/commit/602522b)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
|
||||||
|
* **tsc-wrapped:** set correct version number ([897555c](https://github.com/angular/angular/commit/897555c))
|
||||||
|
* **tsc-wrapped:** still emit version 1 metadata to allow use of new components in old setups ([bc69c74](https://github.com/angular/angular/commit/bc69c74))
|
||||||
|
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([1ef4696](https://github.com/angular/angular/commit/1ef4696))
|
||||||
|
* **version:** take all of version string after patch version ([f275f36](https://github.com/angular/angular/commit/f275f36))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **core:** update RxJS peer dependency to 5.0.0-rc.4 Please see [this gist](https://gist.github.com/robwormald/19dea0c70a6e01aadced6731aed4f9f7) if you depend on the `cache` operator ([2d6a003](https://github.com/angular/angular/commit/2d6a003)), closes [#13125](https://github.com/angular/angular/issues/13125)
|
||||||
|
* **core:** upgrade zone.js to v0.7.1 ([c4bbafc](https://github.com/angular/angular/commit/c4bbafc))
|
||||||
|
* **build:** record angular version in the dom ([#13164](https://github.com/angular/angular/issues/13164)) ([e628b66](https://github.com/angular/angular/commit/e628b66))
|
||||||
|
* **core:** expose destroy() method on ViewRef ([808275a](https://github.com/angular/angular/commit/808275a))
|
||||||
|
* **core:** properly support inheritance ([f5c8e09](https://github.com/angular/angular/commit/f5c8e09)), closes [#11606](https://github.com/angular/angular/issues/11606) [#12892](https://github.com/angular/angular/issues/12892)
|
||||||
|
* **language-service:** add services to support editors ([#12987](https://github.com/angular/angular/issues/12987)) ([519a324](https://github.com/angular/angular/commit/519a324))
|
||||||
|
* **router:** add support for custom route reuse strategies ([42cf06f](https://github.com/angular/angular/commit/42cf06f))
|
||||||
|
* **tools:** allow disabling annotation lowering ([c1a62e2](https://github.com/angular/angular/commit/c1a62e2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.2.4"></a>
|
||||||
|
## [2.2.4](https://github.com/angular/angular/compare/2.2.3...2.2.4) (2016-11-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **common:** update DatePipe to allow closure compilation ([eba53fd](https://github.com/angular/angular/commit/eba53fd))
|
||||||
|
* **compiler:** fix performance regression caused by 5b0f9e2 ([ee2d6e5](https://github.com/angular/angular/commit/ee2d6e5)), closes [#13146](https://github.com/angular/angular/issues/13146)
|
||||||
|
* **compiler-cli:** fix paths in source maps to be relative ([eb173bc](https://github.com/angular/angular/commit/eb173bc)), closes [#13040](https://github.com/angular/angular/issues/13040)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.2.3"></a>
|
||||||
|
## [2.2.3](https://github.com/angular/angular/compare/2.2.2...2.2.3) (2016-11-23)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** Revert: fix versions of `@angular/tsc-wrapped` ([015ca47](https://github.com/angular/angular/commit/015ca47))
|
||||||
|
* **animations:** Revert: blend in all previously transitioned styles into next animation if interrupted ([c12e56e](https://github.com/angular/angular/commit/c12e56e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.2.2"></a>
|
||||||
|
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
|
||||||
|
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
|
||||||
|
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
|
||||||
|
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
|
||||||
|
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
|
||||||
|
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
|
||||||
|
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
|
||||||
|
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
|
||||||
|
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
|
||||||
|
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.3.0-beta.0"></a>
|
||||||
|
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
|
||||||
|
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
|
||||||
|
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
|
||||||
|
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
|
||||||
|
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
|
||||||
|
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
|
||||||
|
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
|
||||||
|
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
|
||||||
|
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
|
||||||
|
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
|
||||||
|
|
||||||
|
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.2.1"></a>
|
||||||
|
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
|
||||||
|
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
|
||||||
|
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
|
||||||
|
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
|
||||||
|
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
|
||||||
|
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
|
||||||
|
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
|
||||||
|
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
|
||||||
|
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
|
||||||
|
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
|
||||||
|
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
|
||||||
|
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
|
||||||
|
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
|
||||||
|
|
||||||
|
|
||||||
<a name="2.2.0"></a>
|
<a name="2.2.0"></a>
|
||||||
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
|
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
|
||||||
|
|
||||||
@ -798,7 +1073,7 @@ prefix using `animate-` must now be preixed using `bind-animate-`.
|
|||||||
* core:
|
* core:
|
||||||
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
|
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
|
||||||
created during bootstrap instead by calling `NgModuleRef.destroy`.
|
created during bootstrap instead by calling `NgModuleRef.destroy`.
|
||||||
- `AplicationRef.registerDisposeListener` is deprecated.
|
- `ApplicationRef.registerDisposeListener` is deprecated.
|
||||||
Use the `ngOnDestroy` lifecycle hook for providers or
|
Use the `ngOnDestroy` lifecycle hook for providers or
|
||||||
`NgModuleRef.onDestroy` instead.
|
`NgModuleRef.onDestroy` instead.
|
||||||
- `disposePlatform` is deprecated. Use `destroyPlatform` instead.
|
- `disposePlatform` is deprecated. Use `destroyPlatform` instead.
|
||||||
|
@ -57,7 +57,7 @@ We want to fix all the issues as soon as possible, but before fixing a bug we ne
|
|||||||
- 3rd-party libraries and their versions
|
- 3rd-party libraries and their versions
|
||||||
- and most importantly - a use-case that fails
|
- and most importantly - a use-case that fails
|
||||||
|
|
||||||
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demostrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demostrating the problem.
|
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demonstrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demonstrating the problem.
|
||||||
|
|
||||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
|||||||
[github]: https://github.com/angular/angular
|
[github]: https://github.com/angular/angular
|
||||||
[gitter]: https://gitter.im/angular/angular
|
[gitter]: https://gitter.im/angular/angular
|
||||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||||
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
|
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
|
||||||
[jsfiddle]: http://jsfiddle.net
|
[jsfiddle]: http://jsfiddle.net
|
||||||
[plunker]: http://plnkr.co/edit
|
[plunker]: http://plnkr.co/edit
|
||||||
[runnable]: http://runnable.com
|
[runnable]: http://runnable.com
|
||||||
|
35
DEVELOPER.md
35
DEVELOPER.md
@ -1,6 +1,6 @@
|
|||||||
# Building and Testing Angular 2 for JS
|
# Building and Testing Angular
|
||||||
|
|
||||||
This document describes how to set up your development environment to build and test Angular 2 JS version.
|
This document describes how to set up your development environment to build and test Angular.
|
||||||
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,6 +74,15 @@ 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**:
|
||||||
@ -126,7 +135,7 @@ $ gulp public-api:update
|
|||||||
|
|
||||||
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||||
|
|
||||||
## Formatting your source code
|
## <a name="clang-format"></a> Formatting your source code
|
||||||
|
|
||||||
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
|
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
|
||||||
is not properly formatted, the CI will fail and the PR can not be merged.
|
is not properly formatted, the CI will fail and the PR can not be merged.
|
||||||
@ -137,4 +146,24 @@ You can automatically format your code by running:
|
|||||||
$ gulp format
|
$ gulp format
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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,8 +24,9 @@ with it.
|
|||||||
* `comp: forms`: `@kara`
|
* `comp: forms`: `@kara`
|
||||||
* `comp: http`: `@jeffbcross`
|
* `comp: http`: `@jeffbcross`
|
||||||
* `comp: i18n`: `@vicb`
|
* `comp: i18n`: `@vicb`
|
||||||
|
* `comp: language service`: `@chuckjaz`
|
||||||
* `comp: metadata-extractor`: `@chuckjaz`
|
* `comp: metadata-extractor`: `@chuckjaz`
|
||||||
* `comp: router`: `@vsavkin`
|
* `comp: router`: `@vicb`
|
||||||
* `comp: testing`: `@juliemr`
|
* `comp: testing`: `@juliemr`
|
||||||
* `comp: upgrade`: `@mhevery`
|
* `comp: upgrade`: `@mhevery`
|
||||||
* `comp: web-worker`: `@vicb`
|
* `comp: web-worker`: `@vicb`
|
||||||
|
@ -36,7 +36,7 @@ var CIconfiguration = {
|
|||||||
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||||
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||||
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||||
'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||||
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
|
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,10 +44,10 @@ var customLaunchers = {
|
|||||||
'DartiumWithWebPlatform':
|
'DartiumWithWebPlatform':
|
||||||
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
|
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
|
||||||
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
|
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
|
||||||
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
|
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'},
|
||||||
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
|
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
|
||||||
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
|
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
|
||||||
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
|
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '50'},
|
||||||
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
|
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
|
||||||
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
|
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
|
||||||
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
|
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
|
||||||
@ -96,7 +96,7 @@ var customLaunchers = {
|
|||||||
'BS_IE10': {
|
'BS_IE10': {
|
||||||
base: 'BrowserStack',
|
base: 'BrowserStack',
|
||||||
browser: 'ie',
|
browser: 'ie',
|
||||||
browser_version: '10.0',
|
browser_version: '10.1',
|
||||||
os: 'Windows',
|
os: 'Windows',
|
||||||
os_version: '8'
|
os_version: '8'
|
||||||
},
|
},
|
||||||
|
45
build.sh
45
build.sh
@ -17,9 +17,14 @@ PACKAGES=(core
|
|||||||
upgrade
|
upgrade
|
||||||
router
|
router
|
||||||
compiler-cli
|
compiler-cli
|
||||||
|
language-service
|
||||||
benchpress)
|
benchpress)
|
||||||
BUILD_ALL=true
|
BUILD_ALL=true
|
||||||
BUNDLE=true
|
BUNDLE=true
|
||||||
|
VERSION_PREFIX=$(node -p "require('./package.json').version")
|
||||||
|
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
|
||||||
|
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
|
||||||
|
REMOVE_BENCHPRESS=false
|
||||||
|
|
||||||
for ARG in "$@"; do
|
for ARG in "$@"; do
|
||||||
case "$ARG" in
|
case "$ARG" in
|
||||||
@ -31,6 +36,10 @@ for ARG in "$@"; do
|
|||||||
--bundle=*)
|
--bundle=*)
|
||||||
BUNDLE=( "${ARG#--bundle=}" )
|
BUNDLE=( "${ARG#--bundle=}" )
|
||||||
;;
|
;;
|
||||||
|
--publish)
|
||||||
|
VERSION_SUFFIX=""
|
||||||
|
REMOVE_BENCHPRESS=true
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown option $ARG."
|
echo "Unknown option $ARG."
|
||||||
exit 1
|
exit 1
|
||||||
@ -38,6 +47,10 @@ for ARG in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||||
|
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||||
|
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
|
||||||
|
|
||||||
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
|
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
|
||||||
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
||||||
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
|
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
|
||||||
@ -106,7 +119,13 @@ do
|
|||||||
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
||||||
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
|
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
|
||||||
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
|
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
|
||||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
|
||||||
|
if [[ ${PACKAGE} != router ]]; then
|
||||||
|
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||||
|
fi
|
||||||
|
if [[ ${PACKAGE} == router ]]; then
|
||||||
|
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
|
||||||
|
fi
|
||||||
|
|
||||||
rm -rf ${DESTDIR}
|
rm -rf ${DESTDIR}
|
||||||
|
|
||||||
@ -126,6 +145,11 @@ do
|
|||||||
$TSC -p ${SRCDIR}/tsconfig-testing.json
|
$TSC -p ${SRCDIR}/tsconfig-testing.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -e ${SRCDIR}/tsconfig-2015.json ]]; then
|
||||||
|
echo "====== COMPILING ESM: ${TSC} -p ${SRCDIR}/tsconfig-2015.json"
|
||||||
|
${TSC} -p ${SRCDIR}/tsconfig-2015.json
|
||||||
|
fi
|
||||||
|
|
||||||
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
|
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
|
||||||
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
|
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
@ -157,7 +181,6 @@ do
|
|||||||
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
||||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||||
|
|
||||||
|
|
||||||
if [[ -e rollup-testing.config.js ]]; then
|
if [[ -e rollup-testing.config.js ]]; then
|
||||||
echo "====== Rollup ${PACKAGE} testing"
|
echo "====== Rollup ${PACKAGE} testing"
|
||||||
../../../node_modules/.bin/rollup -c rollup-testing.config.js
|
../../../node_modules/.bin/rollup -c rollup-testing.config.js
|
||||||
@ -191,8 +214,24 @@ do
|
|||||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
|
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
|
||||||
fi
|
fi
|
||||||
) 2>&1 | grep -v "as external dependency"
|
) 2>&1 | grep -v "as external dependency"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
echo "====== VERSION: Updating version references"
|
||||||
|
cd ${DESTDIR}
|
||||||
|
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
|
||||||
|
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
|
||||||
|
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
|
||||||
|
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
|
||||||
|
)
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
|
||||||
./modules/@angular/examples/build.sh
|
./modules/@angular/examples/build.sh
|
||||||
|
|
||||||
|
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "==== Removing benchpress from publication"
|
||||||
|
rm -r dist/packages-dist/benchpress
|
||||||
|
fi
|
||||||
|
@ -4,7 +4,7 @@ machine:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pre:
|
pre:
|
||||||
- npm install -g npm
|
- npm install -g npm@3.6.0
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
@ -24,17 +24,25 @@ 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/long-stack-trace-zone.js',
|
'node_modules/zone.js/dist/zone.js',
|
||||||
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',
|
'node_modules/zone.js/dist/long-stack-trace-zone.js',
|
||||||
'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js',
|
'node_modules/zone.js/dist/proxy.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', 'node_modules/systemjs/dist/system.src.js',
|
'shims_for_IE.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', 'tools/build/file2modulename.js', 'test-main.js',
|
'node_modules/reflect-metadata/Reflect.js',
|
||||||
{pattern: 'dist/all/empty.*', included: false, watched: false}, {
|
'tools/build/file2modulename.js',
|
||||||
|
'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
|
||||||
@ -48,11 +56,15 @@ module.exports = function(config) {
|
|||||||
|
|
||||||
exclude: [
|
exclude: [
|
||||||
'dist/all/@angular/**/e2e_test/**',
|
'dist/all/@angular/**/e2e_test/**',
|
||||||
'dist/all/@angular/router/**',
|
'dist/all/@angular/**/*node_only_spec.js',
|
||||||
'dist/all/@angular/compiler-cli/**',
|
|
||||||
'dist/all/@angular/benchpress/**',
|
'dist/all/@angular/benchpress/**',
|
||||||
'dist/all/angular1_router.js',
|
'dist/all/@angular/compiler-cli/**',
|
||||||
|
'dist/all/@angular/compiler/test/aot/**',
|
||||||
|
'dist/all/@angular/examples/**/e2e_test/*',
|
||||||
|
'dist/all/@angular/language-service/**',
|
||||||
|
'dist/all/@angular/router/**',
|
||||||
'dist/all/@angular/platform-browser/testing/e2e_util.js',
|
'dist/all/@angular/platform-browser/testing/e2e_util.js',
|
||||||
|
'dist/all/angular1_router.js',
|
||||||
'dist/examples/**/e2e_test/**',
|
'dist/examples/**/e2e_test/**',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -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.0-beta.12",
|
"rxjs": "^5.0.1",
|
||||||
"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"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable:no-console */
|
||||||
import {browser} from 'protractor';
|
import {browser} from 'protractor';
|
||||||
|
|
||||||
const assertEventsContainsName = function(events: any[], eventName: string) {
|
const assertEventsContainsName = function(events: any[], eventName: string) {
|
||||||
|
@ -11,9 +11,6 @@
|
|||||||
* @description
|
* @description
|
||||||
* Entry point for all public APIs of the common package.
|
* Entry point for all public APIs of the common package.
|
||||||
*/
|
*/
|
||||||
export * from './src/location';
|
export * from './src/common';
|
||||||
export {NgLocalization} from './src/localization';
|
|
||||||
export {CommonModule} from './src/common_module';
|
|
||||||
|
|
||||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './src/directives/index';
|
// This file only reexports content of the `src` folder. Keep it that way.
|
||||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './src/pipes/index';
|
|
||||||
|
20
modules/@angular/common/src/common.ts
Normal file
20
modules/@angular/common/src/common.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* @description
|
||||||
|
* Entry point for all public APIs of the common package.
|
||||||
|
*/
|
||||||
|
export * from './location/index';
|
||||||
|
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||||
|
export {CommonModule} from './common_module';
|
||||||
|
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
||||||
|
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||||
|
export {VERSION} from './version';
|
||||||
|
export {Version} from '@angular/core';
|
@ -89,9 +89,18 @@ 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() ngForTrackBy: TrackByFn;
|
@Input()
|
||||||
|
set ngForTrackBy(fn: TrackByFn) {
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
|
||||||
|
}
|
||||||
|
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>,
|
||||||
@ -119,7 +128,7 @@ export class NgFor implements DoCheck, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDoCheck() {
|
ngDoCheck(): void {
|
||||||
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>`
|
* - `<template [ngIf]="condition"><div>...</div></template>`
|
||||||
*
|
*
|
||||||
|
* 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()
|
||||||
this._viewContainer.clear();
|
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._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; }
|
||||||
|
@ -23,9 +23,23 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
|||||||
*/
|
*/
|
||||||
export function getPluralCategory(
|
export function getPluralCategory(
|
||||||
value: number, cases: string[], ngLocalization: NgLocalization): string {
|
value: number, cases: string[], ngLocalization: NgLocalization): string {
|
||||||
const nbCase = `=${value}`;
|
let key = `=${value}`;
|
||||||
|
|
||||||
return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
|
if (cases.indexOf(key) > -1) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = ngLocalization.getPluralCategory(value);
|
||||||
|
|
||||||
|
if (cases.indexOf(key) > -1) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cases.indexOf('other') > -1) {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`No plural message found for value "${value}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,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:
|
||||||
@ -417,4 +431,4 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
|
|||||||
default:
|
default:
|
||||||
return Plural.Other;
|
return Plural.Other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './location/platform_location';
|
|
||||||
export * from './location/location_strategy';
|
|
||||||
export * from './location/hash_location_strategy';
|
|
||||||
export * from './location/path_location_strategy';
|
|
||||||
export * from './location/location';
|
|
@ -17,6 +17,8 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @whatItDoes Use URL hash for storing application location data.
|
||||||
|
* @description
|
||||||
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
|
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||||
* {@link Location} service to represent its state in the
|
* {@link Location} service to represent its state in the
|
||||||
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
|
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
|
||||||
@ -27,18 +29,7 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
|||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
*
|
*
|
||||||
* ```
|
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
|
||||||
* import {Component, NgModule} from '@angular/core';
|
|
||||||
* import {
|
|
||||||
* LocationStrategy,
|
|
||||||
* HashLocationStrategy
|
|
||||||
* } from '@angular/common';
|
|
||||||
*
|
|
||||||
* @NgModule({
|
|
||||||
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
|
|
||||||
* })
|
|
||||||
* class AppModule {}
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
|
13
modules/@angular/common/src/location/index.ts
Normal file
13
modules/@angular/common/src/location/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './platform_location';
|
||||||
|
export * from './location_strategy';
|
||||||
|
export * from './hash_location_strategy';
|
||||||
|
export * from './path_location_strategy';
|
||||||
|
export * from './location';
|
@ -12,7 +12,8 @@ import {LocationStrategy} from './location_strategy';
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `Location` is a service that applications can use to interact with a browser's URL.
|
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
|
||||||
|
* @description
|
||||||
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
|
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
|
||||||
* to the URL's path or the URL's hash segment.
|
* to the URL's path or the URL's hash segment.
|
||||||
*
|
*
|
||||||
@ -28,19 +29,7 @@ import {LocationStrategy} from './location_strategy';
|
|||||||
* - `/my/app/user/123/` **is not** normalized
|
* - `/my/app/user/123/` **is not** normalized
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
*
|
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||||
* ```
|
|
||||||
* import {Component} from '@angular/core';
|
|
||||||
* import {Location} from '@angular/common';
|
|
||||||
*
|
|
||||||
* @Component({selector: 'app-component'})
|
|
||||||
* class AppCmp {
|
|
||||||
* constructor(location: Location) {
|
|
||||||
* location.go('/foo');
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -96,7 +85,7 @@ export class Location {
|
|||||||
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
|
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
|
||||||
*/
|
*/
|
||||||
prepareExternalUrl(url: string): string {
|
prepareExternalUrl(url: string): string {
|
||||||
if (url.length > 0 && !url.startsWith('/')) {
|
if (url && url[0] !== '/') {
|
||||||
url = '/' + url;
|
url = '/' + url;
|
||||||
}
|
}
|
||||||
return this._platformStrategy.prepareExternalUrl(url);
|
return this._platformStrategy.prepareExternalUrl(url);
|
||||||
@ -143,7 +132,7 @@ export class Location {
|
|||||||
* is.
|
* is.
|
||||||
*/
|
*/
|
||||||
public static normalizeQueryParams(params: string): string {
|
public static normalizeQueryParams(params: string): string {
|
||||||
return (params.length > 0 && params.substring(0, 1) != '?') ? ('?' + params) : params;
|
return params && params[0] !== '?' ? '?' + params : params;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,25 +164,13 @@ export class Location {
|
|||||||
/**
|
/**
|
||||||
* If url has a trailing slash, remove it, otherwise return url as is.
|
* If url has a trailing slash, remove it, otherwise return url as is.
|
||||||
*/
|
*/
|
||||||
public static stripTrailingSlash(url: string): string {
|
public static stripTrailingSlash(url: string): string { return url.replace(/\/$/, ''); }
|
||||||
if (/\/$/g.test(url)) {
|
|
||||||
url = url.substring(0, url.length - 1);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _stripBaseHref(baseHref: string, url: string): string {
|
function _stripBaseHref(baseHref: string, url: string): string {
|
||||||
if (baseHref.length > 0 && url.startsWith(baseHref)) {
|
return baseHref && url.startsWith(baseHref) ? url.substring(baseHref.length) : url;
|
||||||
return url.substring(baseHref.length);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _stripIndexHtml(url: string): string {
|
function _stripIndexHtml(url: string): string {
|
||||||
if (/\/index.html$/g.test(url)) {
|
return url.replace(/\/index.html$/, '');
|
||||||
// '/index.html'.length == 11
|
|
||||||
return url.substring(0, url.length - 11);
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
|
|||||||
/**
|
/**
|
||||||
* `LocationStrategy` is responsible for representing and reading route state
|
* `LocationStrategy` is responsible for representing and reading route state
|
||||||
* from the browser's URL. Angular provides two strategies:
|
* from the browser's URL. Angular provides two strategies:
|
||||||
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
|
* {@link HashLocationStrategy} and {@link PathLocationStrategy}.
|
||||||
*
|
*
|
||||||
* This is used under the hood of the {@link Location} service.
|
* This is used under the hood of the {@link Location} service.
|
||||||
*
|
*
|
||||||
|
@ -17,14 +17,13 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @whatItDoes Use URL for storing application location data.
|
||||||
|
* @description
|
||||||
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||||
* {@link Location} service to represent its state in the
|
* {@link Location} service to represent its state in the
|
||||||
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
|
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
|
||||||
* browser's URL.
|
* browser's URL.
|
||||||
*
|
*
|
||||||
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
|
|
||||||
* provided in {@link ROUTER_PROVIDERS}.
|
|
||||||
*
|
|
||||||
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
|
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
|
||||||
* or add a base element to the document. This URL prefix that will be preserved
|
* or add a base element to the document. This URL prefix that will be preserved
|
||||||
* when generating and recognizing URLs.
|
* when generating and recognizing URLs.
|
||||||
@ -37,6 +36,10 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
|||||||
* `location.go('/foo')`, the browser's URL will become
|
* `location.go('/foo')`, the browser's URL will become
|
||||||
* `example.com/my/app/foo`.
|
* `example.com/my/app/foo`.
|
||||||
*
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||||
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -59,7 +59,7 @@ const _observableStrategy = new ObservableStrategy();
|
|||||||
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
|
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
|
||||||
*
|
*
|
||||||
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
|
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
|
||||||
* to the view. The Observable continuesly updates the view with the current time.
|
* to the view. The Observable continuously updates the view with the current time.
|
||||||
*
|
*
|
||||||
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
|
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
|
||||||
*
|
*
|
||||||
|
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to transform a single word to titlecase.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
function titleCaseWord(word: string) {
|
||||||
|
if (!word) return word;
|
||||||
|
return word[0].toUpperCase() + word.substr(1).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.split(/\b/g).map(word => titleCaseWord(word)).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,11 +7,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||||
import {DateFormatter} from '../facade/intl';
|
|
||||||
import {NumberWrapper, isDate} from '../facade/lang';
|
import {NumberWrapper, isDate} from '../facade/lang';
|
||||||
|
|
||||||
|
import {DateFormatter} from './intl';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @ngModule CommonModule
|
||||||
* @whatItDoes Formats a date according to locale rules.
|
* @whatItDoes Formats a date according to locale rules.
|
||||||
|
@ -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,
|
||||||
|
@ -44,7 +44,8 @@ const DATE_FORMATS_SPLIT =
|
|||||||
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
|
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
|
||||||
|
|
||||||
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
||||||
yMMMdjms: datePartGetterFactory(combine([
|
// Keys are quoted so they do not get renamed during closure compilation.
|
||||||
|
'yMMMdjms': datePartGetterFactory(combine([
|
||||||
digitCondition('year', 1),
|
digitCondition('year', 1),
|
||||||
nameCondition('month', 3),
|
nameCondition('month', 3),
|
||||||
digitCondition('day', 1),
|
digitCondition('day', 1),
|
||||||
@ -52,68 +53,70 @@ const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
|||||||
digitCondition('minute', 1),
|
digitCondition('minute', 1),
|
||||||
digitCondition('second', 1),
|
digitCondition('second', 1),
|
||||||
])),
|
])),
|
||||||
yMdjm: datePartGetterFactory(combine([
|
'yMdjm': datePartGetterFactory(combine([
|
||||||
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
|
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
|
||||||
digitCondition('hour', 1), digitCondition('minute', 1)
|
digitCondition('hour', 1), digitCondition('minute', 1)
|
||||||
])),
|
])),
|
||||||
yMMMMEEEEd: datePartGetterFactory(combine([
|
'yMMMMEEEEd': datePartGetterFactory(combine([
|
||||||
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
|
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
|
||||||
digitCondition('day', 1)
|
digitCondition('day', 1)
|
||||||
])),
|
])),
|
||||||
yMMMMd: datePartGetterFactory(
|
'yMMMMd': datePartGetterFactory(
|
||||||
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
|
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
|
||||||
yMMMd: datePartGetterFactory(
|
'yMMMd': datePartGetterFactory(
|
||||||
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
|
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
|
||||||
yMd: datePartGetterFactory(
|
'yMd': datePartGetterFactory(
|
||||||
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
|
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
|
||||||
jms: datePartGetterFactory(combine(
|
'jms': datePartGetterFactory(combine(
|
||||||
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
|
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
|
||||||
jm: datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
|
||||||
};
|
};
|
||||||
|
|
||||||
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
|
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
|
||||||
yyyy: datePartGetterFactory(digitCondition('year', 4)),
|
// Keys are quoted so they do not get renamed.
|
||||||
yy: datePartGetterFactory(digitCondition('year', 2)),
|
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
|
||||||
y: datePartGetterFactory(digitCondition('year', 1)),
|
'yy': datePartGetterFactory(digitCondition('year', 2)),
|
||||||
MMMM: datePartGetterFactory(nameCondition('month', 4)),
|
'y': datePartGetterFactory(digitCondition('year', 1)),
|
||||||
MMM: datePartGetterFactory(nameCondition('month', 3)),
|
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
|
||||||
MM: datePartGetterFactory(digitCondition('month', 2)),
|
'MMM': datePartGetterFactory(nameCondition('month', 3)),
|
||||||
M: datePartGetterFactory(digitCondition('month', 1)),
|
'MM': datePartGetterFactory(digitCondition('month', 2)),
|
||||||
LLLL: datePartGetterFactory(nameCondition('month', 4)),
|
'M': datePartGetterFactory(digitCondition('month', 1)),
|
||||||
L: datePartGetterFactory(nameCondition('month', 1)),
|
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
|
||||||
dd: datePartGetterFactory(digitCondition('day', 2)),
|
'L': datePartGetterFactory(nameCondition('month', 1)),
|
||||||
d: datePartGetterFactory(digitCondition('day', 1)),
|
'dd': datePartGetterFactory(digitCondition('day', 2)),
|
||||||
HH: digitModifier(
|
'd': datePartGetterFactory(digitCondition('day', 1)),
|
||||||
|
'HH': digitModifier(
|
||||||
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
|
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
|
||||||
H: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
||||||
hh: digitModifier(
|
'hh': digitModifier(
|
||||||
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
|
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
|
||||||
h: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||||
jj: datePartGetterFactory(digitCondition('hour', 2)),
|
'jj': datePartGetterFactory(digitCondition('hour', 2)),
|
||||||
j: datePartGetterFactory(digitCondition('hour', 1)),
|
'j': datePartGetterFactory(digitCondition('hour', 1)),
|
||||||
mm: digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
|
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
|
||||||
m: datePartGetterFactory(digitCondition('minute', 1)),
|
'm': datePartGetterFactory(digitCondition('minute', 1)),
|
||||||
ss: digitModifier(datePartGetterFactory(digitCondition('second', 2))),
|
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
|
||||||
s: datePartGetterFactory(digitCondition('second', 1)),
|
's': datePartGetterFactory(digitCondition('second', 1)),
|
||||||
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
|
||||||
// we can be just safely rely on using `sss` since we currently don't support single or two digit
|
// we can be just safely rely on using `sss` since we currently don't support single or two digit
|
||||||
// fractions
|
// fractions
|
||||||
sss: datePartGetterFactory(digitCondition('second', 3)),
|
'sss': datePartGetterFactory(digitCondition('second', 3)),
|
||||||
EEEE: datePartGetterFactory(nameCondition('weekday', 4)),
|
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
|
||||||
EEE: datePartGetterFactory(nameCondition('weekday', 3)),
|
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
|
||||||
EE: datePartGetterFactory(nameCondition('weekday', 2)),
|
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
|
||||||
E: datePartGetterFactory(nameCondition('weekday', 1)),
|
'E': datePartGetterFactory(nameCondition('weekday', 1)),
|
||||||
a: hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||||
Z: timeZoneGetter('short'),
|
'Z': timeZoneGetter('short'),
|
||||||
z: timeZoneGetter('long'),
|
'z': timeZoneGetter('long'),
|
||||||
ww: datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
||||||
// first Thursday of the year. not support ?
|
// first Thursday of the year. not support ?
|
||||||
w: datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
'w':
|
||||||
|
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
||||||
// of the year not support ?
|
// of the year not support ?
|
||||||
G: datePartGetterFactory(nameCondition('era', 1)),
|
'G': datePartGetterFactory(nameCondition('era', 1)),
|
||||||
GG: datePartGetterFactory(nameCondition('era', 2)),
|
'GG': datePartGetterFactory(nameCondition('era', 2)),
|
||||||
GGG: datePartGetterFactory(nameCondition('era', 3)),
|
'GGG': datePartGetterFactory(nameCondition('era', 3)),
|
||||||
GGGG: datePartGetterFactory(nameCondition('era', 4))
|
'GGGG': datePartGetterFactory(nameCondition('era', 4))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -184,7 +187,8 @@ function dateFormatter(format: string, date: Date, locale: string): string {
|
|||||||
|
|
||||||
if (fn) return fn(date, locale);
|
if (fn) return fn(date, locale);
|
||||||
|
|
||||||
let parts = DATE_FORMATTER_CACHE.get(format);
|
const cacheKey = format;
|
||||||
|
let parts = DATE_FORMATTER_CACHE.get(cacheKey);
|
||||||
|
|
||||||
if (!parts) {
|
if (!parts) {
|
||||||
parts = [];
|
parts = [];
|
||||||
@ -202,7 +206,7 @@ function dateFormatter(format: string, date: Date, locale: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DATE_FORMATTER_CACHE.set(format, parts);
|
DATE_FORMATTER_CACHE.set(cacheKey, parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.reduce((text, part) => {
|
return parts.reduce((text, part) => {
|
@ -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,9 +8,9 @@
|
|||||||
|
|
||||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||||
|
|
||||||
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
|
|
||||||
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
||||||
|
|
||||||
|
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
19
modules/@angular/common/src/version.ts
Normal file
19
modules/@angular/common/src/version.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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* @description
|
||||||
|
* Entry point for all public APIs of the common package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Version} from '@angular/core';
|
||||||
|
/**
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export const VERSION = new Version('0.0.0-PLACEHOLDER');
|
@ -294,6 +294,16 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('track by', () => {
|
describe('track by', () => {
|
||||||
|
it('should throw if trackBy is not a function', async(() => {
|
||||||
|
const template =
|
||||||
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
getComponent().items = [{id: 1}, {id: 2}];
|
||||||
|
expect(() => fixture.detectChanges())
|
||||||
|
.toThrowError(/trackBy must be a function, but received null/);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should set the context to the component instance', async(() => {
|
it('should set the context to the component instance', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
||||||
|
@ -153,6 +153,78 @@ export function main() {
|
|||||||
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
||||||
.toBe(true);
|
.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');
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,13 +7,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {LOCALE_ID} from '@angular/core';
|
import {LOCALE_ID} from '@angular/core';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed, inject} from '@angular/core/testing';
|
||||||
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
|
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
|
||||||
|
|
||||||
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
|
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('l10n', () => {
|
describe('l10n', () => {
|
||||||
|
|
||||||
@ -141,6 +138,23 @@ export function main() {
|
|||||||
expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5');
|
expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5');
|
||||||
expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other');
|
expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fallback to other when the case is not present', () => {
|
||||||
|
const l10n = new NgLocaleLocalization('ro');
|
||||||
|
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
|
||||||
|
// 2 -> 'few'
|
||||||
|
expect(getPluralCategory(2, ['one', 'other'], l10n)).toEqual('other');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
it('should report an error when the "other" category is not present', () => {
|
||||||
|
expect(() => {
|
||||||
|
const l10n = new NgLocaleLocalization('ro');
|
||||||
|
// 2 -> 'few'
|
||||||
|
getPluralCategory(2, ['one'], l10n);
|
||||||
|
}).toThrowError('No plural message found for value "2"');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* @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 return titlecase for subsequent words',
|
||||||
|
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
|
||||||
|
|
||||||
|
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
|
||||||
|
|
||||||
|
it('should persist whitespace',
|
||||||
|
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
|
||||||
|
|
||||||
|
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(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
@ -22,6 +22,7 @@
|
|||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +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
|
||||||
*/
|
*/
|
||||||
|
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||||
export {CodeGenerator} from './src/codegen';
|
export {CodeGenerator} from './src/codegen';
|
||||||
|
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
|
||||||
export {Extractor} from './src/extractor';
|
export {Extractor} from './src/extractor';
|
||||||
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
|
|
||||||
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
|
|
||||||
|
|
||||||
export * from '@angular/tsc-wrapped';
|
export * from '@angular/tsc-wrapped';
|
||||||
|
export {VERSION} from './src/version';
|
||||||
|
|
||||||
|
|
||||||
|
// TODO(hansl): moving to Angular 4 need to update this API.
|
||||||
|
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
:host {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
<div>
|
||||||
|
<h1 i18n>hello world</h1>
|
||||||
|
<a [routerLink]="['lazy']">lazy</a>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
@ -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
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: 'app.component.html',
|
||||||
|
styleUrls: ['app.component.css'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
|
||||||
|
@Component({selector: 'home-view', template: 'home!'})
|
||||||
|
export class HomeView {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent, HomeView],
|
||||||
|
imports: [
|
||||||
|
BrowserModule, RouterModule.forRoot([
|
||||||
|
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
|
||||||
|
{path: 'feature2', loadChildren: 'feature2/feature2.module#Feature2Module'},
|
||||||
|
{path: '', component: HomeView}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({selector: 'feature-component', template: 'foo.html'})
|
||||||
|
export class FeatureComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FeatureComponent],
|
||||||
|
imports: [RouterModule.forChild([{path: '', component: FeatureComponent}])]
|
||||||
|
})
|
||||||
|
export class FeatureModule {
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({selector: 'lazy-feature-comp', template: 'lazy feature!'})
|
||||||
|
export class LazyFeatureComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild([
|
||||||
|
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
||||||
|
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
|
||||||
|
])],
|
||||||
|
declarations: [LazyFeatureComponent]
|
||||||
|
})
|
||||||
|
export class LazyFeatureModule {
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({selector: 'feature-component', template: 'foo.html'})
|
||||||
|
export class FeatureComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FeatureComponent],
|
||||||
|
imports: [RouterModule.forChild([
|
||||||
|
{path: '', component: FeatureComponent},
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
export default class DefaultModule {
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({selector: 'feature-component', template: 'foo.html'})
|
||||||
|
export class FeatureComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FeatureComponent],
|
||||||
|
imports: [RouterModule.forChild([
|
||||||
|
{path: '', component: FeatureComponent}, {path: 'd', loadChildren: './default.module'} {
|
||||||
|
path: 'e',
|
||||||
|
loadChildren: 'feature/feature.module#FeatureModule'
|
||||||
|
}
|
||||||
|
])]
|
||||||
|
})
|
||||||
|
export class Feature2Module {
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({selector: 'lazy-comp', template: 'lazy!'})
|
||||||
|
export class LazyComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild([
|
||||||
|
{path: '', component: LazyComponent, pathMatch: 'full'},
|
||||||
|
{path: 'feature', loadChildren: './feature/feature.module#FeatureModule'},
|
||||||
|
{path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'}
|
||||||
|
])],
|
||||||
|
declarations: [LazyComponent]
|
||||||
|
})
|
||||||
|
export class LazyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SecondModule {}
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
// For TypeScript 1.8, we have to lay out generated files
|
||||||
|
// in the same source directory with your code.
|
||||||
|
"genDir": ".",
|
||||||
|
"debug": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "",
|
||||||
|
"declaration": true,
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"baseUrl": "."
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
|
|
||||||
<!ATTLIST translationbundle lang CDATA #REQUIRED>
|
|
||||||
<!ELEMENT translation (#PCDATA|ph)*>
|
|
||||||
<!ATTLIST translation id CDATA #REQUIRED>
|
|
||||||
<!ELEMENT ph EMPTY>
|
|
||||||
<!ATTLIST ph name CDATA #REQUIRED>
|
|
||||||
]>
|
|
||||||
<translationbundle>
|
|
||||||
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
|
|
||||||
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
|
|
||||||
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
|
|
||||||
</translationbundle>
|
|
@ -11,7 +11,9 @@ import {FormsModule} from '@angular/forms';
|
|||||||
import {ServerModule} from '@angular/platform-server';
|
import {ServerModule} from '@angular/platform-server';
|
||||||
import {MdButtonModule} from '@angular2-material/button';
|
import {MdButtonModule} from '@angular2-material/button';
|
||||||
|
|
||||||
import {ThirdpartyModule} from '../third_party_src/module';
|
// Note: don't refer to third_party_src as we want to test that
|
||||||
|
// we can compile components from node_modules!
|
||||||
|
import {ThirdpartyModule} from 'third_party/module';
|
||||||
|
|
||||||
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
|
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
|
||||||
import {AnimateCmp} from './animate';
|
import {AnimateCmp} from './animate';
|
||||||
@ -19,7 +21,7 @@ import {BasicComp} from './basic';
|
|||||||
import {ComponentUsingThirdParty} from './comp_using_3rdp';
|
import {ComponentUsingThirdParty} from './comp_using_3rdp';
|
||||||
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
|
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
|
||||||
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
|
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
|
||||||
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
|
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomeLibModule, SomePipeInRootModule, SomeService} from './module_fixtures';
|
||||||
import {CompWithNgContent, ProjectingComp} from './projection';
|
import {CompWithNgContent, ProjectingComp} from './projection';
|
||||||
import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, DirectiveForQuery} from './queries';
|
import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, DirectiveForQuery} from './queries';
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
MdButtonModule,
|
MdButtonModule,
|
||||||
ModuleUsingCustomElements,
|
ModuleUsingCustomElements,
|
||||||
someLibModuleWithProviders(),
|
SomeLibModule.withProviders(),
|
||||||
ThirdpartyModule,
|
ThirdpartyModule,
|
||||||
],
|
],
|
||||||
providers: [SomeService],
|
providers: [SomeService],
|
||||||
|
@ -63,17 +63,13 @@ export function provideValueWithEntryComponents(value: any) {
|
|||||||
entryComponents: [CompUsingLibModuleDirectiveAndPipe],
|
entryComponents: [CompUsingLibModuleDirectiveAndPipe],
|
||||||
})
|
})
|
||||||
export class SomeLibModule {
|
export class SomeLibModule {
|
||||||
}
|
static withProviders() {
|
||||||
|
return {
|
||||||
// TODO(tbosch): Make this a static method in `SomeLibModule` once
|
ngModule: SomeLibModule,
|
||||||
// our static reflector supports it.
|
providers: [
|
||||||
// See https://github.com/angular/angular/issues/10266.
|
ServiceUsingLibModule, provideValueWithEntryComponents(
|
||||||
export function someLibModuleWithProviders(): ModuleWithProviders {
|
[{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
|
||||||
return {
|
]
|
||||||
ngModule: SomeLibModule,
|
};
|
||||||
providers: [
|
}
|
||||||
ServiceUsingLibModule,
|
|
||||||
provideValueWithEntryComponents([{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
|||||||
<!ELEMENT ex (#PCDATA)>
|
<!ELEMENT ex (#PCDATA)>
|
||||||
]>
|
]>
|
||||||
<messagebundle>
|
<messagebundle>
|
||||||
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
|
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
|
||||||
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
|
<msg id="3492007542396725315">Welcome</msg>
|
||||||
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
|
<msg id="3772663375917578720">other-3rdP-component</msg>
|
||||||
</messagebundle>
|
</messagebundle>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -44,10 +44,6 @@ 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/>
|
||||||
@ -58,6 +54,10 @@ 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>
|
||||||
@ -79,5 +79,4 @@ describe('template i18n extraction output', () => {
|
|||||||
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
|
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
|
||||||
expect(xlf).toEqual(EXPECTED_XLIFF);
|
expect(xlf).toEqual(EXPECTED_XLIFF);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-console */
|
||||||
|
|
||||||
|
// Must be imported first, because angular2 decorators throws on load.
|
||||||
|
import 'reflect-metadata';
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import {tsc} from '@angular/tsc-wrapped/src/tsc';
|
||||||
|
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
|
||||||
|
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main method.
|
||||||
|
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||||
|
* properly read and wrote.
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
console.log(`testing ngtools API...`);
|
||||||
|
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => codeGenTest())
|
||||||
|
.then(() => i18nTest())
|
||||||
|
.then(() => lazyRoutesTest())
|
||||||
|
.then(() => {
|
||||||
|
console.log('All done!');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err.stack);
|
||||||
|
console.error('Test failed');
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeGenTest() {
|
||||||
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
|
const project = path.join(basePath, 'tsconfig-build.json');
|
||||||
|
const readResources: string[] = [];
|
||||||
|
const wroteFiles: string[] = [];
|
||||||
|
|
||||||
|
const config = tsc.readConfiguration(project, basePath);
|
||||||
|
const hostContext = new NodeCompilerHostContext();
|
||||||
|
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
||||||
|
const host: ts.CompilerHost = Object.assign(
|
||||||
|
{}, delegateHost,
|
||||||
|
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
|
||||||
|
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||||
|
|
||||||
|
config.ngOptions.basePath = basePath;
|
||||||
|
|
||||||
|
console.log(`>>> running codegen for ${project}`);
|
||||||
|
return __NGTOOLS_PRIVATE_API_2
|
||||||
|
.codeGen({
|
||||||
|
basePath,
|
||||||
|
compilerOptions: config.parsed.options, program, host,
|
||||||
|
|
||||||
|
angularCompilerOptions: config.ngOptions,
|
||||||
|
|
||||||
|
// i18n options.
|
||||||
|
i18nFormat: null,
|
||||||
|
i18nFile: null,
|
||||||
|
locale: null,
|
||||||
|
|
||||||
|
readResource: (fileName: string) => {
|
||||||
|
readResources.push(fileName);
|
||||||
|
return hostContext.readResource(fileName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log(`>>> codegen done, asserting read and wrote files`);
|
||||||
|
|
||||||
|
// Assert for each file that it has been read and each `ts` has a written file associated.
|
||||||
|
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||||
|
|
||||||
|
allFiles.forEach((fileName: string) => {
|
||||||
|
// Skip tsconfig.
|
||||||
|
if (fileName.match(/tsconfig-build.json$/)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that file was read.
|
||||||
|
if (fileName.match(/\.module\.ts$/)) {
|
||||||
|
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
|
||||||
|
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
|
||||||
|
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||||
|
assert(
|
||||||
|
readResources.indexOf(fileName) != -1,
|
||||||
|
`Expected resource "${fileName}" to be read.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`done, no errors.`);
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.error(e.stack);
|
||||||
|
console.error('Compilation failed');
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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() {
|
||||||
|
const basePath = path.join(__dirname, '../ngtools_src');
|
||||||
|
const project = path.join(basePath, 'tsconfig-build.json');
|
||||||
|
|
||||||
|
const config = tsc.readConfiguration(project, basePath);
|
||||||
|
const host = ts.createCompilerHost(config.parsed.options, true);
|
||||||
|
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||||
|
|
||||||
|
config.ngOptions.basePath = basePath;
|
||||||
|
|
||||||
|
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
|
||||||
|
program,
|
||||||
|
host,
|
||||||
|
angularCompilerOptions: config.ngOptions,
|
||||||
|
entryModule: 'app.module#AppModule'
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectations: {[route: string]: string} = {
|
||||||
|
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||||
|
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||||
|
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||||
|
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||||
|
'./default.module': 'feature2/default.module.ts',
|
||||||
|
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(lazyRoutes).forEach((route: string) => {
|
||||||
|
assert(route in expectations, `Found a route that was not expected: "${route}".`);
|
||||||
|
assert(
|
||||||
|
lazyRoutes[route] == path.join(basePath, expectations[route]),
|
||||||
|
`Route "${route}" does not point to the expected absolute path ` +
|
||||||
|
`"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that all expectations were met.
|
||||||
|
assert.deepEqual(
|
||||||
|
Object.keys(lazyRoutes), Object.keys(expectations), `Expected routes listed to be: \n` +
|
||||||
|
` ${JSON.stringify(Object.keys(expectations))}\n` +
|
||||||
|
`Actual:\n` +
|
||||||
|
` ${JSON.stringify(Object.keys(lazyRoutes))}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-console */
|
||||||
|
|
||||||
|
// Must be imported first, because angular2 decorators throws on load.
|
||||||
|
import 'reflect-metadata';
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import {tsc} from '@angular/tsc-wrapped/src/tsc';
|
||||||
|
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main method.
|
||||||
|
* Standalone program that executes the real codegen and tests that
|
||||||
|
* ngsummary.json files are used for libraries.
|
||||||
|
*/
|
||||||
|
function main() {
|
||||||
|
console.log(`testing usage of ngsummary.json files in libraries...`);
|
||||||
|
const basePath = path.resolve(__dirname, '..');
|
||||||
|
const project = path.resolve(basePath, 'tsconfig-build.json');
|
||||||
|
const readFiles: string[] = [];
|
||||||
|
const writtenFiles: {fileName: string, content: string}[] = [];
|
||||||
|
|
||||||
|
class AssertingHostContext extends NodeCompilerHostContext {
|
||||||
|
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));
|
||||||
|
return super.readFile(fileName);
|
||||||
|
}
|
||||||
|
readResource(s: string): Promise<string> {
|
||||||
|
readFiles.push(path.relative(basePath, s));
|
||||||
|
return super.readResource(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = tsc.readConfiguration(project, basePath);
|
||||||
|
config.ngOptions.basePath = basePath;
|
||||||
|
// This flag tells ngc do not recompile libraries.
|
||||||
|
config.ngOptions.generateCodeForLibraries = false;
|
||||||
|
|
||||||
|
console.log(`>>> running codegen for ${project}`);
|
||||||
|
codegen(
|
||||||
|
config,
|
||||||
|
(host) => {
|
||||||
|
host.writeFile = (fileName: string, content: string) => {
|
||||||
|
fileName = path.relative(basePath, fileName);
|
||||||
|
writtenFiles.push({fileName, content});
|
||||||
|
};
|
||||||
|
return new AssertingHostContext();
|
||||||
|
})
|
||||||
|
.then((exitCode: any) => {
|
||||||
|
console.log(`>>> codegen done, asserting read files`);
|
||||||
|
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
|
||||||
|
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
|
||||||
|
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
|
||||||
|
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
|
||||||
|
|
||||||
|
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
|
||||||
|
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
|
||||||
|
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
|
||||||
|
|
||||||
|
console.log(`>>> asserting written files`);
|
||||||
|
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
|
||||||
|
|
||||||
|
console.log(`done, no errors.`);
|
||||||
|
process.exit(exitCode);
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.error(e.stack);
|
||||||
|
console.error('Compilation failed');
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext
|
||||||
|
*/
|
||||||
|
function codegen(
|
||||||
|
config: {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions},
|
||||||
|
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
|
||||||
|
const host = ts.createCompilerHost(config.parsed.options, true);
|
||||||
|
|
||||||
|
// HACK: patch the realpath to solve symlink issue here:
|
||||||
|
// https://github.com/Microsoft/TypeScript/issues/9552
|
||||||
|
// todo(misko): remove once facade symlinks are removed
|
||||||
|
host.realpath = (path) => path;
|
||||||
|
|
||||||
|
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||||
|
|
||||||
|
return CodeGenerator.create(config.ngOptions, {
|
||||||
|
} as any, program, host, hostContextFactory(host)).codegen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertSomeFileMatch(fileNames: string[], pattern: RegExp) {
|
||||||
|
assert(
|
||||||
|
fileNames.some(fileName => pattern.test(fileName)),
|
||||||
|
`Expected some read files match ${pattern}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
|
||||||
|
const matches = fileNames.filter(fileName => pattern.test(fileName));
|
||||||
|
assert(
|
||||||
|
matches.length === 0,
|
||||||
|
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
@ -21,6 +21,8 @@
|
|||||||
"src/module",
|
"src/module",
|
||||||
"src/bootstrap",
|
"src/bootstrap",
|
||||||
"test/all_spec",
|
"test/all_spec",
|
||||||
|
"test/test_ngtools_api",
|
||||||
|
"test/test_summaries",
|
||||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||||
"benchmarks/src/largetable/ng2/index_aot.ts",
|
"benchmarks/src/largetable/ng2/index_aot.ts",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"ng-xi18n": "./src/extract_i18n.js"
|
"ng-xi18n": "./src/extract_i18n.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/tsc-wrapped": "^0.3.0",
|
"@angular/tsc-wrapped": "0.5.0",
|
||||||
"reflect-metadata": "^0.1.2",
|
"reflect-metadata": "^0.1.2",
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
@ -17,14 +17,11 @@ import {readFileSync} from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {PathMappedReflectorHost} from './path_mapped_reflector_host';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
import {Console} from './private_import_core';
|
import {Console} from './private_import_core';
|
||||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
|
||||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
|
|
||||||
|
|
||||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
const GENERATED_META_FILES = /\.json$/;
|
||||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.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.
|
||||||
@ -38,15 +35,15 @@ const PREAMBLE = `/**
|
|||||||
export class CodeGenerator {
|
export class CodeGenerator {
|
||||||
constructor(
|
constructor(
|
||||||
private options: AngularCompilerOptions, private program: ts.Program,
|
private options: AngularCompilerOptions, private program: ts.Program,
|
||||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||||
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
|
private ngCompilerHost: CompilerHost) {}
|
||||||
|
|
||||||
// Write codegen in a directory structure matching the sources.
|
// Write codegen in a directory structure matching the sources.
|
||||||
private calculateEmitPath(filePath: string): string {
|
private calculateEmitPath(filePath: string): string {
|
||||||
let root = this.options.basePath;
|
let root = this.options.basePath;
|
||||||
for (const eachRootDir of this.options.rootDirs || []) {
|
for (const eachRootDir of this.options.rootDirs || []) {
|
||||||
if (this.options.trace) {
|
if (this.options.trace) {
|
||||||
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||||
}
|
}
|
||||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||||
root = eachRootDir;
|
root = eachRootDir;
|
||||||
@ -64,33 +61,31 @@ export class CodeGenerator {
|
|||||||
return path.join(this.options.genDir, relativePath);
|
return path.join(this.options.genDir, relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
codegen(options: {transitiveModules: boolean}): Promise<any> {
|
codegen(): Promise<any> {
|
||||||
const staticSymbols =
|
return this.compiler
|
||||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
.compileAll(this.program.getSourceFiles().map(
|
||||||
|
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
|
||||||
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
|
.then(generatedModules => {
|
||||||
generatedModules.forEach(generatedModule => {
|
generatedModules.forEach(generatedModule => {
|
||||||
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
|
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||||
this.host.writeFile(
|
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
PREAMBLE + generatedModule.source;
|
||||||
});
|
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
|
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
|
||||||
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
|
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
|
||||||
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator {
|
ngCompilerHost?: CompilerHost): CodeGenerator {
|
||||||
resourceLoader = resourceLoader || {
|
if (!ngCompilerHost) {
|
||||||
get: (s: string) => {
|
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||||
if (!compilerHost.fileExists(s)) {
|
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||||
// TODO: We should really have a test for error cases like this!
|
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
new CompilerHost(program, options, context);
|
||||||
}
|
}
|
||||||
return Promise.resolve(compilerHost.readFile(s));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const transFile = cliOptions.i18nFile;
|
const transFile = cliOptions.i18nFile;
|
||||||
const locale = cliOptions.locale;
|
const locale = cliOptions.locale;
|
||||||
let transContent: string = '';
|
let transContent: string = '';
|
||||||
@ -101,84 +96,12 @@ export class CodeGenerator {
|
|||||||
}
|
}
|
||||||
transContent = readFileSync(transFile, 'utf8');
|
transContent = readFileSync(transFile, 'utf8');
|
||||||
}
|
}
|
||||||
|
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
|
||||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
debug: options.debug === true,
|
||||||
if (!reflectorHost) {
|
translations: transContent,
|
||||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
i18nFormat: cliOptions.i18nFormat,
|
||||||
reflectorHost = usePathMapping ?
|
locale: cliOptions.locale
|
||||||
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) :
|
|
||||||
new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
|
||||||
}
|
|
||||||
const staticReflector = new StaticReflector(reflectorHost);
|
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
|
||||||
const htmlParser =
|
|
||||||
new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat);
|
|
||||||
const config = new compiler.CompilerConfig({
|
|
||||||
genDebugInfo: options.debug === true,
|
|
||||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
|
||||||
logBindingUpdate: false,
|
|
||||||
useJit: false
|
|
||||||
});
|
});
|
||||||
const normalizer =
|
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
||||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
|
||||||
const expressionParser = new compiler.Parser(new compiler.Lexer());
|
|
||||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
|
||||||
const console = new Console();
|
|
||||||
const tmplParser = new compiler.TemplateParser(
|
|
||||||
expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
|
||||||
const resolver = new compiler.CompileMetadataResolver(
|
|
||||||
new compiler.NgModuleResolver(staticReflector),
|
|
||||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
|
||||||
elementSchemaRegistry, normalizer, staticReflector);
|
|
||||||
// TODO(vicb): do not pass cliOptions.i18nFormat here
|
|
||||||
const offlineCompiler = new compiler.OfflineCompiler(
|
|
||||||
resolver, tmplParser, new compiler.StyleCompiler(urlResolver),
|
|
||||||
new compiler.ViewCompiler(config, elementSchemaRegistry),
|
|
||||||
new compiler.DirectiveWrapperCompiler(
|
|
||||||
config, expressionParser, elementSchemaRegistry, console),
|
|
||||||
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
|
|
||||||
cliOptions.locale, cliOptions.i18nFormat,
|
|
||||||
new compiler.AnimationParser(elementSchemaRegistry));
|
|
||||||
|
|
||||||
return new CodeGenerator(
|
|
||||||
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractProgramSymbols(
|
|
||||||
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
|
|
||||||
options: AngularCompilerOptions): StaticSymbol[] {
|
|
||||||
// Compare with false since the default should be true
|
|
||||||
const skipFileNames =
|
|
||||||
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
|
||||||
|
|
||||||
const staticSymbols: StaticSymbol[] = [];
|
|
||||||
|
|
||||||
program.getSourceFiles()
|
|
||||||
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
|
|
||||||
.forEach(sourceFile => {
|
|
||||||
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName);
|
|
||||||
|
|
||||||
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
|
|
||||||
if (!moduleMetadata) {
|
|
||||||
console.log(`WARNING: no metadata found for ${absSrcPath}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = moduleMetadata['metadata'];
|
|
||||||
|
|
||||||
if (!metadata) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const symbol of Object.keys(metadata)) {
|
|
||||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
|
||||||
// Ignore symbols that are only included to record error information.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return staticSymbols;
|
|
||||||
}
|
|
||||||
|
321
modules/@angular/compiler-cli/src/compiler_host.ts
Normal file
321
modules/@angular/compiler-cli/src/compiler_host.ts
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {AotCompilerHost, StaticSymbol} from '@angular/compiler';
|
||||||
|
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
|
const DTS = /\.d\.ts$/;
|
||||||
|
const NODE_MODULES = '/node_modules/';
|
||||||
|
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||||
|
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||||
|
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||||
|
|
||||||
|
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
||||||
|
readResource(fileName: string): Promise<string>;
|
||||||
|
assumeFileExists(fileName: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompilerHost implements AotCompilerHost {
|
||||||
|
protected metadataCollector = new MetadataCollector();
|
||||||
|
private isGenDirChildOfRootDir: boolean;
|
||||||
|
protected basePath: string;
|
||||||
|
private genDir: string;
|
||||||
|
private resolverCache = new Map<string, ModuleMetadata[]>();
|
||||||
|
protected resolveModuleNameHost: CompilerHostContext;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected program: ts.Program, protected options: AngularCompilerOptions,
|
||||||
|
protected context: CompilerHostContext) {
|
||||||
|
// normalize the path so that it never ends with '/'.
|
||||||
|
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
|
||||||
|
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
|
||||||
|
|
||||||
|
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||||
|
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||||
|
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.
|
||||||
|
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||||
|
|
||||||
|
moduleNameToFileName(m: string, containingFile: string): string|null {
|
||||||
|
if (!containingFile || !containingFile.length) {
|
||||||
|
if (m.indexOf('.') === 0) {
|
||||||
|
throw new Error('Resolution of relative paths requires a containing file.');
|
||||||
|
}
|
||||||
|
// Any containing file gives the same result for absolute imports
|
||||||
|
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||||
|
}
|
||||||
|
m = m.replace(EXT, '');
|
||||||
|
const resolved =
|
||||||
|
ts.resolveModuleName(
|
||||||
|
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
|
||||||
|
.resolvedModule;
|
||||||
|
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We want a moduleId that will appear in import statements in the generated code.
|
||||||
|
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
||||||
|
*
|
||||||
|
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
|
||||||
|
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
|
||||||
|
* existing file.
|
||||||
|
*
|
||||||
|
* | genDir | node_module | rootDir
|
||||||
|
* --------------+----------+-------------+----------
|
||||||
|
* generated | relative | relative | n/a
|
||||||
|
* existing file | n/a | absolute | relative(*)
|
||||||
|
*
|
||||||
|
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
|
||||||
|
*/
|
||||||
|
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||||
|
// If a file does not yet exist (because we compile it later), we still need to
|
||||||
|
// assume it exists it so that the `resolve` method works!
|
||||||
|
if (!this.context.fileExists(importedFile)) {
|
||||||
|
this.context.assumeFileExists(importedFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
containingFile = this.rewriteGenDirPath(containingFile);
|
||||||
|
const containingDir = path.dirname(containingFile);
|
||||||
|
// drop extension
|
||||||
|
importedFile = importedFile.replace(EXT, '');
|
||||||
|
|
||||||
|
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
|
||||||
|
const importModule = nodeModulesIndex === -1 ?
|
||||||
|
null :
|
||||||
|
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
|
||||||
|
const isGeneratedFile = IS_GENERATED.test(importedFile);
|
||||||
|
|
||||||
|
if (isGeneratedFile) {
|
||||||
|
// rewrite to genDir path
|
||||||
|
if (importModule) {
|
||||||
|
// it is generated, therefore we do a relative path to the factory
|
||||||
|
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
|
||||||
|
} else {
|
||||||
|
// assume that import is also in `genDir`
|
||||||
|
importedFile = this.rewriteGenDirPath(importedFile);
|
||||||
|
return this.dotRelative(containingDir, importedFile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// user code import
|
||||||
|
if (importModule) {
|
||||||
|
return importModule;
|
||||||
|
} else {
|
||||||
|
if (!this.isGenDirChildOfRootDir) {
|
||||||
|
// assume that they are on top of each other.
|
||||||
|
importedFile = importedFile.replace(this.basePath, this.genDir);
|
||||||
|
}
|
||||||
|
return this.dotRelative(containingDir, importedFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private dotRelative(from: string, to: string): string {
|
||||||
|
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
|
||||||
|
return rPath.startsWith('.') ? rPath : './' + rPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
|
||||||
|
*/
|
||||||
|
private rewriteGenDirPath(filepath: string) {
|
||||||
|
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
|
||||||
|
if (nodeModulesIndex !== -1) {
|
||||||
|
// If we are in node_modulse, transplant them into `genDir`.
|
||||||
|
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
|
||||||
|
} else {
|
||||||
|
// pretend that containing file is on top of the `genDir` to normalize the paths.
|
||||||
|
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
|
||||||
|
return filepath.replace(this.basePath, this.genDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getSourceFile(filePath: string): ts.SourceFile {
|
||||||
|
const sf = this.program.getSourceFile(filePath);
|
||||||
|
if (!sf) {
|
||||||
|
if (this.context.fileExists(filePath)) {
|
||||||
|
const sourceText = this.context.readFile(filePath);
|
||||||
|
return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
||||||
|
}
|
||||||
|
throw new Error(`Source file ${filePath} not present in program.`);
|
||||||
|
}
|
||||||
|
return sf;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||||
|
if (!this.context.fileExists(filePath)) {
|
||||||
|
// If the file doesn't exists then we cannot return metadata for the file.
|
||||||
|
// This will occur if the user refernced a declared module for which no file
|
||||||
|
// exists for the module (i.e. jQuery or angularjs).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (DTS.test(filePath)) {
|
||||||
|
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
||||||
|
if (this.context.fileExists(metadataPath)) {
|
||||||
|
return this.readMetadata(metadataPath, filePath);
|
||||||
|
} else {
|
||||||
|
// 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 {
|
||||||
|
const sf = this.getSourceFile(filePath);
|
||||||
|
const metadata = this.metadataCollector.getMetadata(sf);
|
||||||
|
return metadata ? [metadata] : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
|
||||||
|
let metadatas = this.resolverCache.get(filePath);
|
||||||
|
if (metadatas) {
|
||||||
|
return metadatas;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
|
||||||
|
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
|
||||||
|
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||||
|
[];
|
||||||
|
const v1Metadata = metadatas.find(m => m.version === 1);
|
||||||
|
let v3Metadata = metadatas.find(m => m.version === 3);
|
||||||
|
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
|
||||||
|
// as the only difference between the versions is whether all exports are contained in
|
||||||
|
// the metadata and the `extends` clause.
|
||||||
|
let v3Metadata: ModuleMetadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
|
||||||
|
if (v1Metadata.exports) {
|
||||||
|
v3Metadata.exports = v1Metadata.exports;
|
||||||
|
}
|
||||||
|
for (let prop in v1Metadata.metadata) {
|
||||||
|
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
|
||||||
|
if (exports) {
|
||||||
|
for (let prop in exports.metadata) {
|
||||||
|
if (!v3Metadata.metadata[prop]) {
|
||||||
|
v3Metadata.metadata[prop] = exports.metadata[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exports.exports) {
|
||||||
|
v3Metadata.exports = exports.exports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v3Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
|
||||||
|
|
||||||
|
loadSummary(filePath: string): string|null {
|
||||||
|
if (this.context.fileExists(filePath)) {
|
||||||
|
return this.context.readFile(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputFileName(sourceFilePath: string): string {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompilerHostContextAdapter {
|
||||||
|
protected assumedExists: {[fileName: string]: boolean} = {};
|
||||||
|
|
||||||
|
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ModuleResolutionHostAdapter extends CompilerHostContextAdapter implements
|
||||||
|
CompilerHostContext {
|
||||||
|
public directoryExists: ((directoryName: string) => boolean)|undefined;
|
||||||
|
|
||||||
|
constructor(private host: ts.ModuleResolutionHost) {
|
||||||
|
super();
|
||||||
|
if (host.directoryExists) {
|
||||||
|
this.directoryExists = (directoryName: string) => host.directoryExists(directoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists(fileName: string): boolean {
|
||||||
|
return this.assumedExists[fileName] || this.host.fileExists(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(fileName: string): string { return this.host.readFile(fileName); }
|
||||||
|
|
||||||
|
readResource(s: string) {
|
||||||
|
if (!this.host.fileExists(s)) {
|
||||||
|
// TODO: We should really have a test for error cases like this!
|
||||||
|
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||||
|
}
|
||||||
|
return Promise.resolve(this.host.readFile(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeCompilerHostContext extends CompilerHostContextAdapter implements
|
||||||
|
CompilerHostContext {
|
||||||
|
fileExists(fileName: string): boolean {
|
||||||
|
return this.assumedExists[fileName] || fs.existsSync(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
directoryExists(directoryName: string): boolean {
|
||||||
|
try {
|
||||||
|
return fs.statSync(directoryName).isDirectory();
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
||||||
|
|
||||||
|
readResource(s: string) {
|
||||||
|
if (!this.fileExists(s)) {
|
||||||
|
// TODO: We should really have a test for error cases like this!
|
||||||
|
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||||
|
}
|
||||||
|
return Promise.resolve(this.readFile(s));
|
||||||
|
}
|
||||||
|
}
|
@ -14,52 +14,15 @@
|
|||||||
// 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) {
|
program: ts.Program, host: ts.CompilerHost): Promise<void> {
|
||||||
const resourceLoader: compiler.ResourceLoader = {
|
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
|
||||||
get: (s: string) => {
|
|
||||||
if (!host.fileExists(s)) {
|
|
||||||
// TODO: We should really have a test for error cases like this!
|
|
||||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
|
||||||
}
|
|
||||||
return Promise.resolve(host.readFile(s));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const extractor =
|
|
||||||
Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader);
|
|
||||||
|
|
||||||
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
|
||||||
|
|
||||||
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:
|
|
||||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
|
||||||
ext = 'xlf';
|
|
||||||
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
|
|
||||||
host.writeFile(dstPath, messageBundle.write(serializer), false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point
|
// Entry point
|
||||||
|
@ -14,86 +14,75 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {extractProgramSymbols} from './codegen';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
import {ReflectorHost} from './reflector_host';
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
|
||||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
|
||||||
|
|
||||||
export class Extractor {
|
export class Extractor {
|
||||||
constructor(
|
constructor(
|
||||||
private options: tsc.AngularCompilerOptions, private program: ts.Program,
|
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
|
||||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
|
||||||
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
|
private program: ts.Program) {}
|
||||||
private metadataResolver: compiler.CompileMetadataResolver) {}
|
|
||||||
|
|
||||||
extract(): Promise<compiler.MessageBundle> {
|
extract(formatName: string): Promise<void> {
|
||||||
const programSymbols: StaticSymbol[] =
|
// Checks the format and returns the extension
|
||||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
const ext = this.getExtension(formatName);
|
||||||
|
|
||||||
return compiler
|
const promiseBundle = this.extractBundle();
|
||||||
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
|
||||||
.then(({files}) => {
|
|
||||||
const errors: compiler.ParseError[] = [];
|
|
||||||
|
|
||||||
files.forEach(file => {
|
return promiseBundle.then(bundle => {
|
||||||
const compMetas: compiler.CompileDirectiveMetadata[] = [];
|
const content = this.serialize(bundle, ext);
|
||||||
file.directives.forEach(directiveType => {
|
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
|
||||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
this.host.writeFile(dstPath, content, false);
|
||||||
if (dirMeta && dirMeta.isComponent) {
|
});
|
||||||
compMetas.push(dirMeta);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
compMetas.forEach(compMeta => {
|
|
||||||
const html = compMeta.template.template;
|
|
||||||
const interpolationConfig =
|
|
||||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
|
||||||
errors.push(
|
|
||||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (errors.length) {
|
extractBundle(): Promise<compiler.MessageBundle> {
|
||||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
const files = this.program.getSourceFiles().map(
|
||||||
}
|
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
|
||||||
|
|
||||||
return this.messageBundle;
|
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, translationsFormat: string, program: ts.Program,
|
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
|
||||||
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
|
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
|
||||||
reflectorHost?: ReflectorHost): Extractor {
|
if (!ngCompilerHost) {
|
||||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||||
|
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||||
|
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||||
|
new CompilerHost(program, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost);
|
||||||
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options);
|
|
||||||
const staticReflector = new StaticReflector(reflectorHost);
|
|
||||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
|
||||||
|
|
||||||
const config = new compiler.CompilerConfig({
|
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
|
||||||
genDebugInfo: options.debug === true,
|
|
||||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
|
||||||
logBindingUpdate: false,
|
|
||||||
useJit: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const normalizer =
|
|
||||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
|
||||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
|
||||||
const resolver = new compiler.CompileMetadataResolver(
|
|
||||||
new compiler.NgModuleResolver(staticReflector),
|
|
||||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
|
||||||
elementSchemaRegistry, normalizer, staticReflector);
|
|
||||||
|
|
||||||
// TODO(vicb): implicit tags & attributes
|
|
||||||
const messageBundle = new compiler.MessageBundle(htmlParser, [], {});
|
|
||||||
|
|
||||||
return new Extractor(
|
|
||||||
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,24 +14,34 @@ 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(
|
||||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
||||||
host: ts.CompilerHost) {
|
host: ts.CompilerHost) {
|
||||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
|
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
||||||
transitiveModules: true
|
}
|
||||||
|
|
||||||
|
export function main(
|
||||||
|
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
|
||||||
|
const project = args.p || args.project || '.';
|
||||||
|
const cliOptions = new tsc.NgcCliOptions(args);
|
||||||
|
|
||||||
|
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
|
||||||
|
if (e instanceof tsc.UserError || e instanceof SyntaxError) {
|
||||||
|
consoleError(e.message);
|
||||||
|
return Promise.resolve(1);
|
||||||
|
} else {
|
||||||
|
consoleError(e.stack);
|
||||||
|
consoleError('Compilation failed');
|
||||||
|
return Promise.resolve(1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// CLI entry point
|
// CLI entry point
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
const args = require('minimist')(process.argv.slice(2));
|
const args = require('minimist')(process.argv.slice(2));
|
||||||
const project = args.p || args.project || '.';
|
main(args).then((exitCode: number) => process.exit(exitCode));
|
||||||
const cliOptions = new tsc.NgcCliOptions(args);
|
|
||||||
tsc.main(project, cliOptions, codegen).then(exitCode => process.exit(exitCode)).catch(e => {
|
|
||||||
console.error(e.stack);
|
|
||||||
console.error('Compilation failed');
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
150
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
150
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a private API for the ngtools toolkit.
|
||||||
|
*
|
||||||
|
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||||
|
* something else.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
|
||||||
|
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {CodeGenerator} from './codegen';
|
||||||
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
|
import {Extractor} from './extractor';
|
||||||
|
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||||
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
|
|
||||||
|
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||||
|
basePath: string;
|
||||||
|
compilerOptions: ts.CompilerOptions;
|
||||||
|
program: ts.Program;
|
||||||
|
host: ts.CompilerHost;
|
||||||
|
|
||||||
|
angularCompilerOptions: AngularCompilerOptions;
|
||||||
|
|
||||||
|
// i18n options.
|
||||||
|
i18nFormat: string;
|
||||||
|
i18nFile: string;
|
||||||
|
locale: string;
|
||||||
|
|
||||||
|
readResource: (fileName: string) => Promise<string>;
|
||||||
|
|
||||||
|
// Every new property under this line should be optional.
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
||||||
|
program: ts.Program;
|
||||||
|
host: ts.CompilerHost;
|
||||||
|
angularCompilerOptions: AngularCompilerOptions;
|
||||||
|
entryModule: string;
|
||||||
|
|
||||||
|
// Every new property under this line should be optional.
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
||||||
|
|
||||||
|
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
|
||||||
|
* passed in the interface.
|
||||||
|
*/
|
||||||
|
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
|
||||||
|
constructor(
|
||||||
|
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
|
||||||
|
super(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
readResource(path: string) { return this._readResource(path); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export class NgTools_InternalApi_NG_2 {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<void> {
|
||||||
|
const hostContext: CompilerHostContext =
|
||||||
|
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||||
|
const cliOptions: NgcCliOptions = {
|
||||||
|
i18nFormat: options.i18nFormat,
|
||||||
|
i18nFile: options.i18nFile,
|
||||||
|
locale: options.locale,
|
||||||
|
basePath: options.basePath
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the Code Generator.
|
||||||
|
const codeGenerator = CodeGenerator.create(
|
||||||
|
options.angularCompilerOptions, cliOptions, options.program, options.host, hostContext);
|
||||||
|
|
||||||
|
return codeGenerator.codegen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
|
||||||
|
NgTools_InternalApi_NG_2_LazyRouteMap {
|
||||||
|
const angularCompilerOptions = options.angularCompilerOptions;
|
||||||
|
const program = options.program;
|
||||||
|
|
||||||
|
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
|
||||||
|
const usePathMapping =
|
||||||
|
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
|
||||||
|
const ngCompilerHost: AotCompilerHost = usePathMapping ?
|
||||||
|
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
||||||
|
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
||||||
|
|
||||||
|
const symbolCache = new StaticSymbolCache();
|
||||||
|
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);
|
||||||
|
|
||||||
|
return Object.keys(routeMap).reduce(
|
||||||
|
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
|
||||||
|
acc[route] = routeMap[route].absoluteFilePath;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
205
modules/@angular/compiler-cli/src/ngtools_impl.ts
Normal file
205
modules/@angular/compiler-cli/src/ngtools_impl.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a private API for the ngtools toolkit.
|
||||||
|
*
|
||||||
|
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||||
|
* something else.
|
||||||
|
*/
|
||||||
|
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// We cannot depend directly to @angular/router.
|
||||||
|
type Route = any;
|
||||||
|
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
||||||
|
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
|
||||||
|
|
||||||
|
|
||||||
|
// LazyRoute information between the extractors.
|
||||||
|
export interface LazyRoute {
|
||||||
|
routeDef: RouteDef;
|
||||||
|
absoluteFilePath: string;
|
||||||
|
}
|
||||||
|
export type LazyRouteMap = {
|
||||||
|
[route: string]: LazyRoute
|
||||||
|
};
|
||||||
|
|
||||||
|
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
|
||||||
|
// the user, and this is a helper class to extract information from it.
|
||||||
|
export class RouteDef {
|
||||||
|
private constructor(public readonly path: string, public readonly className: string = null) {}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return (this.className === null || this.className == 'default') ?
|
||||||
|
this.path :
|
||||||
|
`${this.path}#${this.className}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromString(entry: string): RouteDef {
|
||||||
|
const split = entry.split('#');
|
||||||
|
return new RouteDef(split[0], split[1] || null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {LazyRouteMap}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function listLazyRoutesOfModule(
|
||||||
|
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
|
||||||
|
const entryRouteDef = RouteDef.fromString(entryModule);
|
||||||
|
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
|
||||||
|
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
|
||||||
|
const className = entryRouteDef.className;
|
||||||
|
|
||||||
|
// List loadChildren of this single module.
|
||||||
|
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
|
||||||
|
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
|
||||||
|
const lazyRoutes: LazyRoute[] =
|
||||||
|
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
|
||||||
|
const routes: LazyRouteMap = {};
|
||||||
|
|
||||||
|
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
||||||
|
const route: string = lazyRoute.routeDef.toString();
|
||||||
|
_assertRoute(routes, lazyRoute);
|
||||||
|
routes[route] = lazyRoute;
|
||||||
|
|
||||||
|
const lazyModuleSymbol = reflector.findDeclaration(
|
||||||
|
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||||
|
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||||
|
|
||||||
|
// Populate the map using the routes we just found.
|
||||||
|
subRoutes.forEach(subRoute => {
|
||||||
|
_assertRoute(routes, subRoute);
|
||||||
|
routes[subRoute.routeDef.toString()] = subRoute;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to resolve a module, and returns its absolute path.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
|
||||||
|
const result = host.moduleNameToFileName(modulePath, containingFile);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an exception if a route is in a route map, but does not point to the same module.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
|
||||||
|
const r = route.routeDef.toString();
|
||||||
|
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
|
||||||
|
throw new Error(
|
||||||
|
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
|
||||||
|
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
|
||||||
|
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
|
||||||
|
'load the proper one.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
|
||||||
|
* module and all statically referred modules.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _extractLazyRoutesFromStaticModule(
|
||||||
|
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
|
||||||
|
ROUTES: StaticSymbol): LazyRoute[] {
|
||||||
|
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
|
||||||
|
const allRoutes: any =
|
||||||
|
(moduleMetadata.imports || [])
|
||||||
|
.filter(i => 'providers' in i)
|
||||||
|
.reduce((mem: Route[], m: any) => {
|
||||||
|
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
|
||||||
|
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
|
||||||
|
|
||||||
|
const lazyRoutes: LazyRoute[] =
|
||||||
|
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
|
||||||
|
const routeDef = RouteDef.fromString(route);
|
||||||
|
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
|
||||||
|
acc.push({routeDef, absoluteFilePath});
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const importedSymbols = ((moduleMetadata.imports || []) as any[])
|
||||||
|
.filter(i => i instanceof StaticSymbol) as StaticSymbol[];
|
||||||
|
|
||||||
|
return importedSymbols
|
||||||
|
.reduce(
|
||||||
|
(acc: LazyRoute[], i: StaticSymbol) => {
|
||||||
|
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
|
||||||
|
},
|
||||||
|
[])
|
||||||
|
.concat(lazyRoutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the NgModule Metadata of a symbol.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _getNgModuleMetadata(staticSymbol: StaticSymbol, reflector: StaticReflector): NgModule {
|
||||||
|
const ngModules = reflector.annotations(staticSymbol).filter((s: any) => s instanceof NgModule);
|
||||||
|
if (ngModules.length === 0) {
|
||||||
|
throw new Error(`${staticSymbol.name} is not an NgModule`);
|
||||||
|
}
|
||||||
|
return ngModules[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the routes from the provider list.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _collectRoutes(
|
||||||
|
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
|
||||||
|
return providers.reduce((routeList: Route[], p: any) => {
|
||||||
|
if (p.provide === ROUTES) {
|
||||||
|
return routeList.concat(p.useValue);
|
||||||
|
} else if (Array.isArray(p)) {
|
||||||
|
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
|
||||||
|
} else {
|
||||||
|
return routeList;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the loadChildren values of a list of Route.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _collectLoadChildren(routes: Route[]): string[] {
|
||||||
|
return routes.reduce((m, r) => {
|
||||||
|
if (r.loadChildren) {
|
||||||
|
return m.concat(r.loadChildren);
|
||||||
|
} else if (Array.isArray(r)) {
|
||||||
|
return m.concat(_collectLoadChildren(r));
|
||||||
|
} else if (r.children) {
|
||||||
|
return m.concat(_collectLoadChildren(r.children));
|
||||||
|
} else {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
}
|
@ -6,29 +6,27 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {StaticSymbol} from '@angular/compiler';
|
||||||
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
|
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
import {CompilerHost, CompilerHostContext} from './compiler_host';
|
||||||
import {StaticSymbol} from './static_reflector';
|
|
||||||
|
|
||||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
const DTS = /\.d\.ts$/;
|
const DTS = /\.d\.ts$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This version of the reflector host expects that the program will be compiled
|
* This version of the AotCompilerHost expects that the program will be compiled
|
||||||
* and executed with a "path mapped" directory structure, where generated files
|
* and executed with a "path mapped" directory structure, where generated files
|
||||||
* are in a parallel tree with the sources, and imported using a `./` relative
|
* are in a parallel tree with the sources, and imported using a `./` relative
|
||||||
* import. This requires using TS `rootDirs` option and also teaching the module
|
* import. This requires using TS `rootDirs` option and also teaching the module
|
||||||
* loader what to do.
|
* loader what to do.
|
||||||
*/
|
*/
|
||||||
export class PathMappedReflectorHost extends ReflectorHost {
|
export class PathMappedCompilerHost extends CompilerHost {
|
||||||
constructor(
|
constructor(program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext) {
|
||||||
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
|
super(program, options, context);
|
||||||
context?: ReflectorHostContext) {
|
|
||||||
super(program, compilerHost, options, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCanonicalFileName(fileName: string): string {
|
getCanonicalFileName(fileName: string): string {
|
||||||
@ -42,16 +40,23 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
|||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected resolve(m: string, containingFile: string) {
|
moduleNameToFileName(m: string, containingFile: string) {
|
||||||
|
if (!containingFile || !containingFile.length) {
|
||||||
|
if (m.indexOf('.') === 0) {
|
||||||
|
throw new Error('Resolution of relative paths requires a containing file.');
|
||||||
|
}
|
||||||
|
// Any containing file gives the same result for absolute imports
|
||||||
|
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||||
|
}
|
||||||
for (const root of this.options.rootDirs || ['']) {
|
for (const root of this.options.rootDirs || ['']) {
|
||||||
const rootedContainingFile = path.join(root, containingFile);
|
const rootedContainingFile = path.join(root, containingFile);
|
||||||
const resolved =
|
const resolved =
|
||||||
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
|
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
if (this.options.traceResolution) {
|
if (this.options.traceResolution) {
|
||||||
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
||||||
}
|
}
|
||||||
return resolved.resolvedFileName;
|
return this.getCanonicalFileName(resolved.resolvedFileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,12 +67,9 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
|||||||
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
|
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
|
||||||
* they are resolvable by the moduleResolution strategy from the CompilerHost.
|
* they are resolvable by the moduleResolution strategy from the CompilerHost.
|
||||||
*/
|
*/
|
||||||
getImportPath(containingFile: string, importedFile: string): string {
|
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||||
importedFile = this.resolveAssetUrl(importedFile, containingFile);
|
|
||||||
containingFile = this.resolveAssetUrl(containingFile, '');
|
|
||||||
|
|
||||||
if (this.options.traceResolution) {
|
if (this.options.traceResolution) {
|
||||||
console.log(
|
console.error(
|
||||||
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
|
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +84,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolvable = (candidate: string) => {
|
const resolvable = (candidate: string) => {
|
||||||
const resolved = this.getCanonicalFileName(this.resolve(candidate, importedFile));
|
const resolved = this.moduleNameToFileName(candidate, importedFile);
|
||||||
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
|
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,10 +114,10 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
|||||||
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
|
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetadataFor(filePath: string): ModuleMetadata {
|
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||||
for (const root of this.options.rootDirs || []) {
|
for (const root of this.options.rootDirs || []) {
|
||||||
const rootedPath = path.join(root, filePath);
|
const rootedPath = path.join(root, filePath);
|
||||||
if (!this.compilerHost.fileExists(rootedPath)) {
|
if (!this.context.fileExists(rootedPath)) {
|
||||||
// If the file doesn't exists then we cannot return metadata for the file.
|
// If the file doesn't exists then we cannot return metadata for the file.
|
||||||
// This will occur if the user refernced a declared module for which no file
|
// This will occur if the user refernced a declared module for which no file
|
||||||
// exists for the module (i.e. jQuery or angularjs).
|
// exists for the module (i.e. jQuery or angularjs).
|
||||||
@ -124,16 +126,13 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
|||||||
if (DTS.test(rootedPath)) {
|
if (DTS.test(rootedPath)) {
|
||||||
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
|
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
|
||||||
if (this.context.fileExists(metadataPath)) {
|
if (this.context.fileExists(metadataPath)) {
|
||||||
const metadata = this.readMetadata(metadataPath);
|
return this.readMetadata(metadataPath, rootedPath);
|
||||||
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const sf = this.program.getSourceFile(rootedPath);
|
const sf = this.getSourceFile(rootedPath);
|
||||||
if (!sf) {
|
sf.fileName = sf.fileName;
|
||||||
throw new Error(`Source file ${rootedPath} not present in program.`);
|
const metadata = this.metadataCollector.getMetadata(sf);
|
||||||
}
|
return metadata ? [metadata] : [];
|
||||||
sf.fileName = this.getCanonicalFileName(sf.fileName);
|
|
||||||
return this.metadataCollector.getMetadata(sf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
|
|||||||
|
|
||||||
export type Console = typeof r._Console;
|
export type Console = typeof r._Console;
|
||||||
export var Console: typeof r.Console = r.Console;
|
export var Console: typeof r.Console = r.Console;
|
||||||
|
|
||||||
export var reflector: typeof r.reflector = r.reflector;
|
|
||||||
|
|
||||||
export type SetterFn = typeof r._SetterFn;
|
|
||||||
export type GetterFn = typeof r._GetterFn;
|
|
||||||
export type MethodFn = typeof r._MethodFn;
|
|
||||||
|
@ -1,354 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {AssetUrl, ImportGenerator} from '@angular/compiler';
|
|
||||||
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
|
|
||||||
|
|
||||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
|
||||||
const DTS = /\.d\.ts$/;
|
|
||||||
const NODE_MODULES = '/node_modules/';
|
|
||||||
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
|
|
||||||
|
|
||||||
export interface ReflectorHostContext {
|
|
||||||
fileExists(fileName: string): boolean;
|
|
||||||
directoryExists(directoryName: string): boolean;
|
|
||||||
readFile(fileName: string): string;
|
|
||||||
assumeFileExists(fileName: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
|
||||||
protected metadataCollector = new MetadataCollector();
|
|
||||||
protected context: ReflectorHostContext;
|
|
||||||
private isGenDirChildOfRootDir: boolean;
|
|
||||||
protected basePath: string;
|
|
||||||
private genDir: string;
|
|
||||||
constructor(
|
|
||||||
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
|
|
||||||
protected options: AngularCompilerOptions, context?: ReflectorHostContext) {
|
|
||||||
// normalize the path so that it never ends with '/'.
|
|
||||||
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
|
|
||||||
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
|
|
||||||
|
|
||||||
this.context = context || new NodeReflectorHostContext(compilerHost);
|
|
||||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
|
||||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
|
||||||
}
|
|
||||||
|
|
||||||
angularImportLocations() {
|
|
||||||
return {
|
|
||||||
coreDecorators: '@angular/core/src/metadata',
|
|
||||||
diDecorators: '@angular/core/src/di/metadata',
|
|
||||||
diMetadata: '@angular/core/src/di/metadata',
|
|
||||||
diOpaqueToken: '@angular/core/src/di/opaque_token',
|
|
||||||
animationMetadata: '@angular/core/src/animation/metadata',
|
|
||||||
provider: '@angular/core/src/di/provider'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use absolute paths on disk as canonical.
|
|
||||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
|
||||||
|
|
||||||
protected resolve(m: string, containingFile: string) {
|
|
||||||
m = m.replace(EXT, '');
|
|
||||||
const resolved =
|
|
||||||
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
|
|
||||||
.resolvedModule;
|
|
||||||
return resolved ? resolved.resolvedFileName : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected normalizeAssetUrl(url: string): string {
|
|
||||||
const assetUrl = AssetUrl.parse(url);
|
|
||||||
const path = assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null;
|
|
||||||
return this.getCanonicalFileName(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resolveAssetUrl(url: string, containingFile: string): string {
|
|
||||||
const assetUrl = this.normalizeAssetUrl(url);
|
|
||||||
if (assetUrl) {
|
|
||||||
return this.getCanonicalFileName(this.resolve(assetUrl, containingFile));
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We want a moduleId that will appear in import statements in the generated code.
|
|
||||||
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
|
||||||
*
|
|
||||||
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
|
|
||||||
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
|
|
||||||
* existing file.
|
|
||||||
*
|
|
||||||
* | genDir | node_module | rootDir
|
|
||||||
* --------------+----------+-------------+----------
|
|
||||||
* generated | relative | relative | n/a
|
|
||||||
* existing file | n/a | absolute | relative(*)
|
|
||||||
*
|
|
||||||
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
|
|
||||||
*/
|
|
||||||
getImportPath(containingFile: string, importedFile: string): string {
|
|
||||||
importedFile = this.resolveAssetUrl(importedFile, containingFile);
|
|
||||||
containingFile = this.resolveAssetUrl(containingFile, '');
|
|
||||||
|
|
||||||
// If a file does not yet exist (because we compile it later), we still need to
|
|
||||||
// assume it exists it so that the `resolve` method works!
|
|
||||||
if (!this.compilerHost.fileExists(importedFile)) {
|
|
||||||
this.context.assumeFileExists(importedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
containingFile = this.rewriteGenDirPath(containingFile);
|
|
||||||
const containingDir = path.dirname(containingFile);
|
|
||||||
// drop extension
|
|
||||||
importedFile = importedFile.replace(EXT, '');
|
|
||||||
|
|
||||||
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
|
|
||||||
const importModule = nodeModulesIndex === -1 ?
|
|
||||||
null :
|
|
||||||
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
|
|
||||||
const isGeneratedFile = IS_GENERATED.test(importedFile);
|
|
||||||
|
|
||||||
if (isGeneratedFile) {
|
|
||||||
// rewrite to genDir path
|
|
||||||
if (importModule) {
|
|
||||||
// it is generated, therefore we do a relative path to the factory
|
|
||||||
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
|
|
||||||
} else {
|
|
||||||
// assume that import is also in `genDir`
|
|
||||||
importedFile = this.rewriteGenDirPath(importedFile);
|
|
||||||
return this.dotRelative(containingDir, importedFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// user code import
|
|
||||||
if (importModule) {
|
|
||||||
return importModule;
|
|
||||||
} else {
|
|
||||||
if (!this.isGenDirChildOfRootDir) {
|
|
||||||
// assume that they are on top of each other.
|
|
||||||
importedFile = importedFile.replace(this.basePath, this.genDir);
|
|
||||||
}
|
|
||||||
return this.dotRelative(containingDir, importedFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private dotRelative(from: string, to: string): string {
|
|
||||||
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
|
|
||||||
return rPath.startsWith('.') ? rPath : './' + rPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
|
|
||||||
*/
|
|
||||||
private rewriteGenDirPath(filepath: string) {
|
|
||||||
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
|
|
||||||
if (nodeModulesIndex !== -1) {
|
|
||||||
// If we are in node_modulse, transplant them into `genDir`.
|
|
||||||
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
|
|
||||||
} else {
|
|
||||||
// pretend that containing file is on top of the `genDir` to normalize the paths.
|
|
||||||
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
|
|
||||||
return filepath.replace(this.basePath, this.genDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findDeclaration(
|
|
||||||
module: string, symbolName: string, containingFile: string,
|
|
||||||
containingModule?: string): StaticSymbol {
|
|
||||||
if (!containingFile || !containingFile.length) {
|
|
||||||
if (module.indexOf('.') === 0) {
|
|
||||||
throw new Error('Resolution of relative paths requires a containing file.');
|
|
||||||
}
|
|
||||||
// Any containing file gives the same result for absolute imports
|
|
||||||
containingFile = path.join(this.basePath, 'index.ts');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const assetUrl = this.normalizeAssetUrl(module);
|
|
||||||
if (assetUrl) {
|
|
||||||
module = assetUrl;
|
|
||||||
}
|
|
||||||
const filePath = this.resolve(module, containingFile);
|
|
||||||
|
|
||||||
if (!filePath) {
|
|
||||||
// If the file cannot be found the module is probably referencing a declared module
|
|
||||||
// for which there is no disambiguating file and we also don't need to track
|
|
||||||
// re-exports. Just use the module name.
|
|
||||||
return this.getStaticSymbol(module, symbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tc = this.program.getTypeChecker();
|
|
||||||
const sf = this.program.getSourceFile(filePath);
|
|
||||||
if (!sf || !(<any>sf).symbol) {
|
|
||||||
// The source file was not needed in the compile but we do need the values from
|
|
||||||
// the corresponding .ts files stored in the .metadata.json file. Check the file
|
|
||||||
// for exports to see if the file is exported.
|
|
||||||
return this.resolveExportedSymbol(filePath, symbolName) ||
|
|
||||||
this.getStaticSymbol(filePath, symbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
let symbol = tc.getExportsOfModule((<any>sf).symbol).find(m => m.name === symbolName);
|
|
||||||
if (!symbol) {
|
|
||||||
throw new Error(`can't find symbol ${symbolName} exported from module ${filePath}`);
|
|
||||||
}
|
|
||||||
if (symbol &&
|
|
||||||
symbol.flags & ts.SymbolFlags.Alias) { // This is an alias, follow what it aliases
|
|
||||||
symbol = tc.getAliasedSymbol(symbol);
|
|
||||||
}
|
|
||||||
const declaration = symbol.getDeclarations()[0];
|
|
||||||
const declarationFile = this.getCanonicalFileName(declaration.getSourceFile().fileName);
|
|
||||||
|
|
||||||
return this.getStaticSymbol(declarationFile, symbol.getName());
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`can't resolve module ${module} from ${containingFile}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private typeCache = new Map<string, StaticSymbol>();
|
|
||||||
private resolverCache = new Map<string, ModuleMetadata>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
|
|
||||||
* All types passed to the StaticResolver should be pseudo-types returned by this method.
|
|
||||||
*
|
|
||||||
* @param declarationFile the absolute path of the file where the symbol is declared
|
|
||||||
* @param name the name of the type.
|
|
||||||
*/
|
|
||||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
|
||||||
const memberSuffix = members ? `.${ members.join('.')}` : '';
|
|
||||||
const key = `"${declarationFile}".${name}${memberSuffix}`;
|
|
||||||
let result = this.typeCache.get(key);
|
|
||||||
if (!result) {
|
|
||||||
result = new StaticSymbol(declarationFile, name, members);
|
|
||||||
this.typeCache.set(key, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetadataFor(filePath: string): ModuleMetadata {
|
|
||||||
if (!this.context.fileExists(filePath)) {
|
|
||||||
// If the file doesn't exists then we cannot return metadata for the file.
|
|
||||||
// This will occur if the user refernced a declared module for which no file
|
|
||||||
// exists for the module (i.e. jQuery or angularjs).
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (DTS.test(filePath)) {
|
|
||||||
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
|
||||||
if (this.context.fileExists(metadataPath)) {
|
|
||||||
const metadata = this.readMetadata(metadataPath);
|
|
||||||
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const sf = this.program.getSourceFile(filePath);
|
|
||||||
if (!sf) {
|
|
||||||
if (this.context.fileExists(filePath)) {
|
|
||||||
const sourceText = this.context.readFile(filePath);
|
|
||||||
return this.metadataCollector.getMetadata(
|
|
||||||
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Source file ${filePath} not present in program.`);
|
|
||||||
}
|
|
||||||
return this.metadataCollector.getMetadata(sf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readMetadata(filePath: string) {
|
|
||||||
try {
|
|
||||||
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to read JSON file ${filePath}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getResolverMetadata(filePath: string): ModuleMetadata {
|
|
||||||
let metadata = this.resolverCache.get(filePath);
|
|
||||||
if (!metadata) {
|
|
||||||
metadata = this.getMetadataFor(filePath);
|
|
||||||
this.resolverCache.set(filePath, metadata);
|
|
||||||
}
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
|
|
||||||
const resolveModule = (moduleName: string): string => {
|
|
||||||
const resolvedModulePath = this.getCanonicalFileName(this.resolve(moduleName, filePath));
|
|
||||||
if (!resolvedModulePath) {
|
|
||||||
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
|
|
||||||
}
|
|
||||||
return resolvedModulePath;
|
|
||||||
};
|
|
||||||
const metadata = this.getResolverMetadata(filePath);
|
|
||||||
if (metadata) {
|
|
||||||
// If we have metadata for the symbol, this is the original exporting location.
|
|
||||||
if (metadata.metadata[symbolName]) {
|
|
||||||
return this.getStaticSymbol(filePath, symbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no, try to find the symbol in one of the re-export location
|
|
||||||
if (metadata.exports) {
|
|
||||||
// Try and find the symbol in the list of explicitly re-exported symbols.
|
|
||||||
for (const moduleExport of metadata.exports) {
|
|
||||||
if (moduleExport.export) {
|
|
||||||
const exportSymbol = moduleExport.export.find(symbol => {
|
|
||||||
if (typeof symbol === 'string') {
|
|
||||||
return symbol == symbolName;
|
|
||||||
} else {
|
|
||||||
return symbol.as == symbolName;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (exportSymbol) {
|
|
||||||
let symName = symbolName;
|
|
||||||
if (typeof exportSymbol !== 'string') {
|
|
||||||
symName = exportSymbol.name;
|
|
||||||
}
|
|
||||||
return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find the symbol via export * directives.
|
|
||||||
for (const moduleExport of metadata.exports) {
|
|
||||||
if (!moduleExport.export) {
|
|
||||||
const resolvedModule = resolveModule(moduleExport.from);
|
|
||||||
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
|
|
||||||
if (candidateSymbol) return candidateSymbol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NodeReflectorHostContext implements ReflectorHostContext {
|
|
||||||
constructor(private host: ts.CompilerHost) {}
|
|
||||||
|
|
||||||
private assumedExists: {[fileName: string]: boolean} = {};
|
|
||||||
|
|
||||||
fileExists(fileName: string): boolean {
|
|
||||||
return this.assumedExists[fileName] || this.host.fileExists(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
directoryExists(directoryName: string): boolean {
|
|
||||||
try {
|
|
||||||
return fs.statSync(directoryName).isDirectory();
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
|
||||||
|
|
||||||
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
|
||||||
}
|
|
@ -1,675 +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 {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';
|
|
||||||
|
|
||||||
const SUPPORTED_SCHEMA_VERSION = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The host of the static resolver is expected to be able to provide module metadata in the form of
|
|
||||||
* ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
|
|
||||||
* produced and the module has exported variables or classes with decorators. Module metadata can
|
|
||||||
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
|
|
||||||
*/
|
|
||||||
export interface StaticReflectorHost {
|
|
||||||
/**
|
|
||||||
* Return a ModuleMetadata for the given module.
|
|
||||||
*
|
|
||||||
* @param modulePath is a string identifier for a module as an absolute path.
|
|
||||||
* @returns the metadata for the given module.
|
|
||||||
*/
|
|
||||||
getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a symbol from an import statement form, to the file where it is declared.
|
|
||||||
* @param module the location imported from
|
|
||||||
* @param containingFile for relative imports, the path of the file containing the import
|
|
||||||
*/
|
|
||||||
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
|
|
||||||
|
|
||||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol;
|
|
||||||
|
|
||||||
angularImportLocations(): {
|
|
||||||
coreDecorators: string,
|
|
||||||
diDecorators: string,
|
|
||||||
diMetadata: string,
|
|
||||||
diOpaqueToken: string,
|
|
||||||
animationMetadata: string,
|
|
||||||
provider: string
|
|
||||||
};
|
|
||||||
|
|
||||||
getCanonicalFileName(fileName: string): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A token representing the a reference to a static type.
|
|
||||||
*
|
|
||||||
* This token is unique for a filePath and name and can be used as a hash table key.
|
|
||||||
*/
|
|
||||||
export class StaticSymbol {
|
|
||||||
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A static reflector implements enough of the Reflector API that is necessary to compile
|
|
||||||
* templates statically.
|
|
||||||
*/
|
|
||||||
export class StaticReflector implements ReflectorReader {
|
|
||||||
private annotationCache = new Map<StaticSymbol, any[]>();
|
|
||||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
|
|
||||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
|
||||||
private metadataCache = new Map<string, {[key: string]: any}>();
|
|
||||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
|
||||||
private opaqueToken: StaticSymbol;
|
|
||||||
|
|
||||||
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
|
|
||||||
|
|
||||||
importUri(typeOrFunc: StaticSymbol): string {
|
|
||||||
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
|
||||||
return staticSymbol ? staticSymbol.filePath : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
|
|
||||||
return this.host.findDeclaration(moduleUrl, name, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveEnum(enumIdentifier: any, name: string): any {
|
|
||||||
const staticSymbol: StaticSymbol = enumIdentifier;
|
|
||||||
return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public annotations(type: StaticSymbol): any[] {
|
|
||||||
let annotations = this.annotationCache.get(type);
|
|
||||||
if (!annotations) {
|
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
|
||||||
if (classMetadata['decorators']) {
|
|
||||||
annotations = this.simplify(type, classMetadata['decorators']);
|
|
||||||
} else {
|
|
||||||
annotations = [];
|
|
||||||
}
|
|
||||||
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
|
||||||
}
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public propMetadata(type: StaticSymbol): {[key: string]: any} {
|
|
||||||
let propMetadata = this.propertyCache.get(type);
|
|
||||||
if (!propMetadata) {
|
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
|
||||||
const members = classMetadata ? classMetadata['members'] : {};
|
|
||||||
propMetadata = mapStringMap(members, (propData, propName) => {
|
|
||||||
const prop = (<any[]>propData)
|
|
||||||
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
|
|
||||||
if (prop && prop['decorators']) {
|
|
||||||
return this.simplify(type, prop['decorators']);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.propertyCache.set(type, propMetadata);
|
|
||||||
}
|
|
||||||
return propMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public parameters(type: StaticSymbol): any[] {
|
|
||||||
if (!(type instanceof StaticSymbol)) {
|
|
||||||
throw new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
let parameters = this.parameterCache.get(type);
|
|
||||||
if (!parameters) {
|
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
|
||||||
const members = classMetadata ? classMetadata['members'] : null;
|
|
||||||
const ctorData = members ? members['__ctor__'] : null;
|
|
||||||
if (ctorData) {
|
|
||||||
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
|
|
||||||
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
|
|
||||||
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
|
|
||||||
|
|
||||||
parameters = [];
|
|
||||||
parameterTypes.forEach((paramType, index) => {
|
|
||||||
const nestedResult: any[] = [];
|
|
||||||
if (paramType) {
|
|
||||||
nestedResult.push(paramType);
|
|
||||||
}
|
|
||||||
const decorators = parameterDecorators ? parameterDecorators[index] : null;
|
|
||||||
if (decorators) {
|
|
||||||
nestedResult.push(...decorators);
|
|
||||||
}
|
|
||||||
parameters.push(nestedResult);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!parameters) {
|
|
||||||
parameters = [];
|
|
||||||
}
|
|
||||||
this.parameterCache.set(type, parameters);
|
|
||||||
}
|
|
||||||
return parameters;
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
|
||||||
if (!(type instanceof StaticSymbol)) {
|
|
||||||
throw new Error(
|
|
||||||
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`);
|
|
||||||
}
|
|
||||||
const classMetadata = this.getTypeMetadata(type);
|
|
||||||
const members = classMetadata ? classMetadata['members'] : null;
|
|
||||||
const member: any[] =
|
|
||||||
members && members.hasOwnProperty(lcProperty) ? members[lcProperty] : null;
|
|
||||||
return member ? member.some(a => a['__symbolic'] == 'method') : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
|
||||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerFunction(type: StaticSymbol, fn: any): void {
|
|
||||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeConversionMap(): void {
|
|
||||||
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
|
|
||||||
this.host.angularImportLocations();
|
|
||||||
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
|
||||||
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(diDecorators, 'Optional'), Optional);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'Output'), Output);
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'Directive'), Directive);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'Component'), Component);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
|
||||||
|
|
||||||
// Note: Some metadata classes can be used directly with Provider.deps.
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host);
|
|
||||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
|
||||||
this.registerDecoratorOrConstructor(
|
|
||||||
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
|
|
||||||
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence);
|
|
||||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
public simplify(context: StaticSymbol, value: any): any {
|
|
||||||
const _this = this;
|
|
||||||
let scope = BindingScope.empty;
|
|
||||||
const calling = new Map<StaticSymbol, boolean>();
|
|
||||||
|
|
||||||
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
|
|
||||||
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
|
|
||||||
let staticSymbol: StaticSymbol;
|
|
||||||
if (expression['module']) {
|
|
||||||
staticSymbol = _this.host.findDeclaration(
|
|
||||||
expression['module'], expression['name'], context.filePath);
|
|
||||||
} else {
|
|
||||||
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
|
|
||||||
}
|
|
||||||
return staticSymbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
|
||||||
const moduleMetadata = _this.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), _this.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 (calling.get(functionSymbol)) {
|
|
||||||
throw new Error('Recursion not supported');
|
|
||||||
}
|
|
||||||
calling.set(functionSymbol, true);
|
|
||||||
try {
|
|
||||||
const value = targetFunction['value'];
|
|
||||||
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 defaults: any[] = targetFunction.defaults;
|
|
||||||
if (defaults && defaults.length > args.length) {
|
|
||||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
|
||||||
}
|
|
||||||
const functionScope = BindingScope.build();
|
|
||||||
for (let i = 0; i < parameters.length; i++) {
|
|
||||||
functionScope.define(parameters[i], args[i]);
|
|
||||||
}
|
|
||||||
const oldScope = scope;
|
|
||||||
let result: any;
|
|
||||||
try {
|
|
||||||
scope = functionScope.done();
|
|
||||||
result = simplifyInContext(functionSymbol, value, depth + 1);
|
|
||||||
} finally {
|
|
||||||
scope = oldScope;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
calling.delete(functionSymbol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (depth === 0) {
|
|
||||||
// If depth is 0 we are evaluating the top level expression that is describing element
|
|
||||||
// decorator. In this case, it is a decorator we don't understand, such as a custom
|
|
||||||
// non-angular decorator, and we should just ignore it.
|
|
||||||
return {__symbolic: 'ignore'};
|
|
||||||
}
|
|
||||||
return simplify(
|
|
||||||
{__symbolic: 'error', message: 'Function call not supported', context: callContext});
|
|
||||||
}
|
|
||||||
|
|
||||||
function simplify(expression: any): any {
|
|
||||||
if (isPrimitive(expression)) {
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
if (expression instanceof Array) {
|
|
||||||
const result: any[] = [];
|
|
||||||
for (const item of (<any>expression)) {
|
|
||||||
// Check for a spread expression
|
|
||||||
if (item && item.__symbolic === 'spread') {
|
|
||||||
const spreadArray = simplify(item.expression);
|
|
||||||
if (Array.isArray(spreadArray)) {
|
|
||||||
for (const spreadItem of spreadArray) {
|
|
||||||
result.push(spreadItem);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const value = simplify(item);
|
|
||||||
if (shouldIgnore(value)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result.push(value);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (expression instanceof StaticSymbol) {
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
if (expression) {
|
|
||||||
if (expression['__symbolic']) {
|
|
||||||
let staticSymbol: StaticSymbol;
|
|
||||||
switch (expression['__symbolic']) {
|
|
||||||
case 'binop':
|
|
||||||
let left = simplify(expression['left']);
|
|
||||||
if (shouldIgnore(left)) return left;
|
|
||||||
let right = simplify(expression['right']);
|
|
||||||
if (shouldIgnore(right)) return right;
|
|
||||||
switch (expression['operator']) {
|
|
||||||
case '&&':
|
|
||||||
return left && right;
|
|
||||||
case '||':
|
|
||||||
return left || right;
|
|
||||||
case '|':
|
|
||||||
return left | right;
|
|
||||||
case '^':
|
|
||||||
return left ^ right;
|
|
||||||
case '&':
|
|
||||||
return left & right;
|
|
||||||
case '==':
|
|
||||||
return left == right;
|
|
||||||
case '!=':
|
|
||||||
return left != right;
|
|
||||||
case '===':
|
|
||||||
return left === right;
|
|
||||||
case '!==':
|
|
||||||
return left !== right;
|
|
||||||
case '<':
|
|
||||||
return left < right;
|
|
||||||
case '>':
|
|
||||||
return left > right;
|
|
||||||
case '<=':
|
|
||||||
return left <= right;
|
|
||||||
case '>=':
|
|
||||||
return left >= right;
|
|
||||||
case '<<':
|
|
||||||
return left << right;
|
|
||||||
case '>>':
|
|
||||||
return left >> right;
|
|
||||||
case '+':
|
|
||||||
return left + right;
|
|
||||||
case '-':
|
|
||||||
return left - right;
|
|
||||||
case '*':
|
|
||||||
return left * right;
|
|
||||||
case '/':
|
|
||||||
return left / right;
|
|
||||||
case '%':
|
|
||||||
return left % right;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
case 'if':
|
|
||||||
let condition = simplify(expression['condition']);
|
|
||||||
return condition ? simplify(expression['thenExpression']) :
|
|
||||||
simplify(expression['elseExpression']);
|
|
||||||
case 'pre':
|
|
||||||
let operand = simplify(expression['operand']);
|
|
||||||
if (shouldIgnore(operand)) return operand;
|
|
||||||
switch (expression['operator']) {
|
|
||||||
case '+':
|
|
||||||
return operand;
|
|
||||||
case '-':
|
|
||||||
return -operand;
|
|
||||||
case '!':
|
|
||||||
return !operand;
|
|
||||||
case '~':
|
|
||||||
return ~operand;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
case 'index':
|
|
||||||
let indexTarget = simplify(expression['expression']);
|
|
||||||
let index = simplify(expression['index']);
|
|
||||||
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
|
||||||
return null;
|
|
||||||
case 'select':
|
|
||||||
let selectTarget = simplify(expression['expression']);
|
|
||||||
if (selectTarget instanceof StaticSymbol) {
|
|
||||||
// Access to a static instance variable
|
|
||||||
const declarationValue = resolveReferenceValue(selectTarget);
|
|
||||||
if (declarationValue && declarationValue.statics) {
|
|
||||||
selectTarget = declarationValue.statics;
|
|
||||||
} else {
|
|
||||||
const member: string = expression['member'];
|
|
||||||
const members = selectTarget.members ?
|
|
||||||
(selectTarget.members as string[]).concat(member) :
|
|
||||||
[member];
|
|
||||||
return _this.host.getStaticSymbol(
|
|
||||||
selectTarget.filePath, selectTarget.name, members);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const member = simplify(expression['member']);
|
|
||||||
if (selectTarget && isPrimitive(member)) return simplify(selectTarget[member]);
|
|
||||||
return null;
|
|
||||||
case 'reference':
|
|
||||||
if (!expression.module) {
|
|
||||||
const name: string = expression['name'];
|
|
||||||
const localValue = scope.resolve(name);
|
|
||||||
if (localValue != BindingScope.missing) {
|
|
||||||
return localValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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':
|
|
||||||
return context;
|
|
||||||
case 'function':
|
|
||||||
return context;
|
|
||||||
case 'new':
|
|
||||||
case 'call':
|
|
||||||
// Determine if the function is a built-in conversion
|
|
||||||
let target = expression['expression'];
|
|
||||||
if (target['module']) {
|
|
||||||
staticSymbol = _this.host.findDeclaration(
|
|
||||||
target['module'], target['name'], context.filePath);
|
|
||||||
} else {
|
|
||||||
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
|
|
||||||
}
|
|
||||||
let converter = _this.conversionMap.get(staticSymbol);
|
|
||||||
if (converter) {
|
|
||||||
let args: any[] = expression['arguments'];
|
|
||||||
if (!args) {
|
|
||||||
args = [];
|
|
||||||
}
|
|
||||||
return converter(
|
|
||||||
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if the function is one we can simplify.
|
|
||||||
return simplifyCall(expression);
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
let message = produceErrorMessage(expression);
|
|
||||||
if (expression['line']) {
|
|
||||||
message =
|
|
||||||
`${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`;
|
|
||||||
throw positionalError(
|
|
||||||
message, context.filePath, expression['line'], expression['character']);
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return mapStringMap(expression, (value, name) => simplify(value));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return simplify(value);
|
|
||||||
} catch (e) {
|
|
||||||
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`;
|
|
||||||
if (e.fileName) {
|
|
||||||
throw positionalError(message, e.fileName, e.line, e.column);
|
|
||||||
}
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = simplifyInContext(context, value, 0);
|
|
||||||
if (shouldIgnore(result)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
moduleMetadata = this.host.getMetadataFor(module);
|
|
||||||
if (Array.isArray(moduleMetadata)) {
|
|
||||||
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
|
|
||||||
moduleMetadata[0];
|
|
||||||
}
|
|
||||||
if (!moduleMetadata) {
|
|
||||||
moduleMetadata =
|
|
||||||
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
|
|
||||||
}
|
|
||||||
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
|
|
||||||
throw new Error(
|
|
||||||
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`);
|
|
||||||
}
|
|
||||||
this.metadataCache.set(module, moduleMetadata);
|
|
||||||
}
|
|
||||||
return moduleMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
|
|
||||||
const moduleMetadata = this.getModuleMetadata(type.filePath);
|
|
||||||
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function expandedMessage(error: any): string {
|
|
||||||
switch (error.message) {
|
|
||||||
case 'Reference to non-exported class':
|
|
||||||
if (error.context && error.context.className) {
|
|
||||||
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Variable not initialized':
|
|
||||||
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
|
|
||||||
case 'Destructuring not supported':
|
|
||||||
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
|
|
||||||
case 'Could not resolve type':
|
|
||||||
if (error.context && error.context.typeName) {
|
|
||||||
return `Could not resolve type ${error.context.typeName}`;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'Function call not supported':
|
|
||||||
let prefix =
|
|
||||||
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
|
|
||||||
return prefix +
|
|
||||||
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
|
|
||||||
case 'Reference to a local symbol':
|
|
||||||
if (error.context && error.context.name) {
|
|
||||||
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
function produceErrorMessage(error: any): string {
|
|
||||||
return `Error encountered resolving symbol values statically. ${expandedMessage(error)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
|
|
||||||
{[key: string]: any} {
|
|
||||||
if (!input) return {};
|
|
||||||
const result: {[key: string]: any} = {};
|
|
||||||
Object.keys(input).forEach((key) => {
|
|
||||||
const value = transform(input[key], key);
|
|
||||||
if (!shouldIgnore(value)) {
|
|
||||||
result[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrimitive(o: any): boolean {
|
|
||||||
return o === null || (typeof o !== 'function' && typeof o !== 'object');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BindingScopeBuilder {
|
|
||||||
define(name: string, value: any): BindingScopeBuilder;
|
|
||||||
done(): BindingScope;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class BindingScope {
|
|
||||||
abstract resolve(name: string): any;
|
|
||||||
public static missing = {};
|
|
||||||
public static empty: BindingScope = {resolve: name => BindingScope.missing};
|
|
||||||
|
|
||||||
public static build(): BindingScopeBuilder {
|
|
||||||
const current = new Map<string, any>();
|
|
||||||
return {
|
|
||||||
define: function(name, value) {
|
|
||||||
current.set(name, value);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
done: function() {
|
|
||||||
return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PopulatedScope extends BindingScope {
|
|
||||||
constructor(private bindings: Map<string, any>) { super(); }
|
|
||||||
|
|
||||||
resolve(name: string): any {
|
|
||||||
return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
|
|
||||||
return a === b || (a.name == b.name && a.filePath == b.filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldIgnore(value: any): boolean {
|
|
||||||
return value && value.__symbolic == 'ignore';
|
|
||||||
}
|
|
||||||
|
|
||||||
function positionalError(message: string, fileName: string, line: number, column: number): Error {
|
|
||||||
const result = new Error(message);
|
|
||||||
(result as any).fileName = fileName;
|
|
||||||
(result as any).line = line;
|
|
||||||
(result as any).column = column;
|
|
||||||
return result;
|
|
||||||
}
|
|
19
modules/@angular/compiler-cli/src/version.ts
Normal file
19
modules/@angular/compiler-cli/src/version.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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module
|
||||||
|
* @description
|
||||||
|
* Entry point for all public APIs of the common package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Version} from '@angular/core';
|
||||||
|
/**
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export const VERSION = new Version('0.0.0-PLACEHOLDER');
|
260
modules/@angular/compiler-cli/test/aot_host_spec.ts
Normal file
260
modules/@angular/compiler-cli/test/aot_host_spec.ts
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* @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 {ModuleMetadata} from '@angular/tsc-wrapped';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {CompilerHost} from '../src/compiler_host';
|
||||||
|
|
||||||
|
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
|
||||||
|
|
||||||
|
describe('CompilerHost', () => {
|
||||||
|
let context: MockAotContext;
|
||||||
|
let program: ts.Program;
|
||||||
|
let hostNestedGenDir: CompilerHost;
|
||||||
|
let hostSiblingGenDir: CompilerHost;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
context = new MockAotContext('/tmp/src', clone(FILES));
|
||||||
|
const host = new MockCompilerHost(context);
|
||||||
|
program = ts.createProgram(
|
||||||
|
['main.ts'], {
|
||||||
|
module: ts.ModuleKind.CommonJS,
|
||||||
|
},
|
||||||
|
host);
|
||||||
|
// Force a typecheck
|
||||||
|
const errors = program.getSemanticDiagnostics();
|
||||||
|
if (errors && errors.length) {
|
||||||
|
throw new Error('Expected no errors');
|
||||||
|
}
|
||||||
|
hostNestedGenDir = new CompilerHost(
|
||||||
|
program, {
|
||||||
|
genDir: '/tmp/project/src/gen/',
|
||||||
|
basePath: '/tmp/project/src',
|
||||||
|
skipMetadataEmit: false,
|
||||||
|
strictMetadataEmit: false,
|
||||||
|
skipTemplateCodegen: false,
|
||||||
|
trace: false
|
||||||
|
},
|
||||||
|
context);
|
||||||
|
hostSiblingGenDir = new CompilerHost(
|
||||||
|
program, {
|
||||||
|
genDir: '/tmp/project/gen',
|
||||||
|
basePath: '/tmp/project/src/',
|
||||||
|
skipMetadataEmit: false,
|
||||||
|
strictMetadataEmit: false,
|
||||||
|
skipTemplateCodegen: false,
|
||||||
|
trace: false
|
||||||
|
},
|
||||||
|
context);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('nestedGenDir', () => {
|
||||||
|
it('should import node_module from factory', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/node_modules/@angular/core.d.ts',
|
||||||
|
'/tmp/project/src/gen/my.ngfactory.ts', ))
|
||||||
|
.toEqual('@angular/core');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import factory from factory', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./my.other.ngfactory');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||||
|
.toEqual('../my.other.css.ngstyle');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./a/my.other.shim.ngstyle');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||||
|
.toEqual('../my.other.sass.ngstyle');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import application from factory', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('../my.other');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||||
|
.toEqual('../../my.other');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('../a/my.other');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('../a/my.other.css');
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('../a/my.other.css.shim');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('siblingGenDir', () => {
|
||||||
|
it('should import node_module from factory', () => {
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/node_modules/@angular/core.d.ts',
|
||||||
|
'/tmp/project/src/gen/my.ngfactory.ts'))
|
||||||
|
.toEqual('@angular/core');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import factory from factory', () => {
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./my.other.ngfactory');
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||||
|
.toEqual('../my.other.css');
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./a/my.other.css.shim');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import application from factory', () => {
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./my.other');
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||||
|
.toEqual('../my.other');
|
||||||
|
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||||
|
.toEqual('./a/my.other');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to produce an import from main @angular/core', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName(
|
||||||
|
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
|
||||||
|
.toEqual('@angular/core');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to produce an import from main to a sub-directory', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to produce an import from to a peer file', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
|
||||||
|
.toEqual('./collections');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to produce an import from to a sibling directory', () => {
|
||||||
|
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
|
||||||
|
.toEqual('../lib/utils');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to read a metadata file', () => {
|
||||||
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
|
||||||
|
{__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
||||||
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
|
||||||
|
dummyMetadata
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to read empty metadata ', () => {
|
||||||
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined for missing modules', () => {
|
||||||
|
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add missing v3 metadata from v1 metadata and .d.ts files', () => {
|
||||||
|
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
|
||||||
|
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
|
||||||
|
__symbolic: 'module',
|
||||||
|
version: 3,
|
||||||
|
metadata: {
|
||||||
|
foo: {__symbolic: 'class'},
|
||||||
|
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
|
||||||
|
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
|
||||||
|
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
|
||||||
|
},
|
||||||
|
exports: [{from: './lib/utils2', export: ['Export']}],
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 dummyMetadata: ModuleMetadata = {
|
||||||
|
__symbolic: 'module',
|
||||||
|
version: 3,
|
||||||
|
metadata:
|
||||||
|
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
|
||||||
|
};
|
||||||
|
const FILES: Entry = {
|
||||||
|
'tmp': {
|
||||||
|
'src': {
|
||||||
|
'main.ts': `
|
||||||
|
import * as c from '@angular/core';
|
||||||
|
import * as r from '@angular/router';
|
||||||
|
import * as u from './lib/utils';
|
||||||
|
import * as cs from './lib/collections';
|
||||||
|
import * as u2 from './lib2/utils2';
|
||||||
|
`,
|
||||||
|
'lib': {
|
||||||
|
'utils.ts': dummyModule,
|
||||||
|
'collections.ts': dummyModule,
|
||||||
|
},
|
||||||
|
'lib2': {'utils2.ts': dummyModule},
|
||||||
|
'node_modules': {
|
||||||
|
'@angular': {
|
||||||
|
'core.d.ts': dummyModule,
|
||||||
|
'core.metadata.json':
|
||||||
|
`{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||||
|
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
|
||||||
|
'unused.d.ts': dummyModule,
|
||||||
|
'empty.d.ts': 'export declare var a: string;',
|
||||||
|
'empty.metadata.json': '[]',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'metadata_versions': {
|
||||||
|
'v1.d.ts': `
|
||||||
|
import {ReExport} from './lib/utils2';
|
||||||
|
export {ReExport};
|
||||||
|
|
||||||
|
export {Export} from './lib/utils2';
|
||||||
|
|
||||||
|
export declare class Bar {
|
||||||
|
ngOnInit() {}
|
||||||
|
}
|
||||||
|
export declare class BarChild extends Bar {}
|
||||||
|
`,
|
||||||
|
'v1.metadata.json':
|
||||||
|
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||||
|
'v1_empty.d.ts': `
|
||||||
|
export * from './lib/utils';
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function clone(entry: Entry): Entry {
|
||||||
|
if (typeof entry === 'string') {
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
const result: Directory = {};
|
||||||
|
for (const name in entry) {
|
||||||
|
result[name] = clone(entry[name]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
181
modules/@angular/compiler-cli/test/main_spec.ts
Normal file
181
modules/@angular/compiler-cli/test/main_spec.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import {main} from '../src/main';
|
||||||
|
import {ReflectionCapabilities, reflector} from './private_import_core';
|
||||||
|
|
||||||
|
|
||||||
|
describe('compiler-cli', () => {
|
||||||
|
let basePath: string;
|
||||||
|
let write: (fileName: string, content: string) => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
basePath = makeTempDir();
|
||||||
|
write = (fileName: string, content: string) => {
|
||||||
|
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||||
|
};
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": true,
|
||||||
|
"module": "es2015",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||||
|
|
||||||
|
it('should compile without errors', (done) => {
|
||||||
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalled();
|
||||||
|
expect(exitCode).toEqual(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not print the stack trace if user input file does not exist', (done) => {
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not print the stack trace if user input file is malformed', (done) => {
|
||||||
|
write('test.ts', 'foo;');
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not print the stack trace if cannot find the imported module', (done) => {
|
||||||
|
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
'Error at ' + path.join(basePath, 'test.ts') +
|
||||||
|
`:1:23: Cannot find module './not-exist-deps'.`);
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not print the stack trace if cannot import', (done) => {
|
||||||
|
write('empty-deps.ts', 'export const A = 1;');
|
||||||
|
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
||||||
|
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not print the stack trace if type mismatches', (done) => {
|
||||||
|
write('empty-deps.ts', 'export const A = "abc";');
|
||||||
|
write('test.ts', `
|
||||||
|
import {A} from './empty-deps';
|
||||||
|
A();
|
||||||
|
`);
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: basePath}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error)
|
||||||
|
.toHaveBeenCalledWith(
|
||||||
|
'Error at ' + path.join(basePath, 'test.ts') +
|
||||||
|
':3:7: Cannot invoke an expression whose type lacks a call signature.');
|
||||||
|
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print the stack trace on compiler internal errors', (done) => {
|
||||||
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
|
const mockConsole = {error: (s: string) => {}};
|
||||||
|
|
||||||
|
spyOn(mockConsole, 'error');
|
||||||
|
|
||||||
|
main({p: 'not-exist'}, mockConsole.error)
|
||||||
|
.then((exitCode) => {
|
||||||
|
expect(mockConsole.error).toHaveBeenCalled();
|
||||||
|
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
||||||
|
expect(exitCode).toEqual(1);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
});
|
@ -6,14 +6,14 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ReflectorHostContext} from '@angular/compiler-cli/src/reflector_host';
|
import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export type Entry = string | Directory;
|
export type Entry = string | Directory;
|
||||||
|
|
||||||
export interface Directory { [name: string]: Entry; }
|
export interface Directory { [name: string]: Entry; }
|
||||||
|
|
||||||
export class MockContext implements ReflectorHostContext {
|
export class MockAotContext implements CompilerHostContext {
|
||||||
constructor(public currentDirectory: string, private files: Entry) {}
|
constructor(public currentDirectory: string, private files: Entry) {}
|
||||||
|
|
||||||
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
|
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
|
||||||
@ -28,6 +28,14 @@ export class MockContext implements ReflectorHostContext {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readResource(fileName: string): Promise<string> {
|
||||||
|
const result = this.readFile(fileName);
|
||||||
|
if (result == null) {
|
||||||
|
return Promise.reject(new Error(`Resource not found: ${fileName}`));
|
||||||
|
}
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
writeFile(fileName: string, data: string): void {
|
writeFile(fileName: string, data: string): void {
|
||||||
const parts = fileName.split('/');
|
const parts = fileName.split('/');
|
||||||
const name = parts.pop();
|
const name = parts.pop();
|
||||||
@ -89,7 +97,7 @@ function normalize(parts: string[]): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MockCompilerHost implements ts.CompilerHost {
|
export class MockCompilerHost implements ts.CompilerHost {
|
||||||
constructor(private context: MockContext) {}
|
constructor(private context: MockAotContext) {}
|
||||||
|
|
||||||
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); }
|
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); }
|
||||||
|
|
||||||
|
13
modules/@angular/compiler-cli/test/private_import_core.ts
Normal file
13
modules/@angular/compiler-cli/test/private_import_core.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
|
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||||
|
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
|
export var reflector: typeof r.reflector = r.reflector;
|
@ -1,329 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
import {ReflectorHost} from '../src/reflector_host';
|
|
||||||
|
|
||||||
import {Directory, Entry, MockCompilerHost, MockContext} from './mocks';
|
|
||||||
|
|
||||||
describe('reflector_host', () => {
|
|
||||||
let context: MockContext;
|
|
||||||
let host: ts.CompilerHost;
|
|
||||||
let program: ts.Program;
|
|
||||||
let reflectorNestedGenDir: ReflectorHost;
|
|
||||||
let reflectorSiblingGenDir: ReflectorHost;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
context = new MockContext('/tmp/src', clone(FILES));
|
|
||||||
host = new MockCompilerHost(context);
|
|
||||||
program = ts.createProgram(
|
|
||||||
['main.ts'], {
|
|
||||||
module: ts.ModuleKind.CommonJS,
|
|
||||||
},
|
|
||||||
host);
|
|
||||||
// Force a typecheck
|
|
||||||
const errors = program.getSemanticDiagnostics();
|
|
||||||
if (errors && errors.length) {
|
|
||||||
throw new Error('Expected no errors');
|
|
||||||
}
|
|
||||||
reflectorNestedGenDir = new ReflectorHost(
|
|
||||||
program, host, {
|
|
||||||
genDir: '/tmp/project/src/gen/',
|
|
||||||
basePath: '/tmp/project/src',
|
|
||||||
skipMetadataEmit: false,
|
|
||||||
strictMetadataEmit: false,
|
|
||||||
skipTemplateCodegen: false,
|
|
||||||
trace: false
|
|
||||||
},
|
|
||||||
context);
|
|
||||||
reflectorSiblingGenDir = new ReflectorHost(
|
|
||||||
program, host, {
|
|
||||||
genDir: '/tmp/project/gen',
|
|
||||||
basePath: '/tmp/project/src/',
|
|
||||||
skipMetadataEmit: false,
|
|
||||||
strictMetadataEmit: false,
|
|
||||||
skipTemplateCodegen: false,
|
|
||||||
trace: false
|
|
||||||
},
|
|
||||||
context);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('nestedGenDir', () => {
|
|
||||||
it('should import node_module from factory', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/gen/my.ngfactory.ts',
|
|
||||||
'/tmp/project/node_modules/@angular/core.d.ts'))
|
|
||||||
.toEqual('@angular/core');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should import factory from factory', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
|
||||||
.toEqual('./my.other.ngfactory');
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
|
||||||
.toEqual('../my.other.css');
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
|
||||||
.toEqual('./a/my.other.css.shim');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should import application from factory', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
||||||
.toEqual('../my.other');
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
||||||
.toEqual('../../my.other');
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
|
||||||
.toEqual('../a/my.other');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('nestedGenDir', () => {
|
|
||||||
it('should import node_module from factory', () => {
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/gen/my.ngfactory.ts',
|
|
||||||
'/tmp/project/node_modules/@angular/core.d.ts'))
|
|
||||||
.toEqual('@angular/core');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should import factory from factory', () => {
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
|
||||||
.toEqual('./my.other.ngfactory');
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
|
||||||
.toEqual('../my.other.css');
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
|
||||||
.toEqual('./a/my.other.css.shim');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should import application from factory', () => {
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
||||||
.toEqual('./my.other');
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
|
||||||
.toEqual('../my.other');
|
|
||||||
expect(reflectorSiblingGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
|
||||||
.toEqual('./a/my.other');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should provide the import locations for angular', () => {
|
|
||||||
const {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
|
|
||||||
reflectorNestedGenDir.angularImportLocations();
|
|
||||||
expect(coreDecorators).toEqual('@angular/core/src/metadata');
|
|
||||||
expect(diDecorators).toEqual('@angular/core/src/di/metadata');
|
|
||||||
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
|
|
||||||
expect(animationMetadata).toEqual('@angular/core/src/animation/metadata');
|
|
||||||
expect(provider).toEqual('@angular/core/src/di/provider');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce an import from main @angular/core', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath(
|
|
||||||
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
|
|
||||||
.toEqual('@angular/core');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce an import from main to a sub-directory', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce an import from to a peer file', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts'))
|
|
||||||
.toEqual('./collections');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce an import from to a sibling directory', () => {
|
|
||||||
expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts'))
|
|
||||||
.toEqual('../lib/utils');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce a symbol for an exported symbol', () => {
|
|
||||||
expect(reflectorNestedGenDir.findDeclaration('@angular/router', 'foo', 'main.ts'))
|
|
||||||
.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce a symbol for values space only reference', () => {
|
|
||||||
expect(reflectorNestedGenDir.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
|
|
||||||
.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should be produce the same symbol if asked twice', () => {
|
|
||||||
const foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
|
||||||
const foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
|
||||||
expect(foo1).toBe(foo2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to produce a symbol for a module with no file', () => {
|
|
||||||
expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to read a metadata file', () => {
|
|
||||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
|
|
||||||
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
|
||||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts'))
|
|
||||||
.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to read empty metadata ', () => {
|
|
||||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts'))
|
|
||||||
.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return undefined for missing modules', () => {
|
|
||||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts'))
|
|
||||||
.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to trace a named export', () => {
|
|
||||||
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
||||||
'./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
|
|
||||||
expect(symbol.name).toEqual('One');
|
|
||||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to trace a renamed export', () => {
|
|
||||||
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
||||||
'./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
|
|
||||||
expect(symbol.name).toEqual('Three');
|
|
||||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to trace an export * export', () => {
|
|
||||||
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
||||||
'./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
|
|
||||||
expect(symbol.name).toEqual('Five');
|
|
||||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to trace a multi-level re-export', () => {
|
|
||||||
const symbol = reflectorNestedGenDir.findDeclaration(
|
|
||||||
'./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
|
|
||||||
expect(symbol.name).toEqual('Thirty');
|
|
||||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const dummyModule = 'export let foo: any[];';
|
|
||||||
|
|
||||||
const FILES: Entry = {
|
|
||||||
'tmp': {
|
|
||||||
'src': {
|
|
||||||
'main.ts': `
|
|
||||||
import * as c from '@angular/core';
|
|
||||||
import * as r from '@angular/router';
|
|
||||||
import * as u from './lib/utils';
|
|
||||||
import * as cs from './lib/collections';
|
|
||||||
import * as u2 from './lib2/utils2';
|
|
||||||
`,
|
|
||||||
'lib': {
|
|
||||||
'utils.ts': dummyModule,
|
|
||||||
'collections.ts': dummyModule,
|
|
||||||
},
|
|
||||||
'lib2': {'utils2.ts': dummyModule},
|
|
||||||
'reexport': {
|
|
||||||
'reexport.d.ts': `
|
|
||||||
import * as c from '@angular/core';
|
|
||||||
`,
|
|
||||||
'reexport.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {},
|
|
||||||
exports: [
|
|
||||||
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
|
|
||||||
{from: './src/origin5'}, {from: './src/reexport2'}
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
'src': {
|
|
||||||
'origin1.d.ts': `
|
|
||||||
export class One {}
|
|
||||||
export class Two {}
|
|
||||||
export class Three {}
|
|
||||||
`,
|
|
||||||
'origin1.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {
|
|
||||||
One: {__symbolic: 'class'},
|
|
||||||
Two: {__symbolic: 'class'},
|
|
||||||
Three: {__symbolic: 'class'},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'origin5.d.ts': `
|
|
||||||
export class Five {}
|
|
||||||
`,
|
|
||||||
'origin5.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {
|
|
||||||
Five: {__symbolic: 'class'},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'origin30.d.ts': `
|
|
||||||
export class Thirty {}
|
|
||||||
`,
|
|
||||||
'origin30.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {
|
|
||||||
Thirty: {__symbolic: 'class'},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'originNone.d.ts': dummyModule,
|
|
||||||
'originNone.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {},
|
|
||||||
}),
|
|
||||||
'reexport2.d.ts': dummyModule,
|
|
||||||
'reexport2.metadata.json': JSON.stringify({
|
|
||||||
__symbolic: 'module',
|
|
||||||
version: 1,
|
|
||||||
metadata: {},
|
|
||||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'node_modules': {
|
|
||||||
'@angular': {
|
|
||||||
'core.d.ts': dummyModule,
|
|
||||||
'core.metadata.json':
|
|
||||||
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
|
||||||
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
|
|
||||||
'unused.d.ts': dummyModule,
|
|
||||||
'empty.d.ts': 'export declare var a: string;',
|
|
||||||
'empty.metadata.json': '[]',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function clone(entry: Entry): Entry {
|
|
||||||
if (typeof entry === 'string') {
|
|
||||||
return entry;
|
|
||||||
} else {
|
|
||||||
const result: Directory = {};
|
|
||||||
for (const name in entry) {
|
|
||||||
result[name] = clone(entry[name]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
34
modules/@angular/compiler-cli/tsconfig-2015.json
Normal file
34
modules/@angular/compiler-cli/tsconfig-2015.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"declaration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es2015",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "../../../dist/esm/compiler-cli",
|
||||||
|
"paths": {
|
||||||
|
"@angular/core": ["../../../dist/packages-dist/core"],
|
||||||
|
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||||
|
"@angular/compiler": ["../../../dist/packages-dist/compiler"],
|
||||||
|
"@angular/platform-server": ["../../../dist/packages-dist/platform-server"],
|
||||||
|
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
||||||
|
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
|
||||||
|
},
|
||||||
|
"rootDir": ".",
|
||||||
|
"sourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": ["integrationtest"],
|
||||||
|
"files": [
|
||||||
|
"index.ts",
|
||||||
|
"src/main.ts",
|
||||||
|
"src/extract_i18n.ts",
|
||||||
|
"../../../node_modules/@types/node/index.d.ts",
|
||||||
|
"../../../node_modules/@types/jasmine/index.d.ts",
|
||||||
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
|
]
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["es6", "dom"],
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"declaration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "../../../dist/packages-dist/compiler-cli",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@angular/core": ["../../../dist/packages-dist/core"],
|
"@angular/core": ["../../../dist/packages-dist/core"],
|
||||||
"@angular/common": ["../../../dist/packages-dist/common"],
|
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||||
@ -14,11 +14,11 @@
|
|||||||
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
||||||
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
|
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
|
||||||
},
|
},
|
||||||
"experimentalDecorators": true,
|
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"sourceRoot": ".",
|
"sourceMap": true,
|
||||||
"outDir": "../../../dist/packages-dist/compiler-cli",
|
"inlineSources": true,
|
||||||
"declaration": true,
|
"target": "es5",
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"exclude": ["integrationtest"],
|
"exclude": ["integrationtest"],
|
||||||
|
@ -21,15 +21,24 @@
|
|||||||
* </p>
|
* </p>
|
||||||
* </div>
|
* </div>
|
||||||
*/
|
*/
|
||||||
|
export {VERSION} from './src/version';
|
||||||
export * from './src/template_parser/template_ast';
|
export * from './src/template_parser/template_ast';
|
||||||
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
|
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
|
||||||
export {CompilerConfig, RenderTypes} from './src/config';
|
export {CompilerConfig, RenderTypes} from './src/config';
|
||||||
export * from './src/compile_metadata';
|
export * from './src/compile_metadata';
|
||||||
export * from './src/offline_compiler';
|
export * from './src/aot/compiler_factory';
|
||||||
export {RuntimeCompiler} from './src/runtime_compiler';
|
export * from './src/aot/compiler';
|
||||||
|
export * from './src/aot/compiler_host';
|
||||||
|
export * from './src/aot/static_reflector';
|
||||||
|
export * from './src/aot/static_reflection_capabilities';
|
||||||
|
export * from './src/aot/static_symbol';
|
||||||
|
export * from './src/aot/static_symbol_resolver';
|
||||||
|
export * from './src/aot/summary_resolver';
|
||||||
|
export * from './src/summary_resolver';
|
||||||
|
export {JitCompiler} from './src/jit/compiler';
|
||||||
|
export * from './src/jit/compiler_factory';
|
||||||
export * from './src/url_resolver';
|
export * from './src/url_resolver';
|
||||||
export * from './src/resource_loader';
|
export * from './src/resource_loader';
|
||||||
export * from './src/compiler';
|
|
||||||
export {DirectiveResolver} from './src/directive_resolver';
|
export {DirectiveResolver} from './src/directive_resolver';
|
||||||
export {PipeResolver} from './src/pipe_resolver';
|
export {PipeResolver} from './src/pipe_resolver';
|
||||||
export {NgModuleResolver} from './src/ng_module_resolver';
|
export {NgModuleResolver} from './src/ng_module_resolver';
|
||||||
@ -53,5 +62,5 @@ 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.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
||||||
|
|
||||||
@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
|
|||||||
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
|
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
|
||||||
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
|
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
|
||||||
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
|
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
|
||||||
const EMPTY_MAP = o.literalMap([]);
|
const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
|
||||||
|
const _EMPTY_MAP = o.literalMap([]);
|
||||||
|
const _EMPTY_ARRAY = o.literalArr([]);
|
||||||
|
|
||||||
class _AnimationBuilder implements AnimationAstVisitor {
|
class _AnimationBuilder implements AnimationAstVisitor {
|
||||||
private _fnVarName: string;
|
private _fnVarName: string;
|
||||||
@ -64,11 +66,11 @@ 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));
|
stylesArr.push(o.literalMap(entries, null, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||||
o.importExpr(resolveIdentifier(Identifiers.collectAndResolveStyles)).callFn([
|
o.importExpr(createIdentifier(Identifiers.collectAndResolveStyles)).callFn([
|
||||||
_ANIMATION_COLLECTED_STYLES, o.literalArr(stylesArr)
|
_ANIMATION_COLLECTED_STYLES, o.literalArr(stylesArr)
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@ -76,7 +78,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
|
|
||||||
visitAnimationKeyframe(ast: AnimationKeyframeAst, context: _AnimationBuilderContext):
|
visitAnimationKeyframe(ast: AnimationKeyframeAst, context: _AnimationBuilderContext):
|
||||||
o.Expression {
|
o.Expression {
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationKeyframe)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationKeyframe)).instantiate([
|
||||||
o.literal(ast.offset), ast.styles.visit(this, context)
|
o.literal(ast.offset), ast.styles.visit(this, context)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -98,7 +100,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
const startingStylesExpr = ast.startingStyles.visit(this, context);
|
const startingStylesExpr = ast.startingStyles.visit(this, context);
|
||||||
const keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
|
const keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
|
||||||
const keyframesExpr =
|
const keyframesExpr =
|
||||||
o.importExpr(resolveIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
|
o.importExpr(createIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
|
||||||
_ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR,
|
_ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR,
|
||||||
o.literalArr(keyframeExpressions)
|
o.literalArr(keyframeExpressions)
|
||||||
]);
|
]);
|
||||||
@ -110,24 +112,29 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
_callAnimateMethod(
|
_callAnimateMethod(
|
||||||
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
|
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
|
||||||
context: _AnimationBuilderContext) {
|
context: _AnimationBuilderContext) {
|
||||||
|
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
|
||||||
|
if (context.isExpectingFirstAnimateStep) {
|
||||||
|
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
|
||||||
|
context.isExpectingFirstAnimateStep = false;
|
||||||
|
}
|
||||||
context.totalTransitionTime += ast.duration + ast.delay;
|
context.totalTransitionTime += ast.duration + ast.delay;
|
||||||
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
|
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
|
||||||
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
|
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
|
||||||
o.literal(ast.delay), o.literal(ast.easing)
|
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext):
|
visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext):
|
||||||
o.Expression {
|
o.Expression {
|
||||||
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
|
||||||
o.literalArr(playerExprs)
|
o.literalArr(playerExprs)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
|
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
|
||||||
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
|
return o.importExpr(createIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
|
||||||
o.literalArr(playerExprs)
|
o.literalArr(playerExprs)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
|
|
||||||
context.totalTransitionTime = 0;
|
context.totalTransitionTime = 0;
|
||||||
context.isExpectingFirstStyleStep = true;
|
context.isExpectingFirstStyleStep = true;
|
||||||
|
context.isExpectingFirstAnimateStep = true;
|
||||||
|
|
||||||
const stateChangePreconditions: o.Expression[] = [];
|
const stateChangePreconditions: o.Expression[] = [];
|
||||||
|
|
||||||
@ -187,17 +195,17 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
context.stateMap.registerState(DEFAULT_STATE, {});
|
context.stateMap.registerState(DEFAULT_STATE, {});
|
||||||
|
|
||||||
const statements: o.Statement[] = [];
|
const statements: o.Statement[] = [];
|
||||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
statements.push(_PREVIOUS_ANIMATION_PLAYERS
|
||||||
.callMethod(
|
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||||
'cancelActiveAnimation',
|
'getAnimationPlayers',
|
||||||
[
|
[
|
||||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||||
])
|
.conditional(o.NULL_EXPR, o.literal(this.animationName))
|
||||||
.toStmt());
|
]))
|
||||||
|
.toDeclStmt());
|
||||||
|
|
||||||
|
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
|
||||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
|
|
||||||
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
|
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
|
||||||
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
|
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
|
||||||
|
|
||||||
@ -221,18 +229,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR),
|
_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR),
|
||||||
[_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()]));
|
[_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()]));
|
||||||
|
|
||||||
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
|
const RENDER_STYLES_FN = o.importExpr(createIdentifier(Identifiers.renderStyles));
|
||||||
|
|
||||||
// before we start any animation we want to clear out the starting
|
|
||||||
// styles from the element's style property (since they were placed
|
|
||||||
// there at the end of the last animation
|
|
||||||
statements.push(RENDER_STYLES_FN
|
|
||||||
.callFn([
|
|
||||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
|
||||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
|
||||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
|
||||||
])
|
|
||||||
.toStmt());
|
|
||||||
|
|
||||||
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
||||||
|
|
||||||
@ -241,7 +238,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
statements.push(new o.IfStmt(
|
statements.push(new o.IfStmt(
|
||||||
_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR),
|
_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR),
|
||||||
[_ANIMATION_PLAYER_VAR
|
[_ANIMATION_PLAYER_VAR
|
||||||
.set(o.importExpr(resolveIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
|
.set(o.importExpr(createIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
|
||||||
.toStmt()]));
|
.toStmt()]));
|
||||||
|
|
||||||
// once complete we want to apply the styles on the element
|
// once complete we want to apply the styles on the element
|
||||||
@ -259,16 +256,33 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
.callFn([
|
.callFn([
|
||||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||||
o.importExpr(
|
o.importExpr(
|
||||||
resolveIdentifier(Identifiers.prepareFinalAnimationStyles))
|
createIdentifier(Identifiers.prepareFinalAnimationStyles))
|
||||||
.callFn([
|
.callFn(
|
||||||
_ANIMATION_START_STATE_STYLES_VAR,
|
[
|
||||||
_ANIMATION_END_STATE_STYLES_VAR
|
_ANIMATION_START_STATE_STYLES_VAR,
|
||||||
])
|
_ANIMATION_END_STATE_STYLES_VAR
|
||||||
|
])
|
||||||
])
|
])
|
||||||
.toStmt()
|
.toStmt()
|
||||||
])])
|
])])
|
||||||
.toStmt());
|
.toStmt());
|
||||||
|
|
||||||
|
statements.push(o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer))
|
||||||
|
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
|
||||||
|
.callMethod('destroy', [])
|
||||||
|
.toStmt());
|
||||||
|
|
||||||
|
// before we start any animation we want to clear out the starting
|
||||||
|
// styles from the element's style property (since they were placed
|
||||||
|
// there at the end of the last animation
|
||||||
|
statements.push(RENDER_STYLES_FN
|
||||||
|
.callFn([
|
||||||
|
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||||
|
o.importExpr(createIdentifier(Identifiers.clearStyles))
|
||||||
|
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||||
|
])
|
||||||
|
.toStmt());
|
||||||
|
|
||||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
||||||
.callMethod(
|
.callMethod(
|
||||||
'queueAnimation',
|
'queueAnimation',
|
||||||
@ -279,7 +293,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
.toStmt());
|
.toStmt());
|
||||||
|
|
||||||
statements.push(new o.ReturnStatement(
|
statements.push(new o.ReturnStatement(
|
||||||
o.importExpr(resolveIdentifier(Identifiers.AnimationTransition)).instantiate([
|
o.importExpr(createIdentifier(Identifiers.AnimationTransition)).instantiate([
|
||||||
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR,
|
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR,
|
||||||
_ANIMATION_TIME_VAR
|
_ANIMATION_TIME_VAR
|
||||||
])));
|
])));
|
||||||
@ -288,12 +302,12 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
[
|
[
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
_ANIMATION_FACTORY_VIEW_VAR.name,
|
_ANIMATION_FACTORY_VIEW_VAR.name,
|
||||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(_ANIMATION_FACTORY_ELEMENT_VAR.name, o.DYNAMIC_TYPE),
|
new o.FnParam(_ANIMATION_FACTORY_ELEMENT_VAR.name, o.DYNAMIC_TYPE),
|
||||||
new o.FnParam(_ANIMATION_CURRENT_STATE_VAR.name, o.DYNAMIC_TYPE),
|
new o.FnParam(_ANIMATION_CURRENT_STATE_VAR.name, o.DYNAMIC_TYPE),
|
||||||
new o.FnParam(_ANIMATION_NEXT_STATE_VAR.name, o.DYNAMIC_TYPE)
|
new o.FnParam(_ANIMATION_NEXT_STATE_VAR.name, o.DYNAMIC_TYPE)
|
||||||
],
|
],
|
||||||
statements, o.importType(resolveIdentifier(Identifiers.AnimationTransition)));
|
statements, o.importType(createIdentifier(Identifiers.AnimationTransition)));
|
||||||
}
|
}
|
||||||
|
|
||||||
build(ast: AnimationAst): AnimationEntryCompileResult {
|
build(ast: AnimationAst): AnimationEntryCompileResult {
|
||||||
@ -304,16 +318,17 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
const lookupMap: any[] = [];
|
const lookupMap: any[] = [];
|
||||||
Object.keys(context.stateMap.states).forEach(stateName => {
|
Object.keys(context.stateMap.states).forEach(stateName => {
|
||||||
const value = context.stateMap.states[stateName];
|
const value = context.stateMap.states[stateName];
|
||||||
let variableValue = EMPTY_MAP;
|
let variableValue = _EMPTY_MAP;
|
||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
const styleMap: any[] = [];
|
const styleMap: any[] = [];
|
||||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||||
variableValue = o.literalMap(styleMap);
|
variableValue = o.literalMap(styleMap, null, true);
|
||||||
}
|
}
|
||||||
lookupMap.push([stateName, variableValue]);
|
lookupMap.push([stateName, variableValue]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
const compiledStatesMapStmt =
|
||||||
|
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);
|
||||||
@ -324,6 +339,7 @@ class _AnimationBuilderContext {
|
|||||||
stateMap = new _AnimationBuilderStateMap();
|
stateMap = new _AnimationBuilderStateMap();
|
||||||
endStateAnimateStep: AnimationStepAst = null;
|
endStateAnimateStep: AnimationStepAst = null;
|
||||||
isExpectingFirstStyleStep = false;
|
isExpectingFirstStyleStep = false;
|
||||||
|
isExpectingFirstAnimateStep = false;
|
||||||
totalTransitionTime = 0;
|
totalTransitionTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {Injectable} from '@angular/core';
|
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
|
||||||
|
|
||||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
|
|
||||||
import {StringMapWrapper} from '../facade/collection';
|
import {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';
|
||||||
@ -35,13 +34,13 @@ export class AnimationEntryParseResult {
|
|||||||
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class AnimationParser {
|
export class AnimationParser {
|
||||||
constructor(private _schema: ElementSchemaRegistry) {}
|
constructor(private _schema: ElementSchemaRegistry) {}
|
||||||
|
|
||||||
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
|
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
const componentName = component.type.name;
|
const componentName = identifierName(component.type);
|
||||||
const animationTriggerNames = new Set<string>();
|
const animationTriggerNames = new Set<string>();
|
||||||
const asts = component.template.animations.map(entry => {
|
const asts = component.template.animations.map(entry => {
|
||||||
const result = this.parseEntry(entry);
|
const result = this.parseEntry(entry);
|
||||||
@ -174,6 +173,11 @@ function _normalizeStyleMetadata(
|
|||||||
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||||
schema: ElementSchemaRegistry, errors: AnimationParseError[],
|
schema: ElementSchemaRegistry, errors: AnimationParseError[],
|
||||||
permitStateReferences: boolean): {[key: string]: string | number}[] {
|
permitStateReferences: boolean): {[key: string]: string | number}[] {
|
||||||
|
const offset = entry.offset;
|
||||||
|
if (offset > 1 || offset < 0) {
|
||||||
|
errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`));
|
||||||
|
}
|
||||||
|
|
||||||
const normalizedStyles: {[key: string]: string | number}[] = [];
|
const normalizedStyles: {[key: string]: string | number}[] = [];
|
||||||
entry.styles.forEach(styleEntry => {
|
entry.styles.forEach(styleEntry => {
|
||||||
if (typeof styleEntry === 'string') {
|
if (typeof styleEntry === 'string') {
|
||||||
|
@ -8,114 +8,69 @@
|
|||||||
|
|
||||||
import {SchemaMetadata} from '@angular/core';
|
import {SchemaMetadata} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationCompiler} from './animation/animation_compiler';
|
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||||
import {AnimationParser} from './animation/animation_parser';
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
import {DirectiveNormalizer} from './directive_normalizer';
|
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
|
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from '../facade/collection';
|
||||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
||||||
import {CompileMetadataResolver} from './metadata_resolver';
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
import {NgModuleCompiler} from './ng_module_compiler';
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
import {OutputEmitter} from './output/abstract_emitter';
|
import {OutputEmitter} from '../output/abstract_emitter';
|
||||||
import * as o from './output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
|
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||||
import {TemplateParser} from './template_parser/template_parser';
|
import {SummaryResolver} from '../summary_resolver';
|
||||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler';
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
|
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||||
|
|
||||||
export class SourceModule {
|
import {AotCompilerHost} from './compiler_host';
|
||||||
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
|
import {GeneratedFile} from './generated_file';
|
||||||
}
|
import {StaticSymbol} from './static_symbol';
|
||||||
|
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
|
import {serializeSummaries, summaryFileName} from './summary_serializer';
|
||||||
|
|
||||||
// Returns all the source files and a mapping from modules to directives
|
export class AotCompiler {
|
||||||
export function analyzeNgModules(
|
|
||||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
|
||||||
metadataResolver: CompileMetadataResolver): Promise<{
|
|
||||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
|
||||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
|
|
||||||
}> {
|
|
||||||
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
|
|
||||||
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
|
||||||
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
|
|
||||||
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
|
||||||
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
|
||||||
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
|
||||||
const filePaths = new Set<string>();
|
|
||||||
|
|
||||||
// Looping over all modules to construct:
|
|
||||||
// - a map from file to modules `ngModulesByFile`,
|
|
||||||
// - a map from file to directives `ngDirectivesByFile`,
|
|
||||||
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
|
||||||
ngModuleMetas.forEach((ngModuleMeta) => {
|
|
||||||
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
|
||||||
filePaths.add(srcFileUrl);
|
|
||||||
ngModulesByFile.set(
|
|
||||||
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
|
||||||
|
|
||||||
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
|
||||||
const fileUrl = dirIdentifier.reference.filePath;
|
|
||||||
filePaths.add(fileUrl);
|
|
||||||
ngDirectivesByFile.set(
|
|
||||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
|
|
||||||
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
|
|
||||||
});
|
|
||||||
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
|
|
||||||
const fileUrl = pipeIdentifier.reference.filePath;
|
|
||||||
filePaths.add(fileUrl);
|
|
||||||
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
|
|
||||||
|
|
||||||
filePaths.forEach((srcUrl) => {
|
|
||||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
|
||||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
|
||||||
files.push({srcUrl, directives, ngModules});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
// map directive/pipe to module
|
|
||||||
ngModuleByPipeOrDirective,
|
|
||||||
// list modules and directives for every source file
|
|
||||||
files,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OfflineCompiler {
|
|
||||||
private _animationCompiler = new AnimationCompiler();
|
private _animationCompiler = new AnimationCompiler();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver,
|
||||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||||
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||||
private _localeId: string, private _translationFormat: string,
|
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
|
||||||
private _animationParser: AnimationParser) {}
|
private _translationFormat: string, private _animationParser: AnimationParser,
|
||||||
|
private _symbolResolver: StaticSymbolResolver) {}
|
||||||
|
|
||||||
clearCache() { this._metadataResolver.clearCache(); }
|
clearCache() { this._metadataResolver.clearCache(); }
|
||||||
|
|
||||||
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
|
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
|
||||||
Promise<SourceModule[]> {
|
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
|
||||||
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
|
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||||
.then(({ngModuleByPipeOrDirective, files}) => {
|
analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
|
||||||
|
return Promise
|
||||||
|
.all(ngModules.map(
|
||||||
|
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||||
|
ngModule.type.reference, false)))
|
||||||
|
.then(() => {
|
||||||
const sourceModules = files.map(
|
const sourceModules = files.map(
|
||||||
file => this._compileSrcFile(
|
file => this._compileSrcFile(
|
||||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
|
||||||
|
file.ngModules, file.injectables));
|
||||||
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[], ngModules: StaticSymbol[]): SourceModule[] {
|
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
|
||||||
|
injectables: 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 outputSourceModules: SourceModule[] = [];
|
const generatedFiles: GeneratedFile[] = [];
|
||||||
|
|
||||||
|
generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables));
|
||||||
|
|
||||||
// compile all ng modules
|
// compile all ng modules
|
||||||
exportedVars.push(
|
exportedVars.push(
|
||||||
@ -134,7 +89,7 @@ export class OfflineCompiler {
|
|||||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||||
if (!ngModule) {
|
if (!ngModule) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
|
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_assertComponent(compMeta);
|
_assertComponent(compMeta);
|
||||||
@ -142,7 +97,7 @@ export class OfflineCompiler {
|
|||||||
// compile styles
|
// compile styles
|
||||||
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
||||||
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
||||||
outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
generatedFiles.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||||
});
|
});
|
||||||
|
|
||||||
// compile components
|
// compile components
|
||||||
@ -155,9 +110,25 @@ export class OfflineCompiler {
|
|||||||
if (statements.length > 0) {
|
if (statements.length > 0) {
|
||||||
const srcModule = this._codegenSourceModule(
|
const srcModule = this._codegenSourceModule(
|
||||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||||
outputSourceModules.unshift(srcModule);
|
generatedFiles.unshift(srcModule);
|
||||||
}
|
}
|
||||||
return outputSourceModules;
|
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 {
|
||||||
@ -165,24 +136,24 @@ export class OfflineCompiler {
|
|||||||
const providers: CompileProviderMetadata[] = [];
|
const providers: CompileProviderMetadata[] = [];
|
||||||
|
|
||||||
if (this._localeId) {
|
if (this._localeId) {
|
||||||
providers.push(new CompileProviderMetadata({
|
providers.push({
|
||||||
token: resolveIdentifierToken(Identifiers.LOCALE_ID),
|
token: createIdentifierToken(Identifiers.LOCALE_ID),
|
||||||
useValue: this._localeId,
|
useValue: this._localeId,
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._translationFormat) {
|
if (this._translationFormat) {
|
||||||
providers.push(new CompileProviderMetadata({
|
providers.push({
|
||||||
token: resolveIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
|
token: createIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
|
||||||
useValue: this._translationFormat
|
useValue: this._translationFormat
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
||||||
|
|
||||||
appCompileResult.dependencies.forEach((dep) => {
|
appCompileResult.dependencies.forEach((dep) => {
|
||||||
dep.placeholder.name = _componentFactoryName(dep.comp);
|
dep.placeholder.reference = this._symbolResolver.getStaticSymbol(
|
||||||
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.moduleUrl);
|
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
|
||||||
});
|
});
|
||||||
|
|
||||||
targetStatements.push(...appCompileResult.statements);
|
targetStatements.push(...appCompileResult.statements);
|
||||||
@ -201,14 +172,17 @@ export class OfflineCompiler {
|
|||||||
private _compileComponentFactory(
|
private _compileComponentFactory(
|
||||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
||||||
targetStatements: o.Statement[]): string {
|
targetStatements: o.Statement[]): string {
|
||||||
const hostMeta = createHostComponentMeta(compMeta);
|
const hostMeta = createHostComponentMeta(
|
||||||
|
this._symbolResolver.getStaticSymbol(
|
||||||
|
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
|
||||||
|
compMeta);
|
||||||
const hostViewFactoryVar = this._compileComponent(
|
const hostViewFactoryVar = this._compileComponent(
|
||||||
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
|
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
|
||||||
const compFactoryVar = _componentFactoryName(compMeta.type);
|
const compFactoryVar = _componentFactoryName(compMeta.type);
|
||||||
targetStatements.push(
|
targetStatements.push(
|
||||||
o.variable(compFactoryVar)
|
o.variable(compFactoryVar)
|
||||||
.set(o.importExpr(resolveIdentifier(Identifiers.ComponentFactory), [o.importType(
|
.set(o.importExpr(
|
||||||
compMeta.type)])
|
createIdentifier(Identifiers.ComponentFactory), [o.importType(compMeta.type)])
|
||||||
.instantiate(
|
.instantiate(
|
||||||
[
|
[
|
||||||
o.literal(compMeta.selector),
|
o.literal(compMeta.selector),
|
||||||
@ -216,7 +190,7 @@ export class OfflineCompiler {
|
|||||||
o.importExpr(compMeta.type),
|
o.importExpr(compMeta.type),
|
||||||
],
|
],
|
||||||
o.importType(
|
o.importType(
|
||||||
resolveIdentifier(Identifiers.ComponentFactory),
|
createIdentifier(Identifiers.ComponentFactory),
|
||||||
[o.importType(compMeta.type)], [o.TypeModifier.Const])))
|
[o.importType(compMeta.type)], [o.TypeModifier.Const])))
|
||||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||||
return compFactoryVar;
|
return compFactoryVar;
|
||||||
@ -234,23 +208,24 @@ export class OfflineCompiler {
|
|||||||
|
|
||||||
const parsedTemplate = this._templateParser.parse(
|
const parsedTemplate = this._templateParser.parse(
|
||||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
||||||
compMeta.type.name);
|
identifierName(compMeta.type));
|
||||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||||
const compiledAnimations =
|
const compiledAnimations =
|
||||||
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
||||||
const viewResult = this._viewCompiler.compileComponent(
|
const viewResult = this._viewCompiler.compileComponent(
|
||||||
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
||||||
if (componentStyles) {
|
if (componentStyles) {
|
||||||
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
|
targetStatements.push(
|
||||||
|
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
|
||||||
}
|
}
|
||||||
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
||||||
targetStatements.push(..._resolveViewStatements(viewResult));
|
targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult));
|
||||||
return viewResult.viewClassVar;
|
return viewResult.viewClassVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _codgenStyles(
|
private _codgenStyles(
|
||||||
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
|
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
|
||||||
_resolveStyleStatements(stylesCompileResult, fileSuffix);
|
_resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
|
||||||
return this._codegenSourceModule(
|
return this._codegenSourceModule(
|
||||||
fileUrl, _stylesModuleUrl(
|
fileUrl, _stylesModuleUrl(
|
||||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||||
@ -258,26 +233,29 @@ export class OfflineCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _codegenSourceModule(
|
private _codegenSourceModule(
|
||||||
fileUrl: string, moduleUrl: string, statements: o.Statement[],
|
srcFileUrl: string, genFileUrl: string, statements: o.Statement[],
|
||||||
exportedVars: string[]): SourceModule {
|
exportedVars: string[]): GeneratedFile {
|
||||||
return new SourceModule(
|
return new GeneratedFile(
|
||||||
fileUrl, moduleUrl,
|
srcFileUrl, genFileUrl,
|
||||||
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
|
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
|
function _resolveViewStatements(
|
||||||
|
reflector: StaticSymbolResolver, 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;
|
||||||
vfd.placeholder.moduleUrl = _ngfactoryModuleUrl(vfd.comp.moduleUrl);
|
vfd.placeholder.reference =
|
||||||
|
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(vfd.comp)), dep.name);
|
||||||
} else if (dep instanceof ComponentFactoryDependency) {
|
} else if (dep instanceof ComponentFactoryDependency) {
|
||||||
const cfd = <ComponentFactoryDependency>dep;
|
const cfd = <ComponentFactoryDependency>dep;
|
||||||
cfd.placeholder.name = _componentFactoryName(cfd.comp);
|
cfd.placeholder.reference = reflector.getStaticSymbol(
|
||||||
cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp.moduleUrl);
|
_ngfactoryModuleUrl(identifierModuleUrl(cfd.comp)), _componentFactoryName(cfd.comp));
|
||||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||||
const dwd = <DirectiveWrapperDependency>dep;
|
const dwd = <DirectiveWrapperDependency>dep;
|
||||||
dwd.placeholder.moduleUrl = _ngfactoryModuleUrl(dwd.dir.moduleUrl);
|
dwd.placeholder.reference =
|
||||||
|
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(dwd.dir)), dwd.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return compileResult.statements;
|
return compileResult.statements;
|
||||||
@ -285,9 +263,11 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[]
|
|||||||
|
|
||||||
|
|
||||||
function _resolveStyleStatements(
|
function _resolveStyleStatements(
|
||||||
compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] {
|
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
|
||||||
|
fileSuffix: string): o.Statement[] {
|
||||||
compileResult.dependencies.forEach((dep) => {
|
compileResult.dependencies.forEach((dep) => {
|
||||||
dep.valuePlaceholder.moduleUrl = _stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix);
|
dep.valuePlaceholder.reference = reflector.getStaticSymbol(
|
||||||
|
_stylesModuleUrl(dep.moduleUrl, dep.isShimmed, fileSuffix), dep.name);
|
||||||
});
|
});
|
||||||
return compileResult.statements;
|
return compileResult.statements;
|
||||||
}
|
}
|
||||||
@ -298,16 +278,17 @@ function _ngfactoryModuleUrl(dirUrl: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
|
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
|
||||||
return `${comp.name}NgFactory`;
|
return `${identifierName(comp)}NgFactory`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
|
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
|
||||||
return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`;
|
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _assertComponent(meta: CompileDirectiveMetadata) {
|
function _assertComponent(meta: CompileDirectiveMetadata) {
|
||||||
if (!meta.isComponent) {
|
if (!meta.isComponent) {
|
||||||
throw new Error(`Could not compile '${meta.type.name}' because it is not a component.`);
|
throw new Error(
|
||||||
|
`Could not compile '${identifierName(meta.type)}' because it is not a component.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,31 +306,158 @@ function _splitTypescriptSuffix(path: string): string[] {
|
|||||||
return [path, ''];
|
return [path, ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NgAnalyzedModules {
|
||||||
|
ngModules: CompileNgModuleMetadata[];
|
||||||
|
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
|
||||||
|
files: Array<{
|
||||||
|
srcUrl: string,
|
||||||
|
directives: StaticSymbol[],
|
||||||
|
pipes: StaticSymbol[],
|
||||||
|
ngModules: StaticSymbol[],
|
||||||
|
injectables: StaticSymbol[]
|
||||||
|
}>;
|
||||||
|
symbolsMissingModule?: StaticSymbol[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
|
||||||
|
|
||||||
|
// Returns all the source files and a mapping from modules to directives
|
||||||
|
export function analyzeNgModules(
|
||||||
|
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
||||||
|
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||||
|
const {ngModules, symbolsMissingModule} =
|
||||||
|
_createNgModules(programStaticSymbols, host, metadataResolver);
|
||||||
|
return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyzeAndValidateNgModules(
|
||||||
|
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
||||||
|
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||||
|
const result = analyzeNgModules(programStaticSymbols, host, metadataResolver);
|
||||||
|
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
|
||||||
|
const messages = result.symbolsMissingModule.map(
|
||||||
|
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||||
|
throw new Error(messages.join('\n'));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _analyzeNgModules(
|
||||||
|
programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[],
|
||||||
|
symbolsMissingModule: StaticSymbol[],
|
||||||
|
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||||
|
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
||||||
|
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
|
||||||
|
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||||
|
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
||||||
|
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
||||||
|
const ngPipesByFile = new Map<string, StaticSymbol[]>();
|
||||||
|
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
|
||||||
|
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:
|
||||||
|
// - a map from file to modules `ngModulesByFile`,
|
||||||
|
// - a map from file to directives `ngDirectivesByFile`,
|
||||||
|
// - a map from file to pipes `ngPipesByFile`,
|
||||||
|
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
||||||
|
ngModuleMetas.forEach((ngModuleMeta) => {
|
||||||
|
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
||||||
|
filePaths.add(srcFileUrl);
|
||||||
|
ngModulesByFile.set(
|
||||||
|
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
||||||
|
|
||||||
|
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||||
|
const fileUrl = dirIdentifier.reference.filePath;
|
||||||
|
filePaths.add(fileUrl);
|
||||||
|
ngDirectivesByFile.set(
|
||||||
|
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
|
||||||
|
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
|
||||||
|
});
|
||||||
|
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
|
||||||
|
const fileUrl = pipeIdentifier.reference.filePath;
|
||||||
|
filePaths.add(fileUrl);
|
||||||
|
ngPipesByFile.set(
|
||||||
|
fileUrl, (ngPipesByFile.get(fileUrl) || []).concat(pipeIdentifier.reference));
|
||||||
|
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const files: {
|
||||||
|
srcUrl: string,
|
||||||
|
directives: StaticSymbol[],
|
||||||
|
pipes: StaticSymbol[],
|
||||||
|
ngModules: StaticSymbol[],
|
||||||
|
injectables: StaticSymbol[]
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
filePaths.forEach((srcUrl) => {
|
||||||
|
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||||
|
const pipes = ngPipesByFile.get(srcUrl) || [];
|
||||||
|
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||||
|
const injectables = ngInjectablesByFile.get(srcUrl) || [];
|
||||||
|
files.push({srcUrl, directives, pipes, ngModules, injectables});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// map directive/pipe to module
|
||||||
|
ngModuleByPipeOrDirective,
|
||||||
|
// list modules and directives for every source file
|
||||||
|
files,
|
||||||
|
ngModules: ngModuleMetas, symbolsMissingModule
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractProgramSymbols(
|
||||||
|
staticSymbolResolver: StaticSymbolResolver, files: string[],
|
||||||
|
host: NgAnalyzeModulesHost): StaticSymbol[] {
|
||||||
|
const staticSymbols: StaticSymbol[] = [];
|
||||||
|
files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
|
||||||
|
staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
|
||||||
|
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
|
||||||
|
const symbolMeta = resolvedSymbol.metadata;
|
||||||
|
if (symbolMeta) {
|
||||||
|
if (symbolMeta.__symbolic != 'error') {
|
||||||
|
// Ignore symbols that are only included to record error information.
|
||||||
|
staticSymbols.push(resolvedSymbol.symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return staticSymbols;
|
||||||
|
}
|
||||||
|
|
||||||
// Load the NgModules and check
|
// Load the NgModules and check
|
||||||
// that all directives / pipes that are present in the program
|
// that all directives / pipes that are present in the program
|
||||||
// are also declared by a module.
|
// are also declared by a module.
|
||||||
function _loadNgModules(
|
function _createNgModules(
|
||||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
|
||||||
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
|
metadataResolver: CompileMetadataResolver):
|
||||||
|
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
|
||||||
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
||||||
const programPipesAndDirectives: StaticSymbol[] = [];
|
const programPipesAndDirectives: StaticSymbol[] = [];
|
||||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||||
const loadingPromises: Promise<any>[] = [];
|
|
||||||
|
|
||||||
const addNgModule = (staticSymbol: any) => {
|
const addNgModule = (staticSymbol: any) => {
|
||||||
if (ngModules.has(staticSymbol)) {
|
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
|
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
||||||
if (ngModule) {
|
if (ngModule) {
|
||||||
ngModules.set(ngModule.type.reference, ngModule);
|
ngModules.set(ngModule.type.reference, ngModule);
|
||||||
loadingPromises.push(loading);
|
|
||||||
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
||||||
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
||||||
if (options.transitiveModules) {
|
// For every input module add the list of transitively included modules
|
||||||
// For every input modules add the list of transitively included modules
|
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.reference));
|
||||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return !!ngModule;
|
return !!ngModule;
|
||||||
};
|
};
|
||||||
@ -364,11 +472,5 @@ function _loadNgModules(
|
|||||||
const symbolsMissingModule =
|
const symbolsMissingModule =
|
||||||
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
|
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
|
||||||
|
|
||||||
if (symbolsMissingModule.length) {
|
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
|
||||||
const messages = symbolsMissingModule.map(
|
|
||||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
|
||||||
throw new Error(messages.join('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
|
|
||||||
}
|
}
|
81
modules/@angular/compiler/src/aot/compiler_factory.ts
Normal file
81
modules/@angular/compiler/src/aot/compiler_factory.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
|
import {CompilerConfig} from '../config';
|
||||||
|
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||||
|
import {DirectiveResolver} from '../directive_resolver';
|
||||||
|
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||||
|
import {Lexer} from '../expression_parser/lexer';
|
||||||
|
import {Parser} from '../expression_parser/parser';
|
||||||
|
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
|
||||||
|
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||||
|
import {HtmlParser} from '../ml_parser/html_parser';
|
||||||
|
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||||
|
import {NgModuleResolver} from '../ng_module_resolver';
|
||||||
|
import {TypeScriptEmitter} from '../output/ts_emitter';
|
||||||
|
import {PipeResolver} from '../pipe_resolver';
|
||||||
|
import {Console} from '../private_import_core';
|
||||||
|
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||||
|
import {StyleCompiler} from '../style_compiler';
|
||||||
|
import {TemplateParser} from '../template_parser/template_parser';
|
||||||
|
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
||||||
|
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||||
|
|
||||||
|
import {AotCompiler} from './compiler';
|
||||||
|
import {AotCompilerHost} from './compiler_host';
|
||||||
|
import {AotCompilerOptions} from './compiler_options';
|
||||||
|
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||||
|
import {StaticReflector} from './static_reflector';
|
||||||
|
import {StaticSymbolCache} from './static_symbol';
|
||||||
|
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
|
import {AotSummaryResolver} from './summary_resolver';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AotCompiler based on options and a host.
|
||||||
|
*/
|
||||||
|
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
|
||||||
|
{compiler: AotCompiler, reflector: StaticReflector} {
|
||||||
|
let translations: string = options.translations || '';
|
||||||
|
|
||||||
|
const urlResolver = createOfflineCompileUrlResolver();
|
||||||
|
const symbolCache = new StaticSymbolCache();
|
||||||
|
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
|
||||||
|
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
|
||||||
|
const staticReflector = new StaticReflector(symbolResolver);
|
||||||
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||||
|
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
|
||||||
|
const config = new CompilerConfig({
|
||||||
|
genDebugInfo: options.debug === true,
|
||||||
|
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||||
|
logBindingUpdate: false,
|
||||||
|
useJit: false
|
||||||
|
});
|
||||||
|
const normalizer = new DirectiveNormalizer(
|
||||||
|
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||||
|
const expressionParser = new Parser(new Lexer());
|
||||||
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
const console = new Console();
|
||||||
|
const tmplParser =
|
||||||
|
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||||
|
const resolver = new CompileMetadataResolver(
|
||||||
|
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||||
|
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
||||||
|
staticReflector);
|
||||||
|
// TODO(vicb): do not pass options.i18nFormat here
|
||||||
|
const compiler = new AotCompiler(
|
||||||
|
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||||
|
new ViewCompiler(config, elementSchemaRegistry),
|
||||||
|
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
||||||
|
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
|
||||||
|
options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver);
|
||||||
|
return {compiler, reflector: staticReflector};
|
||||||
|
}
|
26
modules/@angular/compiler/src/aot/compiler_host.ts
Normal file
26
modules/@angular/compiler/src/aot/compiler_host.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ImportResolver} from '../output/path_util';
|
||||||
|
|
||||||
|
import {StaticSymbol} from './static_symbol';
|
||||||
|
import {StaticSymbolResolverHost} from './static_symbol_resolver';
|
||||||
|
import {AotSummaryResolverHost} from './summary_resolver';
|
||||||
|
import {AotSummarySerializerHost} from './summary_serializer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
||||||
|
* services and from underlying file systems.
|
||||||
|
*/
|
||||||
|
export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver,
|
||||||
|
AotSummaryResolverHost, AotSummarySerializerHost {
|
||||||
|
/**
|
||||||
|
* Loads a resource (e.g. html / css)
|
||||||
|
*/
|
||||||
|
loadResource(path: string): Promise<string>;
|
||||||
|
}
|
14
modules/@angular/compiler/src/aot/compiler_options.ts
Normal file
14
modules/@angular/compiler/src/aot/compiler_options.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AotCompilerOptions {
|
||||||
|
debug?: boolean;
|
||||||
|
locale?: string;
|
||||||
|
i18nFormat?: string;
|
||||||
|
translations?: string;
|
||||||
|
}
|
@ -6,8 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
export class GeneratedFile {
|
||||||
* @module
|
constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {}
|
||||||
* @description
|
}
|
||||||
* This module provides a set of common Pipes.
|
|
||||||
*/
|
|
@ -6,8 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from './private_import_core';
|
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
|
||||||
import {StaticReflector} from './static_reflector';
|
import {StaticReflector} from './static_reflector';
|
||||||
|
import {StaticSymbol} from './static_symbol';
|
||||||
|
|
||||||
export class StaticAndDynamicReflectionCapabilities {
|
export class StaticAndDynamicReflectionCapabilities {
|
||||||
static install(staticDelegate: StaticReflector) {
|
static install(staticDelegate: StaticReflector) {
|
||||||
@ -42,7 +43,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, runtime);
|
return this.staticDelegate.resolveIdentifier(name, moduleUrl);
|
||||||
}
|
}
|
||||||
resolveEnum(enumIdentifier: any, name: string): any {
|
resolveEnum(enumIdentifier: any, name: string): any {
|
||||||
if (isStaticType(enumIdentifier)) {
|
if (isStaticType(enumIdentifier)) {
|
668
modules/@angular/compiler/src/aot/static_reflector.ts
Normal file
668
modules/@angular/compiler/src/aot/static_reflector.ts
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
/**
|
||||||
|
* @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 {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||||
|
|
||||||
|
import {ReflectorReader} from '../private_import_core';
|
||||||
|
|
||||||
|
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||||
|
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
|
|
||||||
|
const ANGULAR_IMPORT_LOCATIONS = {
|
||||||
|
coreDecorators: '@angular/core/src/metadata',
|
||||||
|
diDecorators: '@angular/core/src/di/metadata',
|
||||||
|
diMetadata: '@angular/core/src/di/metadata',
|
||||||
|
diOpaqueToken: '@angular/core/src/di/opaque_token',
|
||||||
|
animationMetadata: '@angular/core/src/animation/metadata',
|
||||||
|
provider: '@angular/core/src/di/provider'
|
||||||
|
};
|
||||||
|
|
||||||
|
const HIDDEN_KEY = /^\$.*\$$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A static reflector implements enough of the Reflector API that is necessary to compile
|
||||||
|
* templates statically.
|
||||||
|
*/
|
||||||
|
export class StaticReflector implements ReflectorReader {
|
||||||
|
private annotationCache = new Map<StaticSymbol, any[]>();
|
||||||
|
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||||
|
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||||
|
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||||
|
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||||
|
private opaqueToken: StaticSymbol;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private symbolResolver: StaticSymbolResolver,
|
||||||
|
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||||
|
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
|
||||||
|
private errorRecorder?: (error: any, fileName: string) => void) {
|
||||||
|
this.initializeConversionMap();
|
||||||
|
knownMetadataClasses.forEach(
|
||||||
|
(kc) => this._registerDecoratorOrConstructor(
|
||||||
|
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
|
||||||
|
knownMetadataFunctions.forEach(
|
||||||
|
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
importUri(typeOrFunc: StaticSymbol): string {
|
||||||
|
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
|
||||||
|
return staticSymbol ? staticSymbol.filePath : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
|
||||||
|
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 {
|
||||||
|
const staticSymbol: StaticSymbol = enumIdentifier;
|
||||||
|
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public annotations(type: StaticSymbol): any[] {
|
||||||
|
let annotations = this.annotationCache.get(type);
|
||||||
|
if (!annotations) {
|
||||||
|
annotations = [];
|
||||||
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
if (classMetadata['extends']) {
|
||||||
|
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
|
||||||
|
annotations.push(...parentAnnotations);
|
||||||
|
}
|
||||||
|
if (classMetadata['decorators']) {
|
||||||
|
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
|
||||||
|
annotations.push(...ownAnnotations);
|
||||||
|
}
|
||||||
|
this.annotationCache.set(type, annotations.filter(ann => !!ann));
|
||||||
|
}
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
|
||||||
|
let propMetadata = this.propertyCache.get(type);
|
||||||
|
if (!propMetadata) {
|
||||||
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
propMetadata = {};
|
||||||
|
if (classMetadata['extends']) {
|
||||||
|
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
|
||||||
|
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||||
|
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = classMetadata['members'] || {};
|
||||||
|
Object.keys(members).forEach((propName) => {
|
||||||
|
const propData = members[propName];
|
||||||
|
const prop = (<any[]>propData)
|
||||||
|
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
|
||||||
|
const decorators: any[] = [];
|
||||||
|
if (propMetadata[propName]) {
|
||||||
|
decorators.push(...propMetadata[propName]);
|
||||||
|
}
|
||||||
|
propMetadata[propName] = decorators;
|
||||||
|
if (prop && prop['decorators']) {
|
||||||
|
decorators.push(...this.simplify(type, prop['decorators']));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.propertyCache.set(type, propMetadata);
|
||||||
|
}
|
||||||
|
return propMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parameters(type: StaticSymbol): any[] {
|
||||||
|
if (!(type instanceof StaticSymbol)) {
|
||||||
|
this.reportError(
|
||||||
|
new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`),
|
||||||
|
type);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let parameters = this.parameterCache.get(type);
|
||||||
|
if (!parameters) {
|
||||||
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
const members = classMetadata ? classMetadata['members'] : null;
|
||||||
|
const ctorData = members ? members['__ctor__'] : null;
|
||||||
|
if (ctorData) {
|
||||||
|
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
|
||||||
|
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
|
||||||
|
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
|
||||||
|
parameters = [];
|
||||||
|
parameterTypes.forEach((paramType, index) => {
|
||||||
|
const nestedResult: any[] = [];
|
||||||
|
if (paramType) {
|
||||||
|
nestedResult.push(paramType);
|
||||||
|
}
|
||||||
|
const decorators = parameterDecorators ? parameterDecorators[index] : null;
|
||||||
|
if (decorators) {
|
||||||
|
nestedResult.push(...decorators);
|
||||||
|
}
|
||||||
|
parameters.push(nestedResult);
|
||||||
|
});
|
||||||
|
} else if (classMetadata['extends']) {
|
||||||
|
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
|
||||||
|
}
|
||||||
|
if (!parameters) {
|
||||||
|
parameters = [];
|
||||||
|
}
|
||||||
|
this.parameterCache.set(type, parameters);
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _methodNames(type: any): {[key: string]: boolean} {
|
||||||
|
let methodNames = this.methodCache.get(type);
|
||||||
|
if (!methodNames) {
|
||||||
|
const classMetadata = this.getTypeMetadata(type);
|
||||||
|
methodNames = {};
|
||||||
|
if (classMetadata['extends']) {
|
||||||
|
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
|
||||||
|
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||||
|
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = classMetadata['members'] || {};
|
||||||
|
Object.keys(members).forEach((propName) => {
|
||||||
|
const propData = members[propName];
|
||||||
|
const isMethod = (<any[]>propData).some(a => a['__symbolic'] == 'method');
|
||||||
|
methodNames[propName] = methodNames[propName] || isMethod;
|
||||||
|
});
|
||||||
|
this.methodCache.set(type, methodNames);
|
||||||
|
}
|
||||||
|
return methodNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||||
|
if (!(type instanceof StaticSymbol)) {
|
||||||
|
this.reportError(
|
||||||
|
new Error(
|
||||||
|
`hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`),
|
||||||
|
type);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return !!this._methodNames(type)[lcProperty];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||||
|
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _registerFunction(type: StaticSymbol, fn: any): void {
|
||||||
|
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeConversionMap(): void {
|
||||||
|
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
|
||||||
|
ANGULAR_IMPORT_LOCATIONS;
|
||||||
|
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
||||||
|
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'Directive'), Directive);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'Component'), Component);
|
||||||
|
this._registerDecoratorOrConstructor(
|
||||||
|
this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
||||||
|
|
||||||
|
// Note: Some metadata classes can be used directly with Provider.deps.
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
||||||
|
this._registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
|
||||||
|
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
|
||||||
|
this._registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
|
||||||
|
* All types passed to the StaticResolver should be pseudo-types returned by this method.
|
||||||
|
*
|
||||||
|
* @param declarationFile the absolute path of the file where the symbol is declared
|
||||||
|
* @param name the name of the type.
|
||||||
|
*/
|
||||||
|
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
||||||
|
return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||||
|
if (this.errorRecorder) {
|
||||||
|
this.errorRecorder(error, (context && context.filePath) || path);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public simplify(context: StaticSymbol, value: any): any {
|
||||||
|
const self = this;
|
||||||
|
let scope = BindingScope.empty;
|
||||||
|
const calling = new Map<StaticSymbol, boolean>();
|
||||||
|
|
||||||
|
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
|
||||||
|
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
||||||
|
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
|
||||||
|
return resolvedSymbol ? resolvedSymbol.metadata : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
|
||||||
|
if (targetFunction && targetFunction['__symbolic'] == 'function') {
|
||||||
|
if (calling.get(functionSymbol)) {
|
||||||
|
throw new Error('Recursion not supported');
|
||||||
|
}
|
||||||
|
calling.set(functionSymbol, true);
|
||||||
|
try {
|
||||||
|
const value = targetFunction['value'];
|
||||||
|
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||||
|
const parameters: string[] = targetFunction['parameters'];
|
||||||
|
const defaults: any[] = targetFunction.defaults;
|
||||||
|
if (defaults && defaults.length > args.length) {
|
||||||
|
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||||
|
}
|
||||||
|
const functionScope = BindingScope.build();
|
||||||
|
for (let i = 0; i < parameters.length; i++) {
|
||||||
|
functionScope.define(parameters[i], args[i]);
|
||||||
|
}
|
||||||
|
const oldScope = scope;
|
||||||
|
let result: any;
|
||||||
|
try {
|
||||||
|
scope = functionScope.done();
|
||||||
|
result = simplifyInContext(functionSymbol, value, depth + 1);
|
||||||
|
} finally {
|
||||||
|
scope = oldScope;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
calling.delete(functionSymbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depth === 0) {
|
||||||
|
// If depth is 0 we are evaluating the top level expression that is describing element
|
||||||
|
// decorator. In this case, it is a decorator we don't understand, such as a custom
|
||||||
|
// non-angular decorator, and we should just ignore it.
|
||||||
|
return {__symbolic: 'ignore'};
|
||||||
|
}
|
||||||
|
return simplify(
|
||||||
|
{__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
|
||||||
|
}
|
||||||
|
|
||||||
|
function simplify(expression: any): any {
|
||||||
|
if (isPrimitive(expression)) {
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
if (expression instanceof Array) {
|
||||||
|
const result: any[] = [];
|
||||||
|
for (const item of (<any>expression)) {
|
||||||
|
// Check for a spread expression
|
||||||
|
if (item && item.__symbolic === 'spread') {
|
||||||
|
const spreadArray = simplify(item.expression);
|
||||||
|
if (Array.isArray(spreadArray)) {
|
||||||
|
for (const spreadItem of spreadArray) {
|
||||||
|
result.push(spreadItem);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const value = simplify(item);
|
||||||
|
if (shouldIgnore(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (expression instanceof StaticSymbol) {
|
||||||
|
// Stop simplification at builtin symbols
|
||||||
|
if (expression === self.opaqueToken || self.conversionMap.has(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['__symbolic']) {
|
||||||
|
let staticSymbol: StaticSymbol;
|
||||||
|
switch (expression['__symbolic']) {
|
||||||
|
case 'binop':
|
||||||
|
let left = simplify(expression['left']);
|
||||||
|
if (shouldIgnore(left)) return left;
|
||||||
|
let right = simplify(expression['right']);
|
||||||
|
if (shouldIgnore(right)) return right;
|
||||||
|
switch (expression['operator']) {
|
||||||
|
case '&&':
|
||||||
|
return left && right;
|
||||||
|
case '||':
|
||||||
|
return left || right;
|
||||||
|
case '|':
|
||||||
|
return left | right;
|
||||||
|
case '^':
|
||||||
|
return left ^ right;
|
||||||
|
case '&':
|
||||||
|
return left & right;
|
||||||
|
case '==':
|
||||||
|
return left == right;
|
||||||
|
case '!=':
|
||||||
|
return left != right;
|
||||||
|
case '===':
|
||||||
|
return left === right;
|
||||||
|
case '!==':
|
||||||
|
return left !== right;
|
||||||
|
case '<':
|
||||||
|
return left < right;
|
||||||
|
case '>':
|
||||||
|
return left > right;
|
||||||
|
case '<=':
|
||||||
|
return left <= right;
|
||||||
|
case '>=':
|
||||||
|
return left >= right;
|
||||||
|
case '<<':
|
||||||
|
return left << right;
|
||||||
|
case '>>':
|
||||||
|
return left >> right;
|
||||||
|
case '+':
|
||||||
|
return left + right;
|
||||||
|
case '-':
|
||||||
|
return left - right;
|
||||||
|
case '*':
|
||||||
|
return left * right;
|
||||||
|
case '/':
|
||||||
|
return left / right;
|
||||||
|
case '%':
|
||||||
|
return left % right;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'if':
|
||||||
|
let condition = simplify(expression['condition']);
|
||||||
|
return condition ? simplify(expression['thenExpression']) :
|
||||||
|
simplify(expression['elseExpression']);
|
||||||
|
case 'pre':
|
||||||
|
let operand = simplify(expression['operand']);
|
||||||
|
if (shouldIgnore(operand)) return operand;
|
||||||
|
switch (expression['operator']) {
|
||||||
|
case '+':
|
||||||
|
return operand;
|
||||||
|
case '-':
|
||||||
|
return -operand;
|
||||||
|
case '!':
|
||||||
|
return !operand;
|
||||||
|
case '~':
|
||||||
|
return ~operand;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'index':
|
||||||
|
let indexTarget = simplify(expression['expression']);
|
||||||
|
let index = simplify(expression['index']);
|
||||||
|
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
||||||
|
return null;
|
||||||
|
case 'select':
|
||||||
|
const member = expression['member'];
|
||||||
|
let selectContext = context;
|
||||||
|
let selectTarget = simplify(expression['expression']);
|
||||||
|
if (selectTarget instanceof StaticSymbol) {
|
||||||
|
const members = selectTarget.members.concat(member);
|
||||||
|
selectContext =
|
||||||
|
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||||
|
const declarationValue = resolveReferenceValue(selectContext);
|
||||||
|
if (declarationValue) {
|
||||||
|
return simplifyInContext(selectContext, declarationValue, depth + 1);
|
||||||
|
} else {
|
||||||
|
return selectContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectTarget && isPrimitive(member))
|
||||||
|
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
|
||||||
|
return null;
|
||||||
|
case 'reference':
|
||||||
|
// Note: This only has to deal with variable references,
|
||||||
|
// as symbol references have been converted into StaticSymbols already
|
||||||
|
// in the StaticSymbolResolver!
|
||||||
|
const name: string = expression['name'];
|
||||||
|
const localValue = scope.resolve(name);
|
||||||
|
if (localValue != BindingScope.missing) {
|
||||||
|
return localValue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'class':
|
||||||
|
return context;
|
||||||
|
case 'function':
|
||||||
|
return context;
|
||||||
|
case 'new':
|
||||||
|
case 'call':
|
||||||
|
// Determine if the function is a built-in conversion
|
||||||
|
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
|
||||||
|
if (staticSymbol instanceof StaticSymbol) {
|
||||||
|
if (staticSymbol === self.opaqueToken) {
|
||||||
|
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
|
||||||
|
// but rather return the symbol to which the OpaqueToken is assigned to.
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
const argExpressions: any[] = expression['arguments'] || [];
|
||||||
|
const args =
|
||||||
|
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||||
|
let converter = self.conversionMap.get(staticSymbol);
|
||||||
|
if (converter) {
|
||||||
|
return converter(context, args);
|
||||||
|
} else {
|
||||||
|
// Determine if the function is one we can simplify.
|
||||||
|
const targetFunction = resolveReferenceValue(staticSymbol);
|
||||||
|
return simplifyCall(staticSymbol, targetFunction, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
let message = produceErrorMessage(expression);
|
||||||
|
if (expression['line']) {
|
||||||
|
message =
|
||||||
|
`${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`;
|
||||||
|
throw positionalError(
|
||||||
|
message, context.filePath, expression['line'], expression['character']);
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return mapStringMap(expression, (value, name) => simplify(value));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return simplify(value);
|
||||||
|
} catch (e) {
|
||||||
|
const members = context.members.length ? `.${context.members.join('.')}` : '';
|
||||||
|
const message =
|
||||||
|
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
|
||||||
|
if (e.fileName) {
|
||||||
|
throw positionalError(message, e.fileName, e.line, e.column);
|
||||||
|
}
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordedSimplifyInContext = (context: StaticSymbol, value: any, depth: number) => {
|
||||||
|
try {
|
||||||
|
return simplifyInContext(context, value, depth);
|
||||||
|
} catch (e) {
|
||||||
|
this.reportError(e, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = this.errorRecorder ? recordedSimplifyInContext(context, value, 0) :
|
||||||
|
simplifyInContext(context, value, 0);
|
||||||
|
if (shouldIgnore(result)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
|
||||||
|
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
|
||||||
|
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
|
||||||
|
{__symbolic: 'class'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandedMessage(error: any): string {
|
||||||
|
switch (error.message) {
|
||||||
|
case 'Reference to non-exported class':
|
||||||
|
if (error.context && error.context.className) {
|
||||||
|
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Variable not initialized':
|
||||||
|
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
|
||||||
|
case 'Destructuring not supported':
|
||||||
|
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
|
||||||
|
case 'Could not resolve type':
|
||||||
|
if (error.context && error.context.typeName) {
|
||||||
|
return `Could not resolve type ${error.context.typeName}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Function call not supported':
|
||||||
|
let prefix =
|
||||||
|
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
|
||||||
|
return prefix +
|
||||||
|
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
|
||||||
|
case 'Reference to a local symbol':
|
||||||
|
if (error.context && error.context.name) {
|
||||||
|
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function produceErrorMessage(error: any): string {
|
||||||
|
return `Error encountered resolving symbol values statically. ${expandedMessage(error)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
|
||||||
|
{[key: string]: any} {
|
||||||
|
if (!input) return {};
|
||||||
|
const result: {[key: string]: any} = {};
|
||||||
|
Object.keys(input).forEach((key) => {
|
||||||
|
const value = transform(input[key], key);
|
||||||
|
if (!shouldIgnore(value)) {
|
||||||
|
if (HIDDEN_KEY.test(key)) {
|
||||||
|
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
|
||||||
|
} else {
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrimitive(o: any): boolean {
|
||||||
|
return o === null || (typeof o !== 'function' && typeof o !== 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BindingScopeBuilder {
|
||||||
|
define(name: string, value: any): BindingScopeBuilder;
|
||||||
|
done(): BindingScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BindingScope {
|
||||||
|
abstract resolve(name: string): any;
|
||||||
|
public static missing = {};
|
||||||
|
public static empty: BindingScope = {resolve: name => BindingScope.missing};
|
||||||
|
|
||||||
|
public static build(): BindingScopeBuilder {
|
||||||
|
const current = new Map<string, any>();
|
||||||
|
return {
|
||||||
|
define: function(name, value) {
|
||||||
|
current.set(name, value);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
done: function() {
|
||||||
|
return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PopulatedScope extends BindingScope {
|
||||||
|
constructor(private bindings: Map<string, any>) { super(); }
|
||||||
|
|
||||||
|
resolve(name: string): any {
|
||||||
|
return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIgnore(value: any): boolean {
|
||||||
|
return value && value.__symbolic == 'ignore';
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionalError(message: string, fileName: string, line: number, column: number): Error {
|
||||||
|
const result = new Error(message);
|
||||||
|
(result as any).fileName = fileName;
|
||||||
|
(result as any).line = line;
|
||||||
|
(result as any).column = column;
|
||||||
|
return result;
|
||||||
|
}
|
36
modules/@angular/compiler/src/aot/static_symbol.ts
Normal file
36
modules/@angular/compiler/src/aot/static_symbol.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A token representing the a reference to a static type.
|
||||||
|
*
|
||||||
|
* This token is unique for a filePath and name and can be used as a hash table key.
|
||||||
|
*/
|
||||||
|
export class StaticSymbol {
|
||||||
|
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
289
modules/@angular/compiler/src/aot/static_symbol_resolver.ts
Normal file
289
modules/@angular/compiler/src/aot/static_symbol_resolver.ts
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
modules/@angular/compiler/src/aot/summary_resolver.ts
Normal file
77
modules/@angular/compiler/src/aot/summary_resolver.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* @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 {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
||||||
|
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||||
|
|
||||||
|
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||||
|
import {ResolvedStaticSymbol} from './static_symbol_resolver';
|
||||||
|
import {deserializeSummaries, summaryFileName} from './summary_serializer';
|
||||||
|
|
||||||
|
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
|
|
||||||
|
export interface AotSummaryResolverHost {
|
||||||
|
/**
|
||||||
|
* Loads an NgModule/Directive/Pipe summary file
|
||||||
|
*/
|
||||||
|
loadSummary(filePath: string): string /*|null*/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a file is a source file or not.
|
||||||
|
*/
|
||||||
|
isSourceFile(sourceFilePath: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||||
|
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
|
||||||
|
private loadedFilePaths = new Set<string>();
|
||||||
|
|
||||||
|
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> {
|
||||||
|
this._assertNoMembers(staticSymbol);
|
||||||
|
let summary = this.summaryCache.get(staticSymbol);
|
||||||
|
if (!summary) {
|
||||||
|
this._loadSummaryFile(staticSymbol.filePath);
|
||||||
|
summary = this.summaryCache.get(staticSymbol);
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSymbolsOf(filePath: string): StaticSymbol[] {
|
||||||
|
this._loadSummaryFile(filePath);
|
||||||
|
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _loadSummaryFile(filePath: string) {
|
||||||
|
if (this.loadedFilePaths.has(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadedFilePaths.add(filePath);
|
||||||
|
if (!this.host.isSourceFile(filePath)) {
|
||||||
|
const summaryFilePath = summaryFileName(filePath);
|
||||||
|
let json: string;
|
||||||
|
try {
|
||||||
|
json = this.host.loadSummary(summaryFilePath);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error loading summary file ${summaryFilePath}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (json) {
|
||||||
|
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
|
||||||
|
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
modules/@angular/compiler/src/aot/summary_serializer.ts
Normal file
183
modules/@angular/compiler/src/aot/summary_serializer.ts
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
|
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||||
|
import {ValueTransformer, visitValue} from '../util';
|
||||||
|
|
||||||
|
import {GeneratedFile} from './generated_file';
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
|
import {StaticSymbol} from './aot/static_symbol';
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from './facade/collection';
|
||||||
import {isPresent} from './facade/lang';
|
import {isPresent, stringify} from './facade/lang';
|
||||||
import {LifecycleHooks} from './private_import_core';
|
import {LifecycleHooks, reflector} from './private_import_core';
|
||||||
import {CssSelector} from './selector';
|
import {CssSelector} from './selector';
|
||||||
import {sanitizeIdentifier, splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
|
||||||
function unimplemented(): any {
|
function unimplemented(): any {
|
||||||
throw new Error('unimplemented');
|
throw new Error('unimplemented');
|
||||||
@ -24,10 +25,6 @@ function unimplemented(): any {
|
|||||||
// group 3: "@trigger" from "@trigger"
|
// group 3: "@trigger" from "@trigger"
|
||||||
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
||||||
|
|
||||||
export abstract class CompileMetadataWithIdentifier {
|
|
||||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompileAnimationEntryMetadata {
|
export class CompileAnimationEntryMetadata {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
|
public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
|
||||||
@ -78,24 +75,50 @@ export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMeta
|
|||||||
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
|
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
|
|
||||||
reference: any;
|
|
||||||
name: string;
|
|
||||||
prefix: string;
|
|
||||||
moduleUrl: string;
|
|
||||||
value: any;
|
|
||||||
|
|
||||||
constructor(
|
function _sanitizeIdentifier(name: string): string {
|
||||||
{reference, name, moduleUrl, prefix, value}:
|
return name.replace(/\W/g, '_');
|
||||||
{reference?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
|
}
|
||||||
this.reference = reference;
|
|
||||||
this.name = name;
|
let _anonymousTypeIndex = 0;
|
||||||
this.prefix = prefix;
|
|
||||||
this.moduleUrl = moduleUrl;
|
export function identifierName(compileIdentifier: CompileIdentifierMetadata): string {
|
||||||
this.value = value;
|
if (!compileIdentifier || !compileIdentifier.reference) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
const ref = compileIdentifier.reference;
|
||||||
|
if (ref instanceof StaticSymbol) {
|
||||||
|
return ref.name;
|
||||||
|
}
|
||||||
|
if (ref['__anonymousType']) {
|
||||||
|
return ref['__anonymousType'];
|
||||||
|
}
|
||||||
|
let identifier = stringify(ref);
|
||||||
|
if (identifier.indexOf('(') >= 0) {
|
||||||
|
// case: anonymous functions!
|
||||||
|
identifier = `anonymous_${_anonymousTypeIndex++}`;
|
||||||
|
ref['__anonymousType'] = identifier;
|
||||||
|
} else {
|
||||||
|
identifier = _sanitizeIdentifier(identifier);
|
||||||
|
}
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
get identifier(): CompileIdentifierMetadata { return this; }
|
export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata): string {
|
||||||
|
const ref = compileIdentifier.reference;
|
||||||
|
if (ref instanceof StaticSymbol) {
|
||||||
|
return ref.filePath;
|
||||||
|
}
|
||||||
|
return reflector.importUri(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompileIdentifierMetadata { reference: any; }
|
||||||
|
|
||||||
|
export enum CompileSummaryKind {
|
||||||
|
Pipe,
|
||||||
|
Directive,
|
||||||
|
NgModule,
|
||||||
|
Injectable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,158 +126,70 @@ export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier
|
|||||||
* 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 CompileSummary {
|
export interface CompileTypeSummary {
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
summaryKind: CompileSummaryKind;
|
||||||
|
type: CompileTypeMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileDiDependencyMetadata {
|
export interface CompileDiDependencyMetadata {
|
||||||
isAttribute: boolean;
|
isAttribute?: boolean;
|
||||||
isSelf: boolean;
|
isSelf?: boolean;
|
||||||
isHost: boolean;
|
isHost?: boolean;
|
||||||
isSkipSelf: boolean;
|
isSkipSelf?: boolean;
|
||||||
isOptional: boolean;
|
isOptional?: boolean;
|
||||||
isValue: boolean;
|
isValue?: boolean;
|
||||||
|
token?: CompileTokenMetadata;
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompileProviderMetadata {
|
||||||
token: CompileTokenMetadata;
|
token: CompileTokenMetadata;
|
||||||
value: any;
|
useClass?: CompileTypeMetadata;
|
||||||
|
useValue?: any;
|
||||||
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, token, value}: {
|
useExisting?: CompileTokenMetadata;
|
||||||
isAttribute?: boolean,
|
useFactory?: CompileFactoryMetadata;
|
||||||
isSelf?: boolean,
|
deps?: CompileDiDependencyMetadata[];
|
||||||
isHost?: boolean,
|
multi?: boolean;
|
||||||
isSkipSelf?: boolean,
|
|
||||||
isOptional?: boolean,
|
|
||||||
isValue?: boolean,
|
|
||||||
query?: CompileQueryMetadata,
|
|
||||||
viewQuery?: CompileQueryMetadata,
|
|
||||||
token?: CompileTokenMetadata,
|
|
||||||
value?: any
|
|
||||||
} = {}) {
|
|
||||||
this.isAttribute = !!isAttribute;
|
|
||||||
this.isSelf = !!isSelf;
|
|
||||||
this.isHost = !!isHost;
|
|
||||||
this.isSkipSelf = !!isSkipSelf;
|
|
||||||
this.isOptional = !!isOptional;
|
|
||||||
this.isValue = !!isValue;
|
|
||||||
this.token = token;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileProviderMetadata {
|
export interface CompileFactoryMetadata extends CompileIdentifierMetadata {
|
||||||
token: CompileTokenMetadata;
|
|
||||||
useClass: CompileTypeMetadata;
|
|
||||||
useValue: any;
|
|
||||||
useExisting: CompileTokenMetadata;
|
|
||||||
useFactory: CompileFactoryMetadata;
|
|
||||||
deps: CompileDiDependencyMetadata[];
|
|
||||||
multi: boolean;
|
|
||||||
|
|
||||||
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
|
|
||||||
token?: CompileTokenMetadata,
|
|
||||||
useClass?: CompileTypeMetadata,
|
|
||||||
useValue?: any,
|
|
||||||
useExisting?: CompileTokenMetadata,
|
|
||||||
useFactory?: CompileFactoryMetadata,
|
|
||||||
deps?: CompileDiDependencyMetadata[],
|
|
||||||
multi?: boolean
|
|
||||||
}) {
|
|
||||||
this.token = token;
|
|
||||||
this.useClass = useClass;
|
|
||||||
this.useValue = useValue;
|
|
||||||
this.useExisting = useExisting;
|
|
||||||
this.useFactory = useFactory;
|
|
||||||
this.deps = deps || null;
|
|
||||||
this.multi = !!multi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompileFactoryMetadata extends CompileIdentifierMetadata {
|
|
||||||
diDeps: CompileDiDependencyMetadata[];
|
diDeps: CompileDiDependencyMetadata[];
|
||||||
|
reference: any;
|
||||||
|
}
|
||||||
|
|
||||||
constructor({reference, name, moduleUrl, prefix, diDeps, value}: {
|
export function tokenName(token: CompileTokenMetadata) {
|
||||||
reference?: Function,
|
return isPresent(token.value) ? _sanitizeIdentifier(token.value) :
|
||||||
name?: string,
|
identifierName(token.identifier);
|
||||||
prefix?: string,
|
}
|
||||||
moduleUrl?: string,
|
|
||||||
value?: boolean,
|
export function tokenReference(token: CompileTokenMetadata) {
|
||||||
diDeps?: CompileDiDependencyMetadata[]
|
if (isPresent(token.identifier)) {
|
||||||
}) {
|
return token.identifier.reference;
|
||||||
super({reference: reference, name: name, prefix: prefix, moduleUrl: moduleUrl, value: value});
|
} else {
|
||||||
this.diDeps = _normalizeArray(diDeps);
|
return token.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
|
export interface CompileTokenMetadata {
|
||||||
value: any;
|
value?: any;
|
||||||
identifier: CompileIdentifierMetadata;
|
identifier?: CompileIdentifierMetadata|CompileTypeMetadata;
|
||||||
identifierIsInstance: boolean;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
{value, identifier, identifierIsInstance}:
|
|
||||||
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
|
|
||||||
this.value = value;
|
|
||||||
this.identifier = identifier;
|
|
||||||
this.identifierIsInstance = !!identifierIsInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
get reference(): any {
|
|
||||||
if (isPresent(this.identifier)) {
|
|
||||||
return this.identifier.reference;
|
|
||||||
} else {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string {
|
|
||||||
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata regarding compilation of a type.
|
* Metadata regarding compilation of a type.
|
||||||
*/
|
*/
|
||||||
export class CompileTypeMetadata extends CompileIdentifierMetadata {
|
export interface CompileTypeMetadata extends CompileIdentifierMetadata {
|
||||||
isHost: boolean;
|
|
||||||
diDeps: CompileDiDependencyMetadata[];
|
diDeps: CompileDiDependencyMetadata[];
|
||||||
lifecycleHooks: LifecycleHooks[];
|
lifecycleHooks: LifecycleHooks[];
|
||||||
|
reference: any;
|
||||||
constructor({reference, name, moduleUrl, prefix, isHost, value, diDeps, lifecycleHooks}: {
|
|
||||||
reference?: Type<any>,
|
|
||||||
name?: string,
|
|
||||||
moduleUrl?: string,
|
|
||||||
prefix?: string,
|
|
||||||
isHost?: boolean,
|
|
||||||
value?: any,
|
|
||||||
diDeps?: CompileDiDependencyMetadata[],
|
|
||||||
lifecycleHooks?: LifecycleHooks[];
|
|
||||||
} = {}) {
|
|
||||||
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
|
|
||||||
this.isHost = !!isHost;
|
|
||||||
this.diDeps = _normalizeArray(diDeps);
|
|
||||||
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompileQueryMetadata {
|
export interface CompileQueryMetadata {
|
||||||
selectors: Array<CompileTokenMetadata>;
|
selectors: Array<CompileTokenMetadata>;
|
||||||
descendants: boolean;
|
descendants: boolean;
|
||||||
first: boolean;
|
first: boolean;
|
||||||
propertyName: string;
|
propertyName: string;
|
||||||
read: CompileTokenMetadata;
|
read: CompileTokenMetadata;
|
||||||
|
|
||||||
constructor({selectors, descendants, first, propertyName, read}: {
|
|
||||||
selectors?: Array<CompileTokenMetadata>,
|
|
||||||
descendants?: boolean,
|
|
||||||
first?: boolean,
|
|
||||||
propertyName?: string,
|
|
||||||
read?: CompileTokenMetadata
|
|
||||||
} = {}) {
|
|
||||||
this.selectors = selectors;
|
|
||||||
this.descendants = !!descendants;
|
|
||||||
this.first = !!first;
|
|
||||||
this.propertyName = propertyName;
|
|
||||||
this.read = read;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -276,8 +211,7 @@ export class CompileStylesheetMetadata {
|
|||||||
/**
|
/**
|
||||||
* Summary Metadata regarding compilation of a template.
|
* Summary Metadata regarding compilation of a template.
|
||||||
*/
|
*/
|
||||||
export interface CompileTemplateSummary extends CompileSummary {
|
export interface CompileTemplateSummary {
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
|
||||||
animations: string[];
|
animations: string[];
|
||||||
ngContentSelectors: string[];
|
ngContentSelectors: string[];
|
||||||
encapsulation: ViewEncapsulation;
|
encapsulation: ViewEncapsulation;
|
||||||
@ -325,7 +259,6 @@ export class CompileTemplateMetadata {
|
|||||||
|
|
||||||
toSummary(): CompileTemplateSummary {
|
toSummary(): CompileTemplateSummary {
|
||||||
return {
|
return {
|
||||||
isSummary: true,
|
|
||||||
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
|
||||||
@ -333,8 +266,9 @@ export class CompileTemplateMetadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompileDirectiveSummary extends CompileSummary {
|
// Note: This should only use interfaces as nested data types
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
// as we need to be able to serialize this from/to JSON!
|
||||||
|
export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
isComponent: boolean;
|
isComponent: boolean;
|
||||||
selector: string;
|
selector: string;
|
||||||
@ -355,10 +289,11 @@ export interface CompileDirectiveSummary extends CompileSummary {
|
|||||||
/**
|
/**
|
||||||
* Metadata regarding compilation of a directive.
|
* Metadata regarding compilation of a directive.
|
||||||
*/
|
*/
|
||||||
export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
export class CompileDirectiveMetadata {
|
||||||
static create(
|
static create(
|
||||||
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers,
|
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
|
||||||
viewProviders, queries, viewQueries, entryComponents, template}: {
|
providers, viewProviders, queries, viewQueries, entryComponents, template}: {
|
||||||
|
isHost?: boolean,
|
||||||
type?: CompileTypeMetadata,
|
type?: CompileTypeMetadata,
|
||||||
isComponent?: boolean,
|
isComponent?: boolean,
|
||||||
selector?: string,
|
selector?: string,
|
||||||
@ -367,10 +302,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
inputs?: string[],
|
inputs?: string[],
|
||||||
outputs?: string[],
|
outputs?: string[],
|
||||||
host?: {[key: string]: string},
|
host?: {[key: string]: string},
|
||||||
providers?:
|
providers?: CompileProviderMetadata[],
|
||||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
viewProviders?: CompileProviderMetadata[],
|
||||||
viewProviders?:
|
|
||||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
|
||||||
queries?: CompileQueryMetadata[],
|
queries?: CompileQueryMetadata[],
|
||||||
viewQueries?: CompileQueryMetadata[],
|
viewQueries?: CompileQueryMetadata[],
|
||||||
entryComponents?: CompileIdentifierMetadata[],
|
entryComponents?: CompileIdentifierMetadata[],
|
||||||
@ -412,6 +345,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new CompileDirectiveMetadata({
|
return new CompileDirectiveMetadata({
|
||||||
|
isHost,
|
||||||
type,
|
type,
|
||||||
isComponent: !!isComponent, selector, exportAs, changeDetection,
|
isComponent: !!isComponent, selector, exportAs, changeDetection,
|
||||||
inputs: inputsMap,
|
inputs: inputsMap,
|
||||||
@ -427,6 +361,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
template,
|
template,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
isHost: boolean;
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
isComponent: boolean;
|
isComponent: boolean;
|
||||||
selector: string;
|
selector: string;
|
||||||
@ -446,9 +381,10 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
template: CompileTemplateMetadata;
|
template: CompileTemplateMetadata;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
|
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
||||||
hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries,
|
hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries,
|
||||||
entryComponents, template}: {
|
viewQueries, entryComponents, template}: {
|
||||||
|
isHost?: boolean,
|
||||||
type?: CompileTypeMetadata,
|
type?: CompileTypeMetadata,
|
||||||
isComponent?: boolean,
|
isComponent?: boolean,
|
||||||
selector?: string,
|
selector?: string,
|
||||||
@ -459,15 +395,14 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
hostListeners?: {[key: string]: string},
|
hostListeners?: {[key: string]: string},
|
||||||
hostProperties?: {[key: string]: string},
|
hostProperties?: {[key: string]: string},
|
||||||
hostAttributes?: {[key: string]: string},
|
hostAttributes?: {[key: string]: string},
|
||||||
providers?:
|
providers?: CompileProviderMetadata[],
|
||||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
viewProviders?: CompileProviderMetadata[],
|
||||||
viewProviders?:
|
|
||||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
|
||||||
queries?: CompileQueryMetadata[],
|
queries?: CompileQueryMetadata[],
|
||||||
viewQueries?: CompileQueryMetadata[],
|
viewQueries?: CompileQueryMetadata[],
|
||||||
entryComponents?: CompileIdentifierMetadata[],
|
entryComponents?: CompileIdentifierMetadata[],
|
||||||
template?: CompileTemplateMetadata,
|
template?: CompileTemplateMetadata,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
|
this.isHost = !!isHost;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.isComponent = isComponent;
|
this.isComponent = isComponent;
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
@ -487,11 +422,9 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
this.template = template;
|
this.template = template;
|
||||||
}
|
}
|
||||||
|
|
||||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
|
||||||
|
|
||||||
toSummary(): CompileDirectiveSummary {
|
toSummary(): CompileDirectiveSummary {
|
||||||
return {
|
return {
|
||||||
isSummary: true,
|
summaryKind: CompileSummaryKind.Directive,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
isComponent: this.isComponent,
|
isComponent: this.isComponent,
|
||||||
selector: this.selector,
|
selector: this.selector,
|
||||||
@ -514,16 +447,12 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||||||
/**
|
/**
|
||||||
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
|
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
|
||||||
*/
|
*/
|
||||||
export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
|
export function createHostComponentMeta(
|
||||||
CompileDirectiveMetadata {
|
typeReference: any, compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata {
|
||||||
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
|
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
|
||||||
return CompileDirectiveMetadata.create({
|
return CompileDirectiveMetadata.create({
|
||||||
type: new CompileTypeMetadata({
|
isHost: true,
|
||||||
reference: Object,
|
type: {reference: typeReference, diDeps: [], lifecycleHooks: []},
|
||||||
name: `${compMeta.type.name}_Host`,
|
|
||||||
moduleUrl: compMeta.type.moduleUrl,
|
|
||||||
isHost: true
|
|
||||||
}),
|
|
||||||
template: new CompileTemplateMetadata({
|
template: new CompileTemplateMetadata({
|
||||||
encapsulation: ViewEncapsulation.None,
|
encapsulation: ViewEncapsulation.None,
|
||||||
template: template,
|
template: template,
|
||||||
@ -546,14 +475,13 @@ export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompilePipeSummary extends CompileSummary {
|
export interface CompilePipeSummary extends CompileTypeSummary {
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
name: string;
|
name: string;
|
||||||
pure: boolean;
|
pure: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
export class CompilePipeMetadata {
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
name: string;
|
name: string;
|
||||||
pure: boolean;
|
pure: boolean;
|
||||||
@ -567,42 +495,44 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
this.pure = !!pure;
|
this.pure = !!pure;
|
||||||
}
|
}
|
||||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
|
||||||
|
|
||||||
toSummary(): CompilePipeSummary {
|
toSummary(): CompilePipeSummary {
|
||||||
return {isSummary: true, type: this.type, name: this.name, pure: this.pure};
|
return {
|
||||||
|
summaryKind: CompileSummaryKind.Pipe,
|
||||||
|
type: this.type,
|
||||||
|
name: this.name,
|
||||||
|
pure: this.pure
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompileNgModuleInjectorSummary extends CompileSummary {
|
// Note: This should only use interfaces as nested data types
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
// as we need to be able to serialize this from/to JSON!
|
||||||
|
export interface CompileNgModuleSummary extends CompileTypeSummary {
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
entryComponents: CompileIdentifierMetadata[];
|
|
||||||
providers: CompileProviderMetadata[];
|
|
||||||
importedModules: CompileNgModuleInjectorSummary[];
|
|
||||||
exportedModules: CompileNgModuleInjectorSummary[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompileNgModuleDirectiveSummary extends CompileSummary {
|
// Note: This is transitive over the exported modules.
|
||||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
|
||||||
type: CompileTypeMetadata;
|
|
||||||
exportedDirectives: CompileIdentifierMetadata[];
|
exportedDirectives: CompileIdentifierMetadata[];
|
||||||
|
// Note: This is transitive over the exported modules.
|
||||||
exportedPipes: CompileIdentifierMetadata[];
|
exportedPipes: CompileIdentifierMetadata[];
|
||||||
exportedModules: CompileNgModuleDirectiveSummary[];
|
|
||||||
loadingPromises: Promise<any>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CompileNgModuleSummary =
|
// Note: This is transitive.
|
||||||
CompileNgModuleInjectorSummary & CompileNgModuleDirectiveSummary;
|
entryComponents: CompileIdentifierMetadata[];
|
||||||
|
// Note: This is transitive.
|
||||||
|
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[];
|
||||||
|
// Note: This is transitive.
|
||||||
|
modules: CompileTypeMetadata[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata regarding compilation of a module.
|
* Metadata regarding compilation of a module.
|
||||||
*/
|
*/
|
||||||
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
export class CompileNgModuleMetadata {
|
||||||
type: CompileTypeMetadata;
|
type: CompileTypeMetadata;
|
||||||
declaredDirectives: CompileIdentifierMetadata[];
|
declaredDirectives: CompileIdentifierMetadata[];
|
||||||
exportedDirectives: CompileIdentifierMetadata[];
|
exportedDirectives: CompileIdentifierMetadata[];
|
||||||
declaredPipes: CompileIdentifierMetadata[];
|
declaredPipes: CompileIdentifierMetadata[];
|
||||||
|
|
||||||
exportedPipes: CompileIdentifierMetadata[];
|
exportedPipes: CompileIdentifierMetadata[];
|
||||||
entryComponents: CompileIdentifierMetadata[];
|
entryComponents: CompileIdentifierMetadata[];
|
||||||
bootstrapComponents: CompileIdentifierMetadata[];
|
bootstrapComponents: CompileIdentifierMetadata[];
|
||||||
@ -620,8 +550,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
|||||||
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
|
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
|
||||||
transitiveModule, id}: {
|
transitiveModule, id}: {
|
||||||
type?: CompileTypeMetadata,
|
type?: CompileTypeMetadata,
|
||||||
providers?:
|
providers?: CompileProviderMetadata[],
|
||||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
|
||||||
declaredDirectives?: CompileIdentifierMetadata[],
|
declaredDirectives?: CompileIdentifierMetadata[],
|
||||||
exportedDirectives?: CompileIdentifierMetadata[],
|
exportedDirectives?: CompileIdentifierMetadata[],
|
||||||
declaredPipes?: CompileIdentifierMetadata[],
|
declaredPipes?: CompileIdentifierMetadata[],
|
||||||
@ -649,84 +578,81 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
|||||||
this.transitiveModule = transitiveModule;
|
this.transitiveModule = transitiveModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
|
||||||
|
|
||||||
toSummary(): CompileNgModuleSummary {
|
toSummary(): CompileNgModuleSummary {
|
||||||
return {
|
return {
|
||||||
isSummary: true,
|
summaryKind: CompileSummaryKind.NgModule,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
entryComponents: this.entryComponents,
|
entryComponents: this.transitiveModule.entryComponents,
|
||||||
providers: this.providers,
|
providers: this.transitiveModule.providers,
|
||||||
importedModules: this.importedModules,
|
modules: this.transitiveModule.modules,
|
||||||
exportedModules: this.exportedModules,
|
exportedDirectives: this.transitiveModule.exportedDirectives,
|
||||||
exportedDirectives: this.exportedDirectives,
|
exportedPipes: this.transitiveModule.exportedPipes
|
||||||
exportedPipes: this.exportedPipes,
|
|
||||||
loadingPromises: this.transitiveModule.loadingPromises
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
toInjectorSummary(): CompileNgModuleInjectorSummary {
|
|
||||||
return {
|
|
||||||
isSummary: true,
|
|
||||||
type: this.type,
|
|
||||||
entryComponents: this.entryComponents,
|
|
||||||
providers: this.providers,
|
|
||||||
importedModules: this.importedModules,
|
|
||||||
exportedModules: this.exportedModules
|
|
||||||
};
|
|
||||||
}
|
|
||||||
toDirectiveSummary(): CompileNgModuleDirectiveSummary {
|
|
||||||
return {
|
|
||||||
isSummary: true,
|
|
||||||
type: this.type,
|
|
||||||
exportedDirectives: this.exportedDirectives,
|
|
||||||
exportedPipes: this.exportedPipes,
|
|
||||||
exportedModules: this.exportedModules,
|
|
||||||
loadingPromises: this.transitiveModule.loadingPromises
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransitiveCompileNgModuleMetadata {
|
export class TransitiveCompileNgModuleMetadata {
|
||||||
directivesSet = new Set<any>();
|
directivesSet = new Set<any>();
|
||||||
|
directives: CompileIdentifierMetadata[] = [];
|
||||||
|
exportedDirectivesSet = new Set<any>();
|
||||||
|
exportedDirectives: CompileIdentifierMetadata[] = [];
|
||||||
pipesSet = new Set<any>();
|
pipesSet = new Set<any>();
|
||||||
|
pipes: CompileIdentifierMetadata[] = [];
|
||||||
|
exportedPipesSet = new Set<any>();
|
||||||
|
exportedPipes: CompileIdentifierMetadata[] = [];
|
||||||
|
modulesSet = new Set<any>();
|
||||||
|
modules: CompileTypeMetadata[] = [];
|
||||||
|
entryComponentsSet = new Set<any>();
|
||||||
|
entryComponents: CompileIdentifierMetadata[] = [];
|
||||||
|
|
||||||
constructor(
|
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = [];
|
||||||
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
|
|
||||||
public entryComponents: CompileIdentifierMetadata[],
|
addProvider(provider: CompileProviderMetadata, module: CompileIdentifierMetadata) {
|
||||||
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
|
this.providers.push({provider: provider, module: module});
|
||||||
public loadingPromises: Promise<any>[]) {
|
|
||||||
directives.forEach(dir => this.directivesSet.add(dir.reference));
|
|
||||||
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
|
addDirective(id: CompileIdentifierMetadata) {
|
||||||
T[] {
|
if (!this.directivesSet.has(id.reference)) {
|
||||||
const map = new Map<any, T>();
|
this.directivesSet.add(id.reference);
|
||||||
|
this.directives.push(id);
|
||||||
items.forEach((item) => {
|
|
||||||
if (!map.get(item.identifier.reference)) {
|
|
||||||
map.set(item.identifier.reference, item);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
addExportedDirective(id: CompileIdentifierMetadata) {
|
||||||
return Array.from(map.values());
|
if (!this.exportedDirectivesSet.has(id.reference)) {
|
||||||
|
this.exportedDirectivesSet.add(id.reference);
|
||||||
|
this.exportedDirectives.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addPipe(id: CompileIdentifierMetadata) {
|
||||||
|
if (!this.pipesSet.has(id.reference)) {
|
||||||
|
this.pipesSet.add(id.reference);
|
||||||
|
this.pipes.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addExportedPipe(id: CompileIdentifierMetadata) {
|
||||||
|
if (!this.exportedPipesSet.has(id.reference)) {
|
||||||
|
this.exportedPipesSet.add(id.reference);
|
||||||
|
this.exportedPipes.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addModule(id: CompileTypeMetadata) {
|
||||||
|
if (!this.modulesSet.has(id.reference)) {
|
||||||
|
this.modulesSet.add(id.reference);
|
||||||
|
this.modules.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addEntryComponent(id: CompileIdentifierMetadata) {
|
||||||
|
if (!this.entryComponentsSet.has(id.reference)) {
|
||||||
|
this.entryComponentsSet.add(id.reference);
|
||||||
|
this.entryComponents.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _normalizeArray(obj: any[]): any[] {
|
function _normalizeArray(obj: any[]): any[] {
|
||||||
return obj || [];
|
return obj || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isStaticSymbol(value: any): value is StaticSymbol {
|
|
||||||
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StaticSymbol {
|
|
||||||
name: string;
|
|
||||||
filePath: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProviderMeta {
|
export class ProviderMeta {
|
||||||
token: any;
|
token: any;
|
||||||
useClass: Type<any>;
|
useClass: Type<any>;
|
||||||
|
@ -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 {Identifiers, resolveIdentifier} 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';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
|
|||||||
// private is fine here as no child view will reference the cached value...
|
// private is fine here as no child view will reference the cached value...
|
||||||
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
||||||
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
|
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
|
||||||
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
|
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
|
||||||
.toStmt());
|
.toStmt());
|
||||||
return new CheckBindingField(fieldExpr, bindingId);
|
return new CheckBindingField(fieldExpr, bindingId);
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
|
|||||||
export function createCheckBindingStmt(
|
export function createCheckBindingStmt(
|
||||||
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
|
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
|
||||||
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
|
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
|
||||||
let condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
|
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
|
||||||
throwOnChangeVar, fieldExpr, evalResult.currValExpr
|
throwOnChangeVar, fieldExpr, evalResult.currValExpr
|
||||||
]);
|
]);
|
||||||
if (evalResult.forceUpdate) {
|
if (evalResult.forceUpdate) {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import * as cdAst from '../expression_parser/ast';
|
import * as cdAst from '../expression_parser/ast';
|
||||||
import {isBlank, isPresent} from '../facade/lang';
|
import {isBlank, isPresent} from '../facade/lang';
|
||||||
import {Identifiers, resolveIdentifier} 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';
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
|
|||||||
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
||||||
unwrapperStmts.push(
|
unwrapperStmts.push(
|
||||||
VAL_UNWRAPPER_VAR
|
VAL_UNWRAPPER_VAR
|
||||||
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
.set(o.importExpr(createIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
||||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||||
}
|
}
|
||||||
return unwrapperStmts;
|
return unwrapperStmts;
|
||||||
@ -277,15 +277,20 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
|||||||
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
||||||
|
|
||||||
return ast.expressions.length <= 9 ?
|
return ast.expressions.length <= 9 ?
|
||||||
o.importExpr(resolveIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
|
o.importExpr(createIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
|
||||||
o.importExpr(resolveIdentifier(Identifiers.interpolate)).callFn([
|
o.importExpr(createIdentifier(Identifiers.interpolate)).callFn([
|
||||||
args[0], o.literalArr(args.slice(1))
|
args[0], o.literalArr(args.slice(1))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
|
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
|
||||||
return convertToStatementIfNeeded(
|
const leftMostSafe = this.leftMostSafeNode(ast);
|
||||||
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
|
if (leftMostSafe) {
|
||||||
|
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
||||||
|
} else {
|
||||||
|
return convertToStatementIfNeeded(
|
||||||
|
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
|
visitKeyedWrite(ast: cdAst.KeyedWrite, mode: _Mode): any {
|
||||||
@ -578,7 +583,7 @@ function flattenStatements(arg: any, output: o.Statement[]) {
|
|||||||
|
|
||||||
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
|
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
|
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
|
||||||
}
|
}
|
||||||
const proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
|
const proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
|
||||||
const proxyParams: o.FnParam[] = [];
|
const proxyParams: o.FnParam[] = [];
|
||||||
@ -599,7 +604,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
|
|||||||
function createCachedLiteralMap(
|
function createCachedLiteralMap(
|
||||||
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
|
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
||||||
}
|
}
|
||||||
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
|
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
|
||||||
const proxyParams: o.FnParam[] = [];
|
const proxyParams: o.FnParam[] = [];
|
||||||
|
@ -8,15 +8,12 @@
|
|||||||
|
|
||||||
import {CompileTokenMetadata} from '../compile_metadata';
|
import {CompileTokenMetadata} from '../compile_metadata';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {IdentifierSpec, Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
|
import {IdentifierSpec, Identifiers, createEnumIdentifier, createIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
|
||||||
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
|
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
|
||||||
if (isPresent(token.value)) {
|
if (isPresent(token.value)) {
|
||||||
return o.literal(token.value);
|
return o.literal(token.value);
|
||||||
} else if (token.identifierIsInstance) {
|
|
||||||
return o.importExpr(token.identifier)
|
|
||||||
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
|
|
||||||
} else {
|
} else {
|
||||||
return o.importExpr(token.identifier);
|
return o.importExpr(token.identifier);
|
||||||
}
|
}
|
||||||
@ -24,13 +21,13 @@ export function createDiTokenExpression(token: CompileTokenMetadata): o.Expressi
|
|||||||
|
|
||||||
export function createInlineArray(values: o.Expression[]): o.Expression {
|
export function createInlineArray(values: o.Expression[]): o.Expression {
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
|
return o.importExpr(createIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
|
||||||
}
|
}
|
||||||
const log2 = Math.log(values.length) / Math.log(2);
|
const log2 = Math.log(values.length) / Math.log(2);
|
||||||
const index = Math.ceil(log2);
|
const index = Math.ceil(log2);
|
||||||
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
|
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
|
||||||
Identifiers.InlineArrayDynamic;
|
Identifiers.InlineArrayDynamic;
|
||||||
const identifier = resolveIdentifier(identifierSpec);
|
const identifier = createIdentifier(identifierSpec);
|
||||||
return o.importExpr(identifier).instantiate([
|
return o.importExpr(identifier).instantiate([
|
||||||
<o.Expression>o.literal(values.length)
|
<o.Expression>o.literal(values.length)
|
||||||
].concat(values));
|
].concat(values));
|
||||||
@ -46,7 +43,7 @@ export function createPureProxy(
|
|||||||
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
|
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
|
||||||
}
|
}
|
||||||
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
|
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
|
||||||
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
|
.set(o.importExpr(createIdentifier(pureProxyId)).callFn([fn]))
|
||||||
.toStmt());
|
.toStmt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,5 +53,5 @@ export function createEnumExpression(enumType: IdentifierSpec, enumValue: any):
|
|||||||
if (!enumName) {
|
if (!enumName) {
|
||||||
throw new Error(`Unknown enum value ${enumValue} in ${enumType.name}`);
|
throw new Error(`Unknown enum value ${enumValue} in ${enumType.name}`);
|
||||||
}
|
}
|
||||||
return o.importExpr(resolveEnumIdentifier(resolveIdentifier(enumType), enumName));
|
return o.importExpr(createEnumIdentifier(enumType, enumName));
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
import {SecurityContext} from '@angular/core';
|
import {SecurityContext} from '@angular/core';
|
||||||
|
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
|
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
|
||||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||||
@ -26,7 +26,7 @@ export function writeToRenderer(
|
|||||||
case PropertyBindingType.Property:
|
case PropertyBindingType.Property:
|
||||||
if (logBindingUpdate) {
|
if (logBindingUpdate) {
|
||||||
updateStmts.push(
|
updateStmts.push(
|
||||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
|
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
|
||||||
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
|
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
|
||||||
.toStmt());
|
.toStmt());
|
||||||
}
|
}
|
||||||
@ -91,8 +91,8 @@ function sanitizedValue(
|
|||||||
|
|
||||||
export function triggerAnimation(
|
export function triggerAnimation(
|
||||||
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
||||||
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
|
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
||||||
lastRenderValue: o.Expression) {
|
renderValue: o.Expression, lastRenderValue: o.Expression) {
|
||||||
const detachStmts: o.Statement[] = [];
|
const detachStmts: o.Statement[] = [];
|
||||||
const updateStmts: o.Statement[] = [];
|
const updateStmts: o.Statement[] = [];
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export function triggerAnimation(
|
|||||||
// it's important to normalize the void value as `void` explicitly
|
// it's important to normalize the void value as `void` explicitly
|
||||||
// so that the styles data can be obtained from the stringmap
|
// so that the styles data can be obtained from the stringmap
|
||||||
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
|
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
|
||||||
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
|
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
|
||||||
const animationTransitionVar = o.variable('animationTransition_' + animationName);
|
const animationTransitionVar = o.variable('animationTransition_' + animationName);
|
||||||
|
|
||||||
updateStmts.push(
|
updateStmts.push(
|
||||||
@ -121,23 +121,32 @@ export function triggerAnimation(
|
|||||||
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
|
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
|
||||||
.toDeclStmt());
|
.toDeclStmt());
|
||||||
|
|
||||||
const registerStmts = [
|
const registerStmts: o.Statement[] = [];
|
||||||
animationTransitionVar
|
const animationStartMethodExists = boundOutputs.find(
|
||||||
.callMethod(
|
event => event.isAnimation && event.name == animationName && event.phase == 'start');
|
||||||
'onStart',
|
if (animationStartMethodExists) {
|
||||||
[eventListener.callMethod(
|
registerStmts.push(
|
||||||
o.BuiltinMethod.Bind,
|
animationTransitionVar
|
||||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
.callMethod(
|
||||||
.toStmt(),
|
'onStart',
|
||||||
animationTransitionVar
|
[eventListener.callMethod(
|
||||||
.callMethod(
|
o.BuiltinMethod.Bind,
|
||||||
'onDone',
|
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
||||||
[eventListener.callMethod(
|
.toStmt());
|
||||||
o.BuiltinMethod.Bind,
|
}
|
||||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
|
||||||
.toStmt(),
|
|
||||||
|
|
||||||
];
|
const animationDoneMethodExists = boundOutputs.find(
|
||||||
|
event => event.isAnimation && event.name == animationName && event.phase == 'done');
|
||||||
|
if (animationDoneMethodExists) {
|
||||||
|
registerStmts.push(
|
||||||
|
animationTransitionVar
|
||||||
|
.callMethod(
|
||||||
|
'onDone',
|
||||||
|
[eventListener.callMethod(
|
||||||
|
o.BuiltinMethod.Bind,
|
||||||
|
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
||||||
|
.toStmt());
|
||||||
|
}
|
||||||
|
|
||||||
updateStmts.push(...registerStmts);
|
updateStmts.push(...registerStmts);
|
||||||
detachStmts.push(...registerStmts);
|
detachStmts.push(...registerStmts);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {ViewEncapsulation, isDevMode} from '@angular/core';
|
import {ViewEncapsulation, isDevMode} from '@angular/core';
|
||||||
|
|
||||||
import {CompileIdentifierMetadata} from './compile_metadata';
|
import {CompileIdentifierMetadata} from './compile_metadata';
|
||||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
import {Identifiers, createIdentifier} from './identifiers';
|
||||||
|
|
||||||
function unimplemented(): any {
|
function unimplemented(): any {
|
||||||
throw new Error('unimplemented');
|
throw new Error('unimplemented');
|
||||||
@ -61,7 +61,7 @@ export abstract class RenderTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DefaultRenderTypes implements RenderTypes {
|
export class DefaultRenderTypes implements RenderTypes {
|
||||||
get renderer() { return resolveIdentifier(Identifiers.Renderer); };
|
get renderer() { return createIdentifier(Identifiers.Renderer); };
|
||||||
renderText: any = null;
|
renderText: any = null;
|
||||||
renderElement: any = null;
|
renderElement: any = null;
|
||||||
renderComment: any = null;
|
renderComment: any = null;
|
||||||
|
@ -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 {Component, Injectable, ViewEncapsulation} from '@angular/core';
|
import {Component, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} 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';
|
||||||
@ -18,7 +19,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} from './util';
|
import {SyncAsyncResult, SyntaxError} from './util';
|
||||||
|
|
||||||
export interface PrenormalizedTemplateMetadata {
|
export interface PrenormalizedTemplateMetadata {
|
||||||
componentType: any;
|
componentType: any;
|
||||||
@ -32,7 +33,7 @@ export interface PrenormalizedTemplateMetadata {
|
|||||||
animations?: CompileAnimationEntryMetadata[];
|
animations?: CompileAnimationEntryMetadata[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class DirectiveNormalizer {
|
export class DirectiveNormalizer {
|
||||||
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ export class DirectiveNormalizer {
|
|||||||
} else if (prenormData.templateUrl) {
|
} else if (prenormData.templateUrl) {
|
||||||
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new SyntaxError(
|
||||||
`No template specified for component ${stringify(prenormData.componentType)}`);
|
`No template specified for component ${stringify(prenormData.componentType)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +105,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 Error(`Template parse errors:\n${errorString}`);
|
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
||||||
}
|
}
|
||||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||||
styles: prenomData.styles,
|
styles: prenomData.styles,
|
||||||
|
@ -6,13 +6,16 @@
|
|||||||
* 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, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {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}.
|
||||||
*
|
*
|
||||||
@ -20,7 +23,7 @@ import {splitAtColon} from './util';
|
|||||||
*
|
*
|
||||||
* See {@link Compiler}
|
* See {@link Compiler}
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class DirectiveResolver {
|
export class DirectiveResolver {
|
||||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||||
|
|
||||||
@ -35,7 +38,7 @@ export class DirectiveResolver {
|
|||||||
resolve(type: Type<any>, throwIfNotFound = true): Directive {
|
resolve(type: Type<any>, throwIfNotFound = true): Directive {
|
||||||
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||||
if (typeMetadata) {
|
if (typeMetadata) {
|
||||||
const metadata = typeMetadata.find(isDirectiveMetadata);
|
const metadata = ListWrapper.findLast(typeMetadata, isDirectiveMetadata);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
const propertyMetadata = this._reflector.propMetadata(type);
|
const propertyMetadata = this._reflector.propMetadata(type);
|
||||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
||||||
@ -58,85 +61,74 @@ export class DirectiveResolver {
|
|||||||
const queries: {[key: string]: any} = {};
|
const queries: {[key: string]: any} = {};
|
||||||
|
|
||||||
Object.keys(propertyMetadata).forEach((propName: string) => {
|
Object.keys(propertyMetadata).forEach((propName: string) => {
|
||||||
|
const input = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Input);
|
||||||
propertyMetadata[propName].forEach(a => {
|
if (input) {
|
||||||
if (a instanceof Input) {
|
if (input.bindingPropertyName) {
|
||||||
if (a.bindingPropertyName) {
|
inputs.push(`${propName}: ${input.bindingPropertyName}`);
|
||||||
inputs.push(`${propName}: ${a.bindingPropertyName}`);
|
} else {
|
||||||
} else {
|
inputs.push(propName);
|
||||||
inputs.push(propName);
|
}
|
||||||
|
}
|
||||||
|
const output = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Output);
|
||||||
|
if (output) {
|
||||||
|
if (output.bindingPropertyName) {
|
||||||
|
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
||||||
|
} else {
|
||||||
|
outputs.push(propName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
|
||||||
|
hostBindings.forEach(hostBinding => {
|
||||||
|
if (hostBinding.hostPropertyName) {
|
||||||
|
const startWith = hostBinding.hostPropertyName[0];
|
||||||
|
if (startWith === '(') {
|
||||||
|
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
||||||
|
} else if (startWith === '[') {
|
||||||
|
throw new Error(
|
||||||
|
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||||
}
|
}
|
||||||
} else if (a instanceof Output) {
|
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
||||||
const output: Output = a;
|
} else {
|
||||||
if (output.bindingPropertyName) {
|
host[`[${propName}]`] = propName;
|
||||||
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
|
||||||
} else {
|
|
||||||
outputs.push(propName);
|
|
||||||
}
|
|
||||||
} else if (a instanceof HostBinding) {
|
|
||||||
const hostBinding: HostBinding = a;
|
|
||||||
if (hostBinding.hostPropertyName) {
|
|
||||||
const startWith = hostBinding.hostPropertyName[0];
|
|
||||||
if (startWith === '(') {
|
|
||||||
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
|
||||||
} else if (startWith === '[') {
|
|
||||||
throw new Error(
|
|
||||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
|
||||||
}
|
|
||||||
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
|
||||||
} else {
|
|
||||||
host[`[${propName}]`] = propName;
|
|
||||||
}
|
|
||||||
} else if (a instanceof HostListener) {
|
|
||||||
const hostListener: HostListener = a;
|
|
||||||
const args = hostListener.args || [];
|
|
||||||
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
|
||||||
} else if (a instanceof Query) {
|
|
||||||
queries[propName] = a;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
|
||||||
|
hostListeners.forEach(hostListener => {
|
||||||
|
const args = hostListener.args || [];
|
||||||
|
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
||||||
|
});
|
||||||
|
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
|
||||||
|
if (query) {
|
||||||
|
queries[propName] = query;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
|
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
|
||||||
|
|
||||||
|
private _dedupeBindings(bindings: string[]): string[] {
|
||||||
|
const names = new Set<string>();
|
||||||
|
const reversedResult: string[] = [];
|
||||||
|
// go last to first to allow later entries to overwrite previous entries
|
||||||
|
for (let i = bindings.length - 1; i >= 0; i--) {
|
||||||
|
const binding = bindings[i];
|
||||||
|
const name = this._extractPublicName(binding);
|
||||||
|
if (!names.has(name)) {
|
||||||
|
names.add(name);
|
||||||
|
reversedResult.push(binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reversedResult.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
private _merge(
|
private _merge(
|
||||||
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
||||||
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
||||||
const mergedInputs: string[] = inputs;
|
const mergedInputs =
|
||||||
|
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
||||||
if (directive.inputs) {
|
const mergedOutputs =
|
||||||
const inputNames: string[] =
|
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
||||||
directive.inputs.map((def: string): string => this._extractPublicName(def));
|
|
||||||
|
|
||||||
inputs.forEach((inputDef: string) => {
|
|
||||||
const publicName = this._extractPublicName(inputDef);
|
|
||||||
if (inputNames.indexOf(publicName) > -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mergedInputs.unshift(...directive.inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedOutputs: string[] = outputs;
|
|
||||||
|
|
||||||
if (directive.outputs) {
|
|
||||||
const outputNames: string[] =
|
|
||||||
directive.outputs.map((def: string): string => this._extractPublicName(def));
|
|
||||||
|
|
||||||
outputs.forEach((outputDef: string) => {
|
|
||||||
const publicName = this._extractPublicName(outputDef);
|
|
||||||
if (outputNames.indexOf(publicName) > -1) {
|
|
||||||
throw new Error(
|
|
||||||
`Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mergedOutputs.unshift(...directive.outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
|
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
|
||||||
const mergedQueries =
|
const mergedQueries =
|
||||||
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;
|
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;
|
||||||
|
@ -6,15 +6,14 @@
|
|||||||
* 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} 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';
|
||||||
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
|
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, resolveIdentifier} 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';
|
||||||
@ -51,9 +50,11 @@ 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.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class DirectiveWrapperCompiler {
|
export class DirectiveWrapperCompiler {
|
||||||
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
|
static dirWrapperClassName(id: CompileIdentifierMetadata) {
|
||||||
|
return `Wrapper_${identifierName(id)}`;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private compilerConfig: CompilerConfig, private _exprParser: Parser,
|
private compilerConfig: CompilerConfig, private _exprParser: Parser,
|
||||||
@ -68,7 +69,7 @@ export class DirectiveWrapperCompiler {
|
|||||||
addCheckInputMethod(inputFieldName, builder);
|
addCheckInputMethod(inputFieldName, builder);
|
||||||
});
|
});
|
||||||
addNgDoCheckMethod(builder);
|
addNgDoCheckMethod(builder);
|
||||||
addCheckHostMethod(hostParseResult.hostProps, builder);
|
addCheckHostMethod(hostParseResult.hostProps, hostParseResult.hostListeners, builder);
|
||||||
addHandleEventMethod(hostParseResult.hostListeners, builder);
|
addHandleEventMethod(hostParseResult.hostListeners, builder);
|
||||||
addSubscribeMethod(dirMeta, builder);
|
addSubscribeMethod(dirMeta, builder);
|
||||||
|
|
||||||
@ -117,10 +118,10 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||||||
[
|
[
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
VIEW_VAR.name,
|
VIEW_VAR.name,
|
||||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
COMPONENT_VIEW_VAR.name,
|
COMPONENT_VIEW_VAR.name,
|
||||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||||
],
|
],
|
||||||
this.detachStmts),
|
this.detachStmts),
|
||||||
@ -172,7 +173,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
|||||||
}
|
}
|
||||||
if (builder.compilerConfig.logBindingUpdate) {
|
if (builder.compilerConfig.logBindingUpdate) {
|
||||||
onChangesStmts.push(
|
onChangesStmts.push(
|
||||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
|
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfoForChanges))
|
||||||
.callFn(
|
.callFn(
|
||||||
[VIEW_VAR.prop('renderer'), RENDER_EL_VAR, o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
|
[VIEW_VAR.prop('renderer'), RENDER_EL_VAR, o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
|
||||||
.toStmt());
|
.toStmt());
|
||||||
@ -198,7 +199,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
|||||||
'ngDoCheck',
|
'ngDoCheck',
|
||||||
[
|
[
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||||
],
|
],
|
||||||
@ -214,7 +215,7 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
|||||||
if (builder.genChanges) {
|
if (builder.genChanges) {
|
||||||
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
|
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
|
||||||
.key(o.literal(input))
|
.key(o.literal(input))
|
||||||
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
|
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
|
||||||
.instantiate([field.expression, CURR_VALUE_VAR]))
|
.instantiate([field.expression, CURR_VALUE_VAR]))
|
||||||
.toStmt());
|
.toStmt());
|
||||||
}
|
}
|
||||||
@ -233,14 +234,15 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addCheckHostMethod(
|
function addCheckHostMethod(
|
||||||
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
|
hostProps: BoundElementPropertyAst[], hostEvents: BoundEventAst[],
|
||||||
|
builder: DirectiveWrapperBuilder) {
|
||||||
const stmts: o.Statement[] = [];
|
const stmts: o.Statement[] = [];
|
||||||
const methodParams: o.FnParam[] = [
|
const methodParams: o.FnParam[] = [
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
COMPONENT_VIEW_VAR.name,
|
COMPONENT_VIEW_VAR.name,
|
||||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||||
];
|
];
|
||||||
@ -255,14 +257,14 @@ function addCheckHostMethod(
|
|||||||
if (hostProp.needsRuntimeSecurityContext) {
|
if (hostProp.needsRuntimeSecurityContext) {
|
||||||
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
|
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
|
||||||
methodParams.push(new o.FnParam(
|
methodParams.push(new o.FnParam(
|
||||||
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
|
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
|
||||||
}
|
}
|
||||||
let checkBindingStmts: o.Statement[];
|
let checkBindingStmts: o.Statement[];
|
||||||
if (hostProp.isAnimation) {
|
if (hostProp.isAnimation) {
|
||||||
const {updateStmts, detachStmts} = triggerAnimation(
|
const {updateStmts, detachStmts} = triggerAnimation(
|
||||||
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
|
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
|
||||||
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
|
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
|
||||||
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
|
.or(o.importExpr(createIdentifier(Identifiers.noop))),
|
||||||
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
|
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
|
||||||
checkBindingStmts = updateStmts;
|
checkBindingStmts = updateStmts;
|
||||||
builder.detachStmts.push(...detachStmts);
|
builder.detachStmts.push(...detachStmts);
|
||||||
@ -306,7 +308,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
|
|||||||
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
|
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
|
||||||
const methodParams: o.FnParam[] = [
|
const methodParams: o.FnParam[] = [
|
||||||
new o.FnParam(
|
new o.FnParam(
|
||||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||||
new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)
|
new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)
|
||||||
];
|
];
|
||||||
const stmts: o.Statement[] = [
|
const stmts: o.Statement[] = [
|
||||||
@ -348,9 +350,10 @@ function parseHostBindings(
|
|||||||
const errors: ParseError[] = [];
|
const errors: ParseError[] = [];
|
||||||
const parser =
|
const parser =
|
||||||
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
|
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
|
||||||
const sourceFileName = dirMeta.type.moduleUrl ?
|
const moduleUrl = identifierModuleUrl(dirMeta.type);
|
||||||
`in Directive ${dirMeta.type.name} in ${dirMeta.type.moduleUrl}` :
|
const sourceFileName = moduleUrl ?
|
||||||
`in Directive ${dirMeta.type.name}`;
|
`in Directive ${identifierName(dirMeta.type)} in ${moduleUrl}` :
|
||||||
|
`in Directive ${identifierName(dirMeta.type)}`;
|
||||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||||
const sourceSpan = new ParseSourceSpan(
|
const sourceSpan = new ParseSourceSpan(
|
||||||
new ParseLocation(sourceFile, null, null, null),
|
new ParseLocation(sourceFile, null, null, null),
|
||||||
|
@ -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, isPresent} 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'];
|
||||||
|
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class Lexer {
|
export class Lexer {
|
||||||
tokenize(text: string): Token[] {
|
tokenize(text: string): Token[] {
|
||||||
const scanner = new _Scanner(text);
|
const scanner = new _Scanner(text);
|
||||||
|
@ -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 {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';
|
||||||
@ -31,7 +30,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
|
|||||||
return new RegExp(pattern, 'g');
|
return new RegExp(pattern, 'g');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@CompilerInjectable()
|
||||||
export class Parser {
|
export class Parser {
|
||||||
private errors: ParserError[] = [];
|
private errors: ParserError[] = [];
|
||||||
|
|
||||||
@ -344,7 +343,7 @@ export class _ParseAST {
|
|||||||
while (this.optionalCharacter(chars.$COLON)) {
|
while (this.optionalCharacter(chars.$COLON)) {
|
||||||
args.push(this.parseExpression());
|
args.push(this.parseExpression());
|
||||||
}
|
}
|
||||||
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args);
|
result = new BindingPipe(this.span(result.span.start), result, name, args);
|
||||||
} while (this.optionalOperator('|'));
|
} while (this.optionalOperator('|'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user