Compare commits
218 Commits
Author | SHA1 | Date | |
---|---|---|---|
540b1197a6 | |||
d30cc8461b | |||
f27954e62c | |||
69b52eb2b3 | |||
b9b557cdb0 | |||
a72a002a8d | |||
a0437f8c9d | |||
1c279b3264 | |||
cd03c77364 | |||
f6ef7d6e5a | |||
6aeaca3fb4 | |||
af62050729 | |||
cb69656b56 | |||
2fc0560988 | |||
86c50983d7 | |||
21976446e0 | |||
998ce9ad7e | |||
111523677c | |||
2d74a224d0 | |||
4d6ac9d414 | |||
6557bc34f6 | |||
e2622add07 | |||
ecfad467a1 | |||
5918133784 | |||
700bce9ec1 | |||
a64a35a8c1 | |||
b3dcff0cc1 | |||
124267c87a | |||
547bfa92ef | |||
d40bbf4d5c | |||
94b7031fe9 | |||
df0bf1dd74 | |||
c8a9b70890 | |||
efa2d80df8 | |||
a58e5efd09 | |||
86cf0ef892 | |||
5c568fab86 | |||
566104504c | |||
307d305b2d | |||
0a7364feea | |||
4544b1d7a6 | |||
9e0e6b59d1 | |||
14dd2b367a | |||
91eb8914dd | |||
77823d721f | |||
2afe2d107f | |||
17f40fb75f | |||
98936fdf16 | |||
7383e4a801 | |||
65c9b5b6aa | |||
5fab8710cb | |||
f106a18b96 | |||
8db184d349 | |||
c18eb298eb | |||
3f4aa59cfa | |||
79728b4c41 | |||
413167ab1b | |||
203cc7e1f1 | |||
b0cd514709 | |||
392c9ac214 | |||
a26e054857 | |||
c0b001a6af | |||
c8c1f22f9c | |||
e4d5a5f003 | |||
03d9de33a1 | |||
a8a80cf523 | |||
6c1d7908d5 | |||
9aab6d24eb | |||
5ee8155e4e | |||
21de0f239d | |||
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 |
150
CHANGELOG.md
150
CHANGELOG.md
@ -1,3 +1,149 @@
|
||||
<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)
|
||||
* **dom_adapter:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
|
||||
* **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
|
||||
* **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
|
||||
* **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364)
|
||||
* **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18))
|
||||
* **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980)
|
||||
* **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414)
|
||||
* Better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
|
||||
* Better instructions on running examples and their tests ([203cc7e](https://github.com/angular/angular/commit/203cc7e))
|
||||
* **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403)
|
||||
* **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373)
|
||||
* **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155))
|
||||
* **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac))
|
||||
* **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327)
|
||||
* **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440)
|
||||
|
||||
|
||||
<a name="2.3.0"></a>
|
||||
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** make sure the plural category exists ([#13169](https://github.com/angular/angular/issues/13169)) ([82c81cd](https://github.com/angular/angular/commit/82c81cd)), closes [#12379](https://github.com/angular/angular/issues/12379)
|
||||
* **compiler:** include the summaries of reexported modules / directives / pipes ([#13196](https://github.com/angular/angular/issues/13196)) ([75d1617](https://github.com/angular/angular/commit/75d1617))
|
||||
* **compiler:** serialize any `StaticSymbol` correctly, not matter in which context ([5614c4f](https://github.com/angular/angular/commit/5614c4f))
|
||||
* **compiler:** short-circut expressions with an index ([#13263](https://github.com/angular/angular/issues/13263)) ([f31c947](https://github.com/angular/angular/commit/f31c947)), closes [#13254](https://github.com/angular/angular/issues/13254)
|
||||
* **core:** display framework version on bootstrapped component ([#13252](https://github.com/angular/angular/issues/13252)) ([16efb13](https://github.com/angular/angular/commit/16efb13))
|
||||
* **facade:** cache original format string ([#12764](https://github.com/angular/angular/issues/12764)) ([a132287](https://github.com/angular/angular/commit/a132287))
|
||||
* **http:** set the default Accept header ([#12989](https://github.com/angular/angular/issues/12989)) ([986abbe](https://github.com/angular/angular/commit/986abbe)), closes [#6354](https://github.com/angular/angular/issues/6354)
|
||||
* **language-service:** avoid throwing for invalid class declarations ([#13257](https://github.com/angular/angular/issues/13257)) ([93556a5](https://github.com/angular/angular/commit/93556a5)), closes [#13253](https://github.com/angular/angular/issues/13253)
|
||||
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
|
||||
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
|
||||
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
|
||||
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
|
||||
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
|
||||
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
|
||||
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))
|
||||
* **router:** runs guards every time when unsuccessfully navigating to the same url over and over again ([#13209](https://github.com/angular/angular/issues/13209)) ([d46b8de](https://github.com/angular/angular/commit/d46b8de))
|
||||
* **router:** throw a better error message when angular 1 is not bootstraped ([c767df0](https://github.com/angular/angular/commit/c767df0))
|
||||
* **router:** validate nested routes ([#13224](https://github.com/angular/angular/issues/13224)) ([2893c2c](https://github.com/angular/angular/commit/2893c2c)), closes [#12827](https://github.com/angular/angular/issues/12827)
|
||||
* **tsc-wrapped:** have UserError display the actual error ([393c100](https://github.com/angular/angular/commit/393c100))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** read and write `.ngsummary.json` files ([614a35d](https://github.com/angular/angular/commit/614a35d)), closes [#12787](https://github.com/angular/angular/issues/12787)
|
||||
|
||||
|
||||
|
||||
<a name="2.3.0-rc.0"></a>
|
||||
# [2.3.0-rc.0](https://github.com/angular/angular/compare/2.3.0-beta.0...2.3.0-rc.0) (2016-11-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ef96763](https://github.com/angular/angular/commit/ef96763)), closes [#13013](https://github.com/angular/angular/issues/13013)
|
||||
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([fc5ac1e](https://github.com/angular/angular/commit/fc5ac1e))
|
||||
* **build:** update versions of umd bundles ([#13038](https://github.com/angular/angular/issues/13038)) ([86ffa88](https://github.com/angular/angular/commit/86ffa88)), closes [#13037](https://github.com/angular/angular/issues/13037)
|
||||
* **changelog:** replace beta.1 with beta.0 ([#12961](https://github.com/angular/angular/issues/12961)) ([07a986d](https://github.com/angular/angular/commit/07a986d))
|
||||
* **ci:** pin version of npm on CircleCI ([#12954](https://github.com/angular/angular/issues/12954)) ([a3884db](https://github.com/angular/angular/commit/a3884db))
|
||||
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([7dcca30](https://github.com/angular/angular/commit/7dcca30))
|
||||
* **common:** update DatePipe to allow closure compilation ([b2b7219](https://github.com/angular/angular/commit/b2b7219))
|
||||
* **compiler:** correctly evaluate references to static functions ([#13133](https://github.com/angular/angular/issues/13133)) ([627282d](https://github.com/angular/angular/commit/627282d))
|
||||
* **compiler:** fix performance regression caused by 5b0f9e2 ([43c0e9a](https://github.com/angular/angular/commit/43c0e9a)), closes [#13146](https://github.com/angular/angular/issues/13146)
|
||||
* **compiler:** fix versions of `@angular/tsc-wrapped` ([bccf0e6](https://github.com/angular/angular/commit/bccf0e6))
|
||||
* **compiler-cli:** fix paths in source maps to be relative ([2a3ca7b](https://github.com/angular/angular/commit/2a3ca7b)), closes [#13040](https://github.com/angular/angular/issues/13040)
|
||||
* **compiler-cli:** pin the version of `tsc-wrapped` ([966bcba](https://github.com/angular/angular/commit/966bcba))
|
||||
* **language-service:** harden against partial normalization of directives ([2975d89](https://github.com/angular/angular/commit/2975d89))
|
||||
* **core:** shrinkwrap was out of date with packages. ([e45b7ff](https://github.com/angular/angular/commit/e45b7ff))
|
||||
* **language-service:** make link check pass ([7194fc2](https://github.com/angular/angular/commit/7194fc2))
|
||||
* **router:** guards restore an incorrect url when used with skipLocationChange ([ad20d7d](https://github.com/angular/angular/commit/ad20d7d)), closes [#12825](https://github.com/angular/angular/issues/12825)
|
||||
* **router:** support redirects to named outlets ([602522b](https://github.com/angular/angular/commit/602522b)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
|
||||
* **tsc-wrapped:** set correct version number ([897555c](https://github.com/angular/angular/commit/897555c))
|
||||
* **tsc-wrapped:** still emit version 1 metadata to allow use of new components in old setups ([bc69c74](https://github.com/angular/angular/commit/bc69c74))
|
||||
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([1ef4696](https://github.com/angular/angular/commit/1ef4696))
|
||||
* **version:** take all of version string after patch version ([f275f36](https://github.com/angular/angular/commit/f275f36))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** update RxJS peer dependency to 5.0.0-rc.4 Please see [this gist](https://gist.github.com/robwormald/19dea0c70a6e01aadced6731aed4f9f7) if you depend on the `cache` operator ([2d6a003](https://github.com/angular/angular/commit/2d6a003)), closes [#13125](https://github.com/angular/angular/issues/13125)
|
||||
* **core:** upgrade zone.js to v0.7.1 ([c4bbafc](https://github.com/angular/angular/commit/c4bbafc))
|
||||
* **build:** record angular version in the dom ([#13164](https://github.com/angular/angular/issues/13164)) ([e628b66](https://github.com/angular/angular/commit/e628b66))
|
||||
* **core:** expose destroy() method on ViewRef ([808275a](https://github.com/angular/angular/commit/808275a))
|
||||
* **core:** properly support inheritance ([f5c8e09](https://github.com/angular/angular/commit/f5c8e09)), closes [#11606](https://github.com/angular/angular/issues/11606) [#12892](https://github.com/angular/angular/issues/12892)
|
||||
* **language-service:** add services to support editors ([#12987](https://github.com/angular/angular/issues/12987)) ([519a324](https://github.com/angular/angular/commit/519a324))
|
||||
* **router:** add support for custom route reuse strategies ([42cf06f](https://github.com/angular/angular/commit/42cf06f))
|
||||
* **tools:** allow disabling annotation lowering ([c1a62e2](https://github.com/angular/angular/commit/c1a62e2))
|
||||
|
||||
|
||||
|
||||
<a name="2.2.4"></a>
|
||||
## [2.2.4](https://github.com/angular/angular/compare/2.2.3...2.2.4) (2016-11-30)
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
@ -39,6 +185,8 @@
|
||||
|
||||
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)
|
||||
|
||||
@ -860,7 +1008,7 @@ prefix using `animate-` must now be preixed using `bind-animate-`.
|
||||
* core:
|
||||
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
|
||||
created during bootstrap instead by calling `NgModuleRef.destroy`.
|
||||
- `AplicationRef.registerDisposeListener` is deprecated.
|
||||
- `ApplicationRef.registerDisposeListener` is deprecated.
|
||||
Use the `ngOnDestroy` lifecycle hook for providers or
|
||||
`NgModuleRef.onDestroy` 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
|
||||
- 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.
|
||||
|
||||
@ -244,7 +244,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
[github]: https://github.com/angular/angular
|
||||
[gitter]: https://gitter.im/angular/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
|
||||
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
|
||||
[jsfiddle]: http://jsfiddle.net
|
||||
[plunker]: http://plnkr.co/edit
|
||||
[runnable]: http://runnable.com
|
||||
|
@ -126,7 +126,7 @@ $ gulp public-api:update
|
||||
|
||||
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
|
||||
is not properly formatted, the CI will fail and the PR can not be merged.
|
||||
|
@ -24,8 +24,9 @@ with it.
|
||||
* `comp: forms`: `@kara`
|
||||
* `comp: http`: `@jeffbcross`
|
||||
* `comp: i18n`: `@vicb`
|
||||
* `comp: language service`: `@chuckjaz`
|
||||
* `comp: metadata-extractor`: `@chuckjaz`
|
||||
* `comp: router`: `@vsavkin`
|
||||
* `comp: router`: `@vicb`
|
||||
* `comp: testing`: `@juliemr`
|
||||
* `comp: upgrade`: `@mhevery`
|
||||
* `comp: web-worker`: `@vicb`
|
||||
|
@ -36,7 +36,7 @@ var CIconfiguration = {
|
||||
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS10': {unitTest: {target: '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}}
|
||||
};
|
||||
|
||||
@ -44,10 +44,10 @@ var customLaunchers = {
|
||||
'DartiumWithWebPlatform':
|
||||
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
|
||||
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
|
||||
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
|
||||
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'},
|
||||
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
|
||||
'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_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
|
||||
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
|
||||
@ -96,7 +96,7 @@ var customLaunchers = {
|
||||
'BS_IE10': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'ie',
|
||||
browser_version: '10.0',
|
||||
browser_version: '10.1',
|
||||
os: 'Windows',
|
||||
os_version: '8'
|
||||
},
|
||||
|
7
build.sh
7
build.sh
@ -17,6 +17,7 @@ PACKAGES=(core
|
||||
upgrade
|
||||
router
|
||||
compiler-cli
|
||||
language-service
|
||||
benchpress)
|
||||
BUILD_ALL=true
|
||||
BUNDLE=true
|
||||
@ -144,6 +145,11 @@ do
|
||||
$TSC -p ${SRCDIR}/tsconfig-testing.json
|
||||
fi
|
||||
|
||||
if [[ -e ${SRCDIR}/tsconfig-2015.json ]]; then
|
||||
echo "====== COMPILING ESM: ${TSC} -p ${SRCDIR}/tsconfig-2015.json"
|
||||
${TSC} -p ${SRCDIR}/tsconfig-2015.json
|
||||
fi
|
||||
|
||||
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
|
||||
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
@ -175,7 +181,6 @@ do
|
||||
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||
|
||||
|
||||
if [[ -e rollup-testing.config.js ]]; then
|
||||
echo "====== Rollup ${PACKAGE} testing"
|
||||
../../../node_modules/.bin/rollup -c rollup-testing.config.js
|
||||
|
@ -48,12 +48,15 @@ module.exports = function(config) {
|
||||
|
||||
exclude: [
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/**/*node_only_spec.js',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/angular1_router.js',
|
||||
'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/angular1_router.js',
|
||||
'dist/examples/**/e2e_test/**',
|
||||
],
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"dependencies": {
|
||||
"@angular/core": "^2.0.0-rc.7",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"rxjs": "5.0.0-beta.12",
|
||||
"rxjs": "5.0.0-rc.4",
|
||||
"jpm": "1.1.4",
|
||||
"firefox-profile": "0.4.0",
|
||||
"selenium-webdriver": "^2.53.3"
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/* tslint:disable:no-console */
|
||||
import {browser} from 'protractor';
|
||||
|
||||
const assertEventsContainsName = function(events: any[], eventName: string) {
|
||||
|
@ -11,9 +11,6 @@
|
||||
* @description
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
export * from './src/location';
|
||||
export {NgLocalization} from './src/localization';
|
||||
export {CommonModule} from './src/common_module';
|
||||
export * from './src/common';
|
||||
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './src/directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './src/pipes/index';
|
||||
// This file only reexports content of the `src` folder. Keep it that way.
|
||||
|
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 {NgLocalization} from './localization';
|
||||
export {CommonModule} from './common_module';
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
|
||||
export {VERSION} from './version';
|
||||
export {Version} from '@angular/core';
|
@ -23,9 +23,23 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
|
||||
*/
|
||||
export function getPluralCategory(
|
||||
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}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
* {@link Location} service to represent its state in the
|
||||
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
|
||||
@ -27,18 +29,7 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component, NgModule} from '@angular/core';
|
||||
* import {
|
||||
* LocationStrategy,
|
||||
* HashLocationStrategy
|
||||
* } from '@angular/common';
|
||||
*
|
||||
* @NgModule({
|
||||
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
|
||||
* })
|
||||
* class AppModule {}
|
||||
* ```
|
||||
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
|
||||
*
|
||||
* @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
|
||||
* 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
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component} from '@angular/core';
|
||||
* import {Location} from '@angular/common';
|
||||
*
|
||||
* @Component({selector: 'app-component'})
|
||||
* class AppCmp {
|
||||
* constructor(location: Location) {
|
||||
* location.go('/foo');
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
@ -96,7 +85,7 @@ export class Location {
|
||||
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
|
||||
*/
|
||||
prepareExternalUrl(url: string): string {
|
||||
if (url.length > 0 && !url.startsWith('/')) {
|
||||
if (url && url[0] !== '/') {
|
||||
url = '/' + url;
|
||||
}
|
||||
return this._platformStrategy.prepareExternalUrl(url);
|
||||
@ -143,7 +132,7 @@ export class Location {
|
||||
* is.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public static stripTrailingSlash(url: string): string {
|
||||
if (/\/$/g.test(url)) {
|
||||
url = url.substring(0, url.length - 1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
public static stripTrailingSlash(url: string): string { return url.replace(/\/$/, ''); }
|
||||
}
|
||||
|
||||
function _stripBaseHref(baseHref: string, url: string): string {
|
||||
if (baseHref.length > 0 && url.startsWith(baseHref)) {
|
||||
return url.substring(baseHref.length);
|
||||
}
|
||||
return url;
|
||||
return baseHref && url.startsWith(baseHref) ? url.substring(baseHref.length) : url;
|
||||
}
|
||||
|
||||
function _stripIndexHtml(url: string): string {
|
||||
if (/\/index.html$/g.test(url)) {
|
||||
// '/index.html'.length == 11
|
||||
return url.substring(0, url.length - 11);
|
||||
}
|
||||
return url;
|
||||
return url.replace(/\/index.html$/, '');
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
|
||||
/**
|
||||
* `LocationStrategy` is responsible for representing and reading route state
|
||||
* from the browser's URL. Angular provides two strategies:
|
||||
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
|
||||
* {@link HashLocationStrategy} and {@link PathLocationStrategy}.
|
||||
*
|
||||
* 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
|
||||
* {@link Location} service to represent its state in the
|
||||
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
|
||||
* browser's URL.
|
||||
*
|
||||
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
|
||||
* provided in {@link ROUTER_PROVIDERS}.
|
||||
*
|
||||
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
|
||||
* or add a base element to the document. This URL prefix that will be preserved
|
||||
* when generating and recognizing URLs.
|
||||
@ -37,6 +36,10 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
* `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com/my/app/foo`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
|
@ -59,7 +59,7 @@ const _observableStrategy = new ObservableStrategy();
|
||||
* {@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
|
||||
* 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'}
|
||||
*
|
||||
|
@ -7,11 +7,14 @@
|
||||
*/
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||
import {DateFormatter} from '../facade/intl';
|
||||
|
||||
import {NumberWrapper, isDate} from '../facade/lang';
|
||||
|
||||
import {DateFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Formats a date according to locale rules.
|
||||
|
@ -44,6 +44,7 @@ const DATE_FORMATS_SPLIT =
|
||||
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
|
||||
|
||||
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
||||
// Keys are quoted so they do not get renamed during closure compilation.
|
||||
'yMMMdjms': datePartGetterFactory(combine([
|
||||
digitCondition('year', 1),
|
||||
nameCondition('month', 3),
|
||||
@ -72,48 +73,50 @@ const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
|
||||
};
|
||||
|
||||
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
|
||||
yyyy: datePartGetterFactory(digitCondition('year', 4)),
|
||||
yy: datePartGetterFactory(digitCondition('year', 2)),
|
||||
y: datePartGetterFactory(digitCondition('year', 1)),
|
||||
MMMM: datePartGetterFactory(nameCondition('month', 4)),
|
||||
MMM: datePartGetterFactory(nameCondition('month', 3)),
|
||||
MM: datePartGetterFactory(digitCondition('month', 2)),
|
||||
M: datePartGetterFactory(digitCondition('month', 1)),
|
||||
LLLL: datePartGetterFactory(nameCondition('month', 4)),
|
||||
L: datePartGetterFactory(nameCondition('month', 1)),
|
||||
dd: datePartGetterFactory(digitCondition('day', 2)),
|
||||
d: datePartGetterFactory(digitCondition('day', 1)),
|
||||
HH: digitModifier(
|
||||
// Keys are quoted so they do not get renamed.
|
||||
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
|
||||
'yy': datePartGetterFactory(digitCondition('year', 2)),
|
||||
'y': datePartGetterFactory(digitCondition('year', 1)),
|
||||
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
|
||||
'MMM': datePartGetterFactory(nameCondition('month', 3)),
|
||||
'MM': datePartGetterFactory(digitCondition('month', 2)),
|
||||
'M': datePartGetterFactory(digitCondition('month', 1)),
|
||||
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
|
||||
'L': datePartGetterFactory(nameCondition('month', 1)),
|
||||
'dd': datePartGetterFactory(digitCondition('day', 2)),
|
||||
'd': datePartGetterFactory(digitCondition('day', 1)),
|
||||
'HH': digitModifier(
|
||||
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
|
||||
H: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
||||
hh: digitModifier(
|
||||
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
|
||||
'hh': digitModifier(
|
||||
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
|
||||
h: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
jj: datePartGetterFactory(digitCondition('hour', 2)),
|
||||
j: datePartGetterFactory(digitCondition('hour', 1)),
|
||||
mm: digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
|
||||
m: datePartGetterFactory(digitCondition('minute', 1)),
|
||||
ss: digitModifier(datePartGetterFactory(digitCondition('second', 2))),
|
||||
s: datePartGetterFactory(digitCondition('second', 1)),
|
||||
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
'jj': datePartGetterFactory(digitCondition('hour', 2)),
|
||||
'j': datePartGetterFactory(digitCondition('hour', 1)),
|
||||
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
|
||||
'm': datePartGetterFactory(digitCondition('minute', 1)),
|
||||
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
|
||||
's': datePartGetterFactory(digitCondition('second', 1)),
|
||||
// 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
|
||||
// fractions
|
||||
sss: datePartGetterFactory(digitCondition('second', 3)),
|
||||
EEEE: datePartGetterFactory(nameCondition('weekday', 4)),
|
||||
EEE: datePartGetterFactory(nameCondition('weekday', 3)),
|
||||
EE: datePartGetterFactory(nameCondition('weekday', 2)),
|
||||
E: datePartGetterFactory(nameCondition('weekday', 1)),
|
||||
a: hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
Z: timeZoneGetter('short'),
|
||||
z: timeZoneGetter('long'),
|
||||
ww: datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
||||
// first Thursday of the year. not support ?
|
||||
w: datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
||||
'sss': datePartGetterFactory(digitCondition('second', 3)),
|
||||
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
|
||||
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
|
||||
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
|
||||
'E': datePartGetterFactory(nameCondition('weekday', 1)),
|
||||
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
|
||||
'Z': timeZoneGetter('short'),
|
||||
'z': timeZoneGetter('long'),
|
||||
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
|
||||
// first Thursday of the year. not support ?
|
||||
'w':
|
||||
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
|
||||
// of the year not support ?
|
||||
G: datePartGetterFactory(nameCondition('era', 1)),
|
||||
GG: datePartGetterFactory(nameCondition('era', 2)),
|
||||
GGG: datePartGetterFactory(nameCondition('era', 3)),
|
||||
GGGG: datePartGetterFactory(nameCondition('era', 4))
|
||||
'G': datePartGetterFactory(nameCondition('era', 1)),
|
||||
'GG': datePartGetterFactory(nameCondition('era', 2)),
|
||||
'GGG': datePartGetterFactory(nameCondition('era', 3)),
|
||||
'GGGG': datePartGetterFactory(nameCondition('era', 4))
|
||||
};
|
||||
|
||||
|
||||
@ -184,7 +187,8 @@ function dateFormatter(format: string, date: Date, locale: string): string {
|
||||
|
||||
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) {
|
||||
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) => {
|
@ -8,9 +8,9 @@
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
|
||||
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
||||
|
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');
|
@ -7,13 +7,10 @@
|
||||
*/
|
||||
|
||||
import {LOCALE_ID} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
|
||||
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('l10n', () => {
|
||||
|
||||
@ -141,6 +138,23 @@ export function main() {
|
||||
expect(getPluralCategory(5, ['one', 'other', '=5'], l10n)).toEqual('=5');
|
||||
expect(getPluralCategory(6, ['one', 'other', '=5'], l10n)).toEqual('other');
|
||||
});
|
||||
|
||||
it('should fallback to other when the case is not present', () => {
|
||||
const l10n = new NgLocaleLocalization('ro');
|
||||
expect(getPluralCategory(1, ['one', 'other'], l10n)).toEqual('one');
|
||||
// 2 -> 'few'
|
||||
expect(getPluralCategory(2, ['one', 'other'], l10n)).toEqual('other');
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should report an error when the "other" category is not present', () => {
|
||||
expect(() => {
|
||||
const l10n = new NgLocaleLocalization('ro');
|
||||
// 2 -> 'few'
|
||||
getPluralCategory(2, ['one'], l10n);
|
||||
}).toThrowError('No plural message found for value "2"');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,13 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||
export {CodeGenerator} from './src/codegen';
|
||||
export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host';
|
||||
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
|
||||
export {Extractor} from './src/extractor';
|
||||
|
||||
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>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 {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 {AnimateCmp} from './animate';
|
||||
@ -19,7 +21,7 @@ import {BasicComp} from './basic';
|
||||
import {ComponentUsingThirdParty} from './comp_using_3rdp';
|
||||
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
|
||||
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
|
||||
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
|
||||
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomeLibModule, SomePipeInRootModule, SomeService} from './module_fixtures';
|
||||
import {CompWithNgContent, ProjectingComp} from './projection';
|
||||
import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, DirectiveForQuery} from './queries';
|
||||
|
||||
@ -52,7 +54,7 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
|
||||
FormsModule,
|
||||
MdButtonModule,
|
||||
ModuleUsingCustomElements,
|
||||
someLibModuleWithProviders(),
|
||||
SomeLibModule.withProviders(),
|
||||
ThirdpartyModule,
|
||||
],
|
||||
providers: [SomeService],
|
||||
|
@ -63,17 +63,13 @@ export function provideValueWithEntryComponents(value: any) {
|
||||
entryComponents: [CompUsingLibModuleDirectiveAndPipe],
|
||||
})
|
||||
export class SomeLibModule {
|
||||
}
|
||||
|
||||
// TODO(tbosch): Make this a static method in `SomeLibModule` once
|
||||
// our static reflector supports it.
|
||||
// See https://github.com/angular/angular/issues/10266.
|
||||
export function someLibModuleWithProviders(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: SomeLibModule,
|
||||
providers: [
|
||||
ServiceUsingLibModule,
|
||||
provideValueWithEntryComponents([{a: 'b', component: CompUsingLibModuleDirectiveAndPipe}])
|
||||
]
|
||||
};
|
||||
static withProviders() {
|
||||
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)>
|
||||
]>
|
||||
<messagebundle>
|
||||
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
|
||||
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
|
||||
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
|
||||
<msg id="3772663375917578720">other-3rdP-component</msg>
|
||||
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
|
||||
<msg id="3492007542396725315">Welcome</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
||||
@ -79,5 +79,4 @@ describe('template i18n extraction output', () => {
|
||||
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
|
||||
expect(xlf).toEqual(EXPECTED_XLIFF);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
/* tslint:disable:no-console */
|
||||
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as assert from 'assert';
|
||||
import {tsc} from '@angular/tsc-wrapped/src/tsc';
|
||||
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
|
||||
|
||||
const glob = require('glob');
|
||||
|
||||
|
||||
/**
|
||||
* Main method.
|
||||
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||
* properly read and wrote.
|
||||
*/
|
||||
function main() {
|
||||
console.log(`testing ngtools API...`);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => codeGenTest())
|
||||
.then(() => lazyRoutesTest())
|
||||
.then(() => {
|
||||
console.log('All done!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.stack);
|
||||
console.error('Test failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function codeGenTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
const hostContext = new NodeCompilerHostContext();
|
||||
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
||||
const host: ts.CompilerHost = Object.assign({}, delegateHost, {
|
||||
writeFile: (fileName: string, ...rest: any[]) => {
|
||||
wroteFiles.push(fileName);
|
||||
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
|
||||
}
|
||||
});
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.codeGen({
|
||||
basePath,
|
||||
compilerOptions: config.parsed.options, program, host,
|
||||
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: null,
|
||||
i18nFile: null,
|
||||
locale: null,
|
||||
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
return hostContext.readResource(fileName);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`>>> codegen done, asserting read and wrote files`);
|
||||
|
||||
// Assert for each file that it has been read and each `ts` has a written file associated.
|
||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||
|
||||
allFiles.forEach((fileName: string) => {
|
||||
// Skip tsconfig.
|
||||
if (fileName.match(/tsconfig-build.json$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that file was read.
|
||||
if (fileName.match(/\.module\.ts$/)) {
|
||||
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
|
||||
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
|
||||
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||
assert(
|
||||
readResources.indexOf(fileName) != -1,
|
||||
`Expected resource "${fileName}" to be read.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function lazyRoutesTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
const host = ts.createCompilerHost(config.parsed.options, true);
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
|
||||
program,
|
||||
host,
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
entryModule: 'app.module#AppModule'
|
||||
});
|
||||
|
||||
const expectations: {[route: string]: string} = {
|
||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||
'./default.module': 'feature2/default.module.ts',
|
||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||
};
|
||||
|
||||
Object.keys(lazyRoutes).forEach((route: string) => {
|
||||
assert(route in expectations, `Found a route that was not expected: "${route}".`);
|
||||
assert(
|
||||
lazyRoutes[route] == path.join(basePath, expectations[route]),
|
||||
`Route "${route}" does not point to the expected absolute path ` +
|
||||
`"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`);
|
||||
});
|
||||
|
||||
// Verify that all expectations were met.
|
||||
assert.deepEqual(
|
||||
Object.keys(lazyRoutes), Object.keys(expectations), `Expected routes listed to be: \n` +
|
||||
` ${JSON.stringify(Object.keys(expectations))}\n` +
|
||||
`Actual:\n` +
|
||||
` ${JSON.stringify(Object.keys(lazyRoutes))}\n`);
|
||||
}
|
||||
|
||||
main();
|
@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
/* tslint:disable:no-console */
|
||||
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as assert from 'assert';
|
||||
import {tsc} from '@angular/tsc-wrapped/src/tsc';
|
||||
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
|
||||
|
||||
/**
|
||||
* Main method.
|
||||
* Standalone program that executes the real codegen and tests that
|
||||
* ngsummary.json files are used for libraries.
|
||||
*/
|
||||
function main() {
|
||||
console.log(`testing usage of ngsummary.json files in libraries...`);
|
||||
const basePath = path.resolve(__dirname, '..');
|
||||
const project = path.resolve(basePath, 'tsconfig-build.json');
|
||||
const readFiles: string[] = [];
|
||||
|
||||
class AssertingHostContext extends NodeCompilerHostContext {
|
||||
readFile(fileName: string): string {
|
||||
readFiles.push(path.relative(basePath, fileName));
|
||||
return super.readFile(fileName);
|
||||
}
|
||||
readResource(s: string): Promise<string> {
|
||||
readFiles.push(path.relative(basePath, s));
|
||||
return super.readResource(s);
|
||||
}
|
||||
}
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
config.ngOptions.basePath = basePath;
|
||||
// This flag tells ngc do not recompile libraries.
|
||||
config.ngOptions.generateCodeForLibraries = false;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
codegen(config, (host) => new AssertingHostContext())
|
||||
.then((exitCode: any) => {
|
||||
console.log(`>>> codegen done, asserting read files`);
|
||||
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
|
||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
|
||||
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
|
||||
|
||||
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
|
||||
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
|
||||
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
|
||||
console.log(`done, no errors.`);
|
||||
process.exit(exitCode);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext
|
||||
*/
|
||||
function codegen(
|
||||
config: {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions},
|
||||
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
|
||||
const host = ts.createCompilerHost(config.parsed.options, true);
|
||||
|
||||
// HACK: patch the realpath to solve symlink issue here:
|
||||
// https://github.com/Microsoft/TypeScript/issues/9552
|
||||
// todo(misko): remove once facade symlinks are removed
|
||||
host.realpath = (path) => path;
|
||||
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
return CodeGenerator.create(config.ngOptions, {
|
||||
} as any, program, host, hostContextFactory(host)).codegen();
|
||||
}
|
||||
|
||||
function assertSomeFileMatch(fileNames: string[], pattern: RegExp) {
|
||||
assert(
|
||||
fileNames.some(fileName => pattern.test(fileName)),
|
||||
`Expected some read files match ${pattern}`);
|
||||
}
|
||||
|
||||
function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
|
||||
const matches = fileNames.filter(fileName => pattern.test(fileName));
|
||||
assert(
|
||||
matches.length === 0,
|
||||
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
|
||||
}
|
||||
|
||||
main();
|
@ -21,6 +21,8 @@
|
||||
"src/module",
|
||||
"src/bootstrap",
|
||||
"test/all_spec",
|
||||
"test/test_ngtools_api",
|
||||
"test/test_summaries",
|
||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2/index_aot.ts",
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "^0.4.0",
|
||||
"@angular/tsc-wrapped": "0.5.0",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -17,12 +17,13 @@ import {readFileSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext} from './compiler_host';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {Console} from './private_import_core';
|
||||
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
const GENERATED_META_FILES = /\.json$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
|
||||
const PREAMBLE = `/**
|
||||
* @fileoverview This file is generated by the Angular 2 template compiler.
|
||||
@ -44,7 +45,7 @@ export class CodeGenerator {
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
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) {
|
||||
root = eachRootDir;
|
||||
@ -68,10 +69,11 @@ export class CodeGenerator {
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -82,9 +84,9 @@ export class CodeGenerator {
|
||||
ngCompilerHost?: CompilerHost): CodeGenerator {
|
||||
if (!ngCompilerHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
ngCompilerHost = usePathMapping ?
|
||||
new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) :
|
||||
new CompilerHost(program, tsCompilerHost, options, compilerHostContext);
|
||||
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
|
||||
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
|
||||
new CompilerHost(program, options, context);
|
||||
}
|
||||
const transFile = cliOptions.i18nFile;
|
||||
const locale = cliOptions.locale;
|
||||
|
@ -15,32 +15,27 @@ import * as ts from 'typescript';
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
|
||||
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||
|
||||
export interface CompilerHostContext {
|
||||
fileExists(fileName: string): boolean;
|
||||
directoryExists(directoryName: string): boolean;
|
||||
readFile(fileName: string): string;
|
||||
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
||||
readResource(fileName: string): Promise<string>;
|
||||
assumeFileExists(fileName: string): void;
|
||||
}
|
||||
|
||||
export class CompilerHost implements AotCompilerHost {
|
||||
protected metadataCollector = new MetadataCollector();
|
||||
protected context: CompilerHostContext;
|
||||
private isGenDirChildOfRootDir: boolean;
|
||||
protected basePath: string;
|
||||
private genDir: string;
|
||||
private resolverCache = new Map<string, ModuleMetadata[]>();
|
||||
|
||||
constructor(
|
||||
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
|
||||
protected options: AngularCompilerOptions, context?: CompilerHostContext) {
|
||||
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, '/');
|
||||
|
||||
this.context = context || new NodeCompilerHostContext(compilerHost);
|
||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||
}
|
||||
@ -81,7 +76,7 @@ export class CompilerHost implements AotCompilerHost {
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists it so that the `resolve` method works!
|
||||
if (!this.compilerHost.fileExists(importedFile)) {
|
||||
if (!this.context.fileExists(importedFile)) {
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
|
||||
@ -181,29 +176,32 @@ export class CompilerHost implements AotCompilerHost {
|
||||
const metadatas = metadataOrMetadatas ?
|
||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||
[];
|
||||
const v1Metadata = metadatas.find(m => m['version'] === 1);
|
||||
let v2Metadata = metadatas.find(m => m['version'] === 2);
|
||||
if (!v2Metadata && v1Metadata) {
|
||||
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
|
||||
const v1Metadata = metadatas.find((m: any) => m['version'] === 1);
|
||||
let v3Metadata = metadatas.find((m: any) => m['version'] === 3);
|
||||
if (!v3Metadata && v1Metadata) {
|
||||
// patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file
|
||||
// as the only difference between the versions is whether all exports are contained in
|
||||
// the metadata
|
||||
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
|
||||
// the metadata and the `extends` clause.
|
||||
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
|
||||
if (v1Metadata.exports) {
|
||||
v2Metadata.exports = v1Metadata.exports;
|
||||
v3Metadata.exports = v1Metadata.exports;
|
||||
}
|
||||
for (let prop in v1Metadata.metadata) {
|
||||
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||
}
|
||||
const sourceText = this.context.readFile(dtsFilePath);
|
||||
|
||||
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
|
||||
if (exports) {
|
||||
for (let prop in exports.metadata) {
|
||||
if (!v2Metadata.metadata[prop]) {
|
||||
v2Metadata.metadata[prop] = exports.metadata[prop];
|
||||
if (!v3Metadata.metadata[prop]) {
|
||||
v3Metadata.metadata[prop] = exports.metadata[prop];
|
||||
}
|
||||
}
|
||||
if (exports.exports) {
|
||||
v3Metadata.exports = exports.exports;
|
||||
}
|
||||
}
|
||||
metadatas.push(v2Metadata);
|
||||
metadatas.push(v3Metadata);
|
||||
}
|
||||
this.resolverCache.set(filePath, metadatas);
|
||||
return metadatas;
|
||||
@ -214,17 +212,52 @@ export class CompilerHost implements AotCompilerHost {
|
||||
}
|
||||
|
||||
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
|
||||
|
||||
loadSummary(filePath: string): string { return this.context.readFile(filePath); }
|
||||
|
||||
getOutputFileName(sourceFilePath: string): string {
|
||||
return sourceFilePath.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeCompilerHostContext implements CompilerHostContext {
|
||||
constructor(private host: ts.CompilerHost) {}
|
||||
export class CompilerHostContextAdapter {
|
||||
protected assumedExists: {[fileName: string]: boolean} = {};
|
||||
|
||||
private 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();
|
||||
@ -236,12 +269,10 @@ export class NodeCompilerHostContext implements CompilerHostContext {
|
||||
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
||||
|
||||
readResource(s: string) {
|
||||
if (!this.host.fileExists(s)) {
|
||||
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.host.readFile(s));
|
||||
return Promise.resolve(this.readFile(s));
|
||||
}
|
||||
|
||||
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
||||
}
|
||||
|
@ -41,9 +41,8 @@ function extract(
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
ext = 'xlf';
|
||||
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
||||
serializer = new compiler.Xliff();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {excludeFilePattern} from './codegen';
|
||||
import {CompilerHost} from './compiler_host';
|
||||
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
@ -32,8 +32,10 @@ export class Extractor {
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||
tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options);
|
||||
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost)
|
||||
ngCompilerHost =
|
||||
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
|
||||
const {extractor: ngExtractor} = compiler.Extractor.create(
|
||||
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
|
||||
return new Extractor(ngExtractor, ngCompilerHost, program);
|
||||
|
@ -22,14 +22,25 @@ function codegen(
|
||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
||||
}
|
||||
|
||||
export function main(
|
||||
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
|
||||
const project = args.p || args.project || '.';
|
||||
const cliOptions = new tsc.NgcCliOptions(args);
|
||||
|
||||
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
|
||||
if (e instanceof tsc.UserError) {
|
||||
consoleError(e.message);
|
||||
return Promise.resolve(1);
|
||||
} else {
|
||||
consoleError(e.stack);
|
||||
consoleError('Compilation failed');
|
||||
return Promise.resolve(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
const project = args.p || args.project || '.';
|
||||
const cliOptions = new tsc.NgcCliOptions(args);
|
||||
tsc.main(project, cliOptions, codegen).then(exitCode => process.exit(exitCode)).catch(e => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
process.exit(1);
|
||||
});
|
||||
main(args).then((exitCode: number) => process.exit(exitCode));
|
||||
}
|
||||
|
124
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
124
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a private API for the ngtools toolkit.
|
||||
*
|
||||
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||
* something else.
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, StaticReflector} from '@angular/compiler';
|
||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CodeGenerator} from './codegen';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||
basePath: string;
|
||||
compilerOptions: ts.CompilerOptions;
|
||||
program: ts.Program;
|
||||
host: ts.CompilerHost;
|
||||
|
||||
angularCompilerOptions: AngularCompilerOptions;
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: string;
|
||||
i18nFile: string;
|
||||
locale: string;
|
||||
|
||||
readResource: (fileName: string) => Promise<string>;
|
||||
|
||||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
||||
program: ts.Program;
|
||||
host: ts.CompilerHost;
|
||||
angularCompilerOptions: AngularCompilerOptions;
|
||||
entryModule: string;
|
||||
|
||||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
||||
|
||||
|
||||
/**
|
||||
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||
* passed in the interface.
|
||||
*/
|
||||
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
|
||||
constructor(
|
||||
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
|
||||
super(host);
|
||||
}
|
||||
|
||||
readResource(path: string) { return this._readResource(path); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
export class NgTools_InternalApi_NG_2 {
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<void> {
|
||||
const hostContext: CompilerHostContext =
|
||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||
const cliOptions: NgcCliOptions = {
|
||||
i18nFormat: options.i18nFormat,
|
||||
i18nFile: options.i18nFile,
|
||||
locale: options.locale,
|
||||
basePath: options.basePath
|
||||
};
|
||||
|
||||
// Create the Code Generator.
|
||||
const codeGenerator = CodeGenerator.create(
|
||||
options.angularCompilerOptions, cliOptions, options.program, options.host, hostContext);
|
||||
|
||||
return codeGenerator.codegen();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
|
||||
NgTools_InternalApi_NG_2_LazyRouteMap {
|
||||
const angularCompilerOptions = options.angularCompilerOptions;
|
||||
const program = options.program;
|
||||
|
||||
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
|
||||
const usePathMapping =
|
||||
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
|
||||
const ngCompilerHost: AotCompilerHost = usePathMapping ?
|
||||
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
||||
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
||||
|
||||
const staticReflector = new StaticReflector(ngCompilerHost);
|
||||
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
||||
|
||||
return Object.keys(routeMap).reduce(
|
||||
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
|
||||
acc[route] = routeMap[route].absoluteFilePath;
|
||||
return acc;
|
||||
},
|
||||
{});
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}, []);
|
||||
}
|
@ -25,10 +25,8 @@ const DTS = /\.d\.ts$/;
|
||||
* loader what to do.
|
||||
*/
|
||||
export class PathMappedCompilerHost extends CompilerHost {
|
||||
constructor(
|
||||
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
|
||||
context?: CompilerHostContext) {
|
||||
super(program, compilerHost, options, context);
|
||||
constructor(program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext) {
|
||||
super(program, options, context);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
@ -56,7 +54,7 @@ export class PathMappedCompilerHost extends CompilerHost {
|
||||
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
|
||||
if (resolved) {
|
||||
if (this.options.traceResolution) {
|
||||
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
||||
console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
||||
}
|
||||
return this.getCanonicalFileName(resolved.resolvedFileName);
|
||||
}
|
||||
@ -71,7 +69,7 @@ export class PathMappedCompilerHost extends CompilerHost {
|
||||
*/
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
if (this.options.traceResolution) {
|
||||
console.log(
|
||||
console.error(
|
||||
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
|
||||
}
|
||||
|
||||
@ -119,7 +117,7 @@ export class PathMappedCompilerHost extends CompilerHost {
|
||||
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||
for (const root of this.options.rootDirs || []) {
|
||||
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.
|
||||
// This will occur if the user refernced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
|
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');
|
@ -14,14 +14,13 @@ import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
|
||||
|
||||
describe('CompilerHost', () => {
|
||||
let context: MockAotContext;
|
||||
let host: ts.CompilerHost;
|
||||
let program: ts.Program;
|
||||
let hostNestedGenDir: CompilerHost;
|
||||
let hostSiblingGenDir: CompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new MockAotContext('/tmp/src', clone(FILES));
|
||||
host = new MockCompilerHost(context);
|
||||
const host = new MockCompilerHost(context);
|
||||
program = ts.createProgram(
|
||||
['main.ts'], {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
@ -33,7 +32,7 @@ describe('CompilerHost', () => {
|
||||
throw new Error('Expected no errors');
|
||||
}
|
||||
hostNestedGenDir = new CompilerHost(
|
||||
program, host, {
|
||||
program, {
|
||||
genDir: '/tmp/project/src/gen/',
|
||||
basePath: '/tmp/project/src',
|
||||
skipMetadataEmit: false,
|
||||
@ -43,7 +42,7 @@ describe('CompilerHost', () => {
|
||||
},
|
||||
context);
|
||||
hostSiblingGenDir = new CompilerHost(
|
||||
program, host, {
|
||||
program, {
|
||||
genDir: '/tmp/project/gen',
|
||||
basePath: '/tmp/project/src/',
|
||||
skipMetadataEmit: false,
|
||||
@ -67,11 +66,14 @@ describe('CompilerHost', () => {
|
||||
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
'/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.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
'/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', () => {
|
||||
@ -84,6 +86,12 @@ describe('CompilerHost', () => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -158,12 +166,18 @@ describe('CompilerHost', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
|
||||
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: 2,
|
||||
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
|
||||
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']}],
|
||||
}
|
||||
]);
|
||||
});
|
||||
@ -198,7 +212,17 @@ const FILES: Entry = {
|
||||
}
|
||||
},
|
||||
'metadata_versions': {
|
||||
'v1.d.ts': 'export declare class bar {}',
|
||||
'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"}}}`,
|
||||
}
|
||||
|
177
modules/@angular/compiler-cli/test/main_spec.ts
Normal file
177
modules/@angular/compiler-cli/test/main_spec.ts
Normal file
@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import {main} from '../src/main';
|
||||
import {ReflectionCapabilities, reflector} from './private_import_core';
|
||||
|
||||
|
||||
describe('compiler-cli', () => {
|
||||
let basePath: string;
|
||||
let write: (fileName: string, content: string) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
basePath = makeTempDir();
|
||||
write = (fileName: string, content: string) => {
|
||||
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||
};
|
||||
write('tsconfig.json', `{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"types": [],
|
||||
"outDir": "built",
|
||||
"declaration": true,
|
||||
"module": "es2015"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true
|
||||
},
|
||||
"files": ["test.ts"]
|
||||
}`);
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
it('should compile without errors', (done) => {
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error).not.toHaveBeenCalled();
|
||||
expect(exitCode).toEqual(0);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not print the stack trace if user input file does not exist', (done) => {
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not print the stack trace if user input file is malformed', (done) => {
|
||||
write('test.ts', 'foo;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not print the stack trace if cannot find the imported module', (done) => {
|
||||
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
`:1:23: Cannot find module './not-exist-deps'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not print the stack trace if cannot import', (done) => {
|
||||
write('empty-deps.ts', 'export const A = 1;');
|
||||
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
||||
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should not print the stack trace if type mismatches', (done) => {
|
||||
write('empty-deps.ts', 'export const A = "abc";');
|
||||
write('test.ts', `
|
||||
import {A} from './empty-deps';
|
||||
A();
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
':3:7: Cannot invoke an expression whose type lacks a call signature.');
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
|
||||
it('should print the stack trace on compiler internal errors', (done) => {
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: 'not-exist'}, mockConsole.error)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error).toHaveBeenCalled();
|
||||
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
.catch(e => done.fail(e));
|
||||
});
|
||||
});
|
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;
|
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": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"noImplicitAny": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": true,
|
||||
"module": "commonjs",
|
||||
"outDir": "../../../dist/packages-dist/compiler-cli",
|
||||
"paths": {
|
||||
"@angular/core": ["../../../dist/packages-dist/core"],
|
||||
"@angular/common": ["../../../dist/packages-dist/common"],
|
||||
@ -14,11 +14,11 @@
|
||||
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
|
||||
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
|
||||
},
|
||||
"experimentalDecorators": true,
|
||||
"rootDir": ".",
|
||||
"sourceRoot": ".",
|
||||
"outDir": "../../../dist/packages-dist/compiler-cli",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["integrationtest"],
|
||||
|
@ -21,6 +21,7 @@
|
||||
* </p>
|
||||
* </div>
|
||||
*/
|
||||
export {VERSION} from './src/version';
|
||||
export * from './src/template_parser/template_ast';
|
||||
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
|
||||
export {CompilerConfig, RenderTypes} from './src/config';
|
||||
@ -31,6 +32,7 @@ export * from './src/aot/compiler_host';
|
||||
export * from './src/aot/static_reflector';
|
||||
export * from './src/aot/static_reflection_capabilities';
|
||||
export * from './src/aot/static_symbol';
|
||||
export * from './src/aot/summary_resolver';
|
||||
export {JitCompiler} from './src/jit/compiler';
|
||||
export * from './src/jit/compiler_factory';
|
||||
export * from './src/url_resolver';
|
||||
@ -58,5 +60,4 @@ export * from './src/style_compiler';
|
||||
export * from './src/template_parser/template_parser';
|
||||
export {ViewCompiler} from './src/view_compiler/view_compiler';
|
||||
export {AnimationParser} from './src/animation/animation_parser';
|
||||
|
||||
// This file only reexports content of the `src` folder. Keep it that way.
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
||||
|
||||
@ -69,8 +69,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
stylesArr.push(o.literalMap(entries));
|
||||
});
|
||||
|
||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||
o.importExpr(resolveIdentifier(Identifiers.collectAndResolveStyles)).callFn([
|
||||
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||
o.importExpr(createIdentifier(Identifiers.collectAndResolveStyles)).callFn([
|
||||
_ANIMATION_COLLECTED_STYLES, o.literalArr(stylesArr)
|
||||
])
|
||||
]);
|
||||
@ -78,7 +78,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
visitAnimationKeyframe(ast: AnimationKeyframeAst, context: _AnimationBuilderContext):
|
||||
o.Expression {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationKeyframe)).instantiate([
|
||||
return o.importExpr(createIdentifier(Identifiers.AnimationKeyframe)).instantiate([
|
||||
o.literal(ast.offset), ast.styles.visit(this, context)
|
||||
]);
|
||||
}
|
||||
@ -100,7 +100,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
const startingStylesExpr = ast.startingStyles.visit(this, context);
|
||||
const keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
|
||||
const keyframesExpr =
|
||||
o.importExpr(resolveIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
|
||||
o.importExpr(createIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
|
||||
_ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR,
|
||||
o.literalArr(keyframeExpressions)
|
||||
]);
|
||||
@ -127,14 +127,14 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext):
|
||||
o.Expression {
|
||||
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
|
||||
return o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
|
||||
o.literalArr(playerExprs)
|
||||
]);
|
||||
}
|
||||
|
||||
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
|
||||
const playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
|
||||
return o.importExpr(createIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
|
||||
o.literalArr(playerExprs)
|
||||
]);
|
||||
}
|
||||
@ -199,8 +199,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||
'getAnimationPlayers',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||
.conditional(o.NULL_EXPR, o.literal(this.animationName))
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
@ -228,7 +229,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR),
|
||||
[_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()]));
|
||||
|
||||
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
|
||||
const RENDER_STYLES_FN = o.importExpr(createIdentifier(Identifiers.renderStyles));
|
||||
|
||||
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
||||
|
||||
@ -237,7 +238,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
statements.push(new o.IfStmt(
|
||||
_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR),
|
||||
[_ANIMATION_PLAYER_VAR
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.NoOpAnimationPlayer)).instantiate([]))
|
||||
.toStmt()]));
|
||||
|
||||
// once complete we want to apply the styles on the element
|
||||
@ -255,17 +256,18 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(
|
||||
resolveIdentifier(Identifiers.prepareFinalAnimationStyles))
|
||||
.callFn([
|
||||
_ANIMATION_START_STATE_STYLES_VAR,
|
||||
_ANIMATION_END_STATE_STYLES_VAR
|
||||
])
|
||||
createIdentifier(Identifiers.prepareFinalAnimationStyles))
|
||||
.callFn(
|
||||
[
|
||||
_ANIMATION_START_STATE_STYLES_VAR,
|
||||
_ANIMATION_END_STATE_STYLES_VAR
|
||||
])
|
||||
])
|
||||
.toStmt()
|
||||
])])
|
||||
.toStmt());
|
||||
|
||||
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
|
||||
statements.push(o.importExpr(createIdentifier(Identifiers.AnimationSequencePlayer))
|
||||
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
|
||||
.callMethod('destroy', [])
|
||||
.toStmt());
|
||||
@ -276,7 +278,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
statements.push(RENDER_STYLES_FN
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
||||
o.importExpr(createIdentifier(Identifiers.clearStyles))
|
||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
])
|
||||
.toStmt());
|
||||
@ -291,7 +293,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
.toStmt());
|
||||
|
||||
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_TIME_VAR
|
||||
])));
|
||||
@ -300,12 +302,12 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
[
|
||||
new o.FnParam(
|
||||
_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_CURRENT_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 {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {ParseError} from '../parse_util';
|
||||
@ -41,7 +41,7 @@ export class AnimationParser {
|
||||
|
||||
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
|
||||
const errors: string[] = [];
|
||||
const componentName = component.type.name;
|
||||
const componentName = identifierName(component.type);
|
||||
const animationTriggerNames = new Set<string>();
|
||||
const asts = component.template.animations.map(entry => {
|
||||
const result = this.parseEntry(entry);
|
||||
@ -174,6 +174,11 @@ function _normalizeStyleMetadata(
|
||||
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
schema: ElementSchemaRegistry, errors: AnimationParseError[],
|
||||
permitStateReferences: boolean): {[key: string]: string | number}[] {
|
||||
const offset = entry.offset;
|
||||
if (offset > 1 || offset < 0) {
|
||||
errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`));
|
||||
}
|
||||
|
||||
const normalizedStyles: {[key: string]: string | number}[] = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (typeof styleEntry === 'string') {
|
||||
|
@ -10,11 +10,11 @@ import {SchemaMetadata} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, createHostComponentMeta} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
@ -24,12 +24,11 @@ import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
export class SourceModule {
|
||||
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
|
||||
}
|
||||
import {AotSummaryResolver} from './summary_resolver';
|
||||
import {filterFileByPatterns} from './utils';
|
||||
|
||||
export class AotCompiler {
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
@ -39,31 +38,45 @@ export class AotCompiler {
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _localeId: string, private _translationFormat: string,
|
||||
private _animationParser: AnimationParser, private _staticReflector: StaticReflector,
|
||||
private _options: AotCompilerOptions) {}
|
||||
private _summaryResolver: AotSummaryResolver, private _localeId: string,
|
||||
private _translationFormat: string, private _animationParser: AnimationParser,
|
||||
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {}
|
||||
|
||||
clearCache() { this._metadataResolver.clearCache(); }
|
||||
|
||||
compileAll(rootFiles: string[]): Promise<SourceModule[]> {
|
||||
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
|
||||
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
|
||||
return loadNgModuleDirectives(ngModules).then(() => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
ngModule.type.reference, false)))
|
||||
.then(() => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
|
||||
file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] {
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]): GeneratedFile[] {
|
||||
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
||||
const statements: o.Statement[] = [];
|
||||
const exportedVars: string[] = [];
|
||||
const outputSourceModules: SourceModule[] = [];
|
||||
const generatedFiles: GeneratedFile[] = [];
|
||||
|
||||
// write summary files
|
||||
const summaries: CompileTypeSummary[] = [
|
||||
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
|
||||
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
|
||||
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref))
|
||||
];
|
||||
generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries));
|
||||
|
||||
// compile all ng modules
|
||||
exportedVars.push(
|
||||
@ -82,7 +95,7 @@ export class AotCompiler {
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
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);
|
||||
@ -90,7 +103,7 @@ export class AotCompiler {
|
||||
// compile styles
|
||||
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
||||
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
||||
outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||
generatedFiles.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||
});
|
||||
|
||||
// compile components
|
||||
@ -103,9 +116,9 @@ export class AotCompiler {
|
||||
if (statements.length > 0) {
|
||||
const srcModule = this._codegenSourceModule(
|
||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||
outputSourceModules.unshift(srcModule);
|
||||
generatedFiles.unshift(srcModule);
|
||||
}
|
||||
return outputSourceModules;
|
||||
return generatedFiles;
|
||||
}
|
||||
|
||||
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
|
||||
@ -113,24 +126,24 @@ export class AotCompiler {
|
||||
const providers: CompileProviderMetadata[] = [];
|
||||
|
||||
if (this._localeId) {
|
||||
providers.push(new CompileProviderMetadata({
|
||||
token: resolveIdentifierToken(Identifiers.LOCALE_ID),
|
||||
providers.push({
|
||||
token: createIdentifierToken(Identifiers.LOCALE_ID),
|
||||
useValue: this._localeId,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if (this._translationFormat) {
|
||||
providers.push(new CompileProviderMetadata({
|
||||
token: resolveIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
|
||||
providers.push({
|
||||
token: createIdentifierToken(Identifiers.TRANSLATIONS_FORMAT),
|
||||
useValue: this._translationFormat
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
||||
|
||||
appCompileResult.dependencies.forEach((dep) => {
|
||||
dep.placeholder.name = _componentFactoryName(dep.comp);
|
||||
dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.moduleUrl);
|
||||
dep.placeholder.reference = this._staticReflector.getStaticSymbol(
|
||||
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
|
||||
});
|
||||
|
||||
targetStatements.push(...appCompileResult.statements);
|
||||
@ -149,14 +162,17 @@ export class AotCompiler {
|
||||
private _compileComponentFactory(
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
||||
targetStatements: o.Statement[]): string {
|
||||
const hostMeta = createHostComponentMeta(compMeta);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
this._staticReflector.getStaticSymbol(
|
||||
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
|
||||
compMeta);
|
||||
const hostViewFactoryVar = this._compileComponent(
|
||||
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
|
||||
const compFactoryVar = _componentFactoryName(compMeta.type);
|
||||
targetStatements.push(
|
||||
o.variable(compFactoryVar)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.ComponentFactory), [o.importType(
|
||||
compMeta.type)])
|
||||
.set(o.importExpr(
|
||||
createIdentifier(Identifiers.ComponentFactory), [o.importType(compMeta.type)])
|
||||
.instantiate(
|
||||
[
|
||||
o.literal(compMeta.selector),
|
||||
@ -164,7 +180,7 @@ export class AotCompiler {
|
||||
o.importExpr(compMeta.type),
|
||||
],
|
||||
o.importType(
|
||||
resolveIdentifier(Identifiers.ComponentFactory),
|
||||
createIdentifier(Identifiers.ComponentFactory),
|
||||
[o.importType(compMeta.type)], [o.TypeModifier.Const])))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
return compFactoryVar;
|
||||
@ -182,23 +198,24 @@ export class AotCompiler {
|
||||
|
||||
const parsedTemplate = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
||||
compMeta.type.name);
|
||||
identifierName(compMeta.type));
|
||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
const compiledAnimations =
|
||||
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
||||
const viewResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
|
||||
if (componentStyles) {
|
||||
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
|
||||
targetStatements.push(
|
||||
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix));
|
||||
}
|
||||
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
||||
targetStatements.push(..._resolveViewStatements(viewResult));
|
||||
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult));
|
||||
return viewResult.viewClassVar;
|
||||
}
|
||||
|
||||
private _codgenStyles(
|
||||
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
|
||||
_resolveStyleStatements(stylesCompileResult, fileSuffix);
|
||||
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
|
||||
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix);
|
||||
return this._codegenSourceModule(
|
||||
fileUrl, _stylesModuleUrl(
|
||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||
@ -206,26 +223,29 @@ export class AotCompiler {
|
||||
}
|
||||
|
||||
private _codegenSourceModule(
|
||||
fileUrl: string, moduleUrl: string, statements: o.Statement[],
|
||||
exportedVars: string[]): SourceModule {
|
||||
return new SourceModule(
|
||||
fileUrl, moduleUrl,
|
||||
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
|
||||
srcFileUrl: string, genFileUrl: string, statements: o.Statement[],
|
||||
exportedVars: string[]): GeneratedFile {
|
||||
return new GeneratedFile(
|
||||
srcFileUrl, genFileUrl,
|
||||
this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars));
|
||||
}
|
||||
}
|
||||
|
||||
function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] {
|
||||
function _resolveViewStatements(
|
||||
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] {
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
if (dep instanceof ViewClassDependency) {
|
||||
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) {
|
||||
const cfd = <ComponentFactoryDependency>dep;
|
||||
cfd.placeholder.name = _componentFactoryName(cfd.comp);
|
||||
cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp.moduleUrl);
|
||||
cfd.placeholder.reference = reflector.getStaticSymbol(
|
||||
_ngfactoryModuleUrl(identifierModuleUrl(cfd.comp)), _componentFactoryName(cfd.comp));
|
||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||
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;
|
||||
@ -233,9 +253,11 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[]
|
||||
|
||||
|
||||
function _resolveStyleStatements(
|
||||
compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] {
|
||||
reflector: StaticReflector, compileResult: CompiledStylesheet,
|
||||
fileSuffix: string): o.Statement[] {
|
||||
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;
|
||||
}
|
||||
@ -246,16 +268,17 @@ function _ngfactoryModuleUrl(dirUrl: string): string {
|
||||
}
|
||||
|
||||
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
|
||||
return `${comp.name}NgFactory`;
|
||||
return `${identifierName(comp)}NgFactory`;
|
||||
}
|
||||
|
||||
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) {
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,7 +299,12 @@ function _splitTypescriptSuffix(path: string): string[] {
|
||||
export interface NgAnalyzedModules {
|
||||
ngModules: CompileNgModuleMetadata[];
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
|
||||
files: Array<{
|
||||
srcUrl: string,
|
||||
directives: StaticSymbol[],
|
||||
pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]
|
||||
}>;
|
||||
symbolsMissingModule?: StaticSymbol[];
|
||||
}
|
||||
|
||||
@ -303,14 +331,6 @@ export function analyzeAndValidateNgModules(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wait for the directives in the given modules have been loaded
|
||||
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
|
||||
return Promise
|
||||
.all(ListWrapper.flatten(ngModules.map(
|
||||
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
|
||||
.then(() => {});
|
||||
}
|
||||
|
||||
function _analyzeNgModules(
|
||||
ngModuleMetas: CompileNgModuleMetadata[],
|
||||
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
|
||||
@ -319,11 +339,13 @@ function _analyzeNgModules(
|
||||
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
||||
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
||||
const ngPipesByFile = new Map<string, StaticSymbol[]>();
|
||||
const filePaths = new Set<string>();
|
||||
|
||||
// Looping over all modules to construct:
|
||||
// - a map from file to modules `ngModulesByFile`,
|
||||
// - a map from file to directives `ngDirectivesByFile`,
|
||||
// - a map from file to pipes `ngPipesByFile`,
|
||||
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
||||
ngModuleMetas.forEach((ngModuleMeta) => {
|
||||
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
||||
@ -341,16 +363,23 @@ function _analyzeNgModules(
|
||||
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[], ngModules: StaticSymbol[]}[] = [];
|
||||
const files:
|
||||
{srcUrl: string,
|
||||
directives: StaticSymbol[],
|
||||
pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]}[] = [];
|
||||
|
||||
filePaths.forEach((srcUrl) => {
|
||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||
const pipes = ngPipesByFile.get(srcUrl) || [];
|
||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||
files.push({srcUrl, directives, ngModules});
|
||||
files.push({srcUrl, directives, pipes, ngModules});
|
||||
});
|
||||
|
||||
return {
|
||||
@ -366,10 +395,10 @@ export function extractProgramSymbols(
|
||||
staticReflector: StaticReflector, files: string[],
|
||||
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
|
||||
const staticSymbols: StaticSymbol[] = [];
|
||||
files.filter(fileName => _filterFileByPatterns(fileName, options)).forEach(sourceFile => {
|
||||
files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => {
|
||||
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${sourceFile}`);
|
||||
console.error(`WARNING: no metadata found for ${sourceFile}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -404,16 +433,16 @@ function _createNgModules(
|
||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||
|
||||
const addNgModule = (staticSymbol: any) => {
|
||||
if (ngModules.has(staticSymbol) || !_filterFileByPatterns(staticSymbol.filePath, options)) {
|
||||
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) {
|
||||
return false;
|
||||
}
|
||||
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
|
||||
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
||||
if (ngModule) {
|
||||
ngModules.set(ngModule.type.reference, ngModule);
|
||||
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
||||
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
||||
// For every input module add the list of transitively included modules
|
||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
|
||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.reference));
|
||||
}
|
||||
return !!ngModule;
|
||||
};
|
||||
@ -430,15 +459,3 @@ function _createNgModules(
|
||||
|
||||
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
|
||||
}
|
||||
|
||||
function _filterFileByPatterns(
|
||||
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
|
||||
let match = true;
|
||||
if (options.includeFilePattern) {
|
||||
match = match && !!options.includeFilePattern.exec(fileName);
|
||||
}
|
||||
if (options.excludeFilePattern) {
|
||||
match = match && !options.excludeFilePattern.exec(fileName);
|
||||
}
|
||||
return match;
|
||||
}
|
@ -34,8 +34,7 @@ import {AotCompilerHost} from './compiler_host';
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
|
||||
|
||||
import {AotSummaryResolver} from './summary_resolver';
|
||||
|
||||
/**
|
||||
* Creates a new AotCompiler based on options and a host.
|
||||
@ -61,15 +60,17 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||
const console = new Console();
|
||||
const tmplParser =
|
||||
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
|
||||
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
||||
staticReflector);
|
||||
// TODO(vicb): do not pass options.i18nFormat here
|
||||
const compiler = new AotCompiler(
|
||||
resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||
new ViewCompiler(config, elementSchemaRegistry),
|
||||
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), options.locale,
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
|
||||
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
|
||||
return {compiler, reflector: staticReflector};
|
||||
}
|
||||
|
@ -10,15 +10,16 @@ import {ImportResolver} from '../output/path_util';
|
||||
|
||||
import {StaticReflectorHost} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
import {AotSummaryResolverHost} from './summary_resolver';
|
||||
|
||||
/**
|
||||
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
*/
|
||||
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver {
|
||||
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver,
|
||||
AotSummaryResolverHost {
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
loadResource(path: string): Promise<string>;
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
export class GeneratedFile {
|
||||
constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {}
|
||||
}
|
@ -10,7 +10,7 @@ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, Ho
|
||||
import {ReflectorReader} from '../private_import_core';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
const SUPPORTED_SCHEMA_VERSION = 2;
|
||||
const SUPPORTED_SCHEMA_VERSION = 3;
|
||||
const ANGULAR_IMPORT_LOCATIONS = {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/metadata',
|
||||
@ -20,6 +20,8 @@ const ANGULAR_IMPORT_LOCATIONS = {
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
|
||||
const HIDDEN_KEY = /^\$.*\$$/;
|
||||
|
||||
/**
|
||||
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
@ -70,16 +72,25 @@ export class StaticSymbolCache {
|
||||
export class StaticReflector implements ReflectorReader {
|
||||
private declarationCache = new Map<string, StaticSymbol>();
|
||||
private annotationCache = new Map<StaticSymbol, any[]>();
|
||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
|
||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||
private metadataCache = new Map<string, {[key: string]: any}>();
|
||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||
private opaqueToken: StaticSymbol;
|
||||
|
||||
constructor(
|
||||
private host: StaticReflectorHost,
|
||||
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
|
||||
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
|
||||
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
|
||||
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
|
||||
private errorRecorder?: (error: any, fileName: string) => void) {
|
||||
this.initializeConversionMap();
|
||||
knownMetadataClasses.forEach(
|
||||
(kc) => this._registerDecoratorOrConstructor(
|
||||
this.getStaticSymbol(kc.filePath, kc.name), kc.ctor));
|
||||
knownMetadataFunctions.forEach(
|
||||
(kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn));
|
||||
}
|
||||
|
||||
importUri(typeOrFunc: StaticSymbol): string {
|
||||
@ -99,29 +110,45 @@ export class StaticReflector implements ReflectorReader {
|
||||
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']) {
|
||||
annotations = this.simplify(type, classMetadata['decorators']);
|
||||
} else {
|
||||
annotations = [];
|
||||
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} {
|
||||
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 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']) {
|
||||
return this.simplify(type, prop['decorators']);
|
||||
} else {
|
||||
return [];
|
||||
decorators.push(...this.simplify(type, prop['decorators']));
|
||||
}
|
||||
});
|
||||
this.propertyCache.set(type, propMetadata);
|
||||
@ -131,7 +158,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
|
||||
public parameters(type: StaticSymbol): any[] {
|
||||
if (!(type instanceof StaticSymbol)) {
|
||||
throw new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`);
|
||||
this.reportError(
|
||||
new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`),
|
||||
type);
|
||||
return [];
|
||||
}
|
||||
try {
|
||||
let parameters = this.parameterCache.get(type);
|
||||
@ -155,6 +185,8 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
parameters.push(nestedResult);
|
||||
});
|
||||
} else if (classMetadata['extends']) {
|
||||
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
|
||||
}
|
||||
if (!parameters) {
|
||||
parameters = [];
|
||||
@ -163,28 +195,54 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
return parameters;
|
||||
} catch (e) {
|
||||
console.log(`Failed on type ${JSON.stringify(type)} with error ${e}`);
|
||||
console.error(`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`);
|
||||
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);
|
||||
}
|
||||
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;
|
||||
return methodNames;
|
||||
}
|
||||
|
||||
private registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||
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 {
|
||||
private _registerFunction(type: StaticSymbol, fn: any): void {
|
||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args));
|
||||
}
|
||||
|
||||
@ -193,50 +251,51 @@ export class StaticReflector implements ReflectorReader {
|
||||
ANGULAR_IMPORT_LOCATIONS;
|
||||
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
||||
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
|
||||
this.registerDecoratorOrConstructor(
|
||||
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._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._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
||||
this.registerDecoratorOrConstructor(
|
||||
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._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._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'Directive'), Directive);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this._registerDecoratorOrConstructor(
|
||||
this.findDeclaration(coreDecorators, 'Component'), Component);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
||||
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._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);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -250,11 +309,21 @@ export class StaticReflector implements ReflectorReader {
|
||||
return this.staticSymbolCache.get(declarationFile, name, members);
|
||||
}
|
||||
|
||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||
if (this.errorRecorder) {
|
||||
this.errorRecorder(error, (context && context.filePath) || path);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
|
||||
const resolveModule = (moduleName: string): string => {
|
||||
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
|
||||
if (!resolvedModulePath) {
|
||||
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
|
||||
this.reportError(
|
||||
new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`),
|
||||
null, filePath);
|
||||
}
|
||||
return resolvedModulePath;
|
||||
};
|
||||
@ -287,7 +356,12 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (typeof exportSymbol !== 'string') {
|
||||
symName = exportSymbol.name;
|
||||
}
|
||||
staticSymbol = this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
||||
const resolvedModule = resolveModule(moduleExport.from);
|
||||
if (resolvedModule) {
|
||||
staticSymbol =
|
||||
this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,10 +371,12 @@ export class StaticReflector implements ReflectorReader {
|
||||
for (const moduleExport of metadata['exports']) {
|
||||
if (!moduleExport.export) {
|
||||
const resolvedModule = resolveModule(moduleExport.from);
|
||||
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
|
||||
if (candidateSymbol) {
|
||||
staticSymbol = candidateSymbol;
|
||||
break;
|
||||
if (resolvedModule) {
|
||||
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
|
||||
if (candidateSymbol) {
|
||||
staticSymbol = candidateSymbol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -333,7 +409,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
|
||||
/** @internal */
|
||||
public simplify(context: StaticSymbol, value: any): any {
|
||||
const _this = this;
|
||||
const self = this;
|
||||
let scope = BindingScope.empty;
|
||||
const calling = new Map<StaticSymbol, boolean>();
|
||||
|
||||
@ -342,15 +418,15 @@ export class StaticReflector implements ReflectorReader {
|
||||
let staticSymbol: StaticSymbol;
|
||||
if (expression['module']) {
|
||||
staticSymbol =
|
||||
_this.findDeclaration(expression['module'], expression['name'], context.filePath);
|
||||
self.findDeclaration(expression['module'], expression['name'], context.filePath);
|
||||
} else {
|
||||
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
|
||||
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
|
||||
}
|
||||
return staticSymbol;
|
||||
}
|
||||
|
||||
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
||||
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
|
||||
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
|
||||
const declarationValue =
|
||||
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
|
||||
return declarationValue;
|
||||
@ -360,7 +436,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (value && value.__symbolic === 'new' && value.expression) {
|
||||
const target = value.expression;
|
||||
if (target.__symbolic == 'reference') {
|
||||
return sameSymbol(resolveReference(context, target), _this.opaqueToken);
|
||||
return sameSymbol(resolveReference(context, target), self.opaqueToken);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -542,24 +618,31 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (indexTarget && isPrimitive(index)) return indexTarget[index];
|
||||
return null;
|
||||
case 'select':
|
||||
let selectContext = context;
|
||||
let selectTarget = simplify(expression['expression']);
|
||||
if (selectTarget instanceof StaticSymbol) {
|
||||
// Access to a static instance variable
|
||||
const member: string = expression['member'];
|
||||
const members = selectTarget.members ?
|
||||
(selectTarget.members as string[]).concat(member) :
|
||||
[member];
|
||||
const declarationValue = resolveReferenceValue(selectTarget);
|
||||
selectContext =
|
||||
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||
if (declarationValue && declarationValue.statics) {
|
||||
selectTarget = declarationValue.statics;
|
||||
} else {
|
||||
const member: string = expression['member'];
|
||||
const members = selectTarget.members ?
|
||||
(selectTarget.members as string[]).concat(member) :
|
||||
[member];
|
||||
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||
return selectContext;
|
||||
}
|
||||
}
|
||||
const member = simplify(expression['member']);
|
||||
if (selectTarget && isPrimitive(member)) return simplify(selectTarget[member]);
|
||||
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
|
||||
if (selectTarget && isPrimitive(member))
|
||||
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
|
||||
return null;
|
||||
case 'reference':
|
||||
if (!expression['name']) {
|
||||
return context;
|
||||
}
|
||||
if (!expression.module) {
|
||||
const name: string = expression['name'];
|
||||
const localValue = scope.resolve(name);
|
||||
@ -589,11 +672,11 @@ export class StaticReflector implements ReflectorReader {
|
||||
let target = expression['expression'];
|
||||
if (target['module']) {
|
||||
staticSymbol =
|
||||
_this.findDeclaration(target['module'], target['name'], context.filePath);
|
||||
self.findDeclaration(target['module'], target['name'], context.filePath);
|
||||
} else {
|
||||
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
|
||||
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
|
||||
}
|
||||
let converter = _this.conversionMap.get(staticSymbol);
|
||||
let converter = self.conversionMap.get(staticSymbol);
|
||||
if (converter) {
|
||||
let args: any[] = expression['arguments'];
|
||||
if (!args) {
|
||||
@ -634,7 +717,16 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
}
|
||||
|
||||
const result = simplifyInContext(context, value, 0);
|
||||
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;
|
||||
}
|
||||
@ -662,8 +754,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
{__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}`);
|
||||
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);
|
||||
}
|
||||
@ -717,7 +811,11 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
|
||||
Object.keys(input).forEach((key) => {
|
||||
const value = transform(input[key], key);
|
||||
if (!shouldIgnore(value)) {
|
||||
result[key] = value;
|
||||
if (HIDDEN_KEY.test(key)) {
|
||||
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
@ -6,10 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export function isStaticSymbol(value: any): value is StaticSymbol {
|
||||
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
|
||||
}
|
||||
|
||||
/**
|
||||
* A token representing the a reference to a static type.
|
||||
*
|
||||
|
124
modules/@angular/compiler/src/aot/summary_resolver.ts
Normal file
124
modules/@angular/compiler/src/aot/summary_resolver.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {filterFileByPatterns} from './utils';
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
export interface AotSummaryResolverHost {
|
||||
/**
|
||||
* Loads an NgModule/Directive/Pipe summary file
|
||||
*/
|
||||
loadSummary(filePath: string): string;
|
||||
|
||||
/**
|
||||
* Returns the output file path of a source file.
|
||||
* E.g.
|
||||
* `some_file.ts` -> `some_file.d.ts`
|
||||
*/
|
||||
getOutputFileName(sourceFilePath: string): string;
|
||||
}
|
||||
|
||||
export interface AotSummaryResolverOptions {
|
||||
includeFilePattern?: RegExp;
|
||||
excludeFilePattern?: RegExp;
|
||||
}
|
||||
|
||||
export class AotSummaryResolver implements SummaryResolver {
|
||||
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
|
||||
|
||||
constructor(
|
||||
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
|
||||
private options: AotSummaryResolverOptions) {}
|
||||
|
||||
serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile {
|
||||
const jsonReplacer = (key: string, value: any) => {
|
||||
if (value instanceof StaticSymbol) {
|
||||
// We convert the source filenames into output filenames,
|
||||
// as the generated summary file will be used when the current
|
||||
// compilation unit is used as a library
|
||||
return {
|
||||
'__symbolic__': 'symbol',
|
||||
'name': value.name,
|
||||
'path': this.host.getOutputFileName(value.filePath),
|
||||
'members': value.members
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const allSummaries = summaries.slice();
|
||||
summaries.forEach((summary) => {
|
||||
if (summary.summaryKind === CompileSummaryKind.NgModule) {
|
||||
const moduleMeta = <CompileNgModuleSummary>summary;
|
||||
moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => {
|
||||
if (!filterFileByPatterns(id.reference.filePath, this.options)) {
|
||||
allSummaries.push(this.resolveSummary(id.reference));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new GeneratedFile(
|
||||
srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer));
|
||||
}
|
||||
|
||||
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
|
||||
|
||||
resolveSummary(staticSymbol: StaticSymbol): any {
|
||||
const filePath = staticSymbol.filePath;
|
||||
const name = staticSymbol.name;
|
||||
const cacheKey = this._cacheKey(staticSymbol);
|
||||
if (!filterFileByPatterns(filePath, this.options)) {
|
||||
let summary = this.summaryCache[cacheKey];
|
||||
const summaryFilePath = summaryFileName(filePath);
|
||||
if (!summary) {
|
||||
try {
|
||||
const jsonReviver = (key: string, value: any) => {
|
||||
if (value && value['__symbolic__'] === 'symbol') {
|
||||
// Note: We can't use staticReflector.findDeclaration here:
|
||||
// Summary files can contain symbols of transitive compilation units
|
||||
// (via the providers), and findDeclaration needs .metadata.json / .d.ts files,
|
||||
// but we don't want to depend on these for transitive dependencies.
|
||||
return this.staticReflector.getStaticSymbol(
|
||||
value['path'], value['name'], value['members']);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
const readSummaries: CompileTypeSummary[] =
|
||||
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
|
||||
readSummaries.forEach((summary) => {
|
||||
const filePath = summary.type.reference.filePath;
|
||||
this.summaryCache[this._cacheKey(summary.type.reference)] = summary;
|
||||
});
|
||||
summary = this.summaryCache[cacheKey];
|
||||
} catch (e) {
|
||||
console.error(`Error loading summary file ${summaryFilePath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (!summary) {
|
||||
throw new Error(
|
||||
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
|
||||
}
|
||||
return summary;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function summaryFileName(fileName: string): string {
|
||||
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
|
||||
return `${fileNameWithoutSuffix}.ngsummary.json`;
|
||||
}
|
19
modules/@angular/compiler/src/aot/utils.ts
Normal file
19
modules/@angular/compiler/src/aot/utils.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export function filterFileByPatterns(
|
||||
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
|
||||
let match = true;
|
||||
if (options.includeFilePattern) {
|
||||
match = match && !!options.includeFilePattern.exec(fileName);
|
||||
}
|
||||
if (options.excludeFilePattern) {
|
||||
match = match && !options.excludeFilePattern.exec(fileName);
|
||||
}
|
||||
return match;
|
||||
}
|
@ -8,11 +8,12 @@
|
||||
|
||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {LifecycleHooks} from './private_import_core';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {LifecycleHooks, reflector} from './private_import_core';
|
||||
import {CssSelector} from './selector';
|
||||
import {sanitizeIdentifier, splitAtColon} from './util';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
@ -24,10 +25,6 @@ function unimplemented(): any {
|
||||
// group 3: "@trigger" from "@trigger"
|
||||
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
||||
|
||||
export abstract class CompileMetadataWithIdentifier {
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
}
|
||||
|
||||
export class CompileAnimationEntryMetadata {
|
||||
constructor(
|
||||
public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
|
||||
@ -78,24 +75,50 @@ export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMeta
|
||||
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
|
||||
}
|
||||
|
||||
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
|
||||
reference: any;
|
||||
name: string;
|
||||
prefix: string;
|
||||
moduleUrl: string;
|
||||
value: any;
|
||||
|
||||
constructor(
|
||||
{reference, name, moduleUrl, prefix, value}:
|
||||
{reference?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
|
||||
this.reference = reference;
|
||||
this.name = name;
|
||||
this.prefix = prefix;
|
||||
this.moduleUrl = moduleUrl;
|
||||
this.value = value;
|
||||
function _sanitizeIdentifier(name: string): string {
|
||||
return name.replace(/\W/g, '_');
|
||||
}
|
||||
|
||||
let _anonymousTypeIndex = 0;
|
||||
|
||||
export function identifierName(compileIdentifier: CompileIdentifierMetadata): string {
|
||||
if (!compileIdentifier || !compileIdentifier.reference) {
|
||||
return null;
|
||||
}
|
||||
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 {
|
||||
Template,
|
||||
Pipe,
|
||||
Directive,
|
||||
NgModule
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,158 +126,69 @@ export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier
|
||||
* in other modules / components. However, this data is not enough to compile
|
||||
* the directive / module itself.
|
||||
*/
|
||||
export interface CompileSummary {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
export interface CompileSummary { summaryKind: CompileSummaryKind; }
|
||||
|
||||
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; }
|
||||
|
||||
export interface CompileDiDependencyMetadata {
|
||||
isAttribute?: boolean;
|
||||
isSelf?: boolean;
|
||||
isHost?: boolean;
|
||||
isSkipSelf?: boolean;
|
||||
isOptional?: boolean;
|
||||
isValue?: boolean;
|
||||
token?: CompileTokenMetadata;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
export class CompileDiDependencyMetadata {
|
||||
isAttribute: boolean;
|
||||
isSelf: boolean;
|
||||
isHost: boolean;
|
||||
isSkipSelf: boolean;
|
||||
isOptional: boolean;
|
||||
isValue: boolean;
|
||||
export interface CompileProviderMetadata {
|
||||
token: CompileTokenMetadata;
|
||||
value: any;
|
||||
|
||||
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, token, value}: {
|
||||
isAttribute?: boolean,
|
||||
isSelf?: boolean,
|
||||
isHost?: boolean,
|
||||
isSkipSelf?: boolean,
|
||||
isOptional?: boolean,
|
||||
isValue?: boolean,
|
||||
query?: CompileQueryMetadata,
|
||||
viewQuery?: CompileQueryMetadata,
|
||||
token?: CompileTokenMetadata,
|
||||
value?: any
|
||||
} = {}) {
|
||||
this.isAttribute = !!isAttribute;
|
||||
this.isSelf = !!isSelf;
|
||||
this.isHost = !!isHost;
|
||||
this.isSkipSelf = !!isSkipSelf;
|
||||
this.isOptional = !!isOptional;
|
||||
this.isValue = !!isValue;
|
||||
this.token = token;
|
||||
this.value = value;
|
||||
}
|
||||
useClass?: CompileTypeMetadata;
|
||||
useValue?: any;
|
||||
useExisting?: CompileTokenMetadata;
|
||||
useFactory?: CompileFactoryMetadata;
|
||||
deps?: CompileDiDependencyMetadata[];
|
||||
multi?: boolean;
|
||||
}
|
||||
|
||||
export class CompileProviderMetadata {
|
||||
token: CompileTokenMetadata;
|
||||
useClass: CompileTypeMetadata;
|
||||
useValue: any;
|
||||
useExisting: CompileTokenMetadata;
|
||||
useFactory: CompileFactoryMetadata;
|
||||
deps: CompileDiDependencyMetadata[];
|
||||
multi: boolean;
|
||||
|
||||
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
|
||||
token?: CompileTokenMetadata,
|
||||
useClass?: CompileTypeMetadata,
|
||||
useValue?: any,
|
||||
useExisting?: CompileTokenMetadata,
|
||||
useFactory?: CompileFactoryMetadata,
|
||||
deps?: CompileDiDependencyMetadata[],
|
||||
multi?: boolean
|
||||
}) {
|
||||
this.token = token;
|
||||
this.useClass = useClass;
|
||||
this.useValue = useValue;
|
||||
this.useExisting = useExisting;
|
||||
this.useFactory = useFactory;
|
||||
this.deps = deps || null;
|
||||
this.multi = !!multi;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileFactoryMetadata extends CompileIdentifierMetadata {
|
||||
export interface CompileFactoryMetadata extends CompileIdentifierMetadata {
|
||||
diDeps: CompileDiDependencyMetadata[];
|
||||
reference: any;
|
||||
}
|
||||
|
||||
constructor({reference, name, moduleUrl, prefix, diDeps, value}: {
|
||||
reference?: Function,
|
||||
name?: string,
|
||||
prefix?: string,
|
||||
moduleUrl?: string,
|
||||
value?: boolean,
|
||||
diDeps?: CompileDiDependencyMetadata[]
|
||||
}) {
|
||||
super({reference: reference, name: name, prefix: prefix, moduleUrl: moduleUrl, value: value});
|
||||
this.diDeps = _normalizeArray(diDeps);
|
||||
export function tokenName(token: CompileTokenMetadata) {
|
||||
return isPresent(token.value) ? _sanitizeIdentifier(token.value) :
|
||||
identifierName(token.identifier);
|
||||
}
|
||||
|
||||
export function tokenReference(token: CompileTokenMetadata) {
|
||||
if (isPresent(token.identifier)) {
|
||||
return token.identifier.reference;
|
||||
} else {
|
||||
return token.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
|
||||
value: any;
|
||||
identifier: CompileIdentifierMetadata;
|
||||
identifierIsInstance: boolean;
|
||||
|
||||
constructor(
|
||||
{value, identifier, identifierIsInstance}:
|
||||
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
|
||||
this.value = value;
|
||||
this.identifier = identifier;
|
||||
this.identifierIsInstance = !!identifierIsInstance;
|
||||
}
|
||||
|
||||
get reference(): any {
|
||||
if (isPresent(this.identifier)) {
|
||||
return this.identifier.reference;
|
||||
} else {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
|
||||
}
|
||||
export interface CompileTokenMetadata {
|
||||
value?: any;
|
||||
identifier?: CompileIdentifierMetadata|CompileTypeMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a type.
|
||||
*/
|
||||
export class CompileTypeMetadata extends CompileIdentifierMetadata {
|
||||
isHost: boolean;
|
||||
export interface CompileTypeMetadata extends CompileIdentifierMetadata {
|
||||
diDeps: CompileDiDependencyMetadata[];
|
||||
lifecycleHooks: LifecycleHooks[];
|
||||
|
||||
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);
|
||||
}
|
||||
reference: any;
|
||||
}
|
||||
|
||||
export class CompileQueryMetadata {
|
||||
export interface CompileQueryMetadata {
|
||||
selectors: Array<CompileTokenMetadata>;
|
||||
descendants: boolean;
|
||||
first: boolean;
|
||||
propertyName: string;
|
||||
read: CompileTokenMetadata;
|
||||
|
||||
constructor({selectors, descendants, first, propertyName, read}: {
|
||||
selectors?: Array<CompileTokenMetadata>,
|
||||
descendants?: boolean,
|
||||
first?: boolean,
|
||||
propertyName?: string,
|
||||
read?: CompileTokenMetadata
|
||||
} = {}) {
|
||||
this.selectors = selectors;
|
||||
this.descendants = !!descendants;
|
||||
this.first = !!first;
|
||||
this.propertyName = propertyName;
|
||||
this.read = read;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,7 +211,6 @@ export class CompileStylesheetMetadata {
|
||||
* Summary Metadata regarding compilation of a template.
|
||||
*/
|
||||
export interface CompileTemplateSummary extends CompileSummary {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
animations: string[];
|
||||
ngContentSelectors: string[];
|
||||
encapsulation: ViewEncapsulation;
|
||||
@ -325,7 +258,7 @@ export class CompileTemplateMetadata {
|
||||
|
||||
toSummary(): CompileTemplateSummary {
|
||||
return {
|
||||
isSummary: true,
|
||||
summaryKind: CompileSummaryKind.Template,
|
||||
animations: this.animations.map(anim => anim.name),
|
||||
ngContentSelectors: this.ngContentSelectors,
|
||||
encapsulation: this.encapsulation
|
||||
@ -333,8 +266,9 @@ export class CompileTemplateMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompileDirectiveSummary extends CompileSummary {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
// Note: This should only use interfaces as nested data types
|
||||
// as we need to be able to serialize this from/to JSON!
|
||||
export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
type: CompileTypeMetadata;
|
||||
isComponent: boolean;
|
||||
selector: string;
|
||||
@ -355,10 +289,11 @@ export interface CompileDirectiveSummary extends CompileSummary {
|
||||
/**
|
||||
* Metadata regarding compilation of a directive.
|
||||
*/
|
||||
export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
export class CompileDirectiveMetadata {
|
||||
static create(
|
||||
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers,
|
||||
viewProviders, queries, viewQueries, entryComponents, template}: {
|
||||
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
|
||||
providers, viewProviders, queries, viewQueries, entryComponents, template}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string,
|
||||
@ -367,10 +302,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
inputs?: string[],
|
||||
outputs?: string[],
|
||||
host?: {[key: string]: string},
|
||||
providers?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
viewProviders?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
providers?: CompileProviderMetadata[],
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
@ -412,6 +345,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
}
|
||||
|
||||
return new CompileDirectiveMetadata({
|
||||
isHost,
|
||||
type,
|
||||
isComponent: !!isComponent, selector, exportAs, changeDetection,
|
||||
inputs: inputsMap,
|
||||
@ -427,6 +361,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
template,
|
||||
});
|
||||
}
|
||||
isHost: boolean;
|
||||
type: CompileTypeMetadata;
|
||||
isComponent: boolean;
|
||||
selector: string;
|
||||
@ -446,9 +381,10 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
template: CompileTemplateMetadata;
|
||||
|
||||
constructor(
|
||||
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
|
||||
hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries,
|
||||
entryComponents, template}: {
|
||||
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
||||
hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries,
|
||||
viewQueries, entryComponents, template}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string,
|
||||
@ -459,15 +395,14 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
hostListeners?: {[key: string]: string},
|
||||
hostProperties?: {[key: string]: string},
|
||||
hostAttributes?: {[key: string]: string},
|
||||
providers?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
viewProviders?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
providers?: CompileProviderMetadata[],
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
} = {}) {
|
||||
this.isHost = !!isHost;
|
||||
this.type = type;
|
||||
this.isComponent = isComponent;
|
||||
this.selector = selector;
|
||||
@ -487,11 +422,9 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
|
||||
toSummary(): CompileDirectiveSummary {
|
||||
return {
|
||||
isSummary: true,
|
||||
summaryKind: CompileSummaryKind.Directive,
|
||||
type: this.type,
|
||||
isComponent: this.isComponent,
|
||||
selector: this.selector,
|
||||
@ -514,16 +447,12 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
/**
|
||||
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
|
||||
*/
|
||||
export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
|
||||
CompileDirectiveMetadata {
|
||||
export function createHostComponentMeta(
|
||||
typeReference: any, compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata {
|
||||
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
|
||||
return CompileDirectiveMetadata.create({
|
||||
type: new CompileTypeMetadata({
|
||||
reference: Object,
|
||||
name: `${compMeta.type.name}_Host`,
|
||||
moduleUrl: compMeta.type.moduleUrl,
|
||||
isHost: true
|
||||
}),
|
||||
isHost: true,
|
||||
type: {reference: typeReference, diDeps: [], lifecycleHooks: []},
|
||||
template: new CompileTemplateMetadata({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: template,
|
||||
@ -546,14 +475,13 @@ export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
|
||||
});
|
||||
}
|
||||
|
||||
export interface CompilePipeSummary extends CompileSummary {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
export interface CompilePipeSummary extends CompileTypeSummary {
|
||||
type: CompileTypeMetadata;
|
||||
name: string;
|
||||
pure: boolean;
|
||||
}
|
||||
|
||||
export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
||||
export class CompilePipeMetadata {
|
||||
type: CompileTypeMetadata;
|
||||
name: string;
|
||||
pure: boolean;
|
||||
@ -567,42 +495,44 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
||||
this.name = name;
|
||||
this.pure = !!pure;
|
||||
}
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
|
||||
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 {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
// Note: This should only use interfaces as nested data types
|
||||
// as we need to be able to serialize this from/to JSON!
|
||||
export interface CompileNgModuleSummary extends CompileTypeSummary {
|
||||
type: CompileTypeMetadata;
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
providers: CompileProviderMetadata[];
|
||||
importedModules: CompileNgModuleInjectorSummary[];
|
||||
exportedModules: CompileNgModuleInjectorSummary[];
|
||||
}
|
||||
|
||||
export interface CompileNgModuleDirectiveSummary extends CompileSummary {
|
||||
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
|
||||
type: CompileTypeMetadata;
|
||||
// Note: This is transitive over the exported modules.
|
||||
exportedDirectives: CompileIdentifierMetadata[];
|
||||
// Note: This is transitive over the exported modules.
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
exportedModules: CompileNgModuleDirectiveSummary[];
|
||||
directiveLoaders: (() => Promise<void>)[];
|
||||
}
|
||||
|
||||
export type CompileNgModuleSummary =
|
||||
CompileNgModuleInjectorSummary & CompileNgModuleDirectiveSummary;
|
||||
// Note: This is transitive.
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
// Note: This is transitive.
|
||||
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[];
|
||||
// Note: This is transitive.
|
||||
modules: CompileTypeMetadata[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a module.
|
||||
*/
|
||||
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
export class CompileNgModuleMetadata {
|
||||
type: CompileTypeMetadata;
|
||||
declaredDirectives: CompileIdentifierMetadata[];
|
||||
exportedDirectives: CompileIdentifierMetadata[];
|
||||
declaredPipes: CompileIdentifierMetadata[];
|
||||
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
bootstrapComponents: CompileIdentifierMetadata[];
|
||||
@ -620,8 +550,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
|
||||
transitiveModule, id}: {
|
||||
type?: CompileTypeMetadata,
|
||||
providers?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
providers?: CompileProviderMetadata[],
|
||||
declaredDirectives?: CompileIdentifierMetadata[],
|
||||
exportedDirectives?: CompileIdentifierMetadata[],
|
||||
declaredPipes?: CompileIdentifierMetadata[],
|
||||
@ -649,69 +578,75 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
this.transitiveModule = transitiveModule;
|
||||
}
|
||||
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
|
||||
toSummary(): CompileNgModuleSummary {
|
||||
return {
|
||||
isSummary: true,
|
||||
summaryKind: CompileSummaryKind.NgModule,
|
||||
type: this.type,
|
||||
entryComponents: this.entryComponents,
|
||||
providers: this.providers,
|
||||
importedModules: this.importedModules,
|
||||
exportedModules: this.exportedModules,
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
};
|
||||
}
|
||||
|
||||
toInjectorSummary(): CompileNgModuleInjectorSummary {
|
||||
return {
|
||||
isSummary: true,
|
||||
type: this.type,
|
||||
entryComponents: this.entryComponents,
|
||||
providers: this.providers,
|
||||
importedModules: this.importedModules,
|
||||
exportedModules: this.exportedModules
|
||||
};
|
||||
}
|
||||
toDirectiveSummary(): CompileNgModuleDirectiveSummary {
|
||||
return {
|
||||
isSummary: true,
|
||||
type: this.type,
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
exportedModules: this.exportedModules,
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
entryComponents: this.transitiveModule.entryComponents,
|
||||
providers: this.transitiveModule.providers,
|
||||
modules: this.transitiveModule.modules,
|
||||
exportedDirectives: this.transitiveModule.exportedDirectives,
|
||||
exportedPipes: this.transitiveModule.exportedPipes
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class TransitiveCompileNgModuleMetadata {
|
||||
directivesSet = new Set<any>();
|
||||
directives: CompileIdentifierMetadata[] = [];
|
||||
exportedDirectivesSet = new Set<any>();
|
||||
exportedDirectives: CompileIdentifierMetadata[] = [];
|
||||
pipesSet = new Set<any>();
|
||||
pipes: CompileIdentifierMetadata[] = [];
|
||||
exportedPipesSet = new Set<any>();
|
||||
exportedPipes: CompileIdentifierMetadata[] = [];
|
||||
modulesSet = new Set<any>();
|
||||
modules: CompileTypeMetadata[] = [];
|
||||
entryComponentsSet = new Set<any>();
|
||||
entryComponents: CompileIdentifierMetadata[] = [];
|
||||
|
||||
constructor(
|
||||
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
|
||||
public entryComponents: CompileIdentifierMetadata[],
|
||||
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
|
||||
public directiveLoaders: (() => Promise<void>)[]) {
|
||||
directives.forEach(dir => this.directivesSet.add(dir.reference));
|
||||
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
|
||||
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = [];
|
||||
|
||||
addProvider(provider: CompileProviderMetadata, module: CompileIdentifierMetadata) {
|
||||
this.providers.push({provider: provider, module: module});
|
||||
}
|
||||
}
|
||||
|
||||
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
|
||||
T[] {
|
||||
const map = new Map<any, T>();
|
||||
|
||||
items.forEach((item) => {
|
||||
if (!map.get(item.identifier.reference)) {
|
||||
map.set(item.identifier.reference, item);
|
||||
addDirective(id: CompileIdentifierMetadata) {
|
||||
if (!this.directivesSet.has(id.reference)) {
|
||||
this.directivesSet.add(id.reference);
|
||||
this.directives.push(id);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
addExportedDirective(id: CompileIdentifierMetadata) {
|
||||
if (!this.exportedDirectivesSet.has(id.reference)) {
|
||||
this.exportedDirectivesSet.add(id.reference);
|
||||
this.exportedDirectives.push(id);
|
||||
}
|
||||
}
|
||||
addPipe(id: CompileIdentifierMetadata) {
|
||||
if (!this.pipesSet.has(id.reference)) {
|
||||
this.pipesSet.add(id.reference);
|
||||
this.pipes.push(id);
|
||||
}
|
||||
}
|
||||
addExportedPipe(id: CompileIdentifierMetadata) {
|
||||
if (!this.exportedPipesSet.has(id.reference)) {
|
||||
this.exportedPipesSet.add(id.reference);
|
||||
this.exportedPipes.push(id);
|
||||
}
|
||||
}
|
||||
addModule(id: CompileTypeMetadata) {
|
||||
if (!this.modulesSet.has(id.reference)) {
|
||||
this.modulesSet.add(id.reference);
|
||||
this.modules.push(id);
|
||||
}
|
||||
}
|
||||
addEntryComponent(id: CompileIdentifierMetadata) {
|
||||
if (!this.entryComponentsSet.has(id.reference)) {
|
||||
this.entryComponentsSet.add(id.reference);
|
||||
this.entryComponents.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _normalizeArray(obj: any[]): any[] {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* 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 * as o from '../output/output_ast';
|
||||
|
||||
@ -22,7 +22,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
|
||||
// private is fine here as no child view will reference the cached value...
|
||||
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.toStmt());
|
||||
return new CheckBindingField(fieldExpr, bindingId);
|
||||
}
|
||||
@ -30,7 +30,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
|
||||
export function createCheckBindingStmt(
|
||||
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
|
||||
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
|
||||
let condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
|
||||
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
|
||||
throwOnChangeVar, fieldExpr, evalResult.currValExpr
|
||||
]);
|
||||
if (evalResult.forceUpdate) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
@ -116,7 +116,7 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
|
||||
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
||||
unwrapperStmts.push(
|
||||
VAL_UNWRAPPER_VAR
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
}
|
||||
return unwrapperStmts;
|
||||
@ -277,15 +277,20 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
args.push(o.literal(ast.strings[ast.strings.length - 1]));
|
||||
|
||||
return ast.expressions.length <= 9 ?
|
||||
o.importExpr(resolveIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
|
||||
o.importExpr(resolveIdentifier(Identifiers.interpolate)).callFn([
|
||||
o.importExpr(createIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
|
||||
o.importExpr(createIdentifier(Identifiers.interpolate)).callFn([
|
||||
args[0], o.literalArr(args.slice(1))
|
||||
]);
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
|
||||
return convertToStatementIfNeeded(
|
||||
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
|
||||
const leftMostSafe = this.leftMostSafeNode(ast);
|
||||
if (leftMostSafe) {
|
||||
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
||||
} else {
|
||||
return convertToStatementIfNeeded(
|
||||
mode, this.visit(ast.obj, _Mode.Expression).key(this.visit(ast.key, _Mode.Expression)));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 proxyParams: o.FnParam[] = [];
|
||||
@ -599,7 +604,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
|
||||
function createCachedLiteralMap(
|
||||
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
||||
if (entries.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
|
||||
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
|
||||
}
|
||||
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
|
||||
const proxyParams: o.FnParam[] = [];
|
||||
|
@ -8,15 +8,12 @@
|
||||
|
||||
import {CompileTokenMetadata} from '../compile_metadata';
|
||||
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';
|
||||
|
||||
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
|
||||
if (isPresent(token.value)) {
|
||||
return o.literal(token.value);
|
||||
} else if (token.identifierIsInstance) {
|
||||
return o.importExpr(token.identifier)
|
||||
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
|
||||
} else {
|
||||
return o.importExpr(token.identifier);
|
||||
}
|
||||
@ -24,13 +21,13 @@ export function createDiTokenExpression(token: CompileTokenMetadata): o.Expressi
|
||||
|
||||
export function createInlineArray(values: o.Expression[]): o.Expression {
|
||||
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 index = Math.ceil(log2);
|
||||
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
|
||||
Identifiers.InlineArrayDynamic;
|
||||
const identifier = resolveIdentifier(identifierSpec);
|
||||
const identifier = createIdentifier(identifierSpec);
|
||||
return o.importExpr(identifier).instantiate([
|
||||
<o.Expression>o.literal(values.length)
|
||||
].concat(values));
|
||||
@ -46,7 +43,7 @@ export function createPureProxy(
|
||||
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
|
||||
}
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
|
||||
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
|
||||
.set(o.importExpr(createIdentifier(pureProxyId)).callFn([fn]))
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
@ -56,5 +53,5 @@ export function createEnumExpression(enumType: IdentifierSpec, enumValue: any):
|
||||
if (!enumName) {
|
||||
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 {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
|
||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
@ -26,7 +26,7 @@ export function writeToRenderer(
|
||||
case PropertyBindingType.Property:
|
||||
if (logBindingUpdate) {
|
||||
updateStmts.push(
|
||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
|
||||
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
|
||||
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
}
|
||||
@ -91,8 +91,8 @@ function sanitizedValue(
|
||||
|
||||
export function triggerAnimation(
|
||||
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
||||
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
|
||||
lastRenderValue: o.Expression) {
|
||||
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
||||
renderValue: o.Expression, lastRenderValue: o.Expression) {
|
||||
const detachStmts: o.Statement[] = [];
|
||||
const updateStmts: o.Statement[] = [];
|
||||
|
||||
@ -104,7 +104,7 @@ export function triggerAnimation(
|
||||
// it's important to normalize the void value as `void` explicitly
|
||||
// so that the styles data can be obtained from the stringmap
|
||||
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
|
||||
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
|
||||
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
|
||||
const animationTransitionVar = o.variable('animationTransition_' + animationName);
|
||||
|
||||
updateStmts.push(
|
||||
@ -121,23 +121,32 @@ export function triggerAnimation(
|
||||
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
|
||||
.toDeclStmt());
|
||||
|
||||
const registerStmts = [
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onStart',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
||||
.toStmt(),
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onDone',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
||||
.toStmt(),
|
||||
const registerStmts: o.Statement[] = [];
|
||||
const animationStartMethodExists = boundOutputs.find(
|
||||
event => event.isAnimation && event.name == animationName && event.phase == 'start');
|
||||
if (animationStartMethodExists) {
|
||||
registerStmts.push(
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onStart',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
];
|
||||
const animationDoneMethodExists = boundOutputs.find(
|
||||
event => event.isAnimation && event.name == animationName && event.phase == 'done');
|
||||
if (animationDoneMethodExists) {
|
||||
registerStmts.push(
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onDone',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
updateStmts.push(...registerStmts);
|
||||
detachStmts.push(...registerStmts);
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {ViewEncapsulation, isDevMode} from '@angular/core';
|
||||
|
||||
import {CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, createIdentifier} from './identifiers';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
@ -61,7 +61,7 @@ export abstract class RenderTypes {
|
||||
}
|
||||
|
||||
export class DefaultRenderTypes implements RenderTypes {
|
||||
get renderer() { return resolveIdentifier(Identifiers.Renderer); };
|
||||
get renderer() { return createIdentifier(Identifiers.Renderer); };
|
||||
renderText: any = null;
|
||||
renderElement: any = null;
|
||||
renderComment: any = null;
|
||||
|
@ -8,11 +8,12 @@
|
||||
|
||||
import {Component, Directive, HostBinding, HostListener, Injectable, 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 {ReflectorReader, reflector} from './private_import_core';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
|
||||
/*
|
||||
* Resolve a `Type` for {@link Directive}.
|
||||
*
|
||||
@ -35,7 +36,7 @@ export class DirectiveResolver {
|
||||
resolve(type: Type<any>, throwIfNotFound = true): Directive {
|
||||
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||
if (typeMetadata) {
|
||||
const metadata = typeMetadata.find(isDirectiveMetadata);
|
||||
const metadata = ListWrapper.findLast(typeMetadata, isDirectiveMetadata);
|
||||
if (metadata) {
|
||||
const propertyMetadata = this._reflector.propMetadata(type);
|
||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
||||
@ -58,85 +59,74 @@ export class DirectiveResolver {
|
||||
const queries: {[key: string]: any} = {};
|
||||
|
||||
Object.keys(propertyMetadata).forEach((propName: string) => {
|
||||
|
||||
propertyMetadata[propName].forEach(a => {
|
||||
if (a instanceof Input) {
|
||||
if (a.bindingPropertyName) {
|
||||
inputs.push(`${propName}: ${a.bindingPropertyName}`);
|
||||
} else {
|
||||
inputs.push(propName);
|
||||
const input = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Input);
|
||||
if (input) {
|
||||
if (input.bindingPropertyName) {
|
||||
inputs.push(`${propName}: ${input.bindingPropertyName}`);
|
||||
} else {
|
||||
inputs.push(propName);
|
||||
}
|
||||
}
|
||||
const output = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Output);
|
||||
if (output) {
|
||||
if (output.bindingPropertyName) {
|
||||
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
||||
} else {
|
||||
outputs.push(propName);
|
||||
}
|
||||
}
|
||||
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
|
||||
hostBindings.forEach(hostBinding => {
|
||||
if (hostBinding.hostPropertyName) {
|
||||
const startWith = hostBinding.hostPropertyName[0];
|
||||
if (startWith === '(') {
|
||||
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
||||
} else if (startWith === '[') {
|
||||
throw new Error(
|
||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||
}
|
||||
} else if (a instanceof Output) {
|
||||
const output: Output = a;
|
||||
if (output.bindingPropertyName) {
|
||||
outputs.push(`${propName}: ${output.bindingPropertyName}`);
|
||||
} else {
|
||||
outputs.push(propName);
|
||||
}
|
||||
} else if (a instanceof HostBinding) {
|
||||
const hostBinding: HostBinding = a;
|
||||
if (hostBinding.hostPropertyName) {
|
||||
const startWith = hostBinding.hostPropertyName[0];
|
||||
if (startWith === '(') {
|
||||
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
||||
} else if (startWith === '[') {
|
||||
throw new Error(
|
||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||
}
|
||||
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
||||
} else {
|
||||
host[`[${propName}]`] = propName;
|
||||
}
|
||||
} else if (a instanceof HostListener) {
|
||||
const hostListener: HostListener = a;
|
||||
const args = hostListener.args || [];
|
||||
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
||||
} else if (a instanceof Query) {
|
||||
queries[propName] = a;
|
||||
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
||||
} else {
|
||||
host[`[${propName}]`] = propName;
|
||||
}
|
||||
});
|
||||
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
|
||||
hostListeners.forEach(hostListener => {
|
||||
const args = hostListener.args || [];
|
||||
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
||||
});
|
||||
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
|
||||
if (query) {
|
||||
queries[propName] = query;
|
||||
}
|
||||
});
|
||||
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
||||
}
|
||||
|
||||
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
|
||||
|
||||
private _dedupeBindings(bindings: string[]): string[] {
|
||||
const names = new Set<string>();
|
||||
const reversedResult: string[] = [];
|
||||
// go last to first to allow later entries to overwrite previous entries
|
||||
for (let i = bindings.length - 1; i >= 0; i--) {
|
||||
const binding = bindings[i];
|
||||
const name = this._extractPublicName(binding);
|
||||
if (!names.has(name)) {
|
||||
names.add(name);
|
||||
reversedResult.push(binding);
|
||||
}
|
||||
}
|
||||
return reversedResult.reverse();
|
||||
}
|
||||
|
||||
private _merge(
|
||||
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
||||
queries: {[key: string]: any}, directiveType: Type<any>): Directive {
|
||||
const mergedInputs: string[] = inputs;
|
||||
|
||||
if (directive.inputs) {
|
||||
const inputNames: string[] =
|
||||
directive.inputs.map((def: string): string => this._extractPublicName(def));
|
||||
|
||||
inputs.forEach((inputDef: string) => {
|
||||
const publicName = this._extractPublicName(inputDef);
|
||||
if (inputNames.indexOf(publicName) > -1) {
|
||||
throw new Error(
|
||||
`Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
||||
}
|
||||
});
|
||||
|
||||
mergedInputs.unshift(...directive.inputs);
|
||||
}
|
||||
|
||||
const mergedOutputs: string[] = outputs;
|
||||
|
||||
if (directive.outputs) {
|
||||
const outputNames: string[] =
|
||||
directive.outputs.map((def: string): string => this._extractPublicName(def));
|
||||
|
||||
outputs.forEach((outputDef: string) => {
|
||||
const publicName = this._extractPublicName(outputDef);
|
||||
if (outputNames.indexOf(publicName) > -1) {
|
||||
throw new Error(
|
||||
`Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
|
||||
}
|
||||
});
|
||||
mergedOutputs.unshift(...directive.outputs);
|
||||
}
|
||||
|
||||
const mergedInputs =
|
||||
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
||||
const mergedOutputs =
|
||||
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
||||
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host;
|
||||
const mergedQueries =
|
||||
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries;
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
||||
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
||||
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
|
||||
import {CompilerConfig} from './config';
|
||||
import {Parser} from './expression_parser/parser';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, createIdentifier} from './identifiers';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
@ -53,7 +53,9 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
|
||||
*/
|
||||
@Injectable()
|
||||
export class DirectiveWrapperCompiler {
|
||||
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
|
||||
static dirWrapperClassName(id: CompileIdentifierMetadata) {
|
||||
return `Wrapper_${identifierName(id)}`;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private compilerConfig: CompilerConfig, private _exprParser: Parser,
|
||||
@ -68,7 +70,7 @@ export class DirectiveWrapperCompiler {
|
||||
addCheckInputMethod(inputFieldName, builder);
|
||||
});
|
||||
addNgDoCheckMethod(builder);
|
||||
addCheckHostMethod(hostParseResult.hostProps, builder);
|
||||
addCheckHostMethod(hostParseResult.hostProps, hostParseResult.hostListeners, builder);
|
||||
addHandleEventMethod(hostParseResult.hostListeners, builder);
|
||||
addSubscribeMethod(dirMeta, builder);
|
||||
|
||||
@ -117,10 +119,10 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
[
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name,
|
||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(
|
||||
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),
|
||||
],
|
||||
this.detachStmts),
|
||||
@ -172,7 +174,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
||||
}
|
||||
if (builder.compilerConfig.logBindingUpdate) {
|
||||
onChangesStmts.push(
|
||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
|
||||
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfoForChanges))
|
||||
.callFn(
|
||||
[VIEW_VAR.prop('renderer'), RENDER_EL_VAR, o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
|
||||
.toStmt());
|
||||
@ -198,7 +200,7 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
||||
'ngDoCheck',
|
||||
[
|
||||
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(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
],
|
||||
@ -214,7 +216,7 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
||||
if (builder.genChanges) {
|
||||
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
|
||||
.key(o.literal(input))
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
|
||||
.instantiate([field.expression, CURR_VALUE_VAR]))
|
||||
.toStmt());
|
||||
}
|
||||
@ -233,14 +235,15 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
||||
}
|
||||
|
||||
function addCheckHostMethod(
|
||||
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
|
||||
hostProps: BoundElementPropertyAst[], hostEvents: BoundEventAst[],
|
||||
builder: DirectiveWrapperBuilder) {
|
||||
const stmts: o.Statement[] = [];
|
||||
const methodParams: 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(
|
||||
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(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
];
|
||||
@ -255,14 +258,14 @@ function addCheckHostMethod(
|
||||
if (hostProp.needsRuntimeSecurityContext) {
|
||||
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
|
||||
methodParams.push(new o.FnParam(
|
||||
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
|
||||
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
|
||||
}
|
||||
let checkBindingStmts: o.Statement[];
|
||||
if (hostProp.isAnimation) {
|
||||
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)
|
||||
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
|
||||
.or(o.importExpr(createIdentifier(Identifiers.noop))),
|
||||
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
|
||||
checkBindingStmts = updateStmts;
|
||||
builder.detachStmts.push(...detachStmts);
|
||||
@ -306,7 +309,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
|
||||
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
|
||||
const methodParams: o.FnParam[] = [
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(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)
|
||||
];
|
||||
const stmts: o.Statement[] = [
|
||||
@ -348,9 +351,10 @@ function parseHostBindings(
|
||||
const errors: ParseError[] = [];
|
||||
const parser =
|
||||
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
|
||||
const sourceFileName = dirMeta.type.moduleUrl ?
|
||||
`in Directive ${dirMeta.type.name} in ${dirMeta.type.moduleUrl}` :
|
||||
`in Directive ${dirMeta.type.name}`;
|
||||
const moduleUrl = identifierModuleUrl(dirMeta.type);
|
||||
const sourceFileName = moduleUrl ?
|
||||
`in Directive ${identifierName(dirMeta.type)} in ${moduleUrl}` :
|
||||
`in Directive ${identifierName(dirMeta.type)}`;
|
||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
new ParseLocation(sourceFile, null, null, null),
|
||||
|
@ -344,7 +344,7 @@ export class _ParseAST {
|
||||
while (this.optionalCharacter(chars.$COLON)) {
|
||||
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('|'));
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,16 @@
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
|
||||
export function digestMessage(message: i18n.Message): string {
|
||||
export function digest(message: i18n.Message): string {
|
||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
}
|
||||
|
||||
export function decimalDigest(message: i18n.Message): string {
|
||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||
return computeMsgId(parts.join(''), message.meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the i18n ast to something xml-like in order to generate an UID.
|
||||
*
|
||||
@ -39,7 +45,7 @@ class _SerializerVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context: any): any {
|
||||
return `<ph name="${ph.name}">${ph.value}</ph>`;
|
||||
return ph.value ? `<ph name="${ph.name}">${ph.value}</ph>` : `<ph name="${ph.name}"/>`;
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
|
||||
@ -53,6 +59,21 @@ export function serializeNodes(nodes: i18n.Node[]): string[] {
|
||||
return nodes.map(a => a.visit(serializerVisitor, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the i18n ast to something xml-like in order to generate an UID.
|
||||
*
|
||||
* Ignore the ICU expressions so that message IDs stays identical if only the expression changes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
|
||||
visitIcu(icu: i18n.Icu, context: any): any {
|
||||
let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
|
||||
// Do not take the expression into account
|
||||
return `{${icu.type}, ${strCases.join(', ')}}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the SHA1 of the given string
|
||||
*
|
||||
@ -63,7 +84,7 @@ export function serializeNodes(nodes: i18n.Node[]): string[] {
|
||||
*/
|
||||
export function sha1(str: string): string {
|
||||
const utf8 = utf8Encode(str);
|
||||
const words32 = stringToWords32(utf8);
|
||||
const words32 = stringToWords32(utf8, Endian.Big);
|
||||
const len = utf8.length * 8;
|
||||
|
||||
const w = new Array(80);
|
||||
@ -90,15 +111,99 @@ export function sha1(str: string): string {
|
||||
[a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];
|
||||
}
|
||||
|
||||
const sha1 = words32ToString([a, b, c, d, e]);
|
||||
return byteStringToHexString(words32ToByteString([a, b, c, d, e]));
|
||||
}
|
||||
|
||||
let hex: string = '';
|
||||
for (let i = 0; i < sha1.length; i++) {
|
||||
const b = sha1.charCodeAt(i);
|
||||
hex += (b >>> 4 & 0x0f).toString(16) + (b & 0x0f).toString(16);
|
||||
function fk(index: number, b: number, c: number, d: number): [number, number] {
|
||||
if (index < 20) {
|
||||
return [(b & c) | (~b & d), 0x5a827999];
|
||||
}
|
||||
|
||||
return hex.toLowerCase();
|
||||
if (index < 40) {
|
||||
return [b ^ c ^ d, 0x6ed9eba1];
|
||||
}
|
||||
|
||||
if (index < 60) {
|
||||
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
||||
}
|
||||
|
||||
return [b ^ c ^ d, 0xca62c1d6];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the fingerprint of the given string
|
||||
*
|
||||
* The output is 64 bit number encoded as a decimal string
|
||||
*
|
||||
* based on:
|
||||
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
|
||||
*/
|
||||
export function fingerprint(str: string): [number, number] {
|
||||
const utf8 = utf8Encode(str);
|
||||
|
||||
let [hi, lo] = [hash32(utf8, 0), hash32(utf8, 102072)];
|
||||
|
||||
if (hi == 0 && (lo == 0 || lo == 1)) {
|
||||
hi = hi ^ 0x130f9bef;
|
||||
lo = lo ^ -0x6b5f56d8;
|
||||
}
|
||||
|
||||
return [hi, lo];
|
||||
}
|
||||
|
||||
export function computeMsgId(msg: string, meaning: string): string {
|
||||
let [hi, lo] = fingerprint(msg);
|
||||
|
||||
if (meaning) {
|
||||
const [him, lom] = fingerprint(meaning);
|
||||
[hi, lo] = add64(rol64([hi, lo], 1), [him, lom]);
|
||||
}
|
||||
|
||||
return byteStringToDecString(words32ToByteString([hi & 0x7fffffff, lo]));
|
||||
}
|
||||
|
||||
function hash32(str: string, c: number): number {
|
||||
let [a, b] = [0x9e3779b9, 0x9e3779b9];
|
||||
let i: number;
|
||||
|
||||
const len = str.length;
|
||||
|
||||
for (i = 0; i + 12 <= len; i += 12) {
|
||||
a = add32(a, wordAt(str, i, Endian.Little));
|
||||
b = add32(b, wordAt(str, i + 4, Endian.Little));
|
||||
c = add32(c, wordAt(str, i + 8, Endian.Little));
|
||||
[a, b, c] = mix([a, b, c]);
|
||||
}
|
||||
|
||||
a = add32(a, wordAt(str, i, Endian.Little));
|
||||
b = add32(b, wordAt(str, i + 4, Endian.Little));
|
||||
// the first byte of c is reserved for the length
|
||||
c = add32(c, len);
|
||||
c = add32(c, wordAt(str, i + 8, Endian.Little) << 8);
|
||||
|
||||
return mix([a, b, c])[2];
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
function mix([a, b, c]: [number, number, number]): [number, number, number] {
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 13;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 8;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 13;
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 12;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 16;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 5;
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 3;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 10;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 15;
|
||||
return [a, b, c];
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
// Utils
|
||||
|
||||
enum Endian {
|
||||
Little,
|
||||
Big,
|
||||
}
|
||||
|
||||
function utf8Encode(str: string): string {
|
||||
@ -131,10 +236,9 @@ function decodeSurrogatePairs(str: string, index: number): number {
|
||||
}
|
||||
|
||||
const high = str.charCodeAt(index);
|
||||
let low: number;
|
||||
|
||||
if (high >= 0xd800 && high <= 0xdfff && str.length > index + 1) {
|
||||
low = str.charCodeAt(index + 1);
|
||||
const low = byteAt(str, index + 1);
|
||||
if (low >= 0xdc00 && low <= 0xdfff) {
|
||||
return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
|
||||
}
|
||||
@ -143,50 +247,126 @@ function decodeSurrogatePairs(str: string, index: number): number {
|
||||
return high;
|
||||
}
|
||||
|
||||
function stringToWords32(str: string): number[] {
|
||||
const words32 = Array(str.length >>> 2);
|
||||
function add32(a: number, b: number): number {
|
||||
return add32to64(a, b)[1];
|
||||
}
|
||||
|
||||
function add32to64(a: number, b: number): [number, number] {
|
||||
const low = (a & 0xffff) + (b & 0xffff);
|
||||
const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
|
||||
return [high >>> 16, (high << 16) | (low & 0xffff)];
|
||||
}
|
||||
|
||||
function add64([ah, al]: [number, number], [bh, bl]: [number, number]): [number, number] {
|
||||
const [carry, l] = add32to64(al, bl);
|
||||
const h = add32(add32(ah, bh), carry);
|
||||
return [h, l];
|
||||
}
|
||||
|
||||
function sub32(a: number, b: number): number {
|
||||
const low = (a & 0xffff) - (b & 0xffff);
|
||||
const high = (a >> 16) - (b >> 16) + (low >> 16);
|
||||
return (high << 16) | (low & 0xffff);
|
||||
}
|
||||
|
||||
// Rotate a 32b number left `count` position
|
||||
function rol32(a: number, count: number): number {
|
||||
return (a << count) | (a >>> (32 - count));
|
||||
}
|
||||
|
||||
// Rotate a 64b number left `count` position
|
||||
function rol64([hi, lo]: [number, number], count: number): [number, number] {
|
||||
const h = (hi << count) | (lo >>> (32 - count));
|
||||
const l = (lo << count) | (hi >>> (32 - count));
|
||||
return [h, l];
|
||||
}
|
||||
|
||||
function stringToWords32(str: string, endian: Endian): number[] {
|
||||
const words32 = Array((str.length + 3) >>> 2);
|
||||
|
||||
for (let i = 0; i < words32.length; i++) {
|
||||
words32[i] = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
words32[i >>> 2] |= (str.charCodeAt(i) & 0xff) << 8 * (3 - i & 0x3);
|
||||
words32[i] = wordAt(str, i * 4, endian);
|
||||
}
|
||||
|
||||
return words32;
|
||||
}
|
||||
|
||||
function words32ToString(words32: number[]): string {
|
||||
function byteAt(str: string, index: number): number {
|
||||
return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;
|
||||
}
|
||||
|
||||
function wordAt(str: string, index: number, endian: Endian): number {
|
||||
let word = 0;
|
||||
if (endian === Endian.Big) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
word += byteAt(str, index + i) << (24 - 8 * i);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
word += byteAt(str, index + i) << 8 * i;
|
||||
}
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
function words32ToByteString(words32: number[]): string {
|
||||
return words32.reduce((str, word) => str + word32ToByteString(word), '');
|
||||
}
|
||||
|
||||
function word32ToByteString(word: number): string {
|
||||
let str = '';
|
||||
for (let i = 0; i < words32.length * 4; i++) {
|
||||
str += String.fromCharCode((words32[i >>> 2] >>> 8 * (3 - i & 0x3)) & 0xff);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function fk(index: number, b: number, c: number, d: number): [number, number] {
|
||||
if (index < 20) {
|
||||
return [(b & c) | (~b & d), 0x5a827999];
|
||||
function byteStringToHexString(str: string): string {
|
||||
let hex: string = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const b = byteAt(str, i);
|
||||
hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);
|
||||
}
|
||||
|
||||
if (index < 40) {
|
||||
return [b ^ c ^ d, 0x6ed9eba1];
|
||||
}
|
||||
|
||||
if (index < 60) {
|
||||
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
||||
}
|
||||
|
||||
return [b ^ c ^ d, 0xca62c1d6];
|
||||
return hex.toLowerCase();
|
||||
}
|
||||
|
||||
function add32(a: number, b: number): number {
|
||||
const low = (a & 0xffff) + (b & 0xffff);
|
||||
const high = (a >> 16) + (b >> 16) + (low >> 16);
|
||||
return (high << 16) | (low & 0xffff);
|
||||
// based on http://www.danvk.org/hex2dec.html (JS can not handle more than 56b)
|
||||
function byteStringToDecString(str: string): string {
|
||||
let decimal = '';
|
||||
let toThePower = '1';
|
||||
|
||||
for (let i = str.length - 1; i >= 0; i--) {
|
||||
decimal = addBigInt(decimal, numberTimesBigInt(byteAt(str, i), toThePower));
|
||||
toThePower = numberTimesBigInt(256, toThePower);
|
||||
}
|
||||
|
||||
return decimal.split('').reverse().join('');
|
||||
}
|
||||
|
||||
function rol32(a: number, count: number): number {
|
||||
return (a << count) | (a >>> (32 - count));
|
||||
}
|
||||
// x and y decimal, lowest significant digit first
|
||||
function addBigInt(x: string, y: string): string {
|
||||
let sum = '';
|
||||
const len = Math.max(x.length, y.length);
|
||||
for (let i = 0, carry = 0; i < len || carry; i++) {
|
||||
const tmpSum = carry + +(x[i] || 0) + +(y[i] || 0);
|
||||
if (tmpSum >= 10) {
|
||||
carry = 1;
|
||||
sum += tmpSum - 10;
|
||||
} else {
|
||||
carry = 0;
|
||||
sum += tmpSum;
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
function numberTimesBigInt(num: number, b: string): string {
|
||||
let product = '';
|
||||
let bToThePower = b;
|
||||
for (; num !== 0; num = num >>> 1) {
|
||||
if (num & 1) product = addBigInt(product, bToThePower);
|
||||
bToThePower = addBigInt(bToThePower, bToThePower);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
@ -12,9 +12,10 @@
|
||||
*/
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {analyzeAndValidateNgModules, extractProgramSymbols, loadNgModuleDirectives} from '../aot/compiler';
|
||||
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
|
||||
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
|
||||
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
|
||||
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
@ -41,7 +42,7 @@ export interface ExtractorOptions {
|
||||
* The host of the Extractor disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
*/
|
||||
export interface ExtractorHost extends StaticReflectorHost {
|
||||
export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost {
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
@ -58,32 +59,36 @@ export class Extractor {
|
||||
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
|
||||
return loadNgModuleDirectives(ngModules).then(() => {
|
||||
const errors: ParseError[] = [];
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
ngModule.type.reference, false)))
|
||||
.then(() => {
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const compMetas: CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
files.forEach(file => {
|
||||
const compMetas: CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return this.messageBundle;
|
||||
});
|
||||
return this.messageBundle;
|
||||
});
|
||||
}
|
||||
|
||||
static create(host: ExtractorHost, options: ExtractorOptions):
|
||||
@ -106,7 +111,8 @@ export class Extractor {
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
|
||||
new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options),
|
||||
elementSchemaRegistry, normalizer, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||
|
@ -10,7 +10,6 @@ import * as html from '../ml_parser/ast';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
|
||||
import {digestMessage} from './digest';
|
||||
import * as i18n from './i18n_ast';
|
||||
import {createI18nMessageFactory} from './i18n_parser';
|
||||
import {I18nError} from './parse_util';
|
||||
@ -214,8 +213,8 @@ class _Visitor implements html.Visitor {
|
||||
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
|
||||
// message
|
||||
const i18nAttr = _getI18nAttr(el);
|
||||
const isImplicit = this._implicitTags.some((tag: string): boolean => el.name === tag) &&
|
||||
!this._inIcu && !this._isInTranslatableSection;
|
||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||
!this._isInTranslatableSection;
|
||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
||||
|
||||
@ -348,14 +347,14 @@ class _Visitor implements html.Visitor {
|
||||
// no-op when called in extraction mode (returns [])
|
||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||
if (message && this._mode === _VisitorMode.Merge) {
|
||||
const id = digestMessage(message);
|
||||
const nodes = this._translations.get(id);
|
||||
const nodes = this._translations.get(message);
|
||||
|
||||
if (nodes) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
this._reportError(el, `Translation unavailable for message id="${id}"`);
|
||||
this._reportError(
|
||||
el, `Translation unavailable for message id="${this._translations.digest(message)}"`);
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -384,19 +383,20 @@ class _Visitor implements html.Visitor {
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const id = digestMessage(message);
|
||||
const nodes = this._translations.get(id);
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
const value = (nodes[0] as html.Text).value;
|
||||
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
|
||||
} else {
|
||||
this._reportError(
|
||||
el, `Unexpected translation for attribute "${attr.name}" (id="${id}")`);
|
||||
el,
|
||||
`Unexpected translation for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
|
||||
}
|
||||
} else {
|
||||
this._reportError(
|
||||
el, `Translation unavailable for attribute "${attr.name}" (id="${id}")`);
|
||||
el,
|
||||
`Translation unavailable for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
|
||||
}
|
||||
} else {
|
||||
translatedAttributes.push(attr);
|
||||
|
@ -12,18 +12,20 @@ export class Message {
|
||||
/**
|
||||
* @param nodes message AST
|
||||
* @param placeholders maps placeholder names to static content
|
||||
* @param placeholderToMsgIds maps placeholder names to translatable message IDs (used for ICU
|
||||
* messages)
|
||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||
* @param meaning
|
||||
* @param description
|
||||
*/
|
||||
constructor(
|
||||
public nodes: Node[], public placeholders: {[name: string]: string},
|
||||
public placeholderToMsgIds: {[name: string]: string}, public meaning: string,
|
||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||
public description: string) {}
|
||||
}
|
||||
|
||||
export interface Node { visit(visitor: Visitor, context?: any): any; }
|
||||
export interface Node {
|
||||
sourceSpan: ParseSourceSpan;
|
||||
visit(visitor: Visitor, context?: any): any;
|
||||
}
|
||||
|
||||
export class Text implements Node {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
@ -31,6 +33,7 @@ export class Text implements Node {
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitText(this, context); }
|
||||
}
|
||||
|
||||
// TODO(vicb): do we really need this node (vs an array) ?
|
||||
export class Container implements Node {
|
||||
constructor(public children: Node[], public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
@ -38,6 +41,7 @@ export class Container implements Node {
|
||||
}
|
||||
|
||||
export class Icu implements Node {
|
||||
public expressionPlaceholder: string;
|
||||
constructor(
|
||||
public expression: string, public type: string, public cases: {[k: string]: Node},
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
@ -55,13 +59,13 @@ export class TagPlaceholder implements Node {
|
||||
}
|
||||
|
||||
export class Placeholder implements Node {
|
||||
constructor(public value: string, public name: string = '', public sourceSpan: ParseSourceSpan) {}
|
||||
constructor(public value: string, public name: string, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitPlaceholder(this, context); }
|
||||
}
|
||||
|
||||
export class IcuPlaceholder implements Node {
|
||||
constructor(public value: Icu, public name: string = '', public sourceSpan: ParseSourceSpan) {}
|
||||
constructor(public value: Icu, public name: string, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitIcuPlaceholder(this, context); }
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/in
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
|
||||
import {mergeTranslations} from './extractor_merger';
|
||||
import {MessageBundle} from './message_bundle';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {Xliff} from './serializers/xliff';
|
||||
import {Xmb} from './serializers/xmb';
|
||||
@ -41,32 +40,29 @@ export class I18NHtmlParser implements HtmlParser {
|
||||
}
|
||||
|
||||
// TODO(vicb): add support for implicit tags / attributes
|
||||
const messageBundle = new MessageBundle(this._htmlParser, [], {});
|
||||
const errors = messageBundle.updateFromTemplate(source, url, interpolationConfig);
|
||||
|
||||
if (errors && errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors.concat(errors));
|
||||
if (parseResult.errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors);
|
||||
}
|
||||
|
||||
const serializer = this._createSerializer(interpolationConfig);
|
||||
const translationBundle =
|
||||
TranslationBundle.load(this._translations, url, messageBundle, serializer);
|
||||
const serializer = this._createSerializer();
|
||||
const translationBundle = TranslationBundle.load(this._translations, url, serializer);
|
||||
|
||||
return mergeTranslations(parseResult.rootNodes, translationBundle, interpolationConfig, [], {});
|
||||
}
|
||||
|
||||
private _createSerializer(interpolationConfig: InterpolationConfig): Serializer {
|
||||
private _createSerializer(): Serializer {
|
||||
const format = (this._translationsFormat || 'xlf').toLowerCase();
|
||||
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
return new Xmb();
|
||||
case 'xtb':
|
||||
return new Xtb(this._htmlParser, interpolationConfig);
|
||||
return new Xtb();
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
return new Xliff(this._htmlParser, interpolationConfig);
|
||||
return new Xliff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import * as html from '../ml_parser/ast';
|
||||
import {getHtmlTagDefinition} from '../ml_parser/html_tags';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {digestMessage} from './digest';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {PlaceholderRegistry} from './serializers/placeholder';
|
||||
@ -34,8 +33,8 @@ class _I18nVisitor implements html.Visitor {
|
||||
private _isIcu: boolean;
|
||||
private _icuDepth: number;
|
||||
private _placeholderRegistry: PlaceholderRegistry;
|
||||
private _placeholderToContent: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _placeholderToContent: {[phName: string]: string};
|
||||
private _placeholderToMessage: {[phName: string]: i18n.Message};
|
||||
|
||||
constructor(
|
||||
private _expressionParser: ExpressionParser,
|
||||
@ -46,12 +45,12 @@ class _I18nVisitor implements html.Visitor {
|
||||
this._icuDepth = 0;
|
||||
this._placeholderRegistry = new PlaceholderRegistry();
|
||||
this._placeholderToContent = {};
|
||||
this._placeholderToIds = {};
|
||||
this._placeholderToMessage = {};
|
||||
|
||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||
|
||||
return new i18n.Message(
|
||||
i18nodes, this._placeholderToContent, this._placeholderToIds, meaning, description);
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
||||
}
|
||||
|
||||
visitElement(el: html.Element, context: any): i18n.Node {
|
||||
@ -99,7 +98,13 @@ class _I18nVisitor implements html.Visitor {
|
||||
this._icuDepth--;
|
||||
|
||||
if (this._isIcu || this._icuDepth > 0) {
|
||||
// If the message (vs a part of the message) is an ICU message returns it
|
||||
// Returns an ICU node when:
|
||||
// - the message (vs a part of the message) is an ICU message, or
|
||||
// - the ICU message is nested.
|
||||
const expPh = this._placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
|
||||
i18nIcu.expressionPlaceholder = expPh;
|
||||
this._placeholderToContent[expPh] = icu.switchValue;
|
||||
|
||||
return i18nIcu;
|
||||
}
|
||||
|
||||
@ -110,7 +115,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
||||
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||
this._placeholderToIds[phName] = digestMessage(visitor.toI18nMessage([icu], '', ''));
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseError} from '../parse_util';
|
||||
|
||||
import {digestMessage} from './digest';
|
||||
import {extractMessages} from './extractor_merger';
|
||||
import {Message} from './i18n_ast';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
@ -19,7 +18,7 @@ import {Serializer} from './serializers/serializer';
|
||||
* A container for message extracted from the templates.
|
||||
*/
|
||||
export class MessageBundle {
|
||||
private _messageMap: {[id: string]: Message} = {};
|
||||
private _messages: Message[] = [];
|
||||
|
||||
constructor(
|
||||
private _htmlParser: HtmlParser, private _implicitTags: string[],
|
||||
@ -40,11 +39,10 @@ export class MessageBundle {
|
||||
return i18nParserResult.errors;
|
||||
}
|
||||
|
||||
i18nParserResult.messages.forEach(
|
||||
(message) => { this._messageMap[digestMessage(message)] = message; });
|
||||
this._messages.push(...i18nParserResult.messages);
|
||||
}
|
||||
|
||||
getMessageMap(): {[id: string]: Message} { return this._messageMap; }
|
||||
getMessages(): Message[] { return this._messages; }
|
||||
|
||||
write(serializer: Serializer): string { return serializer.write(this._messageMap); }
|
||||
write(serializer: Serializer): string { return serializer.write(this._messages); }
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ const TAG_TO_PLACEHOLDER_NAMES: {[k: string]: string} = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates unique names for placeholder with different content
|
||||
* Creates unique names for placeholder with different content.
|
||||
*
|
||||
* Returns the same placeholder name when the content is identical.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@ -93,6 +95,10 @@ export class PlaceholderRegistry {
|
||||
return uniqueName;
|
||||
}
|
||||
|
||||
getUniquePlaceholder(name: string): string {
|
||||
return this._generateUniqueName(name.toUpperCase());
|
||||
}
|
||||
|
||||
// Generate a hash for a tag - does not take attribute order into account
|
||||
private _hashTag(tag: string, attrs: {[k: string]: string}, isVoid: boolean): string {
|
||||
const start = `<${tag}`;
|
||||
@ -105,18 +111,8 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
let name = base;
|
||||
let next = this._placeHolderNameCounts[name];
|
||||
|
||||
if (!next) {
|
||||
next = 1;
|
||||
} else {
|
||||
name += `_${next}`;
|
||||
next++;
|
||||
}
|
||||
|
||||
this._placeHolderNameCounts[base] = next;
|
||||
|
||||
return name;
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
}
|
||||
}
|
||||
|
@ -6,36 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
|
||||
export interface Serializer {
|
||||
write(messageMap: {[id: string]: i18n.Message}): string;
|
||||
write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]};
|
||||
}
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
// Generate a map of placeholder to content indexed by message ids
|
||||
export function extractPlaceholders(messageBundle: MessageBundle) {
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders: {[id: string]: {[name: string]: string}} = {};
|
||||
|
||||
Object.keys(messageMap).forEach(msgId => {
|
||||
placeholders[msgId] = messageMap[msgId].placeholders;
|
||||
});
|
||||
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
// Generate a map of placeholder to message ids indexed by message ids
|
||||
export function extractPlaceholderToIds(messageBundle: MessageBundle) {
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholderToIds: {[id: string]: {[name: string]: string}} = {};
|
||||
|
||||
Object.keys(messageMap).forEach(msgId => {
|
||||
placeholderToIds[msgId] = messageMap[msgId].placeholderToMsgIds;
|
||||
});
|
||||
|
||||
return placeholderToIds;
|
||||
digest(message: i18n.Message): string;
|
||||
}
|
@ -7,15 +7,12 @@
|
||||
*/
|
||||
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import {ParseError} from '../../parse_util';
|
||||
import {digest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
|
||||
import {Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _VERSION = '1.2';
|
||||
@ -23,6 +20,7 @@ const _XMLNS = 'urn:oasis:names:tc:xliff:document:1.2';
|
||||
// TODO(vicb): make this a param (s/_/-/)
|
||||
const _SOURCE_LANG = 'en';
|
||||
const _PLACEHOLDER_TAG = 'x';
|
||||
|
||||
const _SOURCE_TAG = 'source';
|
||||
const _TARGET_TAG = 'target';
|
||||
const _UNIT_TAG = 'trans-unit';
|
||||
@ -30,17 +28,19 @@ const _UNIT_TAG = 'trans-unit';
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
|
||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
export class Xliff implements Serializer {
|
||||
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
write(messageMap: {[id: string]: i18n.Message}): string {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
const transUnits: xml.Node[] = [];
|
||||
|
||||
Object.keys(messageMap).forEach((id) => {
|
||||
const message = messageMap[id];
|
||||
messages.forEach(message => {
|
||||
const id = this.digest(message);
|
||||
|
||||
const transUnit = new xml.Tag(_UNIT_TAG, {id: id, datatype: 'html'});
|
||||
// deduplicate messages
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const transUnit = new xml.Tag(_UNIT_TAG, {id, datatype: 'html'});
|
||||
transUnit.children.push(
|
||||
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
|
||||
new xml.CR(8), new xml.Tag(_TARGET_TAG));
|
||||
@ -75,38 +75,28 @@ export class Xliff implements Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
|
||||
// Parse the xtb file into xml nodes
|
||||
const result = new XmlParser().parse(content, url);
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
// xliff to xml nodes
|
||||
const xliffParser = new XliffParser();
|
||||
const {mlNodesByMsgId, errors} = xliffParser.parse(content, url);
|
||||
|
||||
if (result.errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Replace the placeholders, messages are now string
|
||||
const {messages, errors} = new _LoadVisitor().parse(result.rootNodes, messageBundle);
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Convert the string messages to html ast
|
||||
// TODO(vicb): map error message back to the original message in xtb
|
||||
const messageMap: {[id: string]: ml.Node[]} = {};
|
||||
const parseErrors: ParseError[] = [];
|
||||
|
||||
Object.keys(messages).forEach((id) => {
|
||||
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
|
||||
parseErrors.push(...res.errors);
|
||||
messageMap[id] = res.rootNodes;
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
const converter = new XmlToI18n();
|
||||
Object.keys(mlNodesByMsgId).forEach(msgId => {
|
||||
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
|
||||
errors.push(...e);
|
||||
i18nNodesByMsgId[msgId] = i18nNodes;
|
||||
});
|
||||
|
||||
if (parseErrors.length) {
|
||||
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
|
||||
if (errors.length) {
|
||||
throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
return messageMap;
|
||||
return i18nNodesByMsgId;
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _WriteVisitor implements i18n.Visitor {
|
||||
@ -166,75 +156,46 @@ class _WriteVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
// TODO(vicb): add error management (structure)
|
||||
// TODO(vicb): factorize (xtb) ?
|
||||
class _LoadVisitor implements ml.Visitor {
|
||||
private _messageNodes: [string, ml.Node[]][];
|
||||
private _translatedMessages: {[id: string]: string};
|
||||
private _msgId: string;
|
||||
private _target: ml.Node[];
|
||||
// Extract messages as xml nodes from the xliff file
|
||||
class XliffParser implements ml.Visitor {
|
||||
private _unitMlNodes: ml.Node[];
|
||||
private _errors: I18nError[];
|
||||
private _placeholders: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
|
||||
|
||||
parse(nodes: ml.Node[], messageBundle: MessageBundle):
|
||||
{messages: {[k: string]: string}, errors: I18nError[]} {
|
||||
this._messageNodes = [];
|
||||
this._translatedMessages = {};
|
||||
this._msgId = '';
|
||||
this._target = [];
|
||||
this._errors = [];
|
||||
parse(xliff: string, url: string) {
|
||||
this._unitMlNodes = [];
|
||||
this._mlNodesByMsgId = {};
|
||||
|
||||
// Find all messages
|
||||
ml.visitAll(this, nodes, null);
|
||||
const xml = new XmlParser().parse(xliff, url, false);
|
||||
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders = extractPlaceholders(messageBundle);
|
||||
const placeholderToIds = extractPlaceholderToIds(messageBundle);
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes, null);
|
||||
|
||||
this._messageNodes
|
||||
.filter(message => {
|
||||
// Remove any messages that is not present in the source message bundle.
|
||||
return messageMap.hasOwnProperty(message[0]);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Because there could be no ICU placeholders inside an ICU message,
|
||||
// we do not need to take into account the `placeholderToMsgIds` of the referenced
|
||||
// messages, those would always be empty
|
||||
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
|
||||
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.forEach(message => {
|
||||
const id = message[0];
|
||||
this._placeholders = placeholders[id] || {};
|
||||
this._placeholderToIds = placeholderToIds[id] || {};
|
||||
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
|
||||
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
|
||||
});
|
||||
|
||||
return {messages: this._translatedMessages, errors: this._errors};
|
||||
return {
|
||||
mlNodesByMsgId: this._mlNodesByMsgId,
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(element: ml.Element, context: any): any {
|
||||
switch (element.name) {
|
||||
case _UNIT_TAG:
|
||||
this._target = null;
|
||||
const msgId = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!msgId) {
|
||||
this._unitMlNodes = null;
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
this._msgId = msgId.value;
|
||||
}
|
||||
ml.visitAll(this, element.children, null);
|
||||
if (this._msgId !== null) {
|
||||
this._messageNodes.push([this._msgId, this._target]);
|
||||
const id = idAttr.value;
|
||||
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
|
||||
this._addError(element, `Duplicated translations for msg ${id}`);
|
||||
} else {
|
||||
ml.visitAll(this, element.children, null);
|
||||
if (this._unitMlNodes) {
|
||||
this._mlNodesByMsgId[id] = this._unitMlNodes;
|
||||
} else {
|
||||
this._addError(element, `Message ${id} misses a translation`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -243,48 +204,65 @@ class _LoadVisitor implements ml.Visitor {
|
||||
break;
|
||||
|
||||
case _TARGET_TAG:
|
||||
this._target = element.children;
|
||||
break;
|
||||
|
||||
case _PLACEHOLDER_TAG:
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
const id = idAttr.value;
|
||||
if (this._placeholders.hasOwnProperty(id)) {
|
||||
return this._placeholders[id];
|
||||
}
|
||||
if (this._placeholderToIds.hasOwnProperty(id) &&
|
||||
this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])) {
|
||||
return this._translatedMessages[this._placeholderToIds[id]];
|
||||
}
|
||||
// TODO(vicb): better error message for when
|
||||
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])
|
||||
this._addError(element, `The placeholder "${id}" does not exists in the source message`);
|
||||
}
|
||||
this._unitMlNodes = element.children;
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(vicb): assert file structure, xliff version
|
||||
// For now only recurse on unhandled nodes
|
||||
ml.visitAll(this, element.children, null);
|
||||
}
|
||||
}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {}
|
||||
|
||||
visitText(text: ml.Text, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any {}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ml nodes (xliff syntax) to i18n nodes
|
||||
class XmlToI18n implements ml.Visitor {
|
||||
private _errors: I18nError[];
|
||||
|
||||
convert(nodes: ml.Node[]) {
|
||||
this._errors = [];
|
||||
return {
|
||||
i18nNodes: ml.visitAll(this, nodes),
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: ml.Text, context: any): any { return text.value; }
|
||||
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any { return ''; }
|
||||
visitElement(el: ml.Element, context: any): i18n.Placeholder {
|
||||
if (el.name === _PLACEHOLDER_TAG) {
|
||||
const nameAttr = el.attrs.find((attr) => attr.name === 'id');
|
||||
if (nameAttr) {
|
||||
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
|
||||
}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
this._addError(el, `Unexpected tag`);
|
||||
}
|
||||
}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
}
|
||||
visitExpansion(icu: ml.Expansion, context: any) {}
|
||||
|
||||
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any) {}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any) {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
|
@ -6,9 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
@ -39,12 +38,18 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
write(messageMap: {[k: string]: i18n.Message}): string {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _Visitor();
|
||||
const rootNode = new xml.Tag(_MESSAGES_TAG);
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
let rootNode = new xml.Tag(_MESSAGES_TAG);
|
||||
|
||||
messages.forEach(message => {
|
||||
const id = this.digest(message);
|
||||
|
||||
// deduplicate messages
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
Object.keys(messageMap).forEach((id) => {
|
||||
const message = messageMap[id];
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@ -71,9 +76,11 @@ export class Xmb implements Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]} {
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
throw new Error('Unsupported');
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
@ -86,7 +93,7 @@ class _Visitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expression}, ${icu.type}, `)];
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
@ -123,3 +130,7 @@ class _Visitor implements i18n.Visitor {
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
||||
export function digest(message: i18n.Message): string {
|
||||
return decimalDigest(message);
|
||||
}
|
@ -7,112 +7,63 @@
|
||||
*/
|
||||
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import {ParseError} from '../../parse_util';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
write(messageMap: {[id: string]: i18n.Message}): string { throw new Error('Unsupported'); }
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
// xtb to xml nodes
|
||||
const xtbParser = new XtbParser();
|
||||
const {mlNodesByMsgId, errors} = xtbParser.parse(content, url);
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
|
||||
// Parse the xtb file into xml nodes
|
||||
const result = new XmlParser().parse(content, url);
|
||||
|
||||
if (result.errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Replace the placeholders, messages are now string
|
||||
const {messages, errors} = new _Visitor().parse(result.rootNodes, messageBundle);
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
const converter = new XmlToI18n();
|
||||
Object.keys(mlNodesByMsgId).forEach(msgId => {
|
||||
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
|
||||
errors.push(...e);
|
||||
i18nNodesByMsgId[msgId] = i18nNodes;
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Convert the string messages to html ast
|
||||
// TODO(vicb): map error message back to the original message in xtb
|
||||
const messageMap: {[id: string]: ml.Node[]} = {};
|
||||
const parseErrors: ParseError[] = [];
|
||||
|
||||
Object.keys(messages).forEach((id) => {
|
||||
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
|
||||
parseErrors.push(...res.errors);
|
||||
messageMap[id] = res.rootNodes;
|
||||
});
|
||||
|
||||
if (parseErrors.length) {
|
||||
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
|
||||
}
|
||||
|
||||
return messageMap;
|
||||
return i18nNodesByMsgId;
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _Visitor implements ml.Visitor {
|
||||
private _messageNodes: [string, ml.Node[]][];
|
||||
private _translatedMessages: {[id: string]: string};
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
class XtbParser implements ml.Visitor {
|
||||
private _bundleDepth: number;
|
||||
private _translationDepth: number;
|
||||
private _errors: I18nError[];
|
||||
private _placeholders: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
|
||||
|
||||
parse(nodes: ml.Node[], messageBundle: MessageBundle):
|
||||
{messages: {[k: string]: string}, errors: I18nError[]} {
|
||||
this._messageNodes = [];
|
||||
this._translatedMessages = {};
|
||||
parse(xtb: string, url: string) {
|
||||
this._bundleDepth = 0;
|
||||
this._translationDepth = 0;
|
||||
this._errors = [];
|
||||
this._mlNodesByMsgId = {};
|
||||
|
||||
// Find all messages
|
||||
ml.visitAll(this, nodes, null);
|
||||
const xml = new XmlParser().parse(xtb, url, true);
|
||||
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders = extractPlaceholders(messageBundle);
|
||||
const placeholderToIds = extractPlaceholderToIds(messageBundle);
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes);
|
||||
|
||||
this._messageNodes
|
||||
.filter(message => {
|
||||
// Remove any messages that is not present in the source message bundle.
|
||||
return messageMap.hasOwnProperty(message[0]);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Because there could be no ICU placeholders inside an ICU message,
|
||||
// we do not need to take into account the `placeholderToMsgIds` of the referenced
|
||||
// messages, those would always be empty
|
||||
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
|
||||
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.forEach(message => {
|
||||
const id = message[0];
|
||||
this._placeholders = placeholders[id] || {};
|
||||
this._placeholderToIds = placeholderToIds[id] || {};
|
||||
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
|
||||
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
|
||||
});
|
||||
|
||||
return {messages: this._translatedMessages, errors: this._errors};
|
||||
return {
|
||||
mlNodesByMsgId: this._mlNodesByMsgId,
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(element: ml.Element, context: any): any {
|
||||
@ -127,40 +78,16 @@ class _Visitor implements ml.Visitor {
|
||||
break;
|
||||
|
||||
case _TRANSLATION_TAG:
|
||||
this._translationDepth++;
|
||||
if (this._translationDepth > 1) {
|
||||
this._addError(element, `<${_TRANSLATION_TAG}> elements can not be nested`);
|
||||
}
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_TRANSLATION_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
// ICU placeholders are reference to other messages.
|
||||
// The referenced message might not have been decoded yet.
|
||||
// We need to have all messages available to make sure deps are decoded first.
|
||||
// TODO(vicb): report an error on duplicate id
|
||||
this._messageNodes.push([idAttr.value, element.children]);
|
||||
}
|
||||
this._translationDepth--;
|
||||
break;
|
||||
|
||||
case _PLACEHOLDER_TAG:
|
||||
const nameAttr = element.attrs.find((attr) => attr.name === 'name');
|
||||
if (!nameAttr) {
|
||||
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
|
||||
} else {
|
||||
const name = nameAttr.value;
|
||||
if (this._placeholders.hasOwnProperty(name)) {
|
||||
return this._placeholders[name];
|
||||
const id = idAttr.value;
|
||||
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
|
||||
this._addError(element, `Duplicated translations for msg ${id}`);
|
||||
} else {
|
||||
this._mlNodesByMsgId[id] = element.children;
|
||||
}
|
||||
if (this._placeholderToIds.hasOwnProperty(name) &&
|
||||
this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])) {
|
||||
return this._translatedMessages[this._placeholderToIds[name]];
|
||||
}
|
||||
// TODO(vicb): better error message for when
|
||||
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])
|
||||
this._addError(
|
||||
element, `The placeholder "${name}" does not exists in the source message`);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -169,23 +96,68 @@ class _Visitor implements ml.Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
}
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {}
|
||||
|
||||
visitText(text: ml.Text, context: any): any { return text.value; }
|
||||
visitText(text: ml.Text, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any { return ''; }
|
||||
visitComment(comment: ml.Comment, context: any): any {}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {
|
||||
const strCases = expansion.cases.map(c => c.visit(this, null));
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {}
|
||||
|
||||
return `{${expansion.switchValue}, ${expansion.type}, strCases.join(' ')}`;
|
||||
}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
|
||||
return `${expansionCase.value} {${ml.visitAll(this, expansionCase.expression, null)}}`;
|
||||
}
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ml nodes (xtb syntax) to i18n nodes
|
||||
class XmlToI18n implements ml.Visitor {
|
||||
private _errors: I18nError[];
|
||||
|
||||
convert(nodes: ml.Node[]) {
|
||||
this._errors = [];
|
||||
return {
|
||||
i18nNodes: ml.visitAll(this, nodes),
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitExpansion(icu: ml.Expansion, context: any) {
|
||||
const caseMap: {[value: string]: i18n.Node} = {};
|
||||
|
||||
ml.visitAll(this, icu.cases).forEach(c => {
|
||||
caseMap[c.value] = new i18n.Container(c.nodes, icu.sourceSpan);
|
||||
});
|
||||
|
||||
return new i18n.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
|
||||
}
|
||||
|
||||
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {
|
||||
return {
|
||||
value: icuCase.value,
|
||||
nodes: ml.visitAll(this, icuCase.expression),
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(el: ml.Element, context: any): i18n.Placeholder {
|
||||
if (el.name === _PLACEHOLDER_TAG) {
|
||||
const nameAttr = el.attrs.find((attr) => attr.name === 'name');
|
||||
if (nameAttr) {
|
||||
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
|
||||
}
|
||||
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
|
||||
} else {
|
||||
this._addError(el, `Unexpected tag`);
|
||||
}
|
||||
}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any) {}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any) {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
|
@ -7,22 +7,120 @@
|
||||
*/
|
||||
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import {MessageBundle} from './message_bundle';
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
*/
|
||||
export class TranslationBundle {
|
||||
constructor(private _messageMap: {[id: string]: html.Node[]} = {}) {}
|
||||
private _i18nToHtml: I18nToHtmlVisitor;
|
||||
|
||||
static load(content: string, url: string, messageBundle: MessageBundle, serializer: Serializer):
|
||||
TranslationBundle {
|
||||
return new TranslationBundle(serializer.load(content, url, messageBundle));
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
}
|
||||
|
||||
get(id: string): html.Node[] { return this._messageMap[id]; }
|
||||
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn);
|
||||
}
|
||||
|
||||
has(id: string): boolean { return id in this._messageMap; }
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
if (html.errors.length) {
|
||||
throw new Error(html.errors.join('\n'));
|
||||
}
|
||||
|
||||
return html.nodes;
|
||||
}
|
||||
|
||||
has(srcMsg: i18n.Message): boolean { return this.digest(srcMsg) in this._i18nNodesByMsgId; }
|
||||
}
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
|
||||
// text to html
|
||||
const url = srcMsg.nodes[0].sourceSpan.start.file.url;
|
||||
const html = new HtmlParser().parse(text, url, true);
|
||||
|
||||
return {
|
||||
nodes: html.rootNodes,
|
||||
errors: [...this._errors, ...html.errors],
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, context?: any): string { return text.value; }
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): any {
|
||||
return container.children.map(n => n.visit(this)).join('');
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): any {
|
||||
const cases = Object.keys(icu.cases).map(k => `${k} {${icu.cases[k].visit(this)}}`);
|
||||
|
||||
// TODO(vicb): Once all format switch to using expression placeholders
|
||||
// we should throw when the placeholder is not in the source message
|
||||
const exp = this._srcMsg.placeholders.hasOwnProperty(icu.expression) ?
|
||||
this._srcMsg.placeholders[icu.expression] :
|
||||
icu.expression;
|
||||
|
||||
return `{${exp}, ${icu.type}, ${cases.join(' ')}}`;
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
|
||||
if (this._srcMsg.placeholderToMessage.hasOwnProperty(phName)) {
|
||||
return this._convertToText(this._srcMsg.placeholderToMessage[phName]);
|
||||
}
|
||||
|
||||
this._addError(ph, `Unknown placeholder`);
|
||||
return '';
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._srcMsg = srcMsg;
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
return text;
|
||||
}
|
||||
|
||||
this._addError(srcMsg.nodes[0], `Missing translation for message ${digest}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
private _addError(el: i18n.Node, msg: string) {
|
||||
this._errors.push(new I18nError(el.sourceSpan, msg));
|
||||
}
|
||||
}
|
@ -8,8 +8,8 @@
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol, isStaticSymbol} from './aot/static_symbol';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
|
||||
|
||||
const APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
|
||||
@ -347,27 +347,25 @@ export function assetUrl(pkg: string, path: string = null, type: string = 'src')
|
||||
}
|
||||
|
||||
export function resolveIdentifier(identifier: IdentifierSpec) {
|
||||
let moduleUrl = identifier.moduleUrl;
|
||||
return reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
|
||||
}
|
||||
|
||||
export function createIdentifier(identifier: IdentifierSpec): CompileIdentifierMetadata {
|
||||
const reference =
|
||||
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
|
||||
if (isStaticSymbol(reference)) {
|
||||
moduleUrl = reference.filePath;
|
||||
}
|
||||
return new CompileIdentifierMetadata(
|
||||
{name: identifier.name, moduleUrl: moduleUrl, reference: reference});
|
||||
return {reference: reference};
|
||||
}
|
||||
|
||||
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
|
||||
return new CompileTokenMetadata({identifier: identifier});
|
||||
return {identifier: identifier};
|
||||
}
|
||||
|
||||
export function resolveIdentifierToken(identifier: IdentifierSpec): CompileTokenMetadata {
|
||||
return identifierToken(resolveIdentifier(identifier));
|
||||
export function createIdentifierToken(identifier: IdentifierSpec): CompileTokenMetadata {
|
||||
return identifierToken(createIdentifier(identifier));
|
||||
}
|
||||
|
||||
export function resolveEnumIdentifier(
|
||||
enumType: CompileIdentifierMetadata, name: string): CompileIdentifierMetadata {
|
||||
const resolvedEnum = reflector.resolveEnum(enumType.reference, name);
|
||||
return new CompileIdentifierMetadata(
|
||||
{name: `${enumType.name}.${name}`, moduleUrl: enumType.moduleUrl, reference: resolvedEnum});
|
||||
export function createEnumIdentifier(
|
||||
enumType: IdentifierSpec, name: string): CompileIdentifierMetadata {
|
||||
const resolvedEnum = reflector.resolveEnum(resolveIdentifier(enumType), name);
|
||||
return {reference: resolvedEnum};
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFac
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta, identifierName} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
@ -70,6 +70,14 @@ export class JitCompiler implements Compiler {
|
||||
return this._compileModuleAndAllComponents(moduleType, false).asyncResult;
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
const template = this._compiledTemplateCache.get(component);
|
||||
if (!template) {
|
||||
throw new Error(`The component ${stringify(component)} is not yet compiled!`);
|
||||
}
|
||||
return template.compMeta.template.ngContentSelectors;
|
||||
}
|
||||
|
||||
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<NgModuleFactory<T>> {
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
@ -101,16 +109,14 @@ export class JitCompiler implements Compiler {
|
||||
|
||||
private _loadModules(mainModule: any, isSync: boolean): Promise<any> {
|
||||
const loadingPromises: Promise<any>[] = [];
|
||||
const {ngModule, loading} = this._metadataResolver.loadNgModuleMetadata(mainModule, isSync);
|
||||
loadingPromises.push(loading);
|
||||
const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule);
|
||||
// Note: the loadingPromise for a module only includes the loading of the exported directives
|
||||
// of imported modules.
|
||||
// However, for runtime compilation, we want to transitively compile all modules,
|
||||
// so we also need to call loadNgModuleMetadata for all nested modules.
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
loadingPromises.push(
|
||||
this._metadataResolver.loadNgModuleMetadata(localModuleMeta.type.reference, isSync)
|
||||
.loading);
|
||||
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
localModuleMeta.reference, isSync));
|
||||
});
|
||||
return Promise.all(loadingPromises);
|
||||
}
|
||||
@ -126,14 +132,13 @@ export class JitCompiler implements Compiler {
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
dep.placeholder.reference =
|
||||
this._assertComponentKnown(dep.comp.reference, true).proxyComponentFactory;
|
||||
dep.placeholder.name = `compFactory_${dep.comp.name}`;
|
||||
});
|
||||
if (!this._compilerConfig.useJit) {
|
||||
ngModuleFactory =
|
||||
interpretStatements(compileResult.statements, compileResult.ngModuleFactoryVar);
|
||||
} else {
|
||||
ngModuleFactory = jitStatements(
|
||||
`/${moduleMeta.type.name}/module.ngfactory.js`, compileResult.statements,
|
||||
`/${identifierName(moduleMeta.type)}/module.ngfactory.js`, compileResult.statements,
|
||||
compileResult.ngModuleFactoryVar);
|
||||
}
|
||||
this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
|
||||
@ -151,7 +156,7 @@ export class JitCompiler implements Compiler {
|
||||
|
||||
ngModule.transitiveModule.modules.forEach((localModuleSummary) => {
|
||||
const localModuleMeta =
|
||||
this._metadataResolver.getNgModuleMetadata(localModuleSummary.type.reference);
|
||||
this._metadataResolver.getNgModuleMetadata(localModuleSummary.reference);
|
||||
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
moduleByDirective.set(dirIdentifier.reference, localModuleMeta);
|
||||
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
|
||||
@ -169,7 +174,7 @@ export class JitCompiler implements Compiler {
|
||||
});
|
||||
ngModule.transitiveModule.modules.forEach((localModuleSummary) => {
|
||||
const localModuleMeta =
|
||||
this._metadataResolver.getNgModuleMetadata(localModuleSummary.type.reference);
|
||||
this._metadataResolver.getNgModuleMetadata(localModuleSummary.reference);
|
||||
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
|
||||
if (dirMeta.isComponent) {
|
||||
@ -215,7 +220,11 @@ export class JitCompiler implements Compiler {
|
||||
if (!compiledTemplate) {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(compType);
|
||||
assertComponent(compMeta);
|
||||
const hostMeta = createHostComponentMeta(compMeta);
|
||||
|
||||
const HostClass = function HostClass() {};
|
||||
(<any>HostClass).overriddenName = `${identifierName(compMeta.type)}_Host`;
|
||||
|
||||
const hostMeta = createHostComponentMeta(HostClass, compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
true, compMeta.selector, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||
@ -264,8 +273,8 @@ export class JitCompiler implements Compiler {
|
||||
directiveWrapperClass = interpretStatements(statements, compileResult.dirWrapperClassVar);
|
||||
} else {
|
||||
directiveWrapperClass = jitStatements(
|
||||
`/${moduleMeta.type.name}/${dirMeta.type.name}/wrapper.ngfactory.js`, statements,
|
||||
compileResult.dirWrapperClassVar);
|
||||
`/${identifierName(moduleMeta.type)}/${identifierName(dirMeta.type)}/wrapper.ngfactory.js`,
|
||||
statements, compileResult.dirWrapperClassVar);
|
||||
}
|
||||
this._compiledDirectiveWrapperCache.set(dirMeta.type.reference, directiveWrapperClass);
|
||||
}
|
||||
@ -288,9 +297,9 @@ export class JitCompiler implements Compiler {
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
const parsedTemplate = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
||||
compMeta.type.name);
|
||||
identifierName(compMeta.type));
|
||||
const compiledAnimations =
|
||||
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
|
||||
const compileResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||
pipes, compiledAnimations);
|
||||
@ -300,12 +309,10 @@ export class JitCompiler implements Compiler {
|
||||
const vfd = <ViewClassDependency>dep;
|
||||
depTemplate = this._assertComponentKnown(vfd.comp.reference, false);
|
||||
vfd.placeholder.reference = depTemplate.proxyViewClass;
|
||||
vfd.placeholder.name = `View_${vfd.comp.name}`;
|
||||
} else if (dep instanceof ComponentFactoryDependency) {
|
||||
const cfd = <ComponentFactoryDependency>dep;
|
||||
depTemplate = this._assertComponentKnown(cfd.comp.reference, true);
|
||||
cfd.placeholder.reference = depTemplate.proxyComponentFactory;
|
||||
cfd.placeholder.name = `compFactory_${cfd.comp.name}`;
|
||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||
const dwd = <DirectiveWrapperDependency>dep;
|
||||
dwd.placeholder.reference = this._assertDirectiveWrapper(dwd.dir.reference);
|
||||
@ -319,7 +326,7 @@ export class JitCompiler implements Compiler {
|
||||
viewClass = interpretStatements(statements, compileResult.viewClassVar);
|
||||
} else {
|
||||
viewClass = jitStatements(
|
||||
`/${template.ngModule.type.name}/${template.compType.name}/${template.isHost?'host':'component'}.ngfactory.js`,
|
||||
`/${identifierName(template.ngModule.type)}/${identifierName(template.compType)}/${template.isHost?'host':'component'}.ngfactory.js`,
|
||||
statements, compileResult.viewClassVar);
|
||||
}
|
||||
template.compiled(viewClass);
|
||||
@ -332,7 +339,6 @@ export class JitCompiler implements Compiler {
|
||||
const nestedStylesArr = this._resolveAndEvalStylesCompileResult(
|
||||
nestedCompileResult, externalStylesheetsByModuleUrl);
|
||||
dep.valuePlaceholder.reference = nestedStylesArr;
|
||||
dep.valuePlaceholder.name = `importedStyles${i}`;
|
||||
});
|
||||
}
|
||||
|
||||
@ -343,7 +349,8 @@ export class JitCompiler implements Compiler {
|
||||
if (!this._compilerConfig.useJit) {
|
||||
return interpretStatements(result.statements, result.stylesVar);
|
||||
} else {
|
||||
return jitStatements(`/${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar);
|
||||
return jitStatements(
|
||||
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, result.stylesVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -380,7 +387,8 @@ class CompiledTemplate {
|
||||
|
||||
function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
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.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,6 +416,11 @@ class ModuleBoundCompiler implements Compiler {
|
||||
return this._delegate.compileModuleAndAllComponentsAsync(moduleType);
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
return this._delegate.getNgContentSelectors(component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears all caches
|
||||
*/
|
||||
|
@ -26,6 +26,7 @@ import {ResourceLoader} from '../resource_loader';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
@ -46,6 +47,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
{provide: Reflector, useValue: reflector},
|
||||
{provide: ReflectorReader, useExisting: Reflector},
|
||||
{provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
|
||||
SummaryResolver,
|
||||
Console,
|
||||
Lexer,
|
||||
Parser,
|
||||
|
@ -6,25 +6,27 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Host, Inject, Injectable, ModuleWithProviders, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
|
||||
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Directive, Host, Inject, Injectable, ModuleWithProviders, OpaqueToken, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
|
||||
|
||||
import {isStaticSymbol} from './aot/static_symbol';
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
|
||||
import * as cpl from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifierToken} from './identifiers';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {hasLifecycleHook} from './lifecycle_reflector';
|
||||
import {NgModuleResolver} from './ng_module_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {SummaryResolver} from './summary_resolver';
|
||||
import {getUrlScheme} from './url_resolver';
|
||||
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
|
||||
|
||||
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, visitValue} from './util';
|
||||
|
||||
export type ErrorCollector = (error: any, type?: any) => void;
|
||||
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
||||
|
||||
// Design notes:
|
||||
// - don't lazily create metadata:
|
||||
@ -36,40 +38,24 @@ import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, vi
|
||||
@Injectable()
|
||||
export class CompileMetadataResolver {
|
||||
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
|
||||
private _directiveSummaryCache = new Map<Type<any>, cpl.CompileDirectiveSummary>();
|
||||
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
|
||||
private _pipeCache = new Map<Type<any>, cpl.CompilePipeMetadata>();
|
||||
private _pipeSummaryCache = new Map<Type<any>, cpl.CompilePipeSummary>();
|
||||
private _ngModuleCache = new Map<Type<any>, cpl.CompileNgModuleMetadata>();
|
||||
private _ngModuleOfTypes = new Map<Type<any>, Type<any>>();
|
||||
private _anonymousTypes = new Map<Object, number>();
|
||||
private _anonymousTypeIndex = 0;
|
||||
|
||||
constructor(
|
||||
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
||||
private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver,
|
||||
private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _directiveNormalizer: DirectiveNormalizer,
|
||||
private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
private sanitizeTokenName(token: any): string {
|
||||
let identifier = stringify(token);
|
||||
if (identifier.indexOf('(') >= 0) {
|
||||
// case: anonymous functions!
|
||||
let found = this._anonymousTypes.get(token);
|
||||
if (!found) {
|
||||
this._anonymousTypes.set(token, this._anonymousTypeIndex++);
|
||||
found = this._anonymousTypes.get(token);
|
||||
}
|
||||
identifier = `anonymous_token_${found}_`;
|
||||
}
|
||||
return sanitizeIdentifier(identifier);
|
||||
}
|
||||
private _reflector: ReflectorReader = reflector,
|
||||
@Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {}
|
||||
|
||||
clearCacheFor(type: Type<any>) {
|
||||
const dirMeta = this._directiveCache.get(type);
|
||||
this._directiveCache.delete(type);
|
||||
this._directiveSummaryCache.delete(type);
|
||||
this._summaryCache.delete(type);
|
||||
this._pipeCache.delete(type);
|
||||
this._pipeSummaryCache.delete(type);
|
||||
this._ngModuleOfTypes.delete(type);
|
||||
// Clear all of the NgModule as they contain transitive information!
|
||||
this._ngModuleCache.clear();
|
||||
@ -80,9 +66,8 @@ export class CompileMetadataResolver {
|
||||
|
||||
clearCache() {
|
||||
this._directiveCache.clear();
|
||||
this._directiveSummaryCache.clear();
|
||||
this._summaryCache.clear();
|
||||
this._pipeCache.clear();
|
||||
this._pipeSummaryCache.clear();
|
||||
this._ngModuleCache.clear();
|
||||
this._ngModuleOfTypes.clear();
|
||||
this._directiveNormalizer.clearCache();
|
||||
@ -142,55 +127,65 @@ export class CompileMetadataResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
|
||||
let summary = this._summaryCache.get(type);
|
||||
if (!summary) {
|
||||
summary = this._summaryResolver.resolveSummary(type);
|
||||
this._summaryCache.set(type, summary);
|
||||
}
|
||||
return summary && summary.summaryKind === kind ? summary : null;
|
||||
}
|
||||
|
||||
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
|
||||
if (this._directiveCache.has(directiveType)) {
|
||||
return;
|
||||
}
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
|
||||
const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType);
|
||||
|
||||
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
|
||||
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
|
||||
type: nonNormalizedMetadata.type,
|
||||
isComponent: nonNormalizedMetadata.isComponent,
|
||||
selector: nonNormalizedMetadata.selector,
|
||||
exportAs: nonNormalizedMetadata.exportAs,
|
||||
changeDetection: nonNormalizedMetadata.changeDetection,
|
||||
inputs: nonNormalizedMetadata.inputs,
|
||||
outputs: nonNormalizedMetadata.outputs,
|
||||
hostListeners: nonNormalizedMetadata.hostListeners,
|
||||
hostProperties: nonNormalizedMetadata.hostProperties,
|
||||
hostAttributes: nonNormalizedMetadata.hostAttributes,
|
||||
providers: nonNormalizedMetadata.providers,
|
||||
viewProviders: nonNormalizedMetadata.viewProviders,
|
||||
queries: nonNormalizedMetadata.queries,
|
||||
viewQueries: nonNormalizedMetadata.viewQueries,
|
||||
entryComponents: nonNormalizedMetadata.entryComponents,
|
||||
type: metadata.type,
|
||||
isComponent: metadata.isComponent,
|
||||
selector: metadata.selector,
|
||||
exportAs: metadata.exportAs,
|
||||
changeDetection: metadata.changeDetection,
|
||||
inputs: metadata.inputs,
|
||||
outputs: metadata.outputs,
|
||||
hostListeners: metadata.hostListeners,
|
||||
hostProperties: metadata.hostProperties,
|
||||
hostAttributes: metadata.hostAttributes,
|
||||
providers: metadata.providers,
|
||||
viewProviders: metadata.viewProviders,
|
||||
queries: metadata.queries,
|
||||
viewQueries: metadata.viewQueries,
|
||||
entryComponents: metadata.entryComponents,
|
||||
template: templateMetadata
|
||||
});
|
||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||
return normalizedDirMeta;
|
||||
};
|
||||
|
||||
if (nonNormalizedMetadata.isComponent) {
|
||||
if (metadata.isComponent) {
|
||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||
componentType: directiveType,
|
||||
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
|
||||
encapsulation: nonNormalizedMetadata.template.encapsulation,
|
||||
template: nonNormalizedMetadata.template.template,
|
||||
templateUrl: nonNormalizedMetadata.template.templateUrl,
|
||||
styles: nonNormalizedMetadata.template.styles,
|
||||
styleUrls: nonNormalizedMetadata.template.styleUrls,
|
||||
animations: nonNormalizedMetadata.template.animations,
|
||||
interpolation: nonNormalizedMetadata.template.interpolation
|
||||
moduleUrl: componentModuleUrl(this._reflector, directiveType, annotation),
|
||||
encapsulation: metadata.template.encapsulation,
|
||||
template: metadata.template.template,
|
||||
templateUrl: metadata.template.templateUrl,
|
||||
styles: metadata.template.styles,
|
||||
styleUrls: metadata.template.styleUrls,
|
||||
animations: metadata.template.animations,
|
||||
interpolation: metadata.template.interpolation
|
||||
});
|
||||
if (templateMeta.syncResult) {
|
||||
createDirectiveMetadata(templateMeta.syncResult);
|
||||
return null;
|
||||
} else {
|
||||
if (isSync) {
|
||||
throw new ComponentStillLoadingError(directiveType);
|
||||
this._reportError(new ComponentStillLoadingError(directiveType), directiveType);
|
||||
return null;
|
||||
}
|
||||
return templateMeta.asyncResult.then(createDirectiveMetadata);
|
||||
}
|
||||
@ -201,18 +196,17 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
getNonNormalizedDirectiveMetadata(directiveType: any):
|
||||
{annotation: Directive, metadata: cpl.CompileDirectiveMetadata} {
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
@ -233,7 +227,7 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let viewProviders: cpl.CompileProviderMetadata[] = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
@ -243,13 +237,12 @@ export class CompileMetadataResolver {
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
`viewProviders for "${stringify(directiveType)}"`, [], directiveType);
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||
@ -257,14 +250,18 @@ export class CompileMetadataResolver {
|
||||
} else {
|
||||
// Directive
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||
this._reportError(
|
||||
new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`),
|
||||
directiveType);
|
||||
selector = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let providers: cpl.CompileProviderMetadata[] = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
|
||||
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`,
|
||||
[], directiveType);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
@ -273,11 +270,11 @@ export class CompileMetadataResolver {
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
return cpl.CompileDirectiveMetadata.create({
|
||||
const metadata = cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!nonNormalizedTemplateMetadata,
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
type: this._getTypeMetadata(directiveType),
|
||||
template: nonNormalizedTemplateMetadata,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
@ -289,6 +286,7 @@ export class CompileMetadataResolver {
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
});
|
||||
return {metadata, annotation: dirMeta};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -298,17 +296,22 @@ export class CompileMetadataResolver {
|
||||
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
const dirMeta = this._directiveCache.get(directiveType);
|
||||
if (!dirMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`),
|
||||
directiveType);
|
||||
}
|
||||
return dirMeta;
|
||||
}
|
||||
|
||||
getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary {
|
||||
const dirSummary = this._directiveSummaryCache.get(dirType);
|
||||
const dirSummary =
|
||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||
if (!dirSummary) {
|
||||
throw new Error(
|
||||
`Illegal state: getDirectiveSummary can only be called after loadNgModuleMetadata for a module that imports it. Directive ${stringify(dirType)}.`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Illegal state: Could not load the summary for directive ${stringify(dirType)}.`),
|
||||
dirType);
|
||||
}
|
||||
return dirSummary;
|
||||
}
|
||||
@ -317,51 +320,39 @@ export class CompileMetadataResolver {
|
||||
|
||||
isPipe(type: any) { return this._pipeResolver.isPipe(type); }
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given module.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
*/
|
||||
getNgModuleMetadata(moduleType: any): cpl.CompileNgModuleMetadata {
|
||||
const modMeta = this._ngModuleCache.get(moduleType);
|
||||
if (!modMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getNgModuleMetadata can only be called after loadNgModuleMetadata. Module ${stringify(moduleType)}.`);
|
||||
getNgModuleSummary(moduleType: any): cpl.CompileNgModuleSummary {
|
||||
let moduleSummary =
|
||||
<cpl.CompileNgModuleSummary>this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule);
|
||||
if (!moduleSummary) {
|
||||
const moduleMeta = this.getNgModuleMetadata(moduleType, false);
|
||||
moduleSummary = moduleMeta ? moduleMeta.toSummary() : null;
|
||||
if (moduleSummary) {
|
||||
this._summaryCache.set(moduleType, moduleSummary);
|
||||
}
|
||||
}
|
||||
return modMeta;
|
||||
}
|
||||
|
||||
private _loadNgModuleSummary(moduleType: any, isSync: boolean): cpl.CompileNgModuleSummary {
|
||||
// TODO(tbosch): add logic to read summary files!
|
||||
// - needs to add directive / pipe summaries to this._directiveSummaryCache /
|
||||
// this._pipeSummaryCache as well!
|
||||
const moduleMeta = this._loadNgModuleMetadata(moduleType, isSync, false);
|
||||
return moduleMeta ? moduleMeta.toSummary() : null;
|
||||
return moduleSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an NgModule and all of its directives. This includes loading the exported directives of
|
||||
* imported modules,
|
||||
* but not private directives of imported modules.
|
||||
* Loads the declared directives and pipes of an NgModule.
|
||||
*/
|
||||
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
|
||||
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
const loading = ngModule ?
|
||||
Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
|
||||
Promise.resolve(null);
|
||||
return {ngModule, loading};
|
||||
loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
Promise<any> {
|
||||
const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound);
|
||||
const loading: Promise<any>[] = [];
|
||||
if (ngModule) {
|
||||
ngModule.declaredDirectives.forEach((id) => {
|
||||
const promise = this._loadDirectiveMetadata(id.reference, isSync);
|
||||
if (promise) {
|
||||
loading.push(promise);
|
||||
}
|
||||
});
|
||||
ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference));
|
||||
}
|
||||
return Promise.all(loading);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NgModule metadata without loading the directives.
|
||||
*/
|
||||
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
}
|
||||
|
||||
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
let compileMeta = this._ngModuleCache.get(moduleType);
|
||||
if (compileMeta) {
|
||||
@ -376,7 +367,7 @@ export class CompileMetadataResolver {
|
||||
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
|
||||
const importedModules: cpl.CompileNgModuleSummary[] = [];
|
||||
const exportedModules: cpl.CompileNgModuleSummary[] = [];
|
||||
const providers: any[] = [];
|
||||
const providers: cpl.CompileProviderMetadata[] = [];
|
||||
const entryComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const schemas: SchemaMetadata[] = [];
|
||||
@ -392,20 +383,26 @@ export class CompileMetadataResolver {
|
||||
if (moduleWithProviders.providers) {
|
||||
providers.push(...this._getProvidersMetadata(
|
||||
moduleWithProviders.providers, entryComponents,
|
||||
`provider for the NgModule '${stringify(importedModuleType)}'`));
|
||||
`provider for the NgModule '${stringify(importedModuleType)}'`, [], importedType));
|
||||
}
|
||||
}
|
||||
|
||||
if (importedModuleType) {
|
||||
const importedModuleSummary = this._loadNgModuleSummary(importedModuleType, isSync);
|
||||
const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
|
||||
if (!importedModuleSummary) {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
importedModules.push(importedModuleSummary);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -413,15 +410,17 @@ export class CompileMetadataResolver {
|
||||
if (meta.exports) {
|
||||
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
|
||||
if (!isValidType(exportedType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
const exportedModuleSummary = this._loadNgModuleSummary(exportedType, isSync);
|
||||
const exportedModuleSummary = this.getNgModuleSummary(exportedType);
|
||||
if (exportedModuleSummary) {
|
||||
exportedModules.push(exportedModuleSummary);
|
||||
} else {
|
||||
exportedNonModuleIdentifiers.push(
|
||||
this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType)));
|
||||
exportedNonModuleIdentifiers.push(this._getIdentifierMetadata(exportedType));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -432,27 +431,28 @@ export class CompileMetadataResolver {
|
||||
if (meta.declarations) {
|
||||
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
|
||||
if (!isValidType(declaredType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
const declaredIdentifier =
|
||||
this._getIdentifierMetadata(declaredType, staticTypeModuleUrl(declaredType));
|
||||
const declaredIdentifier = this._getIdentifierMetadata(declaredType);
|
||||
if (this._directiveResolver.isDirective(declaredType)) {
|
||||
transitiveModule.directivesSet.add(declaredType);
|
||||
transitiveModule.directives.push(declaredIdentifier);
|
||||
transitiveModule.addDirective(declaredIdentifier);
|
||||
declaredDirectives.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
transitiveModule.directiveLoaders.push(
|
||||
() => this._loadDirectiveMetadata(declaredType, isSync));
|
||||
} else if (this._pipeResolver.isPipe(declaredType)) {
|
||||
transitiveModule.pipesSet.add(declaredType);
|
||||
transitiveModule.addPipe(declaredIdentifier);
|
||||
transitiveModule.pipes.push(declaredIdentifier);
|
||||
declaredPipes.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
this._loadPipeMetadata(declaredType);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -462,11 +462,15 @@ export class CompileMetadataResolver {
|
||||
exportedNonModuleIdentifiers.forEach((exportedId) => {
|
||||
if (transitiveModule.directivesSet.has(exportedId.reference)) {
|
||||
exportedDirectives.push(exportedId);
|
||||
transitiveModule.addExportedDirective(exportedId);
|
||||
} else if (transitiveModule.pipesSet.has(exportedId.reference)) {
|
||||
exportedPipes.push(exportedId);
|
||||
transitiveModule.addExportedPipe(exportedId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`),
|
||||
moduleType);
|
||||
}
|
||||
});
|
||||
|
||||
@ -474,24 +478,26 @@ export class CompileMetadataResolver {
|
||||
// so that they overwrite any other provider we already added.
|
||||
if (meta.providers) {
|
||||
providers.push(...this._getProvidersMetadata(
|
||||
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`));
|
||||
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`,
|
||||
[], moduleType));
|
||||
}
|
||||
|
||||
if (meta.entryComponents) {
|
||||
entryComponents.push(
|
||||
...flattenAndDedupeArray(meta.entryComponents)
|
||||
.map(type => this._getTypeMetadata(type, staticTypeModuleUrl(type))));
|
||||
...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type)));
|
||||
}
|
||||
|
||||
if (meta.bootstrap) {
|
||||
const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => {
|
||||
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
|
||||
if (!isValidType(type)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`),
|
||||
moduleType);
|
||||
return;
|
||||
}
|
||||
return this._getTypeMetadata(type, staticTypeModuleUrl(type));
|
||||
bootstrapComponents.push(this._getTypeMetadata(type));
|
||||
});
|
||||
bootstrapComponents.push(...typeMetadata);
|
||||
}
|
||||
|
||||
entryComponents.push(...bootstrapComponents);
|
||||
@ -500,11 +506,8 @@ export class CompileMetadataResolver {
|
||||
schemas.push(...flattenAndDedupeArray(meta.schemas));
|
||||
}
|
||||
|
||||
transitiveModule.entryComponents.push(...entryComponents);
|
||||
transitiveModule.providers.push(...providers);
|
||||
|
||||
compileMeta = new cpl.CompileNgModuleMetadata({
|
||||
type: this._getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
|
||||
type: this._getTypeMetadata(moduleType),
|
||||
providers,
|
||||
entryComponents,
|
||||
bootstrapComponents,
|
||||
@ -519,7 +522,9 @@ export class CompileMetadataResolver {
|
||||
id: meta.id,
|
||||
});
|
||||
|
||||
transitiveModule.modules.push(compileMeta.toInjectorSummary());
|
||||
entryComponents.forEach((id) => transitiveModule.addEntryComponent(id));
|
||||
providers.forEach((provider) => transitiveModule.addProvider(provider, compileMeta.type));
|
||||
transitiveModule.addModule(compileMeta.type);
|
||||
this._ngModuleCache.set(moduleType, compileMeta);
|
||||
return compileMeta;
|
||||
}
|
||||
@ -548,10 +553,12 @@ export class CompileMetadataResolver {
|
||||
private _addTypeToModule(type: Type<any>, moduleType: Type<any>) {
|
||||
const oldModule = this._ngModuleOfTypes.get(type);
|
||||
if (oldModule && oldModule !== moduleType) {
|
||||
throw new Error(
|
||||
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
|
||||
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
|
||||
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` +
|
||||
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`),
|
||||
moduleType);
|
||||
}
|
||||
this._ngModuleOfTypes.set(type, moduleType);
|
||||
}
|
||||
@ -560,50 +567,59 @@ export class CompileMetadataResolver {
|
||||
importedModules: cpl.CompileNgModuleSummary[],
|
||||
exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata {
|
||||
// collect `providers` / `entryComponents` from all imported and all exported modules
|
||||
const transitiveModules = getTransitiveImportedModules(importedModules.concat(exportedModules));
|
||||
const providers = flattenArray(transitiveModules.map((ngModule) => ngModule.providers));
|
||||
const entryComponents =
|
||||
flattenArray(transitiveModules.map((ngModule) => ngModule.entryComponents));
|
||||
|
||||
const transitiveExportedModules = getTransitiveExportedModules(importedModules);
|
||||
const directives =
|
||||
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
|
||||
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
|
||||
const directiveLoaders =
|
||||
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
|
||||
return new cpl.TransitiveCompileNgModuleMetadata(
|
||||
transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
|
||||
const result = new cpl.TransitiveCompileNgModuleMetadata();
|
||||
const modulesByToken = new Map<any, Set<any>>();
|
||||
importedModules.concat(exportedModules).forEach((modSummary) => {
|
||||
modSummary.modules.forEach((mod) => result.addModule(mod));
|
||||
modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp));
|
||||
const addedTokens = new Set<any>();
|
||||
modSummary.providers.forEach((entry) => {
|
||||
const tokenRef = cpl.tokenReference(entry.provider.token);
|
||||
let prevModules = modulesByToken.get(tokenRef);
|
||||
if (!prevModules) {
|
||||
prevModules = new Set<any>();
|
||||
modulesByToken.set(tokenRef, prevModules);
|
||||
}
|
||||
const moduleRef = entry.module.reference;
|
||||
// Note: the providers of one module may still contain multiple providers
|
||||
// per token (e.g. for multi providers), and we need to preserve these.
|
||||
if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) {
|
||||
prevModules.add(moduleRef);
|
||||
addedTokens.add(tokenRef);
|
||||
result.addProvider(entry.provider, entry.module);
|
||||
}
|
||||
});
|
||||
});
|
||||
exportedModules.forEach((modSummary) => {
|
||||
modSummary.exportedDirectives.forEach((id) => result.addExportedDirective(id));
|
||||
modSummary.exportedPipes.forEach((id) => result.addExportedPipe(id));
|
||||
});
|
||||
importedModules.forEach((modSummary) => {
|
||||
modSummary.exportedDirectives.forEach((id) => result.addDirective(id));
|
||||
modSummary.exportedPipes.forEach((id) => result.addPipe(id));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
|
||||
cpl.CompileIdentifierMetadata {
|
||||
private _getIdentifierMetadata(type: Type<any>): cpl.CompileIdentifierMetadata {
|
||||
type = resolveForwardRef(type);
|
||||
return new cpl.CompileIdentifierMetadata(
|
||||
{name: this.sanitizeTokenName(type), moduleUrl, reference: type});
|
||||
return {reference: type};
|
||||
}
|
||||
|
||||
private _getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
|
||||
cpl.CompileTypeMetadata {
|
||||
const identifier = this._getIdentifierMetadata(type, moduleUrl);
|
||||
return new cpl.CompileTypeMetadata({
|
||||
name: identifier.name,
|
||||
moduleUrl: identifier.moduleUrl,
|
||||
private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata {
|
||||
const identifier = this._getIdentifierMetadata(type);
|
||||
return {
|
||||
reference: identifier.reference,
|
||||
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies),
|
||||
lifecycleHooks:
|
||||
LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, identifier.reference)),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private _getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
|
||||
private _getFactoryMetadata(factory: Function, dependencies: any[] = null):
|
||||
cpl.CompileFactoryMetadata {
|
||||
factory = resolveForwardRef(factory);
|
||||
return new cpl.CompileFactoryMetadata({
|
||||
name: this.sanitizeTokenName(factory),
|
||||
moduleUrl,
|
||||
reference: factory,
|
||||
diDeps: this._getDependenciesMetadata(factory, dependencies)
|
||||
});
|
||||
return {reference: factory, diDeps: this._getDependenciesMetadata(factory, dependencies)};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -613,17 +629,21 @@ export class CompileMetadataResolver {
|
||||
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
const pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`),
|
||||
pipeType);
|
||||
}
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
getPipeSummary(pipeType: any): cpl.CompilePipeSummary {
|
||||
const pipeSummary = this._pipeSummaryCache.get(pipeType);
|
||||
const pipeSummary =
|
||||
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
|
||||
if (!pipeSummary) {
|
||||
throw new Error(
|
||||
`Illegal state: getPipeSummary can only be called after loadNgModuleMetadata for a module that imports it. Pipe ${stringify(pipeType)}.`);
|
||||
this._reportError(
|
||||
new Error(`Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`),
|
||||
pipeType);
|
||||
}
|
||||
return pipeSummary;
|
||||
}
|
||||
@ -641,12 +661,12 @@ export class CompileMetadataResolver {
|
||||
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
|
||||
|
||||
const pipeMeta = new cpl.CompilePipeMetadata({
|
||||
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
|
||||
type: this._getTypeMetadata(pipeType),
|
||||
name: pipeAnnotation.name,
|
||||
pure: pipeAnnotation.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, pipeMeta);
|
||||
this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
|
||||
this._summaryCache.set(pipeType, pipeMeta.toSummary());
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
@ -689,22 +709,23 @@ export class CompileMetadataResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new cpl.CompileDiDependencyMetadata({
|
||||
return {
|
||||
isAttribute,
|
||||
isHost,
|
||||
isSelf,
|
||||
isSkipSelf,
|
||||
isOptional,
|
||||
token: this._getTokenMetadata(token)
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (hasUnknownDeps) {
|
||||
const depsTokens =
|
||||
dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', ');
|
||||
throw new Error(
|
||||
`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`);
|
||||
this._reportError(
|
||||
new Error(`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`),
|
||||
typeOrFunc);
|
||||
}
|
||||
|
||||
return dependenciesMetadata;
|
||||
@ -714,81 +735,75 @@ export class CompileMetadataResolver {
|
||||
token = resolveForwardRef(token);
|
||||
let compileToken: cpl.CompileTokenMetadata;
|
||||
if (typeof token === 'string') {
|
||||
compileToken = new cpl.CompileTokenMetadata({value: token});
|
||||
compileToken = {value: token};
|
||||
} else {
|
||||
compileToken = new cpl.CompileTokenMetadata({
|
||||
identifier: new cpl.CompileIdentifierMetadata({
|
||||
reference: token,
|
||||
name: this.sanitizeTokenName(token),
|
||||
moduleUrl: staticTypeModuleUrl(token)
|
||||
})
|
||||
});
|
||||
compileToken = {identifier: {reference: token}};
|
||||
}
|
||||
return compileToken;
|
||||
}
|
||||
|
||||
private _getProvidersMetadata(
|
||||
providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
|
||||
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
|
||||
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [],
|
||||
type?: any): cpl.CompileProviderMetadata[] {
|
||||
providers.forEach((provider: any, providerIdx: number) => {
|
||||
provider = resolveForwardRef(provider);
|
||||
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
|
||||
provider = new cpl.ProviderMeta(provider.provide, provider);
|
||||
}
|
||||
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
|
||||
if (Array.isArray(provider)) {
|
||||
compileProvider = this._getProvidersMetadata(provider, targetEntryComponents, debugInfo);
|
||||
} else if (provider instanceof cpl.ProviderMeta) {
|
||||
const tokenMeta = this._getTokenMetadata(provider.token);
|
||||
if (tokenMeta.reference ===
|
||||
resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) {
|
||||
targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider));
|
||||
} else {
|
||||
compileProvider = this.getProviderMetadata(provider);
|
||||
}
|
||||
} else if (isValidType(provider)) {
|
||||
compileProvider = this._getTypeMetadata(provider, staticTypeModuleUrl(provider));
|
||||
this._getProvidersMetadata(provider, targetEntryComponents, debugInfo, compileProviders);
|
||||
} else {
|
||||
const providersInfo =
|
||||
(<string[]>providers.reduce(
|
||||
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
||||
if (seenProviderIdx < providerIdx) {
|
||||
soFar.push(`${stringify(seenProvider)}`);
|
||||
} else if (seenProviderIdx == providerIdx) {
|
||||
soFar.push(`?${stringify(seenProvider)}?`);
|
||||
} else if (seenProviderIdx == providerIdx + 1) {
|
||||
soFar.push('...');
|
||||
}
|
||||
return soFar;
|
||||
},
|
||||
[]))
|
||||
.join(', ');
|
||||
|
||||
throw new Error(
|
||||
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
|
||||
}
|
||||
if (compileProvider) {
|
||||
compileProviders.push(compileProvider);
|
||||
provider = resolveForwardRef(provider);
|
||||
let providerMeta: cpl.ProviderMeta;
|
||||
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
|
||||
providerMeta = new cpl.ProviderMeta(provider.provide, provider);
|
||||
} else if (isValidType(provider)) {
|
||||
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
|
||||
} else {
|
||||
const providersInfo =
|
||||
(<string[]>providers.reduce(
|
||||
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
||||
if (seenProviderIdx < providerIdx) {
|
||||
soFar.push(`${stringify(seenProvider)}`);
|
||||
} else if (seenProviderIdx == providerIdx) {
|
||||
soFar.push(`?${stringify(seenProvider)}?`);
|
||||
} else if (seenProviderIdx == providerIdx + 1) {
|
||||
soFar.push('...');
|
||||
}
|
||||
return soFar;
|
||||
},
|
||||
[]))
|
||||
.join(', ');
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
|
||||
type);
|
||||
}
|
||||
if (providerMeta.token === resolveIdentifier(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS)) {
|
||||
targetEntryComponents.push(...this._getEntryComponentsFromProvider(providerMeta, type));
|
||||
} else {
|
||||
compileProviders.push(this.getProviderMetadata(providerMeta));
|
||||
}
|
||||
}
|
||||
});
|
||||
return compileProviders;
|
||||
}
|
||||
|
||||
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta):
|
||||
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any):
|
||||
cpl.CompileIdentifierMetadata[] {
|
||||
const components: cpl.CompileIdentifierMetadata[] = [];
|
||||
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
|
||||
|
||||
if (provider.useFactory || provider.useExisting || provider.useClass) {
|
||||
throw new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`);
|
||||
this._reportError(
|
||||
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!provider.multi) {
|
||||
throw new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`);
|
||||
this._reportError(
|
||||
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), type);
|
||||
return [];
|
||||
}
|
||||
|
||||
convertToCompileValue(provider.useValue, collectedIdentifiers);
|
||||
extractIdentifiers(provider.useValue, collectedIdentifiers);
|
||||
collectedIdentifiers.forEach((identifier) => {
|
||||
if (this._directiveResolver.isDirective(identifier.reference)) {
|
||||
components.push(identifier);
|
||||
@ -801,26 +816,29 @@ export class CompileMetadataResolver {
|
||||
let compileDeps: cpl.CompileDiDependencyMetadata[];
|
||||
let compileTypeMetadata: cpl.CompileTypeMetadata = null;
|
||||
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null;
|
||||
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
|
||||
|
||||
if (provider.useClass) {
|
||||
compileTypeMetadata = this._getTypeMetadata(
|
||||
provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies);
|
||||
compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies);
|
||||
compileDeps = compileTypeMetadata.diDeps;
|
||||
if (provider.token === provider.useClass) {
|
||||
// use the compileTypeMetadata as it contains information about lifecycleHooks...
|
||||
token = {identifier: compileTypeMetadata};
|
||||
}
|
||||
} else if (provider.useFactory) {
|
||||
compileFactoryMetadata = this._getFactoryMetadata(
|
||||
provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies);
|
||||
compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies);
|
||||
compileDeps = compileFactoryMetadata.diDeps;
|
||||
}
|
||||
|
||||
return new cpl.CompileProviderMetadata({
|
||||
token: this._getTokenMetadata(provider.token),
|
||||
return {
|
||||
token: token,
|
||||
useClass: compileTypeMetadata,
|
||||
useValue: convertToCompileValue(provider.useValue, []),
|
||||
useValue: provider.useValue,
|
||||
useFactory: compileFactoryMetadata,
|
||||
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : null,
|
||||
deps: compileDeps,
|
||||
multi: provider.multi
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private _getQueriesMetadata(
|
||||
@ -848,52 +866,32 @@ export class CompileMetadataResolver {
|
||||
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
|
||||
} else {
|
||||
if (!q.selector) {
|
||||
throw new Error(
|
||||
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`);
|
||||
this._reportError(
|
||||
new Error(
|
||||
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`),
|
||||
typeOrFunc);
|
||||
}
|
||||
selectors = [this._getTokenMetadata(q.selector)];
|
||||
}
|
||||
|
||||
return new cpl.CompileQueryMetadata({
|
||||
return {
|
||||
selectors,
|
||||
first: q.first,
|
||||
descendants: q.descendants, propertyName,
|
||||
read: q.read ? this._getTokenMetadata(q.read) : null
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getTransitiveExportedModules(
|
||||
modules: cpl.CompileNgModuleDirectiveSummary[],
|
||||
targetModules: cpl.CompileNgModuleDirectiveSummary[] = [],
|
||||
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleDirectiveSummary[] {
|
||||
modules.forEach((ngModule) => {
|
||||
if (!visitedModules.has(ngModule.type.reference)) {
|
||||
visitedModules.add(ngModule.type.reference);
|
||||
getTransitiveExportedModules(ngModule.exportedModules, targetModules, visitedModules);
|
||||
// Add after recursing so imported/exported modules are before the module itself.
|
||||
// This is important for overwriting providers of imported modules!
|
||||
targetModules.push(ngModule);
|
||||
private _reportError(error: any, type?: any, otherType?: any) {
|
||||
if (this._errorCollector) {
|
||||
this._errorCollector(error, type);
|
||||
if (otherType) {
|
||||
this._errorCollector(error, otherType);
|
||||
}
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
return targetModules;
|
||||
}
|
||||
|
||||
function getTransitiveImportedModules(
|
||||
modules: cpl.CompileNgModuleInjectorSummary[],
|
||||
targetModules: cpl.CompileNgModuleInjectorSummary[] = [],
|
||||
visitedModules = new Set<Type<any>>()): cpl.CompileNgModuleInjectorSummary[] {
|
||||
modules.forEach((ngModule) => {
|
||||
if (!visitedModules.has(ngModule.type.reference)) {
|
||||
visitedModules.add(ngModule.type.reference);
|
||||
const nestedModules = ngModule.importedModules.concat(ngModule.exportedModules);
|
||||
getTransitiveImportedModules(nestedModules, targetModules, visitedModules);
|
||||
// Add after recursing so imported/exported modules are before the module itself.
|
||||
// This is important for overwriting providers of imported modules!
|
||||
targetModules.push(ngModule);
|
||||
}
|
||||
});
|
||||
return targetModules;
|
||||
}
|
||||
}
|
||||
|
||||
function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
|
||||
@ -922,17 +920,13 @@ function flattenAndDedupeArray(tree: any[]): Array<any> {
|
||||
}
|
||||
|
||||
function isValidType(value: any): boolean {
|
||||
return isStaticSymbol(value) || (value instanceof Type);
|
||||
return (value instanceof StaticSymbol) || (value instanceof Type);
|
||||
}
|
||||
|
||||
function staticTypeModuleUrl(value: any): string {
|
||||
return isStaticSymbol(value) ? value.filePath : null;
|
||||
}
|
||||
|
||||
function componentModuleUrl(
|
||||
export function componentModuleUrl(
|
||||
reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string {
|
||||
if (isStaticSymbol(type)) {
|
||||
return staticTypeModuleUrl(type);
|
||||
if (type instanceof StaticSymbol) {
|
||||
return type.filePath;
|
||||
}
|
||||
|
||||
const moduleId = cmpMetadata.moduleId;
|
||||
@ -949,21 +943,12 @@ function componentModuleUrl(
|
||||
return reflector.importUri(type);
|
||||
}
|
||||
|
||||
function convertToCompileValue(
|
||||
value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
|
||||
return visitValue(value, new _CompileValueConverter(), targetIdentifiers);
|
||||
function extractIdentifiers(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]) {
|
||||
visitValue(value, new _CompileValueConverter(), targetIdentifiers);
|
||||
}
|
||||
|
||||
class _CompileValueConverter extends ValueTransformer {
|
||||
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
|
||||
let identifier: cpl.CompileIdentifierMetadata;
|
||||
if (isStaticSymbol(value)) {
|
||||
identifier = new cpl.CompileIdentifierMetadata(
|
||||
{name: value.name, moduleUrl: value.filePath, reference: value});
|
||||
} else {
|
||||
identifier = new cpl.CompileIdentifierMetadata({reference: value});
|
||||
}
|
||||
targetIdentifiers.push(identifier);
|
||||
return identifier;
|
||||
targetIdentifiers.push({reference: value});
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,10 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
||||
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {convertValueToOutputAst} from './output/value_util';
|
||||
@ -35,9 +35,10 @@ export class NgModuleCompileResult {
|
||||
export class NgModuleCompiler {
|
||||
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
|
||||
NgModuleCompileResult {
|
||||
const sourceFileName = isPresent(ngModuleMeta.type.moduleUrl) ?
|
||||
`in NgModule ${ngModuleMeta.type.name} in ${ngModuleMeta.type.moduleUrl}` :
|
||||
`in NgModule ${ngModuleMeta.type.name}`;
|
||||
const moduleUrl = identifierModuleUrl(ngModuleMeta.type);
|
||||
const sourceFileName = isPresent(moduleUrl) ?
|
||||
`in NgModule ${identifierName(ngModuleMeta.type)} in ${moduleUrl}` :
|
||||
`in NgModule ${identifierName(ngModuleMeta.type)}`;
|
||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
new ParseLocation(sourceFile, null, null, null),
|
||||
@ -46,8 +47,9 @@ export class NgModuleCompiler {
|
||||
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
|
||||
const entryComponentFactories =
|
||||
ngModuleMeta.transitiveModule.entryComponents.map((entryComponent) => {
|
||||
const id = new CompileIdentifierMetadata({name: entryComponent.name});
|
||||
if (ngModuleMeta.bootstrapComponents.indexOf(entryComponent) > -1) {
|
||||
const id: CompileIdentifierMetadata = {reference: null};
|
||||
if (ngModuleMeta.bootstrapComponents.some(
|
||||
(id) => id.reference === entryComponent.reference)) {
|
||||
bootstrapComponentFactories.push(id);
|
||||
}
|
||||
deps.push(new ComponentFactoryDependency(entryComponent, id));
|
||||
@ -59,21 +61,21 @@ export class NgModuleCompiler {
|
||||
const providerParser = new NgModuleProviderAnalyzer(ngModuleMeta, extraProviders, sourceSpan);
|
||||
providerParser.parse().forEach((provider) => builder.addProvider(provider));
|
||||
const injectorClass = builder.build();
|
||||
const ngModuleFactoryVar = `${ngModuleMeta.type.name}NgFactory`;
|
||||
const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`;
|
||||
const ngModuleFactoryStmt =
|
||||
o.variable(ngModuleFactoryVar)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.NgModuleFactory))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.NgModuleFactory))
|
||||
.instantiate(
|
||||
[o.variable(injectorClass.name), o.importExpr(ngModuleMeta.type)],
|
||||
o.importType(
|
||||
resolveIdentifier(Identifiers.NgModuleFactory),
|
||||
createIdentifier(Identifiers.NgModuleFactory),
|
||||
[o.importType(ngModuleMeta.type)], [o.TypeModifier.Const])))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]);
|
||||
|
||||
const stmts: o.Statement[] = [injectorClass, ngModuleFactoryStmt];
|
||||
if (ngModuleMeta.id) {
|
||||
const registerFactoryStmt =
|
||||
o.importExpr(resolveIdentifier(Identifiers.RegisterModuleFactoryFn))
|
||||
o.importExpr(createIdentifier(Identifiers.RegisterModuleFactoryFn))
|
||||
.callFn([o.literal(ngModuleMeta.id), o.variable(ngModuleFactoryVar)])
|
||||
.toStmt();
|
||||
stmts.push(registerFactoryStmt);
|
||||
@ -102,7 +104,7 @@ class _InjectorBuilder implements ClassBuilder {
|
||||
addProvider(resolvedProvider: ProviderAst) {
|
||||
const providerValueExpressions =
|
||||
resolvedProvider.providers.map((provider) => this._getProviderValue(provider));
|
||||
const propName = `_${resolvedProvider.token.name}_${this._instances.size}`;
|
||||
const propName = `_${tokenName(resolvedProvider.token)}_${this._instances.size}`;
|
||||
const instance = this._createProviderProperty(
|
||||
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
resolvedProvider.eager);
|
||||
@ -110,12 +112,12 @@ class _InjectorBuilder implements ClassBuilder {
|
||||
this._destroyStmts.push(instance.callMethod('ngOnDestroy', []).toStmt());
|
||||
}
|
||||
this._tokens.push(resolvedProvider.token);
|
||||
this._instances.set(resolvedProvider.token.reference, instance);
|
||||
this._instances.set(tokenReference(resolvedProvider.token), instance);
|
||||
}
|
||||
|
||||
build(): o.ClassStmt {
|
||||
const getMethodStmts: o.Statement[] = this._tokens.map((token) => {
|
||||
const providerExpr = this._instances.get(token.reference);
|
||||
const providerExpr = this._instances.get(tokenReference(token));
|
||||
return new o.IfStmt(
|
||||
InjectMethodVars.token.identical(createDiTokenExpression(token)),
|
||||
[new o.ReturnStatement(providerExpr)]);
|
||||
@ -143,13 +145,13 @@ class _InjectorBuilder implements ClassBuilder {
|
||||
o.literalArr(this._bootstrapComponentFactories.map(
|
||||
(componentFactory) => o.importExpr(componentFactory)))
|
||||
];
|
||||
const injClassName = `${this._ngModuleMeta.type.name}Injector`;
|
||||
const injClassName = `${identifierName(this._ngModuleMeta.type)}Injector`;
|
||||
return createClassStmt({
|
||||
name: injClassName,
|
||||
ctorParams: [new o.FnParam(
|
||||
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
|
||||
InjectorProps.parent.name, o.importType(createIdentifier(Identifiers.Injector)))],
|
||||
parent: o.importExpr(
|
||||
resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
|
||||
createIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
|
||||
parentArgs: parentArgs,
|
||||
builders: [{methods}, this]
|
||||
});
|
||||
@ -158,7 +160,7 @@ class _InjectorBuilder implements ClassBuilder {
|
||||
private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
|
||||
let result: o.Expression;
|
||||
if (isPresent(provider.useExisting)) {
|
||||
result = this._getDependency(new CompileDiDependencyMetadata({token: provider.useExisting}));
|
||||
result = this._getDependency({token: provider.useExisting});
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
const deps = provider.deps || provider.useFactory.diDeps;
|
||||
const depsExpr = deps.map((dep) => this._getDependency(dep));
|
||||
@ -215,13 +217,12 @@ class _InjectorBuilder implements ClassBuilder {
|
||||
}
|
||||
if (!dep.isSkipSelf) {
|
||||
if (dep.token &&
|
||||
(dep.token.reference === resolveIdentifierToken(Identifiers.Injector).reference ||
|
||||
dep.token.reference ===
|
||||
resolveIdentifierToken(Identifiers.ComponentFactoryResolver).reference)) {
|
||||
(tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector) ||
|
||||
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver))) {
|
||||
result = o.THIS_EXPR;
|
||||
}
|
||||
if (!result) {
|
||||
result = this._instances.get(dep.token.reference);
|
||||
result = this._instances.get(tokenReference(dep.token));
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {Injectable, NgModule, Type} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
|
||||
@ -25,7 +26,8 @@ export class NgModuleResolver {
|
||||
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
|
||||
|
||||
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
|
||||
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata);
|
||||
const ngModuleMeta: NgModule =
|
||||
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||
|
||||
if (isPresent(ngModuleMeta)) {
|
||||
return ngModuleMeta;
|
||||
|
@ -367,8 +367,8 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
ctx.print(`{`, useNewLine);
|
||||
ctx.incIndent();
|
||||
this.visitAllObjects(entry => {
|
||||
ctx.print(`${escapeIdentifier(entry[0], this._escapeDollarInStrings, false)}: `);
|
||||
entry[1].visitExpression(this, ctx);
|
||||
ctx.print(`${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}: `);
|
||||
entry.value.visitExpression(this, ctx);
|
||||
}, ast.entries, ctx, ',', useNewLine);
|
||||
ctx.decIndent();
|
||||
ctx.print(`}`, useNewLine);
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
|
||||
import {identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
|
||||
@ -38,18 +39,21 @@ class JsEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
constructor(private _moduleUrl: string) { super(); }
|
||||
|
||||
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
||||
if (isBlank(ast.value.name)) {
|
||||
const name = identifierName(ast.value);
|
||||
const moduleUrl = identifierModuleUrl(ast.value);
|
||||
if (isBlank(name)) {
|
||||
console.error('>>>', ast.value);
|
||||
throw new Error(`Internal error: unknown identifier ${ast.value}`);
|
||||
}
|
||||
if (isPresent(ast.value.moduleUrl) && ast.value.moduleUrl != this._moduleUrl) {
|
||||
let prefix = this.importsWithPrefixes.get(ast.value.moduleUrl);
|
||||
if (isPresent(moduleUrl) && moduleUrl != this._moduleUrl) {
|
||||
let prefix = this.importsWithPrefixes.get(moduleUrl);
|
||||
if (isBlank(prefix)) {
|
||||
prefix = `import${this.importsWithPrefixes.size}`;
|
||||
this.importsWithPrefixes.set(ast.value.moduleUrl, prefix);
|
||||
this.importsWithPrefixes.set(moduleUrl, prefix);
|
||||
}
|
||||
ctx.print(`${prefix}.`);
|
||||
}
|
||||
ctx.print(ast.value.name);
|
||||
ctx.print(name);
|
||||
return null;
|
||||
}
|
||||
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
|
||||
|
@ -43,14 +43,14 @@ export class BuiltinType extends Type {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalType extends Type {
|
||||
export class ExpressionType extends Type {
|
||||
constructor(
|
||||
public value: CompileIdentifierMetadata, public typeParams: Type[] = null,
|
||||
public value: Expression, public typeParams: Type[] = null,
|
||||
modifiers: TypeModifier[] = null) {
|
||||
super(modifiers);
|
||||
}
|
||||
visitType(visitor: TypeVisitor, context: any): any {
|
||||
return visitor.visitExternalType(this, context);
|
||||
return visitor.visitExpressionType(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||
|
||||
export interface TypeVisitor {
|
||||
visitBuiltintType(type: BuiltinType, context: any): any;
|
||||
visitExternalType(type: ExternalType, context: any): any;
|
||||
visitExpressionType(type: ExpressionType, context: any): any;
|
||||
visitArrayType(type: ArrayType, context: any): any;
|
||||
visitMapType(type: MapType, context: any): any;
|
||||
}
|
||||
@ -413,10 +413,13 @@ export class LiteralArrayExpr extends Expression {
|
||||
}
|
||||
}
|
||||
|
||||
export class LiteralMapEntry {
|
||||
constructor(public key: string, public value: Expression, public quoted: boolean = false) {}
|
||||
}
|
||||
|
||||
export class LiteralMapExpr extends Expression {
|
||||
public valueType: Type = null;
|
||||
constructor(public entries: [string, Expression][], type: MapType = null) {
|
||||
constructor(public entries: LiteralMapEntry[], type: MapType = null) {
|
||||
super(type);
|
||||
if (isPresent(type)) {
|
||||
this.valueType = type.valueType;
|
||||
@ -677,7 +680,8 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||
const entries = ast.entries.map(
|
||||
(entry): [string, Expression] => [entry[0], entry[1].visitExpression(this, context), ]);
|
||||
(entry): LiteralMapEntry => new LiteralMapEntry(
|
||||
entry.key, entry.value.visitExpression(this, context), entry.quoted));
|
||||
return new LiteralMapExpr(entries);
|
||||
}
|
||||
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
|
||||
@ -791,7 +795,7 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||
return ast;
|
||||
}
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||
ast.entries.forEach((entry) => (<Expression>entry[1]).visitExpression(this, context));
|
||||
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
|
||||
return ast;
|
||||
}
|
||||
visitAllExpressions(exprs: Expression[], context: any): void {
|
||||
@ -876,8 +880,14 @@ export function importExpr(id: CompileIdentifierMetadata, typeParams: Type[] = n
|
||||
|
||||
export function importType(
|
||||
id: CompileIdentifierMetadata, typeParams: Type[] = null,
|
||||
typeModifiers: TypeModifier[] = null): ExternalType {
|
||||
return isPresent(id) ? new ExternalType(id, typeParams, typeModifiers) : null;
|
||||
typeModifiers: TypeModifier[] = null): ExpressionType {
|
||||
return isPresent(id) ? expressionType(importExpr(id), typeParams, typeModifiers) : null;
|
||||
}
|
||||
|
||||
export function expressionType(
|
||||
expr: Expression, typeParams: Type[] = null,
|
||||
typeModifiers: TypeModifier[] = null): ExpressionType {
|
||||
return isPresent(expr) ? new ExpressionType(expr, typeParams, typeModifiers) : null;
|
||||
}
|
||||
|
||||
export function literalArr(values: Expression[], type: Type = null): LiteralArrayExpr {
|
||||
@ -885,7 +895,7 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
||||
}
|
||||
|
||||
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
||||
return new LiteralMapExpr(values, type);
|
||||
return new LiteralMapExpr(values.map(entry => new LiteralMapEntry(entry[0], entry[1])), type);
|
||||
}
|
||||
|
||||
export function not(expr: Expression): NotExpr {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user