Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
4f93ac8762 | |||
37ec5b9c1a | |||
612950bdb2 | |||
3804ad1d23 | |||
1b1f228525 | |||
19e9094275 | |||
8ff3ab0e6d | |||
9e8d740a96 | |||
3a3a100b27 | |||
f0575e014c | |||
ea7737ee11 | |||
fadaf1e01a | |||
c716532ff2 | |||
1c112ae66e | |||
193a0ae4a0 | |||
9ceb5d1afe | |||
d6a8b0b686 | |||
3e216dd4ad | |||
7b0aba4655 | |||
7c87c52c38 | |||
541de26f7e | |||
74cb575219 | |||
e90661aaee | |||
73034c75bd | |||
059085b943 | |||
8ab89153e0 | |||
82923d8314 | |||
6921b3d21d | |||
c0ef8b25a6 | |||
e845f3b226 | |||
9779f397b7 | |||
5bb47db887 | |||
343ee8a3a2 | |||
223b5eb367 | |||
7e639aac15 | |||
83dafd3054 | |||
e641636624 | |||
c409860a9f | |||
0101aa31d6 | |||
a5b4af0fdd | |||
d9420311ca | |||
774e1db87c | |||
109f0d16ef | |||
71567d1eee | |||
bb71acc172 | |||
e98d6f0912 | |||
1dbebb184f | |||
8882b86b54 | |||
0965636735 | |||
4d2901d480 | |||
a047124e1a | |||
09e2d20e22 | |||
e3bdf82c0d | |||
0614289608 | |||
7c344a4e49 | |||
250dbc4bc8 | |||
70bbdf55da | |||
41b8d95fa7 | |||
1eece5046d | |||
1ef3eeecbd | |||
94500e0fad | |||
dd53606f69 | |||
6c8b5dda87 | |||
458ccc1aff | |||
07cfd8c432 | |||
23bd0fbfc1 | |||
3d1e536143 | |||
c827097610 | |||
8d4aa82c04 | |||
14e97516cb | |||
bc47a8cc74 | |||
32cc6759ef | |||
d5f1419afe | |||
117fa79c7c | |||
777ba46837 | |||
f3d55068a8 | |||
7ed39ebaaf | |||
091f0a5aaa | |||
315606e02c | |||
5ea373d184 | |||
6e36bb7b20 | |||
3b2fb23805 | |||
bd2eecb4de | |||
3d351a4f5f | |||
5492fada21 | |||
fd4f9acbcf | |||
48528a86e1 | |||
80364def27 | |||
1803beb4d5 | |||
3bcba8a570 | |||
84542d8ae7 | |||
17cb3ec565 | |||
015878afe6 | |||
2af58622c1 | |||
7ffd10541d | |||
481b099d82 | |||
49c4b0fa92 | |||
b8b6b1d27a | |||
892b5ba950 | |||
bd15110c7d | |||
2250082fd7 | |||
87316c52db | |||
606b76d9bb | |||
3d0b1b8184 | |||
261fd16780 | |||
104cc42f6d | |||
a7d28044c5 | |||
055bea2969 | |||
dad0d21b89 | |||
313683f6f3 | |||
338be6d6a5 | |||
4b56f79328 | |||
d7f2a3c71b | |||
1c929ae244 | |||
83d0ff6d13 | |||
d43e5dd44d | |||
61ba223c1a | |||
6164eb25f3 | |||
5e9d3dba3a | |||
16922655ca | |||
7dc12b93fe | |||
1c82b58185 | |||
d6c414c08f | |||
d25d1730c7 | |||
03b35d2e8f | |||
722543739e | |||
56b4296a09 | |||
f1cde4339b | |||
b245b920a6 | |||
f47a71689c | |||
6be55cc214 | |||
504199cf5a | |||
17c5fa9293 | |||
5f49c3ed23 | |||
ebba63057f | |||
5058461af7 | |||
21f5f05893 | |||
f2ee81fa7a | |||
ae1029da35 | |||
230e33f3f1 | |||
ec0ca01224 | |||
1cd73c7a79 | |||
9f6a647908 | |||
29ffdfdffe | |||
5754ecc3e1 | |||
dab15c79dd | |||
21942a88f0 | |||
018865ee6b | |||
f7234378b6 | |||
5f47583c94 | |||
0e7f9f0bff | |||
28a92b2bcd | |||
48be539824 | |||
d788c679b6 | |||
a38f14b39c | |||
6a5e46cedd | |||
6316e5df71 | |||
90fca7c879 | |||
d871ae2dc6 | |||
44e84d87f9 | |||
b9e979e0a5 | |||
cb2aa41782 | |||
189a7e3750 |
248
.pullapprove.yml
Normal file
248
.pullapprove.yml
Normal file
@ -0,0 +1,248 @@
|
||||
# Configuration for pullapprove.com
|
||||
#
|
||||
# Approval access and primary role is determined by info in the project ownership spreadsheet:
|
||||
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
|
||||
#
|
||||
# === GitHub username to Full name map ===
|
||||
#
|
||||
# alexeagle - Alex Eagle
|
||||
# alxhub - Alex Rickabaugh
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# gkalpak - George Kalpakas
|
||||
# IgorMinar - Igor Minar
|
||||
# kara - Kara Erickson
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
# pkozlowski-opensource - Pawel Kozlowski
|
||||
# robwormald - Rob Wormald
|
||||
# tbosch - Tobias Bosch
|
||||
# vicb - Victor Berchet
|
||||
# vikerman - Vikram Subramanian
|
||||
|
||||
version: 2
|
||||
|
||||
group_defaults:
|
||||
required: 1
|
||||
reset_on_reopened:
|
||||
enabled: true
|
||||
approve_by_comment:
|
||||
enabled: false
|
||||
|
||||
groups:
|
||||
root:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
exclude:
|
||||
- "angular.io/*"
|
||||
- "integration/*"
|
||||
- "modules/*"
|
||||
- "tools/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
public-api:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
|
||||
build-and-ci:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*.yml"
|
||||
- "*.json"
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "tools/public_api_guard/*"
|
||||
users:
|
||||
- IgorMinar #primary
|
||||
- mhevery
|
||||
|
||||
integration:
|
||||
conditions:
|
||||
files:
|
||||
- "integration/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- mhevery
|
||||
- tbosch
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
|
||||
core:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/core/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- mhevery
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler/animations:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/compiler/src/animation/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
compiler/i18n:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/compiler/src/i18n/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
compiler:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/compiler/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- vicb
|
||||
- chuckjaz
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
||||
compiler-cli:
|
||||
conditions:
|
||||
files:
|
||||
- "tools/@angular/tsc-wrapped/*"
|
||||
- "modules/@angular/compiler-cli/*"
|
||||
users:
|
||||
- alexeagle
|
||||
- chuckjaz
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
common:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/common/*"
|
||||
users:
|
||||
- pkozlowski-opensource #primary
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
forms:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/forms/*"
|
||||
users:
|
||||
- kara #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
http:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/http/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
- alxhub
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
language-service:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/language-service/*"
|
||||
users:
|
||||
- chuckjaz #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
router:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/router/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
upgrade:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/upgrade/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-browser:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-browser/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
- vicb #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-server:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-server/*"
|
||||
users:
|
||||
- vikerman #primary
|
||||
- alxhub
|
||||
- vicb
|
||||
- tbosch
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
platform-webworker:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/platform-webworker/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
- tbosch #secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
|
||||
|
||||
benchpress:
|
||||
conditions:
|
||||
files:
|
||||
- "modules/@angular/benchpress/*"
|
||||
users:
|
||||
- tbosch #primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
angular.io:
|
||||
conditions:
|
||||
files:
|
||||
- "angular.io/*"
|
||||
users:
|
||||
- IgorMinar
|
||||
- robwormald
|
||||
- petebacondarwin
|
||||
- mhevery #fallback
|
@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '6.6.0'
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
# firefox: "38.0"
|
||||
|
141
CHANGELOG.md
141
CHANGELOG.md
@ -1,3 +1,140 @@
|
||||
<a name="2.4.8"></a>
|
||||
## [2.4.8](https://github.com/angular/angular/compare/2.4.7...2.4.8) (2017-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **forms:** getRawValue should correctly work with nested FormGroups/Arrays ([#12964](https://github.com/angular/angular/issues/12964)) ([ea7737e](https://github.com/angular/angular/commit/ea7737e)), closes [#12963](https://github.com/angular/angular/issues/12963)
|
||||
* **http:** REVERT: remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([9ceb5d1](https://github.com/angular/angular/commit/9ceb5d1))
|
||||
* **platform-browser:** should only add styles with native encapsulation in shadow DOM ([#14313](https://github.com/angular/angular/issues/14313)) ([fadaf1e](https://github.com/angular/angular/commit/fadaf1e)), closes [#7887](https://github.com/angular/angular/issues/7887)
|
||||
* **router:** do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([541de26](https://github.com/angular/angular/commit/541de26)), closes [#12162](https://github.com/angular/angular/issues/12162)
|
||||
* **upgrade:** correctly project content on downgraded components with structural directives ([#14274](https://github.com/angular/angular/issues/14274)) ([74cb575](https://github.com/angular/angular/commit/74cb575)), closes [#14260](https://github.com/angular/angular/issues/14260)
|
||||
* **upgrade:** pass correct values to `ngOnChanges` for interpolation bindings ([#14400](https://github.com/angular/angular/issues/14400)) ([7c87c52](https://github.com/angular/angular/commit/7c87c52))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.7"></a>
|
||||
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **upgrade:** allow non-element selectors for downgraded components ([#14291](https://github.com/angular/angular/issues/14291)) ([5bb47db](https://github.com/angular/angular/commit/5bb47db))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.6"></a>
|
||||
## [2.4.6](https://github.com/angular/angular/compare/2.4.5...2.4.6) (2017-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add PopStateEvent interface ([#13400](https://github.com/angular/angular/issues/13400)) ([71567d1](https://github.com/angular/angular/commit/71567d1)), closes [#13378](https://github.com/angular/angular/issues/13378)
|
||||
* **common:** DatePipe does't throw for NaN ([#14117](https://github.com/angular/angular/issues/14117)) ([32cc675](https://github.com/angular/angular/commit/32cc675)), closes [#14103](https://github.com/angular/angular/issues/14103)
|
||||
* **common:** DatePipe parses input string if it's not a valid date in browser ([#13895](https://github.com/angular/angular/issues/13895)) ([e641636](https://github.com/angular/angular/commit/e641636)), closes [#12334](https://github.com/angular/angular/issues/12334) [#13874](https://github.com/angular/angular/issues/13874)
|
||||
* **common:** introduce isObservable method ([#14067](https://github.com/angular/angular/issues/14067)) ([109f0d1](https://github.com/angular/angular/commit/109f0d1)), closes [#8848](https://github.com/angular/angular/issues/8848)
|
||||
* **compiler:** allow empty translations for attributes ([#14085](https://github.com/angular/angular/issues/14085)) ([f3d5506](https://github.com/angular/angular/commit/f3d5506)), closes [#13897](https://github.com/angular/angular/issues/13897)
|
||||
* **core:** add bootstrapped modules into platform modules list ([#13740](https://github.com/angular/angular/issues/13740)) ([250dbc4](https://github.com/angular/angular/commit/250dbc4)), closes [#12015](https://github.com/angular/angular/issues/12015)
|
||||
* **core:** ViewContainerRef.indexOf should not throw error when empty ([#13220](https://github.com/angular/angular/issues/13220)) ([41b8d95](https://github.com/angular/angular/commit/41b8d95))
|
||||
* **forms:** show a blank line when nothing is selected in IE or Edge ([#13903](https://github.com/angular/angular/issues/13903)) ([09e2d20](https://github.com/angular/angular/commit/09e2d20)), closes [#10010](https://github.com/angular/angular/issues/10010)
|
||||
* **forms:** verify functions passed into async validators returns Observable or Promise ([#14053](https://github.com/angular/angular/issues/14053)) ([774e1db](https://github.com/angular/angular/commit/774e1db))
|
||||
* ngModel should use rxjs/symbol/observable to detect observable ([#14236](https://github.com/angular/angular/issues/14236)) ([7e639aa](https://github.com/angular/angular/commit/7e639aa))
|
||||
* **http:** remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([1eece50](https://github.com/angular/angular/commit/1eece50))
|
||||
* **i18n:** parse ICU messages while normalizing templates ([#14153](https://github.com/angular/angular/issues/14153)) ([8d4aa82](https://github.com/angular/angular/commit/8d4aa82))
|
||||
* **language-service:** do not crash when Angular cannot be located ([#14123](https://github.com/angular/angular/issues/14123)) ([a5b4af0](https://github.com/angular/angular/commit/a5b4af0)), closes [#14122](https://github.com/angular/angular/issues/14122)
|
||||
* **platform-browser:** remove style nodes on destroy ([#13744](https://github.com/angular/angular/issues/13744)) ([0614289](https://github.com/angular/angular/commit/0614289)), closes [#11746](https://github.com/angular/angular/issues/11746)
|
||||
* **router:** fix CanActivate redirect to the root on initial load ([#13929](https://github.com/angular/angular/issues/13929)) ([a047124](https://github.com/angular/angular/commit/a047124)), closes [#13530](https://github.com/angular/angular/issues/13530)
|
||||
* **router:** should find guard provided in a lazy loaded module ([#13989](https://github.com/angular/angular/issues/13989)) ([0965636](https://github.com/angular/angular/commit/0965636)), closes [#12275](https://github.com/angular/angular/issues/12275)
|
||||
* **router:** should allow navigation from root component in ngOnInit hook ([#13932](https://github.com/angular/angular/issues/13932)) ([4d2901d](https://github.com/angular/angular/commit/4d2901d)), closes [#13795](https://github.com/angular/angular/issues/13795)
|
||||
* **testing:** async/fakeAsync/inject/withModule helpers should pass through context to callback functions ([#13718](https://github.com/angular/angular/issues/13718)) ([70bbdf5](https://github.com/angular/angular/commit/70bbdf5))
|
||||
* **upgrade:** detect async downgrade component changes ([#14039](https://github.com/angular/angular/issues/14039)) ([117fa79](https://github.com/angular/angular/commit/117fa79)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.5"></a>
|
||||
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
|
||||
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
|
||||
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
|
||||
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.4"></a>
|
||||
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
|
||||
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
|
||||
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
|
||||
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
|
||||
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
|
||||
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
|
||||
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.3"></a>
|
||||
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
|
||||
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
|
||||
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
|
||||
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
|
||||
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
|
||||
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
|
||||
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
|
||||
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.2"></a>
|
||||
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
|
||||
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
|
||||
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
|
||||
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
|
||||
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
|
||||
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
|
||||
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
|
||||
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
|
||||
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
|
||||
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
|
||||
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
|
||||
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
|
||||
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
|
||||
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
|
||||
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
|
||||
|
||||
|
||||
|
||||
<a name="2.4.1"></a>
|
||||
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always quote string map key values in AOT code ([#13602](https://github.com/angular/angular/issues/13602)) ([6a5e46c](https://github.com/angular/angular/commit/6a5e46c))
|
||||
* **animations:** always recover from a failed animation step ([#13604](https://github.com/angular/angular/issues/13604)) ([d788c67](https://github.com/angular/angular/commit/d788c67))
|
||||
* **compiler:** ignore `@import` in comments ([#13368](https://github.com/angular/angular/issues/13368)) ([6316e5d](https://github.com/angular/angular/commit/6316e5d)), closes [#12196](https://github.com/angular/angular/issues/12196)
|
||||
* **core:** improve error message when component factory cannot be found ([#13541](https://github.com/angular/angular/issues/13541)) ([b9e979e](https://github.com/angular/angular/commit/b9e979e)), closes [#12678](https://github.com/angular/angular/issues/12678)
|
||||
* **router:** should reset location if a navigation by location is successful ([#13545](https://github.com/angular/angular/issues/13545)) ([a38f14b](https://github.com/angular/angular/commit/a38f14b)), closes [#13491](https://github.com/angular/angular/issues/13491)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.0"></a>
|
||||
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
|
||||
|
||||
@ -63,10 +200,10 @@
|
||||
|
||||
### Note ###
|
||||
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
|
||||
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
|
||||
|
||||
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
|
||||
|
27
COMMITTER.md
27
COMMITTER.md
@ -6,29 +6,16 @@ for details about how we maintain a linear commit history, and the rules for com
|
||||
As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request.
|
||||
Someone with committer access will do the rest.
|
||||
|
||||
## The `PR: merge` label and `presubmit-*` branches
|
||||
# Change approvals
|
||||
|
||||
We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for
|
||||
Angular committers and also prevent breakages on master.
|
||||
Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
|
||||
|
||||
When a PR has `pr_state: LGTM` and is ready to merge, you should add the `pr_action: merge` label.
|
||||
Currently (late 2015), we need to ensure that each PR will cleanly merge into the Google-internal version control,
|
||||
so the caretaker reviews the changes manually.
|
||||
|
||||
After this review, the caretaker adds `zomg_admin: do_merge` which is restricted to admins only.
|
||||
A robot running as [mary-poppins](https://github.com/mary-poppins)
|
||||
is notified that the label was added by an authorized person,
|
||||
and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`.
|
||||
# Merging
|
||||
|
||||
(Note: if the automation fails, committers can instead push the commits to a branch following this naming scheme.)
|
||||
Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with "PR: merge" label.
|
||||
This signals to the caretaker that the PR should be merged.
|
||||
|
||||
When a Travis build succeeds for a presubmit branch named following the convention,
|
||||
Travis will re-base the commits, merge to master, and close the PR automatically.
|
||||
# Who is the Caretaker?
|
||||
|
||||
Finally, after merge `mary-poppins` removes the presubmit branch.
|
||||
|
||||
## Administration
|
||||
|
||||
The list of users who can trigger a merge by adding the `zomg_admin: do_merge` label is stored in our appengine app datastore.
|
||||
Edit the contents of the [CoreTeamMember Table](
|
||||
https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember)
|
||||
See [this explanation](https://twitter.com/IgorMinar/status/799365744806854656).
|
||||
|
31
DEVELOPER.md
31
DEVELOPER.md
@ -71,9 +71,24 @@ particular `gulp` and `protractor` commands. If you prefer, you can drop this pa
|
||||
Since global installs can become stale, and required versions can vary by project, we avoid their
|
||||
use in these instructions.
|
||||
|
||||
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
*Option 2*: globally installing the package `npm-run` by running `npm install -g npm-run`
|
||||
(you might need to prefix this command with `sudo`). You will then be able to run locally installed
|
||||
package scripts by invoking: e.g., `npm-run gulp build`
|
||||
(see [npm-run project page](https://github.com/timoxley/npm-run) for more details).
|
||||
|
||||
|
||||
*Option 3*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
|
||||
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
|
||||
|
||||
## Installing Bower Modules
|
||||
|
||||
Now run `bower` to install additional dependencies:
|
||||
|
||||
```shell
|
||||
# Install other Angular project dependencies (bower.json)
|
||||
bower install
|
||||
```
|
||||
|
||||
## Windows only
|
||||
|
||||
In order to create the right symlinks, run **as administrator**:
|
||||
@ -124,7 +139,8 @@ If you happen to modify the public API of Angular, API golden files must be upda
|
||||
$ gulp public-api:update
|
||||
```
|
||||
|
||||
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
|
||||
Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
|
||||
the project before trying to verify after an API change.
|
||||
|
||||
## <a name="clang-format"></a> Formatting your source code
|
||||
|
||||
@ -137,6 +153,14 @@ You can automatically format your code by running:
|
||||
$ gulp format
|
||||
```
|
||||
|
||||
## Linting/verifying your source code
|
||||
|
||||
You can check that your code is properly formatted and adheres to coding style by running:
|
||||
|
||||
``` shell
|
||||
$ gulp lint
|
||||
```
|
||||
|
||||
## Publishing your own personal snapshot build
|
||||
|
||||
You may find that your un-merged change needs some validation from external participants.
|
||||
@ -155,3 +179,6 @@ For subsequent snapshots, just run
|
||||
``` shell
|
||||
$ ./scripts/publish/publish-build-artifacts.sh [github username]
|
||||
```
|
||||
|
||||
The script will publish the build snapshot to a branch with the same name as your current branch,
|
||||
and create it if it doesn't exist.
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014-2016 Google, Inc. http://angular.io
|
||||
Copyright (c) 2014-2017 Google, Inc. http://angular.io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,10 +1,10 @@
|
||||
machine:
|
||||
node:
|
||||
version: 5.4.1
|
||||
version: 6.9.5
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm@3.6.0
|
||||
- npm install -g npm@3.10.7
|
||||
|
||||
test:
|
||||
override:
|
||||
|
@ -19,7 +19,7 @@ One intentional omission from this list is `@angular/compiler`, which is current
|
||||
|
||||
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
|
||||
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature.
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, will be covered by these or similar guarantees in the future as they mature.
|
||||
|
||||
Within the supported packages, we provide guarantees for:
|
||||
|
||||
@ -37,4 +37,4 @@ We explicitly don't consider the following to be our public API surface:
|
||||
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
|
||||
|
||||
|
||||
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
|
||||
Our peer dependencies (e.g. TypeScript, Zone.js, or RxJS) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
|
||||
|
@ -11,8 +11,8 @@
|
||||
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
|
||||
// This is to ensure that we catch env issues before we error while requiring other dependencies.
|
||||
require('./tools/check-environment')({
|
||||
requiredNpmVersion: '>=3.5.3 <4.0.0',
|
||||
requiredNodeVersion: '>=5.4.1 <7.0.0',
|
||||
requiredNpmVersion: '>=3.10.7 <4.0.0',
|
||||
requiredNodeVersion: '>=6.9.5 <7.0.0',
|
||||
});
|
||||
|
||||
const gulp = require('gulp');
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isListLikeIterable} from '../facade/collection';
|
||||
import {isPresent, stringify} from '../facade/lang';
|
||||
import {stringify} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
@ -25,6 +25,8 @@ import {isPresent, stringify} from '../facade/lang';
|
||||
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
||||
*
|
||||
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
||||
*
|
||||
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
@ -132,7 +134,7 @@ export class NgClass implements DoCheck {
|
||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||
} else {
|
||||
Object.keys(rawClassVal).forEach(klass => {
|
||||
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
|
||||
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
|
||||
|
||||
import {getTypeNameForDebugging} from '../facade/lang';
|
||||
|
||||
@ -89,9 +89,23 @@ export class NgForRow {
|
||||
@Directive({selector: '[ngFor][ngForOf]'})
|
||||
export class NgFor implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: any;
|
||||
@Input() ngForTrackBy: TrackByFn;
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFn) {
|
||||
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
||||
// TODO(vicb): use a log service once there is a public one available
|
||||
if (<any>console && <any>console.warn) {
|
||||
console.warn(
|
||||
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
|
||||
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
|
||||
}
|
||||
}
|
||||
this._trackByFn = fn;
|
||||
}
|
||||
|
||||
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
|
||||
|
||||
private _differ: IterableDiffer = null;
|
||||
private _trackByFn: TrackByFn;
|
||||
|
||||
constructor(
|
||||
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForRow>,
|
||||
@ -119,7 +133,7 @@ export class NgFor implements DoCheck, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
ngDoCheck(): void {
|
||||
if (this._differ) {
|
||||
const changes = this._differ.diff(this.ngForOf);
|
||||
if (changes) this._applyChanges(changes);
|
||||
|
@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
|
||||
* @howToUse
|
||||
* ```
|
||||
* <some-element [ngPlural]="value">
|
||||
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
|
||||
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
|
||||
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
|
||||
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
|
||||
* <template ngPluralCase="=0">there is nothing</template>
|
||||
* <template ngPluralCase="=1">there is one</template>
|
||||
* <template ngPluralCase="few">there are a few</template>
|
||||
* </some-element>
|
||||
* ```
|
||||
*
|
||||
@ -90,8 +89,8 @@ export class NgPlural {
|
||||
* @howToUse
|
||||
* ```
|
||||
* <some-element [ngPlural]="value">
|
||||
* <ng-container *ngPluralCase="'=0'">...</ng-container>
|
||||
* <ng-container *ngPluralCase="'other'">...</ng-container>
|
||||
* <template ngPluralCase="=0">...</template>
|
||||
* <template ngPluralCase="other">...</template>
|
||||
* </some-element>
|
||||
*```
|
||||
*
|
||||
@ -104,6 +103,7 @@ export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
const isANumber: boolean = !isNaN(Number(value));
|
||||
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,12 @@ import {EventEmitter, Injectable} from '@angular/core';
|
||||
|
||||
import {LocationStrategy} from './location_strategy';
|
||||
|
||||
/** @experimental */
|
||||
export interface PopStateEvent {
|
||||
pop?: boolean;
|
||||
type?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
|
||||
@ -122,7 +128,7 @@ export class Location {
|
||||
* Subscribe to the platform's `popState` events.
|
||||
*/
|
||||
subscribe(
|
||||
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
|
||||
onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
|
||||
onReturn: () => void = null): Object {
|
||||
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {OpaqueToken} from '@angular/core';
|
||||
/**
|
||||
* This class should not be used directly by an application developer. Instead, use
|
||||
* {@link Location}.
|
||||
@ -50,6 +51,12 @@ export abstract class PlatformLocation {
|
||||
abstract back(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes indicates when a location is initialized
|
||||
* @experimental
|
||||
*/
|
||||
export const LOCATION_INITIALIZED = new OpaqueToken('Location Initialized');
|
||||
|
||||
/**
|
||||
* A serializable version of the event from onPopState or onHashChange
|
||||
*
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
|
||||
import {ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, WrappedValue} from '@angular/core';
|
||||
import {EventEmitter, Observable} from '../facade/async';
|
||||
import {isPromise} from '../private_import_core';
|
||||
import {isObservable, isPromise} from '../private_import_core';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
interface SubscriptionStrategy {
|
||||
@ -66,7 +66,7 @@ const _observableStrategy = new ObservableStrategy();
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'async', pure: false})
|
||||
export class AsyncPipe implements OnDestroy {
|
||||
export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||
private _latestValue: Object = null;
|
||||
private _latestReturnedValue: Object = null;
|
||||
|
||||
@ -116,7 +116,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
return _promiseStrategy;
|
||||
}
|
||||
|
||||
if ((<any>obj).subscribe) {
|
||||
if (isObservable(obj)) {
|
||||
return _observableStrategy;
|
||||
}
|
||||
|
||||
@ -131,7 +131,7 @@ export class AsyncPipe implements OnDestroy {
|
||||
this._obj = null;
|
||||
}
|
||||
|
||||
private _updateLatestValue(async: any, value: Object) {
|
||||
private _updateLatestValue(async: any, value: Object): void {
|
||||
if (async === this._obj) {
|
||||
this._latestValue = value;
|
||||
this._ref.markForCheck();
|
||||
|
@ -7,13 +7,13 @@
|
||||
*/
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
import {NumberWrapper, isDate} from '../facade/lang';
|
||||
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {DateFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
const ISO8601_DATE_REGEX =
|
||||
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
|
||||
// 1 2 3 4 5 6 7 8 9 10 11
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* Where:
|
||||
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime).
|
||||
* - `format` indicates which date/time components to include. The format can be predifined as
|
||||
* - `format` indicates which date/time components to include. The format can be predefined as
|
||||
* shown below or custom as shown in the table.
|
||||
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
|
||||
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
|
||||
@ -103,7 +103,7 @@ export class DatePipe implements PipeTransform {
|
||||
transform(value: any, pattern: string = 'mediumDate'): string {
|
||||
let date: Date;
|
||||
|
||||
if (isBlank(value)) return null;
|
||||
if (isBlank(value) || value !== value) return null;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
@ -130,7 +130,12 @@ export class DatePipe implements PipeTransform {
|
||||
}
|
||||
|
||||
if (!isDate(date)) {
|
||||
throw new InvalidPipeArgumentError(DatePipe, value);
|
||||
let match: RegExpMatchArray;
|
||||
if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
|
||||
date = isoStringToDate(match);
|
||||
} else {
|
||||
throw new InvalidPipeArgumentError(DatePipe, value);
|
||||
}
|
||||
}
|
||||
|
||||
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
|
||||
@ -140,3 +145,31 @@ export class DatePipe implements PipeTransform {
|
||||
function isBlank(obj: any): boolean {
|
||||
return obj == null || obj === '';
|
||||
}
|
||||
|
||||
function isDate(obj: any): obj is Date {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
function isoStringToDate(match: RegExpMatchArray): Date {
|
||||
const date = new Date(0);
|
||||
let tzHour = 0;
|
||||
let tzMin = 0;
|
||||
const dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear;
|
||||
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
|
||||
|
||||
if (match[9]) {
|
||||
tzHour = toInt(match[9] + match[10]);
|
||||
tzMin = toInt(match[9] + match[11]);
|
||||
}
|
||||
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
|
||||
const h = toInt(match[4] || '0') - tzHour;
|
||||
const m = toInt(match[5] || '0') - tzMin;
|
||||
const s = toInt(match[6] || '0');
|
||||
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
|
||||
timeSetter.call(date, h, m, s, ms);
|
||||
return date;
|
||||
}
|
||||
|
||||
function toInt(str: string): number {
|
||||
return parseInt(str, 10);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
@ -35,7 +34,7 @@ export class I18nPluralPipe implements PipeTransform {
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||
if (isBlank(value)) return '';
|
||||
if (value == null) return '';
|
||||
|
||||
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
||||
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||
|
||||
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
||||
function formatNumber(
|
||||
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
||||
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
if (value == null) return null;
|
||||
|
||||
// Convert strings to numbers
|
||||
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
||||
@ -41,13 +41,13 @@ function formatNumber(
|
||||
if (parts === null) {
|
||||
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
||||
}
|
||||
if (isPresent(parts[1])) { // min integer digits
|
||||
if (parts[1] != null) { // min integer digits
|
||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
if (parts[3] != null) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
if (parts[5] != null) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
@ -58,7 +57,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
@Pipe({name: 'slice', pure: false})
|
||||
export class SlicePipe implements PipeTransform {
|
||||
transform(value: any, start: number, end?: number): any {
|
||||
if (isBlank(value)) return value;
|
||||
if (value == null) return value;
|
||||
|
||||
if (!this.supports(value)) {
|
||||
throw new InvalidPipeArgumentError(SlicePipe, value);
|
||||
|
@ -9,3 +9,4 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export const isPromise: typeof r.isPromise = r.isPromise;
|
||||
export const isObservable: typeof r.isObservable = r.isObservable;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ContentChild, TemplateRef} from '@angular/core';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
@ -29,10 +29,7 @@ export function main() {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TestComponent,
|
||||
ComponentUsingTestComponent,
|
||||
],
|
||||
declarations: [TestComponent],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
});
|
||||
@ -77,7 +74,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should iterate over an array of objects', async(() => {
|
||||
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
|
||||
const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
// INIT
|
||||
@ -95,7 +92,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should gracefully handle nulls', async(() => {
|
||||
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
|
||||
const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
@ -140,12 +137,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays', async(() => {
|
||||
const template = '<div>' +
|
||||
'<div template="ngFor let item of items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|' +
|
||||
'</div>' +
|
||||
const template = '<div *ngFor="let item of items">' +
|
||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
|
||||
'</div>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -157,10 +150,9 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays with no intermediate element', async(() => {
|
||||
const template = '<div><template ngFor let-item [ngForOf]="items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div></template></div>';
|
||||
const template = '<div *ngFor="let item of items">' +
|
||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
|
||||
'</div>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [['a', 'b'], ['c']];
|
||||
@ -170,10 +162,11 @@ export function main() {
|
||||
detectChangesAndExpectText('e-1;f-2;g-2;');
|
||||
}));
|
||||
|
||||
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
||||
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
|
||||
const template = `<div *ngFor="let item of items; let i=index">` +
|
||||
`<div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div>` +
|
||||
`</div>`;
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -189,8 +182,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should display indices correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
|
||||
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
@ -202,7 +194,7 @@ export function main() {
|
||||
|
||||
it('should display first item correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>';
|
||||
'<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@ -214,7 +206,7 @@ export function main() {
|
||||
|
||||
it('should display last item correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>';
|
||||
'<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@ -226,7 +218,7 @@ export function main() {
|
||||
|
||||
it('should display even items correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>';
|
||||
'<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@ -238,7 +230,7 @@ export function main() {
|
||||
|
||||
it('should display odd items correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>';
|
||||
'<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [0, 1, 2, 3];
|
||||
@ -249,54 +241,57 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should allow to use a custom template', async(() => {
|
||||
const tcTemplate =
|
||||
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>';
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}});
|
||||
const cutTemplate =
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
const template =
|
||||
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
|
||||
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
||||
}));
|
||||
|
||||
it('should use a default template if a custom one is null', async(() => {
|
||||
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||
const cutTemplate =
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
const template =
|
||||
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
||||
}));
|
||||
|
||||
it('should use a custom template when both default and a custom one are present', async(() => {
|
||||
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||
const cutTemplate =
|
||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
||||
|
||||
const testComponent = fixture.debugElement.children[0];
|
||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
||||
const template =
|
||||
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
|
||||
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
||||
}));
|
||||
|
||||
describe('track by', () => {
|
||||
it('should console.warn if trackBy is not a function', async(() => {
|
||||
// TODO(vicb): expect a warning message when we have a proper log service
|
||||
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.value = 0;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
|
||||
// TODO(vicb): expect no warning message when we have a proper log service
|
||||
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.componentInstance.value = null;
|
||||
detectChangesAndExpectText('abc');
|
||||
fixture.componentInstance.value = undefined;
|
||||
detectChangesAndExpectText('abc');
|
||||
}));
|
||||
|
||||
it('should set the context to the component instance', async(() => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
|
||||
`<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
thisArg = null;
|
||||
@ -306,9 +301,7 @@ export function main() {
|
||||
|
||||
it('should not replace tracked items', async(() => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
||||
<p>{{items[i]}}</p>
|
||||
</template>`;
|
||||
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
const buildItemList = () => {
|
||||
@ -324,7 +317,7 @@ export function main() {
|
||||
|
||||
it('should update implicit local variable on view', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
||||
@ -333,9 +326,10 @@ export function main() {
|
||||
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
||||
detectChangesAndExpectText('red');
|
||||
}));
|
||||
|
||||
it('should move items around and keep them updated ', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
|
||||
`<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
||||
@ -346,8 +340,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should handle added and removed items properly when tracking by index', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
||||
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = ['a', 'b', 'c', 'd'];
|
||||
@ -367,21 +360,16 @@ class Foo {
|
||||
|
||||
@Component({selector: 'test-cmp', template: ''})
|
||||
class TestComponent {
|
||||
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
|
||||
value: any;
|
||||
items: any[] = [1, 2];
|
||||
trackById(index: number, item: any): string { return item['id']; }
|
||||
trackByIndex(index: number, item: any): number { return index; }
|
||||
trackByContext(): void { thisArg = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'outer-cmp', template: ''})
|
||||
class ComponentUsingTestComponent {
|
||||
items: any = [1, 2];
|
||||
}
|
||||
|
||||
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
|
||||
const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
|
||||
|
||||
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.createComponent(TestComponent);
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
@ -28,131 +29,114 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should work in a template attribute', async(() => {
|
||||
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
|
||||
const template = '<span *ngIf="booleanCondition">hello</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
}));
|
||||
|
||||
it('should work in a template element', async(() => {
|
||||
const template =
|
||||
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
|
||||
|
||||
it('should work on a template element', async(() => {
|
||||
const template = '<template [ngIf]="booleanCondition">hello2</template>';
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello2');
|
||||
}));
|
||||
|
||||
it('should toggle node when condition changes', async(() => {
|
||||
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
|
||||
|
||||
const template = '<span *ngIf="booleanCondition">hello</span>';
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
getComponent().booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
}));
|
||||
|
||||
it('should handle nested if correctly', async(() => {
|
||||
const template =
|
||||
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>';
|
||||
'<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
getComponent().booleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
|
||||
getComponent().nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
getComponent().nestedBooleanCondition = true;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
}));
|
||||
|
||||
it('should update several nodes with if', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' +
|
||||
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' +
|
||||
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
|
||||
'</div>';
|
||||
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
|
||||
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
|
||||
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
|
||||
expect(getDOM().getText(fixture.nativeElement))
|
||||
.toEqual('helloNumberhelloStringhelloFunction');
|
||||
|
||||
getComponent().numberCondition = 0;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('helloString');
|
||||
|
||||
getComponent().numberCondition = 1;
|
||||
getComponent().stringCondition = 'bar';
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('helloNumber');
|
||||
}));
|
||||
|
||||
it('should not add the element twice if the condition goes from true to true (JS)',
|
||||
async(() => {
|
||||
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
||||
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
|
||||
const template = '<span *ngIf="numberCondition">hello</span>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
let els = fixture.debugElement.queryAll(By.css('span'));
|
||||
expect(els.length).toEqual(1);
|
||||
getDOM().addClass(els[0].nativeElement, 'marker');
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
|
||||
getComponent().numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
els = fixture.debugElement.queryAll(By.css('span'));
|
||||
expect(els.length).toEqual(1);
|
||||
expect(getDOM().hasClass(els[0].nativeElement, 'marker')).toBe(true);
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
}));
|
||||
|
||||
it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
|
||||
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
|
||||
|
||||
getComponent().numberCondition = 2;
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
||||
.toBe(true);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
describe('ngPlural', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||
@ -33,10 +33,25 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should display the template according to the exact value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().switchValue = 0;
|
||||
detectChangesAndExpectText('you have no messages.');
|
||||
|
||||
getComponent().switchValue = 1;
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
it('should display the template according to the exact numeric value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
@ -51,10 +66,9 @@ export function main() {
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -64,11 +78,10 @@ export function main() {
|
||||
|
||||
|
||||
it('should be applicable to <ng-container> elements', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ng-container [ngPlural]="switchValue">' +
|
||||
const template = '<ng-container [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0">you have no messages.</template>' +
|
||||
'<template ngPluralCase="=1">you have one message.</template>' +
|
||||
'</ng-container></div>';
|
||||
'</ng-container>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -80,11 +93,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should display the template according to the category', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -96,11 +108,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should default to other when no matches are found', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -109,11 +120,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should prioritize value matches over category matches', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
|
@ -29,22 +29,19 @@ export function main() {
|
||||
it('should add styles specified in an object literal', async(() => {
|
||||
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
}));
|
||||
|
||||
it('should add and change styles specified in an object expression', async(() => {
|
||||
const template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
let expr: {[k: string]: string};
|
||||
|
||||
getComponent().expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
expr = getComponent().expr;
|
||||
let expr = getComponent().expr;
|
||||
expr['max-width'] = '30%';
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
||||
|
@ -33,11 +33,10 @@ export function main() {
|
||||
|
||||
describe('switch value changes', () => {
|
||||
it('should switch amongst when values', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@ -51,11 +50,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should switch amongst when values with fallback to default', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<li template="ngSwitchCase \'a\'">when a</li>' +
|
||||
'<li template="ngSwitchDefault">when default</li>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||
'<li *ngSwitchDefault>when default</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default');
|
||||
@ -71,15 +69,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support multiple whens with the same value', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
@ -94,12 +91,11 @@ export function main() {
|
||||
|
||||
describe('when values changes', () => {
|
||||
it('should switch amongst when values', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
|
||||
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="when1">when 1;</li>' +
|
||||
'<li *ngSwitchCase="when2">when 2;</li>' +
|
||||
'<li *ngSwitchDefault>when default;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().when1 = 'a';
|
||||
@ -148,11 +144,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should create the default case if there is no other case', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
@ -160,15 +155,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should allow defaults before cases', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
|
@ -34,29 +34,22 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if templateRef is null', async(() => {
|
||||
const template = `<template [ngTemplateOutlet]="null"></template>`;
|
||||
it('should do nothing if templateRef is `null`', async(() => {
|
||||
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
}));
|
||||
|
||||
it('should insert content specified by TemplateRef', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
const template = `<template #tpl>foo</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
setTplRef(refs.tplRefs.first);
|
||||
detectChangesAndExpectText('foo');
|
||||
}));
|
||||
|
||||
it('should clear content if TemplateRef becomes null', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
it('should clear content if TemplateRef becomes `null`', async(() => {
|
||||
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
|
||||
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
@ -70,7 +63,8 @@ export function main() {
|
||||
|
||||
it('should swap content if TemplateRef changes', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
|
||||
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -83,70 +77,47 @@ export function main() {
|
||||
detectChangesAndExpectText('bar');
|
||||
}));
|
||||
|
||||
it('should display template if context is null', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
|
||||
it('should display template if context is `null`', async(() => {
|
||||
const template = `<template #tpl>foo</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('');
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
setTplRef(refs.tplRefs.first);
|
||||
detectChangesAndExpectText('foo');
|
||||
}));
|
||||
|
||||
it('should reflect initial context and changes', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
|
||||
detectChangesAndExpectText('bar');
|
||||
|
||||
fixture.componentInstance.context.foo = 'alter-bar';
|
||||
|
||||
detectChangesAndExpectText('alter-bar');
|
||||
}));
|
||||
|
||||
it('should reflect user defined $implicit property in the context', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
it('should reflect user defined `$implicit` property in the context', async(() => {
|
||||
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
|
||||
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
|
||||
detectChangesAndExpectText('bar');
|
||||
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
|
||||
detectChangesAndExpectText('bra');
|
||||
}));
|
||||
|
||||
it('should reflect context re-binding', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||
|
||||
detectChangesAndExpectText('brooks');
|
||||
|
||||
fixture.componentInstance.context = {shawshank: 'was here'};
|
||||
|
||||
detectChangesAndExpectText('was here');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
||||
class CaptureTplRefs {
|
||||
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||
@ -162,4 +133,4 @@ function createTestComponent(template: string): ComponentFixture<TestComponent>
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(TestComponent);
|
||||
}
|
||||
}
|
||||
|
@ -53,11 +53,13 @@ export function main() {
|
||||
|
||||
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null));
|
||||
|
||||
it('should return null for NaN', () => expect(pipe.transform(Number.NaN)).toEqual(null));
|
||||
|
||||
it('should support ISO string without time',
|
||||
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform({})).toThrowError(); });
|
||||
() => expect(() => pipe.transform({})).toThrowError(/Invalid argument/));
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
@ -188,8 +190,14 @@ export function main() {
|
||||
|
||||
});
|
||||
|
||||
it('should format invalid in IE ISO date',
|
||||
() => expect(pipe.transform('2017-01-11T09:25:14.014-0500')).toEqual('Jan 11, 2017'));
|
||||
|
||||
it('should format invalid in Safari ISO date',
|
||||
() => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
|
||||
|
||||
it('should remove bidi control characters',
|
||||
() => { expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10); });
|
||||
() => expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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: 'lazy-feature-comp', template: 'lazy feature, nested!'})
|
||||
export class LazyFeatureNestedComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: LazyFeatureNestedComponent, pathMatch: 'full'},
|
||||
])],
|
||||
declarations: [LazyFeatureNestedComponent]
|
||||
})
|
||||
export class LazyFeatureNestedModule {
|
||||
}
|
@ -16,7 +16,10 @@ export class LazyFeatureComponent {
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
||||
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
|
||||
{path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
|
||||
path: 'nested-feature',
|
||||
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
|
||||
}
|
||||
])],
|
||||
declarations: [LazyFeatureComponent]
|
||||
})
|
||||
|
@ -191,6 +191,8 @@ function lazyRoutesTest() {
|
||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||
'./feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
|
||||
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||
'./default.module': 'feature2/default.module.ts',
|
||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||
|
@ -15,6 +15,8 @@
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": ".",
|
||||
"outDir": "../node_modules/third_party"
|
||||
}
|
||||
"outDir": "../node_modules/third_party",
|
||||
// Prevent scanning up the directory tree for types
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
}
|
||||
}
|
@ -14,7 +14,9 @@
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
// Prevent scanning up the directory tree for types
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
|
||||
"files": [
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "0.5.0",
|
||||
"@angular/tsc-wrapped": "0.5.2",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -11,7 +11,6 @@
|
||||
* Intended to be used in a build step.
|
||||
*/
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||
import {readFileSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
@ -19,7 +18,6 @@ import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {Console} from './private_import_core';
|
||||
|
||||
const GENERATED_META_FILES = /\.json$/;
|
||||
|
||||
@ -38,29 +36,6 @@ export class CodeGenerator {
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
@ -68,7 +43,7 @@ export class CodeGenerator {
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
|
@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
return !excludeRegex.test(filePath);
|
||||
}
|
||||
|
||||
calculateEmitPath(filePath: string): string {
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompilerHostContextAdapter {
|
||||
|
@ -15,8 +15,6 @@
|
||||
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';
|
||||
@ -63,29 +61,37 @@ export function listLazyRoutesOfModule(
|
||||
const className = entryRouteDef.className;
|
||||
|
||||
// List loadChildren of this single module.
|
||||
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
|
||||
const appStaticSymbol = 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 = {};
|
||||
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
|
||||
|
||||
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(routes, lazyRoute);
|
||||
routes[route] = lazyRoute;
|
||||
const allLazyRoutes = lazyRoutes.reduce(
|
||||
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
|
||||
LazyRouteMap {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(allRoutes, lazyRoute);
|
||||
allRoutes[route] = lazyRoute;
|
||||
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||
// StaticReflector does not support discovering annotations like `NgModule` on default
|
||||
// exports
|
||||
// Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
|
||||
// we cannot parse its routes to see if they have loadChildren or not.
|
||||
if (!lazyRoute.routeDef.className) {
|
||||
return allRoutes;
|
||||
}
|
||||
|
||||
// Populate the map using the routes we just found.
|
||||
subRoutes.forEach(subRoute => {
|
||||
_assertRoute(routes, subRoute);
|
||||
routes[subRoute.routeDef.toString()] = subRoute;
|
||||
});
|
||||
});
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
|
||||
return routes;
|
||||
const subRoutes =
|
||||
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||
|
||||
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
|
||||
},
|
||||
{});
|
||||
|
||||
return allLazyRoutes;
|
||||
}
|
||||
|
||||
|
||||
@ -192,7 +198,7 @@ function _collectRoutes(
|
||||
*/
|
||||
function _collectLoadChildren(routes: Route[]): string[] {
|
||||
return routes.reduce((m, r) => {
|
||||
if (r.loadChildren) {
|
||||
if (r.loadChildren && typeof r.loadChildren === 'string') {
|
||||
return m.concat(r.loadChildren);
|
||||
} else if (Array.isArray(r)) {
|
||||
return m.concat(_collectLoadChildren(r));
|
||||
|
@ -9,10 +9,10 @@
|
||||
import {__core_private__ as r} from '@angular/core';
|
||||
|
||||
export type ReflectorReader = typeof r._ReflectorReader;
|
||||
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||
|
||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
|
||||
export type Console = typeof r._Console;
|
||||
export var Console: typeof r.Console = r.Console;
|
||||
export const Console: typeof r.Console = r.Console;
|
||||
|
@ -122,11 +122,9 @@ export class MockCompilerHost implements ts.CompilerHost {
|
||||
return ts.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); }
|
||||
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return this.context.currentDirectory;
|
||||
}
|
||||
getCurrentDirectory(): string { return this.context.currentDirectory; }
|
||||
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
|
@ -9,5 +9,5 @@
|
||||
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;
|
||||
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export const reflector: typeof r.reflector = r.reflector;
|
||||
|
@ -66,7 +66,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
ast.styles.forEach(entry => {
|
||||
const entries =
|
||||
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
||||
stylesArr.push(o.literalMap(entries));
|
||||
stylesArr.push(o.literalMap(entries, null, true));
|
||||
});
|
||||
|
||||
return o.importExpr(createIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||
@ -322,12 +322,13 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
if (isPresent(value)) {
|
||||
const styleMap: any[] = [];
|
||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||
variableValue = o.literalMap(styleMap);
|
||||
variableValue = o.literalMap(styleMap, null, true);
|
||||
}
|
||||
lookupMap.push([stateName, variableValue]);
|
||||
});
|
||||
|
||||
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
||||
const compiledStatesMapStmt =
|
||||
this._statesMapVar.set(o.literalMap(lookupMap, null, true)).toDeclStmt();
|
||||
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement];
|
||||
|
||||
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SchemaMetadata} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
|
@ -9,9 +9,10 @@
|
||||
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
|
||||
import {ReflectorReader} from '../private_import_core';
|
||||
import {SyntaxError} from '../util';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
const ANGULAR_IMPORT_LOCATIONS = {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
@ -310,6 +311,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||
const parameters: string[] = targetFunction['parameters'];
|
||||
const defaults: any[] = targetFunction.defaults;
|
||||
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
if (defaults && defaults.length > args.length) {
|
||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||
}
|
||||
@ -499,15 +501,15 @@ export class StaticReflector implements ReflectorReader {
|
||||
return context;
|
||||
}
|
||||
const argExpressions: any[] = expression['arguments'] || [];
|
||||
const args =
|
||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
let converter = self.conversionMap.get(staticSymbol);
|
||||
if (converter) {
|
||||
const args =
|
||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
return converter(context, args);
|
||||
} else {
|
||||
// Determine if the function is one we can simplify.
|
||||
const targetFunction = resolveReferenceValue(staticSymbol);
|
||||
return simplifyCall(staticSymbol, targetFunction, args);
|
||||
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -537,7 +539,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (e.fileName) {
|
||||
throw positionalError(message, e.fileName, e.line, e.column);
|
||||
}
|
||||
throw new Error(message);
|
||||
throw new SyntaxError(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -651,10 +653,6 @@ class PopulatedScope extends BindingScope {
|
||||
}
|
||||
}
|
||||
|
||||
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
function shouldIgnore(value: any): boolean {
|
||||
return value && value.__symbolic == 'ignore';
|
||||
}
|
||||
|
@ -273,7 +273,12 @@ export class StaticSymbolResolver {
|
||||
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
|
||||
const filePath = this.resolveModule(module, containingFile);
|
||||
if (!filePath) {
|
||||
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
|
||||
this.reportError(
|
||||
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
|
||||
containingFile
|
||||
} `: ''}`),
|
||||
null);
|
||||
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
|
||||
}
|
||||
return this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
||||
|
||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
|
@ -5,16 +5,16 @@
|
||||
* 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 {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||
import {ValueTransformer, visitValue} from '../util';
|
||||
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
|
||||
export interface AotSummarySerializerHost {
|
||||
/**
|
||||
* Returns the output file path of a source file.
|
||||
@ -180,4 +180,4 @@ class Deserializer extends ValueTransformer {
|
||||
return super.visitStringMap(map, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,6 @@ import {LifecycleHooks, reflector} from './private_import_core';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
// group 0: "[prop] or (event) or @trigger"
|
||||
// group 1: "prop" from "[prop]"
|
||||
// group 2: "event" from "(event)"
|
||||
|
@ -5,7 +5,6 @@
|
||||
* 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 {Identifiers, createIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
if (varExpr) {
|
||||
result = varExpr.callFn(args);
|
||||
}
|
||||
}
|
||||
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
if (varExpr) {
|
||||
throw new Error('Cannot assign to a reference or variable!');
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,6 @@ import {ViewEncapsulation, isDevMode} from '@angular/core';
|
||||
import {CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {Identifiers, createIdentifier} from './identifiers';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
export class CompilerConfig {
|
||||
public renderTypes: RenderTypes;
|
||||
public defaultEncapsulation: ViewEncapsulation;
|
||||
@ -52,12 +48,12 @@ export class CompilerConfig {
|
||||
* to help tree shaking.
|
||||
*/
|
||||
export abstract class RenderTypes {
|
||||
get renderer(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderText(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderElement(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderComment(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderNode(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); }
|
||||
abstract get renderer(): CompileIdentifierMetadata;
|
||||
abstract get renderText(): CompileIdentifierMetadata;
|
||||
abstract get renderElement(): CompileIdentifierMetadata;
|
||||
abstract get renderComment(): CompileIdentifierMetadata;
|
||||
abstract get renderNode(): CompileIdentifierMetadata;
|
||||
abstract get renderEvent(): CompileIdentifierMetadata;
|
||||
}
|
||||
|
||||
export class DefaultRenderTypes implements RenderTypes {
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ViewEncapsulation} from '@angular/core';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
@ -102,11 +102,12 @@ export class DirectiveNormalizer {
|
||||
templateAbsUrl: string): CompileTemplateMetadata {
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
||||
template, stringify(prenomData.componentType), true, interpolationConfig);
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
|
||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||
styles: prenomData.styles,
|
||||
styleUrls: prenomData.styleUrls,
|
||||
@ -228,9 +229,13 @@ class TemplatePreparseVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
|
||||
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
|
||||
html.visitAll(this, ast.expression);
|
||||
}
|
||||
|
||||
visitComment(ast: html.Comment, context: any): any { return null; }
|
||||
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
||||
visitText(ast: html.Text, context: any): any { return null; }
|
||||
visitExpansion(ast: html.Expansion, context: any): any { return null; }
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
|
||||
}
|
||||
|
@ -14,8 +14,6 @@ import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Resolve a `Type` for {@link Directive}.
|
||||
*
|
||||
|
@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
||||
import {Console, LifecycleHooks} from './private_import_core';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {BindingParser} from './template_parser/binding_parser';
|
||||
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
||||
@ -128,7 +128,6 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
||||
];
|
||||
|
||||
|
||||
const fields: o.ClassField[] = [
|
||||
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import * as chars from '../chars';
|
||||
import {NumberWrapper, isPresent} from '../facade/lang';
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {CompilerInjectable} from '../injectable';
|
||||
|
||||
export enum TokenType {
|
||||
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
|
||||
return new Token(index, TokenType.Error, 0, message);
|
||||
}
|
||||
|
||||
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||
|
||||
class _Scanner {
|
||||
length: number;
|
||||
@ -241,7 +241,7 @@ class _Scanner {
|
||||
this.advance();
|
||||
str += two;
|
||||
}
|
||||
if (isPresent(threeCode) && this.peek == threeCode) {
|
||||
if (threeCode != null && this.peek == threeCode) {
|
||||
this.advance();
|
||||
str += three;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class Extractor {
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
const {files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
|
@ -53,20 +53,22 @@ enum _VisitorMode {
|
||||
* @internal
|
||||
*/
|
||||
class _Visitor implements html.Visitor {
|
||||
private _depth: number;
|
||||
|
||||
// <el i18n>...</el>
|
||||
private _inI18nNode: boolean;
|
||||
private _depth: number;
|
||||
private _inImplicitNode: boolean;
|
||||
|
||||
// <!--i18n-->...<!--/i18n-->
|
||||
private _inI18nBlock: boolean;
|
||||
private _blockMeaningAndDesc: string;
|
||||
private _blockChildren: html.Node[];
|
||||
private _blockStartDepth: number;
|
||||
private _inI18nBlock: boolean;
|
||||
|
||||
// {<icu message>}
|
||||
private _inIcu: boolean;
|
||||
|
||||
// set to void 0 when not in a section
|
||||
private _msgCountAtSectionStart: number;
|
||||
private _errors: I18nError[];
|
||||
private _mode: _VisitorMode;
|
||||
@ -208,50 +210,31 @@ class _Visitor implements html.Visitor {
|
||||
this._depth++;
|
||||
const wasInI18nNode = this._inI18nNode;
|
||||
const wasInImplicitNode = this._inImplicitNode;
|
||||
let childNodes: html.Node[];
|
||||
let childNodes: html.Node[] = [];
|
||||
let translatedChildNodes: html.Node[];
|
||||
|
||||
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
|
||||
// message
|
||||
// Extract:
|
||||
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
|
||||
// - ICU messages
|
||||
const i18nAttr = _getI18nAttr(el);
|
||||
const i18nMeta = i18nAttr ? i18nAttr.value : '';
|
||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||
!this._isInTranslatableSection;
|
||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
||||
this._inImplicitNode = wasInImplicitNode || isImplicit;
|
||||
|
||||
if (!this._isInTranslatableSection && !this._inIcu) {
|
||||
if (i18nAttr) {
|
||||
// explicit translation
|
||||
if (i18nAttr || isTopLevelImplicit) {
|
||||
this._inI18nNode = true;
|
||||
const message = this._addMessage(el.children, i18nAttr.value);
|
||||
childNodes = this._translateMessage(el, message);
|
||||
} else if (isTopLevelImplicit) {
|
||||
// implicit translation
|
||||
this._inI18nNode = true;
|
||||
const message = this._addMessage(el.children);
|
||||
childNodes = this._translateMessage(el, message);
|
||||
const message = this._addMessage(el.children, i18nMeta);
|
||||
translatedChildNodes = this._translateMessage(el, message);
|
||||
}
|
||||
|
||||
if (this._mode == _VisitorMode.Extract) {
|
||||
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
||||
if (isTranslatable) {
|
||||
this._openTranslatableSection(el);
|
||||
}
|
||||
if (isTranslatable) this._openTranslatableSection(el);
|
||||
html.visitAll(this, el.children);
|
||||
if (isTranslatable) {
|
||||
this._closeTranslatableSection(el, el.children);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
|
||||
childNodes = [];
|
||||
el.children.forEach(child => {
|
||||
const visited = child.visit(this, context);
|
||||
if (visited && !this._isInTranslatableSection) {
|
||||
// Do not add the children from translatable sections (= i18n blocks here)
|
||||
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
||||
childNodes = childNodes.concat(visited);
|
||||
}
|
||||
});
|
||||
if (isTranslatable) this._closeTranslatableSection(el, el.children);
|
||||
}
|
||||
} else {
|
||||
if (i18nAttr || isTopLevelImplicit) {
|
||||
@ -263,19 +246,18 @@ class _Visitor implements html.Visitor {
|
||||
// Descend into child nodes for extraction
|
||||
html.visitAll(this, el.children);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._mode == _VisitorMode.Merge) {
|
||||
// Translate attributes in ICU messages
|
||||
childNodes = [];
|
||||
el.children.forEach(child => {
|
||||
const visited = child.visit(this, context);
|
||||
if (visited && !this._isInTranslatableSection) {
|
||||
// Do not add the children from translatable sections (= i18n blocks here)
|
||||
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
||||
childNodes = childNodes.concat(visited);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this._mode === _VisitorMode.Merge) {
|
||||
const visitNodes = translatedChildNodes || el.children;
|
||||
visitNodes.forEach(child => {
|
||||
const visited = child.visit(this, context);
|
||||
if (visited && !this._isInTranslatableSection) {
|
||||
// Do not add the children from translatable sections (= i18n blocks here)
|
||||
// They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
|
||||
childNodes = childNodes.concat(visited);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._visitAttributesOf(el);
|
||||
@ -285,7 +267,6 @@ class _Visitor implements html.Visitor {
|
||||
this._inImplicitNode = wasInImplicitNode;
|
||||
|
||||
if (this._mode === _VisitorMode.Merge) {
|
||||
// There are no childNodes in translatable sections - those nodes will be replace anyway
|
||||
const translatedAttrs = this._translateAttributes(el);
|
||||
return new html.Element(
|
||||
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
||||
@ -344,6 +325,7 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
// Translates the given message given the `TranslationBundle`
|
||||
// This is used for translating elements / blocks - see `_translateAttributes` for attributes
|
||||
// no-op when called in extraction mode (returns [])
|
||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||
if (message && this._mode === _VisitorMode.Merge) {
|
||||
@ -385,7 +367,9 @@ class _Visitor implements html.Visitor {
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
if (nodes.length == 0) {
|
||||
translatedAttributes.push(new html.Attribute(attr.name, '', attr.sourceSpan));
|
||||
} else 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 {
|
||||
@ -420,7 +404,7 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the start of a section, see `_endSection`
|
||||
* Marks the start of a section, see `_closeTranslatableSection`
|
||||
*/
|
||||
private _openTranslatableSection(node: html.Node): void {
|
||||
if (this._isInTranslatableSection) {
|
||||
@ -432,7 +416,7 @@ class _Visitor implements html.Visitor {
|
||||
|
||||
/**
|
||||
* A translatable section could be:
|
||||
* - a translatable element,
|
||||
* - the content of translatable element,
|
||||
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
||||
*/
|
||||
private get _isInTranslatableSection(): boolean {
|
||||
|
@ -111,8 +111,14 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
||||
if (!seen) {
|
||||
this._placeHolderNameCounts[base] = 1;
|
||||
return base;
|
||||
}
|
||||
|
||||
const id = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = id + 1;
|
||||
return `${base}_${id}`;
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,26 @@
|
||||
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
export interface Serializer {
|
||||
write(messages: i18n.Message[]): string;
|
||||
export abstract class Serializer {
|
||||
abstract write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
digest(message: i18n.Message): string;
|
||||
abstract digest(message: i18n.Message): string;
|
||||
|
||||
// Creates a name mapper, see `PlaceholderMapper`
|
||||
// Returning `null` means that no name mapping is used.
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
|
||||
* back.
|
||||
*
|
||||
* It should be used for serialization format that put constraints on the placeholder names.
|
||||
*/
|
||||
export interface PlaceholderMapper {
|
||||
toPublicName(internalName: string): string;
|
||||
|
||||
toInternalName(publicName: string): string;
|
||||
}
|
@ -27,7 +27,7 @@ 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 {
|
||||
export class Xliff extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _MESSAGES_TAG = 'messagebundle';
|
||||
@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
export class Xmb extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const exampleVisitor = new ExampleVisitor();
|
||||
const visitor = new _Visitor();
|
||||
@ -51,6 +51,8 @@ export class Xmb implements Serializer {
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const mapper = this.createNameMapper(message);
|
||||
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@ -62,7 +64,8 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
rootNode.children.push(
|
||||
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
|
||||
new xml.CR(2),
|
||||
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
|
||||
});
|
||||
|
||||
rootNode.children.push(new xml.CR());
|
||||
@ -82,22 +85,29 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
||||
visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [new xml.Text(text.value)];
|
||||
}
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
||||
visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
|
||||
const nodes: xml.Node[] = [];
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
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(`} `));
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
|
||||
});
|
||||
|
||||
nodes.push(new xml.Text(`}`));
|
||||
@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
|
||||
let name = ctx.mapper.toPublicName(ph.startName);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
|
||||
if (ph.isVoid) {
|
||||
// void tags have no children nor closing tags
|
||||
return [startTagPh];
|
||||
}
|
||||
|
||||
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
|
||||
name = ctx.mapper.toPublicName(ph.closeName);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
|
||||
|
||||
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
|
||||
return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this, ctx)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
|
||||
visitDeclaration(decl: xml.Declaration): void {}
|
||||
visitDoctype(doctype: xml.Doctype): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
|
||||
*
|
||||
* Because such restrictions do not exist on placeholder names generated locally, the
|
||||
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
|
||||
* serialized and back from XTB to internal names when an XTB is loaded.
|
||||
*/
|
||||
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
|
||||
private internalToXmb: {[k: string]: string} = {};
|
||||
private xmbToNextId: {[k: string]: number} = {};
|
||||
private xmbToInternal: {[k: string]: string} = {};
|
||||
|
||||
// create a mapping from the message
|
||||
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
|
||||
|
||||
toPublicName(internalName: string): string {
|
||||
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
|
||||
null;
|
||||
}
|
||||
|
||||
toInternalName(publicName: string): string {
|
||||
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, ctx?: any): any { return null; }
|
||||
|
||||
visitContainer(container: i18n.Container, ctx?: any): any {
|
||||
container.children.forEach(child => child.visit(this));
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, ctx?: any): any {
|
||||
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
|
||||
this.addPlaceholder(ph.startName);
|
||||
ph.children.forEach(child => child.visit(this));
|
||||
this.addPlaceholder(ph.closeName);
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
// XMB placeholders could only contains A-Z, 0-9 and _
|
||||
private addPlaceholder(internalName: string): void {
|
||||
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
||||
|
||||
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
|
||||
// Create a new XMB when it has already been used
|
||||
const nextId = this.xmbToNextId[xmbName];
|
||||
this.xmbToNextId[xmbName] = nextId + 1;
|
||||
xmbName = `${xmbName}_${nextId}`;
|
||||
} else {
|
||||
this.xmbToNextId[xmbName] = 1;
|
||||
}
|
||||
|
||||
this.internalToXmb[internalName] = xmbName;
|
||||
this.xmbToInternal[xmbName] = internalName;
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import {XmbPlaceholderMapper, digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
export class Xtb extends Serializer {
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
@ -43,6 +43,10 @@ export class Xtb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
|
@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
@ -21,16 +21,20 @@ export class TranslationBundle {
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
public digest: (m: i18n.Message) => string,
|
||||
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
|
||||
}
|
||||
|
||||
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
|
||||
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);
|
||||
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
|
||||
}
|
||||
|
||||
// Returns the translation as HTML nodes from the given source message.
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
@ -46,15 +50,17 @@ export class TranslationBundle {
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
private _mapper: (name: string) => string;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
private _digest: (m: i18n.Message) => string,
|
||||
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._contextStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
const phName = this._mapper(ph.name);
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
@ -105,14 +111,26 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
/**
|
||||
* Convert a source message to a translated text string:
|
||||
* - text nodes are replaced with their translation,
|
||||
* - placeholders are replaced with their content,
|
||||
* - ICU nodes are converted to ICU expressions.
|
||||
*/
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
|
||||
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
|
||||
this._srcMsg = srcMsg;
|
||||
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
|
||||
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
const context = this._contextStack.pop();
|
||||
this._srcMsg = context.msg;
|
||||
this._mapper = context.mapper;
|
||||
return text;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
|
@ -114,7 +114,7 @@ export class JitCompiler implements Compiler {
|
||||
// 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.
|
||||
// so we also need to call loadNgModuleDirectiveAndPipeMetadata for all nested modules.
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||
localModuleMeta.reference, isSync));
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompilerConfig} from '../config';
|
||||
@ -40,6 +40,8 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
|
||||
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
|
||||
};
|
||||
|
||||
const baseHtmlParser = new OpaqueToken('HtmlParser');
|
||||
|
||||
/**
|
||||
* A set of providers that provide `JitCompiler` and its dependencies to use for
|
||||
* template compilation.
|
||||
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
Console,
|
||||
Lexer,
|
||||
Parser,
|
||||
HtmlParser,
|
||||
{
|
||||
provide: baseHtmlParser,
|
||||
useClass: HtmlParser,
|
||||
},
|
||||
{
|
||||
provide: i18n.I18NHtmlParser,
|
||||
useFactory: (parser: HtmlParser, translations: string, format: string) =>
|
||||
new i18n.I18NHtmlParser(parser, translations, format),
|
||||
deps: [
|
||||
HtmlParser,
|
||||
baseHtmlParser,
|
||||
[new Optional(), new Inject(TRANSLATIONS)],
|
||||
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: HtmlParser,
|
||||
useExisting: i18n.I18NHtmlParser,
|
||||
},
|
||||
TemplateParser,
|
||||
DirectiveNormalizer,
|
||||
CompileMetadataResolver,
|
||||
|
@ -294,14 +294,14 @@ export class CompileMetadataResolver {
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given directive.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
|
||||
*/
|
||||
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
const dirMeta = this._directiveCache.get(directiveType);
|
||||
if (!dirMeta) {
|
||||
this._reportError(
|
||||
new SyntaxError(
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
|
||||
directiveType);
|
||||
}
|
||||
return dirMeta;
|
||||
@ -648,14 +648,14 @@ export class CompileMetadataResolver {
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given pipe.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
* This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
|
||||
*/
|
||||
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
const pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
this._reportError(
|
||||
new SyntaxError(
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
|
||||
pipeType);
|
||||
}
|
||||
return pipeMeta;
|
||||
|
@ -283,7 +283,7 @@ class _TreeBuilder {
|
||||
const tagDef = this.getTagDefinition(el.name);
|
||||
const {parent, container} = this._getParentElementSkippingContainers();
|
||||
|
||||
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) {
|
||||
if (parent && tagDef.requireExtraParent(parent.name)) {
|
||||
const newParent = new html.Element(
|
||||
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
||||
this._insertBeforeContainer(parent, container, newParent);
|
||||
|
@ -9,7 +9,7 @@
|
||||
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, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {NgModule, Type} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
|
||||
@ -30,7 +30,7 @@ export class NgModuleResolver {
|
||||
const ngModuleMeta: NgModule =
|
||||
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||
|
||||
if (isPresent(ngModuleMeta)) {
|
||||
if (ngModuleMeta) {
|
||||
return ngModuleMeta;
|
||||
} else {
|
||||
if (throwIfNotFound) {
|
||||
|
@ -68,13 +68,13 @@ export class MapType extends Type {
|
||||
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
||||
}
|
||||
|
||||
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||
|
||||
export interface TypeVisitor {
|
||||
visitBuiltintType(type: BuiltinType, context: any): any;
|
||||
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||
}
|
||||
|
||||
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||
export var NULL_EXPR = new LiteralExpr(null, null);
|
||||
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||
export const NULL_EXPR = new LiteralExpr(null, null);
|
||||
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||
|
||||
//// Statements
|
||||
export enum StmtModifier {
|
||||
@ -894,8 +894,10 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
||||
return new LiteralArrayExpr(values, type);
|
||||
}
|
||||
|
||||
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
||||
return new LiteralMapExpr(values.map(entry => new LiteralMapEntry(entry[0], entry[1])), type);
|
||||
export function literalMap(
|
||||
values: [string, Expression][], type: MapType = null, quoted: boolean = false): LiteralMapExpr {
|
||||
return new LiteralMapExpr(
|
||||
values.map(entry => new LiteralMapEntry(entry[0], entry[1], quoted)), type);
|
||||
}
|
||||
|
||||
export function not(expr: Expression): NotExpr {
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {ValueTransformer, visitValue} from '../util';
|
||||
|
||||
import * as o from './output_ast';
|
||||
|
@ -48,6 +48,51 @@ export class ParseLocation {
|
||||
}
|
||||
return new ParseLocation(this.file, offset, line, col);
|
||||
}
|
||||
|
||||
// Return the source around the location
|
||||
// Up to `maxChars` or `maxLines` on each side of the location
|
||||
getContext(maxChars: number, maxLines: number): {before: string, after: string} {
|
||||
const content = this.file.content;
|
||||
let startOffset = this.offset;
|
||||
|
||||
if (isPresent(startOffset)) {
|
||||
if (startOffset > content.length - 1) {
|
||||
startOffset = content.length - 1;
|
||||
}
|
||||
let endOffset = startOffset;
|
||||
let ctxChars = 0;
|
||||
let ctxLines = 0;
|
||||
|
||||
while (ctxChars < maxChars && startOffset > 0) {
|
||||
startOffset--;
|
||||
ctxChars++;
|
||||
if (content[startOffset] == '\n') {
|
||||
if (++ctxLines == maxLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctxChars = 0;
|
||||
ctxLines = 0;
|
||||
while (ctxChars < maxChars && endOffset < content.length - 1) {
|
||||
endOffset++;
|
||||
ctxChars++;
|
||||
if (content[endOffset] == '\n') {
|
||||
if (++ctxLines == maxLines) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
before: content.substring(startOffset, this.offset),
|
||||
after: content.substring(this.offset, endOffset + 1),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseSourceFile {
|
||||
@ -74,47 +119,9 @@ export class ParseError {
|
||||
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
|
||||
|
||||
toString(): string {
|
||||
const source = this.span.start.file.content;
|
||||
let ctxStart = this.span.start.offset;
|
||||
let contextStr = '';
|
||||
let details = '';
|
||||
if (isPresent(ctxStart)) {
|
||||
if (ctxStart > source.length - 1) {
|
||||
ctxStart = source.length - 1;
|
||||
}
|
||||
let ctxEnd = ctxStart;
|
||||
let ctxLen = 0;
|
||||
let ctxLines = 0;
|
||||
|
||||
while (ctxLen < 100 && ctxStart > 0) {
|
||||
ctxStart--;
|
||||
ctxLen++;
|
||||
if (source[ctxStart] == '\n') {
|
||||
if (++ctxLines == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctxLen = 0;
|
||||
ctxLines = 0;
|
||||
while (ctxLen < 100 && ctxEnd < source.length - 1) {
|
||||
ctxEnd++;
|
||||
ctxLen++;
|
||||
if (source[ctxEnd] == '\n') {
|
||||
if (++ctxLines == 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
|
||||
source.substring(this.span.start.offset, ctxEnd + 1);
|
||||
contextStr = ` ("${context}")`;
|
||||
}
|
||||
if (this.span.details) {
|
||||
details = `, ${this.span.details}`;
|
||||
}
|
||||
const ctx = this.span.start.getContext(100, 3);
|
||||
const contextStr = ctx ? ` ("${ctx.before}[ERROR ->]${ctx.after}")` : '';
|
||||
const details = this.span.details ? `, ${this.span.details}` : '';
|
||||
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Pipe, Type, resolveForwardRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
|
||||
@ -38,9 +38,9 @@ export class PipeResolver {
|
||||
*/
|
||||
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
||||
const metas = this._reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(metas)) {
|
||||
if (metas) {
|
||||
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
||||
if (isPresent(annotation)) {
|
||||
if (annotation) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
||||
|
||||
@ -114,7 +114,7 @@ export class ProviderElementContext {
|
||||
let queries: CompileQueryMetadata[];
|
||||
while (currentEl !== null) {
|
||||
queries = currentEl._contentQueries.get(tokenReference(token));
|
||||
if (isPresent(queries)) {
|
||||
if (queries) {
|
||||
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
||||
}
|
||||
if (currentEl._directiveAsts.length > 0) {
|
||||
@ -123,7 +123,7 @@ export class ProviderElementContext {
|
||||
currentEl = currentEl._parent;
|
||||
}
|
||||
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
||||
if (isPresent(queries)) {
|
||||
if (queries) {
|
||||
result.push(...queries);
|
||||
}
|
||||
return result;
|
||||
@ -143,7 +143,7 @@ export class ProviderElementContext {
|
||||
return null;
|
||||
}
|
||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||
if (isPresent(transformedProviderAst)) {
|
||||
if (transformedProviderAst) {
|
||||
return transformedProviderAst;
|
||||
}
|
||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||
@ -165,11 +165,11 @@ export class ProviderElementContext {
|
||||
transformedUseExisting = null;
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
} else if (provider.useFactory) {
|
||||
const deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
} else if (provider.useClass) {
|
||||
const deps = provider.deps || provider.useClass.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||
@ -235,7 +235,7 @@ export class ProviderElementContext {
|
||||
}
|
||||
} else {
|
||||
// check parent elements
|
||||
while (!result && isPresent(currElement._parent)) {
|
||||
while (!result && currElement._parent) {
|
||||
const prevElement = currElement;
|
||||
currElement = currElement._parent;
|
||||
if (prevElement._isViewRoot) {
|
||||
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
|
||||
return null;
|
||||
}
|
||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||
if (isPresent(transformedProviderAst)) {
|
||||
if (transformedProviderAst) {
|
||||
return transformedProviderAst;
|
||||
}
|
||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
|
||||
transformedUseExisting = null;
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
} else if (provider.useFactory) {
|
||||
const deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
} else if (provider.useClass) {
|
||||
const deps = provider.deps || provider.useClass.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||
@ -454,7 +454,7 @@ function _resolveProviders(
|
||||
|
||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
||||
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
||||
if (isPresent(component.viewQueries)) {
|
||||
if (component.viewQueries) {
|
||||
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
||||
}
|
||||
return viewQueries;
|
||||
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
|
||||
Map<any, CompileQueryMetadata[]> {
|
||||
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
||||
directives.forEach(directive => {
|
||||
if (isPresent(directive.queries)) {
|
||||
if (directive.queries) {
|
||||
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
||||
}
|
||||
});
|
||||
|
@ -9,12 +9,13 @@
|
||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'g');
|
||||
|
||||
/**
|
||||
|
@ -9,8 +9,6 @@
|
||||
// Some of the code comes from WebComponents.JS
|
||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
|
||||
import {UrlResolver} from './url_resolver';
|
||||
|
||||
export class StyleWithImports {
|
||||
@ -18,8 +16,8 @@ export class StyleWithImports {
|
||||
}
|
||||
|
||||
export function isStyleUrlResolvable(url: string): boolean {
|
||||
if (isBlank(url) || url.length === 0 || url[0] == '/') return false;
|
||||
const schemeMatch = url.match(_urlWithSchemaRe);
|
||||
if (url == null || url.length === 0 || url[0] == '/') return false;
|
||||
const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
|
||||
return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
||||
}
|
||||
|
||||
@ -30,17 +28,20 @@ export function isStyleUrlResolvable(url: string): boolean {
|
||||
export function extractStyleUrls(
|
||||
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
||||
const foundUrls: string[] = [];
|
||||
const modifiedCssText = cssText.replace(_cssImportRe, function(...m: string[]) {
|
||||
const url = m[1] || m[2];
|
||||
if (!isStyleUrlResolvable(url)) {
|
||||
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
||||
return m[0];
|
||||
}
|
||||
foundUrls.push(resolver.resolve(baseUrl, url));
|
||||
return '';
|
||||
});
|
||||
|
||||
const modifiedCssText =
|
||||
cssText.replace(CSS_COMMENT_REGEXP, '').replace(CSS_IMPORT_REGEXP, (...m: string[]) => {
|
||||
const url = m[1] || m[2];
|
||||
if (!isStyleUrlResolvable(url)) {
|
||||
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
||||
return m[0];
|
||||
}
|
||||
foundUrls.push(resolver.resolve(baseUrl, url));
|
||||
return '';
|
||||
});
|
||||
return new StyleWithImports(modifiedCssText, foundUrls);
|
||||
}
|
||||
|
||||
const _cssImportRe = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
||||
const _urlWithSchemaRe = /^([^:/?#]+):/;
|
||||
const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g;
|
||||
const CSS_COMMENT_REGEXP = /\/\*.+?\*\//g;
|
||||
const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
|
||||
|
@ -11,7 +11,6 @@ import {SecurityContext} from '@angular/core';
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
@ -111,14 +110,14 @@ export class BindingParser {
|
||||
}
|
||||
|
||||
parseInlineTemplateBinding(
|
||||
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||
} else if (isPresent(binding.expression)) {
|
||||
} else if (binding.expression) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
@ -136,7 +135,7 @@ export class BindingParser {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
if (binding.expression) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
}
|
||||
});
|
||||
@ -193,7 +192,7 @@ export class BindingParser {
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[]): boolean {
|
||||
const expr = this.parseInterpolation(value, sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
if (expr) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
return true;
|
||||
}
|
||||
@ -374,7 +373,7 @@ export class BindingParser {
|
||||
}
|
||||
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (isPresent(ast)) {
|
||||
if (ast) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((ast, pipeName) => {
|
||||
|
@ -149,7 +149,7 @@ export class TemplateParser {
|
||||
return new TemplateParseResult(result, errors);
|
||||
}
|
||||
|
||||
if (isPresent(this.transforms)) {
|
||||
if (this.transforms) {
|
||||
this.transforms.forEach(
|
||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||
}
|
||||
@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
visitText(text: html.Text, parent: ElementContext): any {
|
||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
if (expr) {
|
||||
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
|
||||
} else {
|
||||
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
|
||||
@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchableAttrs: string[][] = [];
|
||||
const matchableAttrs: [string, string][] = [];
|
||||
const elementOrDirectiveProps: BoundProperty[] = [];
|
||||
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||
const elementVars: VariableAst[] = [];
|
||||
const events: BoundEventAst[] = [];
|
||||
|
||||
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
||||
const templateMatchableAttrs: string[][] = [];
|
||||
const templateMatchableAttrs: [string, string][] = [];
|
||||
const templateElementVars: VariableAst[] = [];
|
||||
|
||||
let hasInlineTemplates = false;
|
||||
@ -268,14 +268,17 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
|
||||
let templateBindingsSource: string|undefined = undefined;
|
||||
let prefixToken: string|undefined = undefined;
|
||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
||||
let templateBindingsSource: string|undefined;
|
||||
let prefixToken: string|undefined;
|
||||
let normalizedName = this._normalizeAttributeName(attr.name);
|
||||
|
||||
if (normalizedName == TEMPLATE_ATTR) {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
templateBindingsSource = attr.value;
|
||||
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||
}
|
||||
|
||||
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||
if (hasTemplateBinding) {
|
||||
if (hasInlineTemplates) {
|
||||
@ -285,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
hasInlineTemplates = true;
|
||||
this._bindingParser.parseInlineTemplateBinding(
|
||||
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, templateElementVars);
|
||||
}
|
||||
|
||||
@ -306,9 +309,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
const elementProps: BoundElementPropertyAst[] =
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||
|
||||
const providerContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||
references, element.sourceSpan);
|
||||
|
||||
const children = html.visitAll(
|
||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||
ElementContext.create(
|
||||
@ -541,10 +546,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
||||
const matchedReferences = new Set<string>();
|
||||
let component: CompileDirectiveSummary = null;
|
||||
|
||||
const directiveAsts = directives.map((directive) => {
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
elementSourceSpan.start, elementSourceSpan.end,
|
||||
`Directive ${identifierName(directive.type)}`);
|
||||
|
||||
if (directive.isComponent) {
|
||||
component = directive;
|
||||
}
|
||||
@ -567,6 +574,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
return new DirectiveAst(
|
||||
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
||||
});
|
||||
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
if (elOrDirRef.value.length > 0) {
|
||||
if (!matchedReferences.has(elOrDirRef.name)) {
|
||||
@ -581,7 +589,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
|
||||
}
|
||||
}); // fix syntax highlighting issue: `
|
||||
});
|
||||
return directiveAsts;
|
||||
}
|
||||
|
||||
@ -742,7 +750,7 @@ class NonBindableVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]);
|
||||
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
|
||||
const selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
@ -811,16 +819,16 @@ class ElementContext {
|
||||
}
|
||||
|
||||
export function createElementCssSelector(
|
||||
elementName: string, matchableAttrs: string[][]): CssSelector {
|
||||
elementName: string, attributes: [string, string][]): CssSelector {
|
||||
const cssSelector = new CssSelector();
|
||||
const elNameNoNs = splitNsName(elementName)[1];
|
||||
|
||||
cssSelector.setElement(elNameNoNs);
|
||||
|
||||
for (let i = 0; i < matchableAttrs.length; i++) {
|
||||
const attrName = matchableAttrs[i][0];
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
const attrName = attributes[i][0];
|
||||
const attrNameNoNs = splitNsName(attrName)[1];
|
||||
const attrValue = matchableAttrs[i][1];
|
||||
const attrValue = attributes[i][1];
|
||||
|
||||
cssSelector.addAttribute(attrNameNoNs, attrValue);
|
||||
if (attrName.toLowerCase() == CLASS_ATTR) {
|
||||
|
@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
|
||||
/**
|
||||
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
|
||||
*/
|
||||
export var DEFAULT_PACKAGE_URL_PROVIDER = {
|
||||
export const DEFAULT_PACKAGE_URL_PROVIDER = {
|
||||
provide: PACKAGE_ROOT_URL,
|
||||
useValue: '/'
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||
import {CompileView, CompileViewRootNode} from './compile_view';
|
||||
import {InjectMethodVars, ViewProperties} from './constants';
|
||||
import {InjectMethodVars} from './constants';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
|
||||
import {getPropertyInView, injectFromViewParentInjector} from './util';
|
||||
|
||||
@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
|
||||
const propName =
|
||||
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
|
||||
const instance = createProviderProperty(
|
||||
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
propName, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
resolvedProvider.eager, this);
|
||||
if (isDirectiveWrapper) {
|
||||
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
|
||||
@ -211,12 +211,7 @@ export class CompileElement extends CompileNode {
|
||||
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
|
||||
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
|
||||
}
|
||||
const queriesWithReads: _QueryWithRead[] = [];
|
||||
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
|
||||
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
|
||||
queriesWithReads.push(
|
||||
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
|
||||
});
|
||||
|
||||
Object.keys(this.referenceTokens).forEach(varName => {
|
||||
const token = this.referenceTokens[varName];
|
||||
let varValue: o.Expression;
|
||||
@ -226,27 +221,6 @@ export class CompileElement extends CompileNode {
|
||||
varValue = this.renderNode;
|
||||
}
|
||||
this.view.locals.set(varName, varValue);
|
||||
const varToken = {value: varName};
|
||||
queriesWithReads.push(
|
||||
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
let value: o.Expression;
|
||||
if (isPresent(queryWithRead.read.identifier)) {
|
||||
// query for an identifier
|
||||
value = this.instances.get(tokenReference(queryWithRead.read));
|
||||
} else {
|
||||
// query for a reference
|
||||
const token = this.referenceTokens[queryWithRead.read.value];
|
||||
if (isPresent(token)) {
|
||||
value = this.instances.get(tokenReference(token));
|
||||
} else {
|
||||
value = this.elementRef;
|
||||
}
|
||||
}
|
||||
if (isPresent(value)) {
|
||||
queryWithRead.query.addValue(value, this.view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -265,12 +239,14 @@ export class CompileElement extends CompileNode {
|
||||
this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
|
||||
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
||||
});
|
||||
}
|
||||
|
||||
finish() {
|
||||
Array.from(this._queries.values())
|
||||
.forEach(
|
||||
queries => queries.forEach(
|
||||
q =>
|
||||
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||
q => q.generateStatements(
|
||||
this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||
}
|
||||
|
||||
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
||||
@ -283,12 +259,11 @@ export class CompileElement extends CompileNode {
|
||||
null;
|
||||
}
|
||||
|
||||
getProviderTokens(): o.Expression[] {
|
||||
return Array.from(this._resolvedProviders.values())
|
||||
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
|
||||
getProviderTokens(): CompileTokenMetadata[] {
|
||||
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
|
||||
}
|
||||
|
||||
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||
const result: CompileQuery[] = [];
|
||||
let currentEl: CompileElement = this;
|
||||
let distance = 0;
|
||||
@ -314,7 +289,7 @@ export class CompileElement extends CompileNode {
|
||||
CompileQuery {
|
||||
const propName =
|
||||
`_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`;
|
||||
const queryList = createQueryList(queryMeta, directiveInstance, propName, this.view);
|
||||
const queryList = createQueryList(propName, this.view);
|
||||
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
|
||||
addQueryToTokenMap(this._queries, query);
|
||||
return query;
|
||||
@ -394,8 +369,8 @@ function createInjectInternalCondition(
|
||||
}
|
||||
|
||||
function createProviderProperty(
|
||||
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[],
|
||||
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression {
|
||||
propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
|
||||
compileElement: CompileElement): o.Expression {
|
||||
const view = compileElement.view;
|
||||
let resolvedProviderValueExpr: o.Expression;
|
||||
let type: o.Type;
|
||||
@ -426,10 +401,3 @@ function createProviderProperty(
|
||||
}
|
||||
return o.THIS_EXPR.prop(propName);
|
||||
}
|
||||
|
||||
class _QueryWithRead {
|
||||
public read: CompileTokenMetadata;
|
||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||
this.read = query.meta.read || match;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import * as o from '../output/output_ast';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
|
||||
@ -34,7 +33,7 @@ export class CompileMethod {
|
||||
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
|
||||
this._newState.sourceAst !== this._currState.sourceAst) {
|
||||
const expr = this._updateDebugContext(this._newState);
|
||||
if (isPresent(expr)) {
|
||||
if (expr) {
|
||||
this._bodyStatements.push(expr.toStmt());
|
||||
}
|
||||
}
|
||||
@ -43,13 +42,12 @@ export class CompileMethod {
|
||||
private _updateDebugContext(newState: _DebugState): o.Expression {
|
||||
this._currState = this._newState = newState;
|
||||
if (this._debugEnabled) {
|
||||
const sourceLocation =
|
||||
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
|
||||
const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
|
||||
|
||||
return o.THIS_EXPR.callMethod('debug', [
|
||||
o.literal(newState.nodeIndex),
|
||||
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
||||
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
||||
sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
||||
sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
||||
]);
|
||||
} else {
|
||||
return null;
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
@ -33,7 +32,7 @@ export class CompileQuery {
|
||||
addValue(value: o.Expression, view: CompileView) {
|
||||
let currentView = view;
|
||||
const elPath: CompileElement[] = [];
|
||||
while (isPresent(currentView) && currentView !== this.view) {
|
||||
while (currentView && currentView !== this.view) {
|
||||
const parentEl = currentView.declarationElement;
|
||||
elPath.unshift(parentEl);
|
||||
currentView = parentEl.view;
|
||||
@ -64,10 +63,10 @@ export class CompileQuery {
|
||||
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
||||
}
|
||||
|
||||
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
const values = createQueryValues(this._values);
|
||||
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
||||
if (isPresent(this.ownerDirectiveExpression)) {
|
||||
if (this.ownerDirectiveExpression) {
|
||||
const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
|
||||
updateStmts.push(
|
||||
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
|
||||
@ -110,9 +109,7 @@ function mapNestedViews(
|
||||
]);
|
||||
}
|
||||
|
||||
export function createQueryList(
|
||||
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
|
||||
compileView: CompileView): o.Expression {
|
||||
export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
|
||||
compileView.fields.push(new o.ClassField(
|
||||
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
|
||||
const expr = o.THIS_EXPR.prop(propertyName);
|
||||
|
@ -12,7 +12,6 @@ import {EventHandlerVars, NameResolver} from '../compiler_util/expression_conver
|
||||
import {createPureProxy} from '../compiler_util/identifier_util';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
@ -119,7 +118,7 @@ export class CompileView implements NameResolver {
|
||||
const directiveInstance = o.THIS_EXPR.prop('context');
|
||||
this.component.viewQueries.forEach((queryMeta, queryIndex) => {
|
||||
const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`;
|
||||
const queryList = createQueryList(queryMeta, directiveInstance, propName, this);
|
||||
const queryList = createQueryList(propName, this);
|
||||
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
|
||||
addQueryToTokenMap(viewQueries, query);
|
||||
});
|
||||
@ -154,11 +153,11 @@ export class CompileView implements NameResolver {
|
||||
}
|
||||
}
|
||||
|
||||
afterNodes() {
|
||||
finish() {
|
||||
Array.from(this.viewQueries.values())
|
||||
.forEach(
|
||||
queries => queries.forEach(
|
||||
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod)));
|
||||
q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
export class ViewTypeEnum {
|
||||
static fromValue(value: ViewType): o.Expression {
|
||||
@ -25,12 +25,6 @@ export class ViewEncapsulationEnum {
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetectionStrategyEnum {
|
||||
static fromValue(value: ChangeDetectionStrategy): o.Expression {
|
||||
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetectorStatusEnum {
|
||||
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
|
||||
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
|
||||
|
@ -15,7 +15,6 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {ViewProperties} from './constants';
|
||||
import {getHandleEventMethodName} from './util';
|
||||
|
||||
export function bindOutputs(
|
||||
@ -133,4 +132,4 @@ type EventSummary = {
|
||||
name: string,
|
||||
target: string,
|
||||
phase: string
|
||||
}
|
||||
};
|
57
modules/@angular/compiler/src/view_compiler/query_binder.ts
Normal file
57
modules/@angular/compiler/src/view_compiler/query_binder.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @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 {CompileTokenMetadata, tokenReference} from '../compile_metadata';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileQuery} from './compile_query';
|
||||
|
||||
|
||||
// Note: We can't do this when we create the CompileElements already,
|
||||
// as we create embedded views before the <template> elements themselves.
|
||||
export function bindQueryValues(ce: CompileElement) {
|
||||
const queriesWithReads: _QueryWithRead[] = [];
|
||||
|
||||
ce.getProviderTokens().forEach((token) => {
|
||||
const queriesForProvider = ce.getQueriesFor(token);
|
||||
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
|
||||
});
|
||||
|
||||
Object.keys(ce.referenceTokens).forEach(varName => {
|
||||
const varToken = {value: varName};
|
||||
queriesWithReads.push(
|
||||
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
let value: o.Expression;
|
||||
if (queryWithRead.read.identifier) {
|
||||
// query for an identifier
|
||||
value = ce.instances.get(tokenReference(queryWithRead.read));
|
||||
} else {
|
||||
// query for a reference
|
||||
const token = ce.referenceTokens[queryWithRead.read.value];
|
||||
if (token) {
|
||||
value = ce.instances.get(tokenReference(token));
|
||||
} else {
|
||||
value = ce.elementRef;
|
||||
}
|
||||
}
|
||||
if (value) {
|
||||
queryWithRead.query.addValue(value, ce.view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class _QueryWithRead {
|
||||
public read: CompileTokenMetadata;
|
||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||
this.read = query.meta.read || match;
|
||||
}
|
||||
}
|
@ -9,8 +9,6 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
|
||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
@ -23,7 +21,7 @@ export function getPropertyInView(
|
||||
} else {
|
||||
let viewProp: o.Expression = o.THIS_EXPR;
|
||||
let currView: CompileView = callingView;
|
||||
while (currView !== definedView && isPresent(currView.declarationElement.view)) {
|
||||
while (currView !== definedView && currView.declarationElement.view) {
|
||||
currView = currView.declarationElement.view;
|
||||
viewProp = viewProp.prop('parentView');
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
|
||||
import {bindOutputs} from './event_binder';
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||
import {bindQueryValues} from './query_binder';
|
||||
|
||||
export function bindView(
|
||||
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
||||
@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
bindQueryValues(compileElement);
|
||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
bindQueryValues(compileElement);
|
||||
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
|
@ -15,7 +15,6 @@ import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
|
||||
import {createClassStmt} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
|
||||
@ -48,13 +47,16 @@ export function buildView(
|
||||
}
|
||||
|
||||
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
||||
view.afterNodes();
|
||||
createViewTopLevelStmts(view, targetStatements);
|
||||
view.nodes.forEach((node) => {
|
||||
if (node instanceof CompileElement && node.hasEmbeddedView) {
|
||||
finishView(node.embeddedView, targetStatements);
|
||||
if (node instanceof CompileElement) {
|
||||
node.finish();
|
||||
if (node.hasEmbeddedView) {
|
||||
finishView(node.embeddedView, targetStatements);
|
||||
}
|
||||
}
|
||||
});
|
||||
view.finish();
|
||||
createViewTopLevelStmts(view, targetStatements);
|
||||
}
|
||||
|
||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
@ -402,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
||||
o.literal(view.component.template.ngContentSelectors.length),
|
||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||
view.styles,
|
||||
o.literalMap(view.animations.map(
|
||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
||||
o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||
null, true),
|
||||
]))
|
||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||
}
|
||||
@ -418,7 +421,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
|
||||
let componentToken: o.Expression = o.NULL_EXPR;
|
||||
const varTokenEntries: any[] = [];
|
||||
if (isPresent(compileElement)) {
|
||||
providerTokens = compileElement.getProviderTokens();
|
||||
providerTokens =
|
||||
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
|
||||
|
||||
if (isPresent(compileElement.component)) {
|
||||
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
||||
}
|
||||
@ -690,7 +695,6 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
|
||||
view.nodes.forEach((node) => {
|
||||
if (node instanceof CompileElement) {
|
||||
if (node.embeddedView) {
|
||||
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
|
||||
stmts.push(new o.IfStmt(
|
||||
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
||||
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([
|
||||
|
2
modules/@angular/compiler/test/aot/README.md
Normal file
2
modules/@angular/compiler/test/aot/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
Tests in this directory are excluded from running in the browser and only running
|
||||
in node.
|
191
modules/@angular/compiler/test/aot/compiler_spec.ts
Normal file
191
modules/@angular/compiler/test/aot/compiler_spec.ts
Normal file
@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @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 {AotCompiler, AotCompilerHost, createAotCompiler} from '@angular/compiler';
|
||||
import {async} from '@angular/core/testing';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReflectionCapabilities, reflector} from './private_import_core';
|
||||
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, settings} from './test_util';
|
||||
|
||||
const DTS = /\.d\.ts$/;
|
||||
|
||||
// These are the files that contain the well known annotations.
|
||||
const CORE_FILES = [
|
||||
'@angular/core/src/metadata.ts', '@angular/core/src/di/metadata.ts',
|
||||
'@angular/core/src/di/opaque_token.ts', '@angular/core/src/animation/metadata.ts',
|
||||
'@angular/core/src/di/provider.ts', '@angular/core/src/linker/view.ts'
|
||||
];
|
||||
|
||||
describe('compiler', () => {
|
||||
let angularFiles: Map<string, string>;
|
||||
|
||||
beforeAll(() => {
|
||||
const emittingHost = new EmittingCompilerHost(CORE_FILES);
|
||||
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
|
||||
emittingProgram.emit();
|
||||
angularFiles = emittingHost.written;
|
||||
});
|
||||
|
||||
describe('Quickstart', () => {
|
||||
let host: MockCompilerHost;
|
||||
let aotHost: MockAotCompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
|
||||
aotHost = new MockAotCompilerHost(host);
|
||||
});
|
||||
|
||||
// Restore reflector since AoT compiler will update it with a new static reflector
|
||||
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
|
||||
|
||||
it('should compile',
|
||||
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
})));
|
||||
|
||||
it('should compile using summaries',
|
||||
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
|
||||
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
|
||||
.not.toBeUndefined();
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
function expectNoDiagnostics(program: ts.Program) {
|
||||
function fileInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
|
||||
|
||||
function lineNoOf(offset: number, text: string): number {
|
||||
let result = 1;
|
||||
for (let i = 0; i < offset; i++) {
|
||||
if (text[i] == '\n') result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function lineInfo(diagnostic: ts.Diagnostic): string {
|
||||
if (diagnostic.file) {
|
||||
const start = diagnostic.start;
|
||||
let end = diagnostic.start + diagnostic.length;
|
||||
const source = diagnostic.file.text;
|
||||
let lineStart = start;
|
||||
let lineEnd = end;
|
||||
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
|
||||
if (lineStart < start) lineStart++;
|
||||
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
|
||||
let line = source.substring(lineStart, lineEnd);
|
||||
const lineIndex = line.indexOf('/n');
|
||||
if (lineIndex > 0) {
|
||||
line = line.substr(0, lineIndex);
|
||||
end = start + lineIndex;
|
||||
}
|
||||
const lineNo = lineNoOf(start, source) + ': ';
|
||||
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
|
||||
chars(end - start, '^');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw new Error(
|
||||
'Errors from TypeScript:\n' +
|
||||
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
|
||||
}
|
||||
}
|
||||
expectNoDiagnostics(program.getOptionsDiagnostics());
|
||||
expectNoDiagnostics(program.getSyntacticDiagnostics());
|
||||
expectNoDiagnostics(program.getSemanticDiagnostics());
|
||||
}
|
||||
|
||||
function isDTS(fileName: string): boolean {
|
||||
return /\.d\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isSource(fileName: string): boolean {
|
||||
return /\.ts$/.test(fileName);
|
||||
}
|
||||
|
||||
function isFactory(fileName: string): boolean {
|
||||
return /\.ngfactory\./.test(fileName);
|
||||
}
|
||||
|
||||
function summaryCompile(
|
||||
host: MockCompilerHost, aotHost: MockAotCompilerHost,
|
||||
preCompile?: (program: ts.Program) => void) {
|
||||
// First compile the program to generate the summary files.
|
||||
return compile(host, aotHost).then(generatedFiles => {
|
||||
// Remove generated files that were not generated from a DTS file
|
||||
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
|
||||
|
||||
// Next compile the program shrowding metadata and only treating .ts files as source.
|
||||
aotHost.hideMetadata();
|
||||
aotHost.tsFilesOnly();
|
||||
|
||||
return compile(host, aotHost);
|
||||
});
|
||||
}
|
||||
|
||||
function compile(
|
||||
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
|
||||
postCompile: (program: ts.Program) => void = expectNoDiagnostics) {
|
||||
const program = ts.createProgram(host.scriptNames, settings, host);
|
||||
if (preCompile) preCompile(program);
|
||||
const {compiler, reflector} = createAotCompiler(aotHost, {});
|
||||
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
|
||||
.then(generatedFiles => {
|
||||
generatedFiles.forEach(
|
||||
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
|
||||
host.override(file.genFileUrl, file.source));
|
||||
const newProgram = ts.createProgram(host.scriptNames, settings, host, program);
|
||||
if (postCompile) postCompile(newProgram);
|
||||
return generatedFiles;
|
||||
});
|
||||
}
|
||||
|
||||
const QUICKSTART = ['/quickstart/app/app.module.ts'];
|
||||
const FILES: MockData = {
|
||||
quickstart: {
|
||||
app: {
|
||||
'app.component.ts': `
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
|
||||
@Component({
|
||||
template: '<h1>Hello {{name}}</h1>'
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
}
|
||||
`,
|
||||
'app.module.ts': `
|
||||
import { NgModule } from '@angular/core/src/metadata';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
`
|
||||
}
|
||||
}
|
||||
};
|
13
modules/@angular/compiler/test/aot/private_import_core.ts
Normal file
13
modules/@angular/compiler/test/aot/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 const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
export const reflector: typeof r.reflector = r.reflector;
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
|
||||
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SyntaxError} from '@angular/compiler';
|
||||
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
|
||||
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
|
||||
@ -344,6 +344,20 @@ describe('StaticReflector', () => {
|
||||
'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
|
||||
});
|
||||
|
||||
it('should throw a SyntaxError without stack trace when the required resource cannot be resolved',
|
||||
() => {
|
||||
expect(
|
||||
() => simplify(
|
||||
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'AppModule'), ({
|
||||
__symbolic: 'error',
|
||||
message:
|
||||
'Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts'
|
||||
})))
|
||||
.toThrowError(
|
||||
SyntaxError,
|
||||
'Error encountered resolving symbol values statically. Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts, resolving symbol AppModule in /tmp/src/function-reference.ts');
|
||||
});
|
||||
|
||||
it('should record data about the error in the exception', () => {
|
||||
let threw = false;
|
||||
try {
|
||||
@ -449,6 +463,40 @@ describe('StaticReflector', () => {
|
||||
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
|
||||
});
|
||||
|
||||
// #13605
|
||||
it('should not throw on unknown decorators', () => {
|
||||
const data = Object.create(DEFAULT_TEST_DATA);
|
||||
const file = '/tmp/src/app.component.ts';
|
||||
data[file] = `
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
export const enum TypeEnum {
|
||||
type
|
||||
}
|
||||
|
||||
export function MyValidationDecorator(p1: any, p2: any): any {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function ValidationFunction(a1: any): any {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: "<h1>Hello {{name}}</h1>",
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
|
||||
@MyValidationDecorator( TypeEnum.type, ValidationFunction({option: 'value'}))
|
||||
myClassProp: number;
|
||||
}`;
|
||||
init(data);
|
||||
const appComponent = reflector.getStaticSymbol(file, 'AppComponent');
|
||||
expect(() => reflector.propMetadata(appComponent)).not.toThrow();
|
||||
});
|
||||
|
||||
describe('inheritance', () => {
|
||||
class ClassDecorator {
|
||||
constructor(public value: any) {}
|
||||
|
389
modules/@angular/compiler/test/aot/test_util.ts
Normal file
389
modules/@angular/compiler/test/aot/test_util.ts
Normal file
@ -0,0 +1,389 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AotCompilerHost} from '@angular/compiler';
|
||||
import {MetadataCollector} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export type MockData = string | MockDirectory;
|
||||
|
||||
export type MockDirectory = {
|
||||
[name: string]: MockData | undefined;
|
||||
};
|
||||
|
||||
export function isDirectory(data: MockData): data is MockDirectory {
|
||||
return typeof data !== 'string';
|
||||
}
|
||||
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
|
||||
const rxjs = /\/rxjs\//;
|
||||
const tsxfile = /\.tsx$/;
|
||||
export const settings: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
declaration: true,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
removeComments: false,
|
||||
noImplicitAny: false,
|
||||
skipLibCheck: true,
|
||||
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
|
||||
types: []
|
||||
};
|
||||
|
||||
export class EmittingCompilerHost implements ts.CompilerHost {
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private scriptNames: string[];
|
||||
private root = '/';
|
||||
private collector = new MetadataCollector();
|
||||
|
||||
constructor(scriptNames: string[]) {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'modules');
|
||||
|
||||
// Rewrite references to scripts with '@angular' to its corresponding location in
|
||||
// the source tree.
|
||||
this.scriptNames = scriptNames.map(
|
||||
f => f.startsWith('@angular/') ? path.join(this.angularSourcePath, f) : f);
|
||||
|
||||
this.root = root;
|
||||
}
|
||||
}
|
||||
|
||||
public getWrittenFiles(): {name: string, content: string}[] {
|
||||
return Array.from(this.writtenFiles).map(f => ({name: f[0], content: f[1]}));
|
||||
}
|
||||
|
||||
public get scripts(): string[] { return this.scriptNames; }
|
||||
|
||||
public get written(): Map<string, string> { return this.writtenFiles; }
|
||||
|
||||
// ts.ModuleResolutionHost
|
||||
fileExists(fileName: string): boolean { return fs.existsSync(fileName); }
|
||||
|
||||
readFile(fileName: string): string {
|
||||
let basename = path.basename(fileName);
|
||||
if (/^lib.*\.d\.ts$/.test(basename)) {
|
||||
let libPath = ts.getDefaultLibFilePath(settings);
|
||||
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
|
||||
}
|
||||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
|
||||
directoryExists(directoryName: string): boolean {
|
||||
return fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory();
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.root; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
return fs.readdirSync(dir).filter(p => {
|
||||
const name = path.join(dir, p);
|
||||
const stat = fs.statSync(name);
|
||||
return stat && stat.isDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
// ts.CompilerHost
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: (message: string) => void): ts.SourceFile {
|
||||
const content = this.readFile(fileName);
|
||||
if (content) {
|
||||
return ts.createSourceFile(fileName, content, languageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
this.writtenFiles.set(fileName, data);
|
||||
if (sourceFiles && sourceFiles.length && DTS.test(fileName)) {
|
||||
const metadataFilePath = fileName.replace(DTS, '.metadata.json');
|
||||
const metadata = this.collector.getMetadata(sourceFiles[0]);
|
||||
if (metadata) this.writtenFiles.set(metadataFilePath, JSON.stringify(metadata));
|
||||
}
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
}
|
||||
|
||||
export class MockCompilerHost implements ts.CompilerHost {
|
||||
scriptNames: string[];
|
||||
|
||||
private angularSourcePath: string|undefined;
|
||||
private nodeModulesPath: string|undefined;
|
||||
private overrides = new Map<string, string>();
|
||||
private writtenFiles = new Map<string, string>();
|
||||
private sourceFiles = new Map<string, ts.SourceFile>();
|
||||
private assumeExists = new Set<string>();
|
||||
private traces: string[] = [];
|
||||
|
||||
constructor(scriptNames: string[], private data: MockData, private angular: Map<string, string>) {
|
||||
this.scriptNames = scriptNames.slice(0);
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
let angularIndex = moduleFilename.indexOf('@angular');
|
||||
let distIndex = moduleFilename.indexOf('/dist/all');
|
||||
if (distIndex >= 0) {
|
||||
const root = moduleFilename.substr(0, distIndex);
|
||||
this.nodeModulesPath = path.join(root, 'node_modules');
|
||||
this.angularSourcePath = path.join(root, 'modules');
|
||||
}
|
||||
}
|
||||
|
||||
// Test API
|
||||
override(fileName: string, content: string) {
|
||||
if (content) {
|
||||
this.overrides.set(fileName, content);
|
||||
} else {
|
||||
this.overrides.delete(fileName);
|
||||
}
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
addScript(fileName: string, content: string) {
|
||||
this.overrides.set(fileName, content);
|
||||
this.scriptNames.push(fileName);
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
assumeFileExists(fileName: string) { this.assumeExists.add(fileName); }
|
||||
|
||||
remove(files: string[]) {
|
||||
// Remove the files from the list of scripts.
|
||||
const fileSet = new Set(files);
|
||||
this.scriptNames = this.scriptNames.filter(f => fileSet.has(f));
|
||||
|
||||
// Remove files from written files
|
||||
files.forEach(f => this.writtenFiles.delete(f));
|
||||
}
|
||||
|
||||
// ts.ModuleResolutionHost
|
||||
fileExists(fileName: string): boolean {
|
||||
if (this.overrides.has(fileName) || this.writtenFiles.has(fileName) ||
|
||||
this.assumeExists.has(fileName)) {
|
||||
return true;
|
||||
}
|
||||
const effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName == fileName) {
|
||||
return open(fileName, this.data) != null;
|
||||
} else {
|
||||
if (fileName.match(rxjs)) {
|
||||
return fs.existsSync(effectiveName);
|
||||
}
|
||||
return this.angular.has(effectiveName);
|
||||
}
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return this.getFileContent(fileName); }
|
||||
|
||||
trace(s: string): void { this.traces.push(s); }
|
||||
|
||||
getCurrentDirectory(): string { return '/'; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const effectiveName = this.getEffectiveName(dir);
|
||||
if (effectiveName === dir) {
|
||||
const data = find(dir, this.data);
|
||||
if (isDirectory(data)) {
|
||||
return Object.keys(data).filter(k => isDirectory(data[k]));
|
||||
}
|
||||
return [];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// ts.CompilerHost
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: (message: string) => void): ts.SourceFile {
|
||||
let result = this.sourceFiles.get(fileName);
|
||||
if (!result) {
|
||||
const content = this.getFileContent(fileName);
|
||||
if (content) {
|
||||
result = ts.createSourceFile(fileName, content, languageVersion);
|
||||
this.sourceFiles.set(fileName, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean) => {
|
||||
this.writtenFiles.set(fileName, data);
|
||||
this.sourceFiles.delete(fileName);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean { return false; }
|
||||
getNewLine(): string { return '\n'; }
|
||||
|
||||
// Private methods
|
||||
private getFileContent(fileName: string): string|undefined {
|
||||
if (this.overrides.has(fileName)) {
|
||||
return this.overrides.get(fileName);
|
||||
}
|
||||
if (this.writtenFiles.has(fileName)) {
|
||||
return this.writtenFiles.get(fileName);
|
||||
}
|
||||
let basename = path.basename(fileName);
|
||||
if (/^lib.*\.d\.ts$/.test(basename)) {
|
||||
let libPath = ts.getDefaultLibFilePath(settings);
|
||||
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
|
||||
} else {
|
||||
let effectiveName = this.getEffectiveName(fileName);
|
||||
if (effectiveName === fileName)
|
||||
return open(fileName, this.data);
|
||||
else {
|
||||
if (fileName.match(rxjs)) {
|
||||
if (fs.existsSync(fileName)) {
|
||||
return fs.readFileSync(fileName, 'utf8');
|
||||
}
|
||||
}
|
||||
return this.angular.get(effectiveName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveName(name: string): string {
|
||||
const node_modules = 'node_modules';
|
||||
const at_angular = '/@angular';
|
||||
const rxjs = '/rxjs';
|
||||
if (name.startsWith('/' + node_modules)) {
|
||||
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
|
||||
return path.join(this.angularSourcePath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
|
||||
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
|
||||
export class MockAotCompilerHost implements AotCompilerHost {
|
||||
private metadataCollector = new MetadataCollector();
|
||||
private metadataVisible: boolean = true;
|
||||
private dtsAreSource: boolean = true;
|
||||
|
||||
constructor(private tsHost: MockCompilerHost) {}
|
||||
|
||||
hideMetadata() { this.metadataVisible = false; }
|
||||
|
||||
tsFilesOnly() { this.dtsAreSource = false; }
|
||||
|
||||
// StaticSymbolResolverHost
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[] {
|
||||
if (!this.tsHost.fileExists(modulePath)) {
|
||||
return undefined;
|
||||
}
|
||||
if (DTS.test(modulePath)) {
|
||||
if (this.metadataVisible) {
|
||||
const metadataPath = modulePath.replace(DTS, '.metadata.json');
|
||||
if (this.tsHost.fileExists(metadataPath)) {
|
||||
let result = JSON.parse(this.tsHost.readFile(metadataPath));
|
||||
return Array.isArray(result) ? result : [result];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const sf = this.tsHost.getSourceFile(modulePath, ts.ScriptTarget.Latest);
|
||||
const metadata = this.metadataCollector.getMetadata(sf);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
|
||||
moduleNameToFileName(moduleName: string, containingFile: string): string|null {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (moduleName.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join('/', 'index.ts');
|
||||
}
|
||||
moduleName = moduleName.replace(EXT, '');
|
||||
const resolved = ts.resolveModuleName(
|
||||
moduleName, containingFile.replace(/\\/g, '/'),
|
||||
{baseDir: '/', genDir: '/'}, this.tsHost)
|
||||
.resolvedModule;
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
}
|
||||
|
||||
// AotSummaryResolverHost
|
||||
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
|
||||
|
||||
isSourceFile(sourceFilePath: string): boolean {
|
||||
return !GENERATED_FILES.test(sourceFilePath) &&
|
||||
(this.dtsAreSource || !DTS.test(sourceFilePath));
|
||||
}
|
||||
|
||||
getOutputFileName(sourceFilePath: string): string {
|
||||
return sourceFilePath.replace(EXT, '') + '.d.ts';
|
||||
}
|
||||
|
||||
// AotCompilerHost
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string|null {
|
||||
return importedFile.replace(EXT, '');
|
||||
}
|
||||
|
||||
loadResource(path: string): Promise<string> {
|
||||
return Promise.resolve(this.tsHost.readFile(path));
|
||||
}
|
||||
}
|
||||
|
||||
function find(fileName: string, data: MockData): MockData|undefined {
|
||||
let names = fileName.split('/');
|
||||
if (names.length && !names[0].length) names.shift();
|
||||
let current = data;
|
||||
for (let name of names) {
|
||||
if (typeof current === 'string')
|
||||
return undefined;
|
||||
else
|
||||
current = (<MockDirectory>current)[name];
|
||||
if (!current) return undefined;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
function open(fileName: string, data: MockData): string|undefined {
|
||||
let result = find(fileName, data);
|
||||
if (typeof result === 'string') {
|
||||
return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function directoryExists(dirname: string, data: MockData): boolean {
|
||||
let result = find(dirname, data);
|
||||
return result && typeof result !== 'string';
|
||||
}
|
@ -342,6 +342,24 @@ export function main() {
|
||||
const HTML = `<div>before<p i18n="m|d">foo</p><!-- comment --></div>`;
|
||||
expect(fakeTranslate(HTML)).toEqual('<div>before<p>**foo**</p></div>');
|
||||
});
|
||||
|
||||
it('should merge empty messages', () => {
|
||||
const HTML = `<div i18n>some element</div>`;
|
||||
const htmlNodes: html.Node[] = parseHtml(HTML);
|
||||
const messages: i18n.Message[] =
|
||||
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
|
||||
|
||||
expect(messages.length).toEqual(1);
|
||||
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
|
||||
i18nMsgMap[digest(messages[0])] = [];
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const output =
|
||||
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
expect(serializeHtmlNodes(output.rootNodes).join('')).toEqual(`<div></div>`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('blocks', () => {
|
||||
@ -381,6 +399,25 @@ export function main() {
|
||||
const HTML = `<p i18n-title="m|d" title=""></p>`;
|
||||
expect(fakeTranslate(HTML)).toEqual('<p title=""></p>');
|
||||
});
|
||||
|
||||
it('should merge empty attributes', () => {
|
||||
const HTML = `<div i18n-title title="some attribute">some element</div>`;
|
||||
const htmlNodes: html.Node[] = parseHtml(HTML);
|
||||
const messages: i18n.Message[] =
|
||||
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
|
||||
|
||||
expect(messages.length).toEqual(1);
|
||||
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
|
||||
i18nMsgMap[digest(messages[0])] = [];
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const output =
|
||||
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
expect(serializeHtmlNodes(output.rootNodes).join(''))
|
||||
.toEqual(`<div title="">some element</div>`);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -412,12 +449,11 @@ function fakeTranslate(
|
||||
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const translatedNodes =
|
||||
mergeTranslations(
|
||||
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs)
|
||||
.rootNodes;
|
||||
const output = mergeTranslations(
|
||||
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs);
|
||||
expect(output.errors).toEqual([]);
|
||||
|
||||
return serializeHtmlNodes(translatedNodes).join('');
|
||||
return serializeHtmlNodes(output.rootNodes).join('');
|
||||
}
|
||||
|
||||
function extract(
|
||||
|
@ -141,12 +141,22 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
|
||||
<!-- make sure that ICU messages are not treated as text nodes -->
|
||||
<div i18n="desc">{
|
||||
response.getItemsList().length,
|
||||
plural,
|
||||
=0 {Found no results}
|
||||
=1 {Found one result}
|
||||
other {Found {{response.getItemsList().length}} results}
|
||||
}</div>
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
count: number;
|
||||
sex: string;
|
||||
sexB: string;
|
||||
response: any = {getItemsList: (): any[] => []};
|
||||
}
|
||||
|
||||
class FrLocalization extends NgLocalization {
|
||||
@ -182,6 +192,12 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
|
||||
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex><a></ex></ph>BAR<ph name="CLOSE_LINK"><ex></a></ex></ph></translation>
|
||||
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@ -197,18 +213,76 @@ const XMB = `
|
||||
<msg id="8670732454866344690">on translatable node</msg>
|
||||
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="1746565782635215">
|
||||
<ph name="ICU"/>
|
||||
<ph name="ICU"><ex>ICU</ex></ph>
|
||||
</msg>
|
||||
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
|
||||
<msg id="7685649297917455806">in a translatable section</msg>
|
||||
<msg id="2387287228265107305">
|
||||
<ph name="START_HEADING_LEVEL1"><ex><h1></ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex></h1></ex></ph>
|
||||
<ph name="START_TAG_DIV"><ex><div></ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
|
||||
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex><a></ex></ph>bar<ph name="CLOSE_LINK"><ex></a></ex></ph></msg>
|
||||
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>;
|
||||
</messagebundle>`;
|
||||
|
||||
const HTML = `
|
||||
<div>
|
||||
<h1 i18n>i18n attribute on tags</h1>
|
||||
|
||||
<div id="i18n-1"><p i18n>nested</p></div>
|
||||
|
||||
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
|
||||
|
||||
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
|
||||
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
|
||||
|
||||
<div>
|
||||
<p id="i18n-4" i18n-title title="on not translatable node"></p>
|
||||
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
|
||||
<p id="i18n-6" i18n-title title></p>
|
||||
</div>
|
||||
|
||||
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
|
||||
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
|
||||
<div i18n id="i18n-8">
|
||||
{sex, select, m {male} f {female}}
|
||||
</div>
|
||||
<div i18n id="i18n-8b">
|
||||
{sexB, select, m {male} f {female}}
|
||||
</div>
|
||||
|
||||
<div i18n id="i18n-9">{{ "count = " + count }}</div>
|
||||
<div i18n id="i18n-10">sex = {{ sex }}</div>
|
||||
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
|
||||
</div>
|
||||
|
||||
<!-- i18n -->
|
||||
<h1 id="i18n-12" >Markers in html comments</h1>
|
||||
<div id="i18n-13" i18n-title title="in a translatable section"></div>
|
||||
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
|
||||
<!-- make sure that ICU messages are not treated as text nodes -->
|
||||
<div i18n="desc">{
|
||||
response.getItemsList().length,
|
||||
plural,
|
||||
=0 {Found no results}
|
||||
=1 {Found one result}
|
||||
other {Found {{response.getItemsList().length}} results}
|
||||
}</div>
|
||||
|
||||
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
|
||||
|
||||
<div i18n>{{ 'test' //i18n(ph="map name") }}</div>
|
||||
`;
|
||||
|
@ -42,7 +42,7 @@ export function main(): void {
|
||||
});
|
||||
}
|
||||
|
||||
class _TestSerializer implements Serializer {
|
||||
class _TestSerializer extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||
.join('//');
|
||||
|
@ -72,6 +72,11 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
|
||||
<note priority="1" from="description">ph names</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="empty target" datatype="html">
|
||||
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
|
||||
<target/>
|
||||
<note priority="1" from="description">ph names</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@ -88,6 +93,7 @@ export function main(): void {
|
||||
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const i18nNodesByMsgId = serializer.load(xliff, 'url');
|
||||
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
@ -109,6 +115,7 @@ export function main(): void {
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
'empty target': '',
|
||||
});
|
||||
});
|
||||
|
||||
|
51
modules/@angular/compiler/test/integration_spec.ts
Normal file
51
modules/@angular/compiler/test/integration_spec.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, Input} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('integration tests', () => {
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
|
||||
|
||||
describe('directives', () => {
|
||||
it('should support dotted selectors', async(() => {
|
||||
@Directive({selector: '[dot.name]'})
|
||||
class MyDir {
|
||||
@Input('dot.name') value: string;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MyDir,
|
||||
TestComponent,
|
||||
],
|
||||
});
|
||||
|
||||
const template = `<div [dot.name]="'foo'"></div>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
|
||||
expect(myDir.value).toEqual('foo');
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmp', template: ''})
|
||||
class TestComponent {
|
||||
}
|
||||
|
||||
function createTestComponent(template: string): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.createComponent(TestComponent);
|
||||
}
|
@ -44,7 +44,7 @@ const baseErrorIdentifier = {
|
||||
runtime: BaseError
|
||||
};
|
||||
|
||||
export var codegenExportsVars = [
|
||||
export const codegenExportsVars = [
|
||||
'getExpressions',
|
||||
];
|
||||
|
||||
@ -190,7 +190,7 @@ const _getExpressionsStmts: o.Statement[] = [
|
||||
]))
|
||||
];
|
||||
|
||||
export var codegenStmts: o.Statement[] = [
|
||||
export const codegenStmts: o.Statement[] = [
|
||||
new o.CommentStmt('This is a comment'),
|
||||
|
||||
new o.ClassStmt(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user