Compare commits
156 Commits
Author | SHA1 | Date | |
---|---|---|---|
80fe41a88e | |||
af652a7c8b | |||
de36f8a3b9 | |||
b658fa9ea0 | |||
2a123463ac | |||
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 |
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
|
language: node_js
|
||||||
sudo: false
|
sudo: false
|
||||||
node_js:
|
node_js:
|
||||||
- '6.6.0'
|
- '6.9.5'
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
# firefox: "38.0"
|
# firefox: "38.0"
|
||||||
|
144
CHANGELOG.md
144
CHANGELOG.md
@ -1,3 +1,143 @@
|
|||||||
|
<a name="2.4.9"></a>
|
||||||
|
## [2.4.9](https://github.com/angular/angular/compare/2.4.8...2.4.9) (2017-03-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **http:** Make ResponseOptionsArgs an interface ([b658fa9](https://github.com/angular/angular/commit/b658fa9)), closes [#13708](https://github.com/angular/angular/issues/13708)
|
||||||
|
* **router:** improve robustness ([#14602](https://github.com/angular/angular/issues/14602)) ([2a12346](https://github.com/angular/angular/commit/2a12346))
|
||||||
|
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* fix(router): do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([de36f8a](https://github.com/angular/angular/commit/de36f8a)), closes [#14681](https://github.com/angular/angular/issues/14681) [#14588](https://github.com/angular/angular/issues/14588)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<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>
|
<a name="2.4.1"></a>
|
||||||
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
||||||
|
|
||||||
@ -76,10 +216,10 @@
|
|||||||
|
|
||||||
### Note ###
|
### 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.
|
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`.
|
`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.
|
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.
|
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.
|
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
|
Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
|
||||||
Angular committers and also prevent breakages on master.
|
|
||||||
|
|
||||||
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.
|
# Merging
|
||||||
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}`.
|
|
||||||
|
|
||||||
(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,
|
# Who is the Caretaker?
|
||||||
Travis will re-base the commits, merge to master, and close the PR automatically.
|
|
||||||
|
|
||||||
Finally, after merge `mary-poppins` removes the presubmit branch.
|
See [this explanation](https://twitter.com/IgorMinar/status/799365744806854656).
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
19
DEVELOPER.md
19
DEVELOPER.md
@ -71,7 +71,13 @@ 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
|
Since global installs can become stale, and required versions can vary by project, we avoid their
|
||||||
use in these instructions.
|
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`.
|
[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
|
## Installing Bower Modules
|
||||||
@ -133,7 +139,8 @@ If you happen to modify the public API of Angular, API golden files must be upda
|
|||||||
$ gulp public-api:update
|
$ gulp public-api:update
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: The command `./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
|
## <a name="clang-format"></a> Formatting your source code
|
||||||
|
|
||||||
@ -146,6 +153,14 @@ You can automatically format your code by running:
|
|||||||
$ gulp format
|
$ gulp format
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Linting/verifying your source code
|
||||||
|
|
||||||
|
You can check that your code is properly formatted and adheres to coding style by running:
|
||||||
|
|
||||||
|
``` shell
|
||||||
|
$ gulp lint
|
||||||
|
```
|
||||||
|
|
||||||
## Publishing your own personal snapshot build
|
## Publishing your own personal snapshot build
|
||||||
|
|
||||||
You may find that your un-merged change needs some validation from external participants.
|
You may find that your un-merged change needs some validation from external participants.
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
machine:
|
machine:
|
||||||
node:
|
node:
|
||||||
version: 5.4.1
|
version: 6.9.5
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pre:
|
pre:
|
||||||
- npm install -g npm@3.6.0
|
- npm install -g npm@3.10.7
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
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.
|
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:
|
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)
|
- 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 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.
|
// This is to ensure that we catch env issues before we error while requiring other dependencies.
|
||||||
require('./tools/check-environment')({
|
require('./tools/check-environment')({
|
||||||
requiredNpmVersion: '>=3.5.3 <4.0.0',
|
requiredNpmVersion: '>=3.10.7 <4.0.0',
|
||||||
requiredNodeVersion: '>=5.4.1 <7.0.0',
|
requiredNodeVersion: '>=6.9.5 <7.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||||
|
|
||||||
import {isListLikeIterable} from '../facade/collection';
|
import {isListLikeIterable} from '../facade/collection';
|
||||||
import {isPresent, stringify} from '../facade/lang';
|
import {stringify} from '../facade/lang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ngModule CommonModule
|
* @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]="{'first': true, 'second': true, 'third': false}">...</some-element>
|
||||||
*
|
*
|
||||||
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
|
||||||
|
*
|
||||||
|
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
@ -132,7 +134,7 @@ export class NgClass implements DoCheck {
|
|||||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||||
} else {
|
} else {
|
||||||
Object.keys(rawClassVal).forEach(klass => {
|
Object.keys(rawClassVal).forEach(klass => {
|
||||||
if (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
|
* 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';
|
import {getTypeNameForDebugging} from '../facade/lang';
|
||||||
|
|
||||||
@ -91,8 +91,13 @@ export class NgFor implements DoCheck, OnChanges {
|
|||||||
@Input() ngForOf: any;
|
@Input() ngForOf: any;
|
||||||
@Input()
|
@Input()
|
||||||
set ngForTrackBy(fn: TrackByFn) {
|
set ngForTrackBy(fn: TrackByFn) {
|
||||||
if (typeof fn !== 'function') {
|
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
||||||
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`);
|
// 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;
|
this._trackByFn = fn;
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
|
|||||||
* @howToUse
|
* @howToUse
|
||||||
* ```
|
* ```
|
||||||
* <some-element [ngPlural]="value">
|
* <some-element [ngPlural]="value">
|
||||||
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
|
* <template ngPluralCase="=0">there is nothing</template>
|
||||||
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
|
* <template ngPluralCase="=1">there is one</template>
|
||||||
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
|
* <template ngPluralCase="few">there are a few</template>
|
||||||
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
|
|
||||||
* </some-element>
|
* </some-element>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@ -90,8 +89,8 @@ export class NgPlural {
|
|||||||
* @howToUse
|
* @howToUse
|
||||||
* ```
|
* ```
|
||||||
* <some-element [ngPlural]="value">
|
* <some-element [ngPlural]="value">
|
||||||
* <ng-container *ngPluralCase="'=0'">...</ng-container>
|
* <template ngPluralCase="=0">...</template>
|
||||||
* <ng-container *ngPluralCase="'other'">...</ng-container>
|
* <template ngPluralCase="other">...</template>
|
||||||
* </some-element>
|
* </some-element>
|
||||||
*```
|
*```
|
||||||
*
|
*
|
||||||
@ -104,6 +103,7 @@ export class NgPluralCase {
|
|||||||
constructor(
|
constructor(
|
||||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||||
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';
|
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.
|
* @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 to the platform's `popState` events.
|
||||||
*/
|
*/
|
||||||
subscribe(
|
subscribe(
|
||||||
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
|
onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
|
||||||
onReturn: () => void = null): Object {
|
onReturn: () => void = null): Object {
|
||||||
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
|
import {ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, WrappedValue} from '@angular/core';
|
||||||
import {EventEmitter, Observable} from '../facade/async';
|
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';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
interface SubscriptionStrategy {
|
interface SubscriptionStrategy {
|
||||||
@ -66,7 +66,7 @@ const _observableStrategy = new ObservableStrategy();
|
|||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
@Pipe({name: 'async', pure: false})
|
@Pipe({name: 'async', pure: false})
|
||||||
export class AsyncPipe implements OnDestroy {
|
export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||||
private _latestValue: Object = null;
|
private _latestValue: Object = null;
|
||||||
private _latestReturnedValue: Object = null;
|
private _latestReturnedValue: Object = null;
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export class AsyncPipe implements OnDestroy {
|
|||||||
return _promiseStrategy;
|
return _promiseStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((<any>obj).subscribe) {
|
if (isObservable(obj)) {
|
||||||
return _observableStrategy;
|
return _observableStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ export class AsyncPipe implements OnDestroy {
|
|||||||
this._obj = null;
|
this._obj = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateLatestValue(async: any, value: Object) {
|
private _updateLatestValue(async: any, value: Object): void {
|
||||||
if (async === this._obj) {
|
if (async === this._obj) {
|
||||||
this._latestValue = value;
|
this._latestValue = value;
|
||||||
this._ref.markForCheck();
|
this._ref.markForCheck();
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||||
|
import {NumberWrapper} from '../facade/lang';
|
||||||
import {NumberWrapper, isDate} from '../facade/lang';
|
|
||||||
|
|
||||||
import {DateFormatter} from './intl';
|
import {DateFormatter} from './intl';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
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
|
* @ngModule CommonModule
|
||||||
@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|||||||
* Where:
|
* Where:
|
||||||
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||||
* (https://www.w3.org/TR/NOTE-datetime).
|
* (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.
|
* 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`)
|
* - `'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`)
|
* - `'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 {
|
transform(value: any, pattern: string = 'mediumDate'): string {
|
||||||
let date: Date;
|
let date: Date;
|
||||||
|
|
||||||
if (isBlank(value)) return null;
|
if (isBlank(value) || value !== value) return null;
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = value.trim();
|
value = value.trim();
|
||||||
@ -130,7 +130,12 @@ export class DatePipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isDate(date)) {
|
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);
|
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
|
||||||
@ -140,3 +145,31 @@ export class DatePipe implements PipeTransform {
|
|||||||
function isBlank(obj: any): boolean {
|
function isBlank(obj: any): boolean {
|
||||||
return obj == null || obj === '';
|
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 {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {isBlank} from '../facade/lang';
|
|
||||||
import {NgLocalization, getPluralCategory} from '../localization';
|
import {NgLocalization, getPluralCategory} from '../localization';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ export class I18nPluralPipe implements PipeTransform {
|
|||||||
constructor(private _localization: NgLocalization) {}
|
constructor(private _localization: NgLocalization) {}
|
||||||
|
|
||||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||||
if (isBlank(value)) return '';
|
if (value == null) return '';
|
||||||
|
|
||||||
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
||||||
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||||
|
|
||||||
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
import {NumberWrapper} from '../facade/lang';
|
||||||
|
|
||||||
import {NumberFormatStyle, NumberFormatter} from './intl';
|
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
|||||||
function formatNumber(
|
function formatNumber(
|
||||||
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
||||||
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
||||||
if (isBlank(value)) return null;
|
if (value == null) return null;
|
||||||
|
|
||||||
// Convert strings to numbers
|
// Convert strings to numbers
|
||||||
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
||||||
@ -41,13 +41,13 @@ function formatNumber(
|
|||||||
if (parts === null) {
|
if (parts === null) {
|
||||||
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
throw new Error(`${digits} is not a valid digit info for number pipes`);
|
||||||
}
|
}
|
||||||
if (isPresent(parts[1])) { // min integer digits
|
if (parts[1] != null) { // min integer digits
|
||||||
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
|
||||||
}
|
}
|
||||||
if (isPresent(parts[3])) { // min fraction digits
|
if (parts[3] != null) { // min fraction digits
|
||||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||||
}
|
}
|
||||||
if (isPresent(parts[5])) { // max fraction digits
|
if (parts[5] != null) { // max fraction digits
|
||||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Pipe, PipeTransform} from '@angular/core';
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
import {isBlank} from '../facade/lang';
|
|
||||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +57,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|||||||
@Pipe({name: 'slice', pure: false})
|
@Pipe({name: 'slice', pure: false})
|
||||||
export class SlicePipe implements PipeTransform {
|
export class SlicePipe implements PipeTransform {
|
||||||
transform(value: any, start: number, end?: number): any {
|
transform(value: any, start: number, end?: number): any {
|
||||||
if (isBlank(value)) return value;
|
if (value == null) return value;
|
||||||
|
|
||||||
if (!this.supports(value)) {
|
if (!this.supports(value)) {
|
||||||
throw new InvalidPipeArgumentError(SlicePipe, value);
|
throw new InvalidPipeArgumentError(SlicePipe, value);
|
||||||
|
@ -9,3 +9,4 @@
|
|||||||
import {__core_private__ as r} from '@angular/core';
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
export const isPromise: typeof r.isPromise = r.isPromise;
|
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 {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 {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
@ -29,10 +29,7 @@ export function main() {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [TestComponent],
|
||||||
TestComponent,
|
|
||||||
ComponentUsingTestComponent,
|
|
||||||
],
|
|
||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -77,7 +74,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should iterate over an array of objects', async(() => {
|
it('should iterate over an array of objects', async(() => {
|
||||||
const template = '<ul><li 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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
// INIT
|
// INIT
|
||||||
@ -95,7 +92,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should gracefully handle nulls', async(() => {
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
detectChangesAndExpectText('');
|
detectChangesAndExpectText('');
|
||||||
@ -140,12 +137,8 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested arrays', async(() => {
|
it('should repeat over nested arrays', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<div *ngFor="let item of items">' +
|
||||||
'<div template="ngFor let item of items">' +
|
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
|
||||||
'<div template="ngFor let subitem of item">' +
|
|
||||||
'{{subitem}}-{{item.length}};' +
|
|
||||||
'</div>|' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>';
|
'</div>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -157,10 +150,9 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested arrays with no intermediate element', async(() => {
|
it('should repeat over nested arrays with no intermediate element', async(() => {
|
||||||
const template = '<div><template ngFor let-item [ngForOf]="items">' +
|
const template = '<div *ngFor="let item of items">' +
|
||||||
'<div template="ngFor let subitem of item">' +
|
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
|
||||||
'{{subitem}}-{{item.length}};' +
|
'</div>';
|
||||||
'</div></template></div>';
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [['a', 'b'], ['c']];
|
getComponent().items = [['a', 'b'], ['c']];
|
||||||
@ -170,10 +162,11 @@ export function main() {
|
|||||||
detectChangesAndExpectText('e-1;f-2;g-2;');
|
detectChangesAndExpectText('e-1;f-2;g-2;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
|
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
|
||||||
const template =
|
const template = `<div *ngFor="let item of items; let i=index">` +
|
||||||
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
`<div>{{i}}|</div>` +
|
||||||
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
`<div *ngIf="i % 2 == 0">even|</div>` +
|
||||||
|
`</div>`;
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -189,8 +182,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display indices correctly', async(() => {
|
it('should display indices correctly', async(() => {
|
||||||
const template =
|
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
|
||||||
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||||
@ -202,7 +194,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display first item correctly', async(() => {
|
it('should display first item correctly', async(() => {
|
||||||
const template =
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -214,7 +206,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display last item correctly', async(() => {
|
it('should display last item correctly', async(() => {
|
||||||
const template =
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -226,7 +218,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display even items correctly', async(() => {
|
it('should display even items correctly', async(() => {
|
||||||
const template =
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2];
|
getComponent().items = [0, 1, 2];
|
||||||
@ -238,7 +230,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should display odd items correctly', async(() => {
|
it('should display odd items correctly', async(() => {
|
||||||
const template =
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [0, 1, 2, 3];
|
getComponent().items = [0, 1, 2, 3];
|
||||||
@ -249,64 +241,57 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should allow to use a custom template', async(() => {
|
it('should allow to use a custom template', async(() => {
|
||||||
const tcTemplate =
|
const template =
|
||||||
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>';
|
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
|
||||||
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}});
|
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
|
||||||
const cutTemplate =
|
fixture = createTestComponent(template);
|
||||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
getComponent().items = ['a', 'b', 'c'];
|
||||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
|
||||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
|
||||||
|
|
||||||
const testComponent = fixture.debugElement.children[0];
|
|
||||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
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(() => {
|
it('should use a default template if a custom one is null', async(() => {
|
||||||
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
const template =
|
||||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`;
|
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
|
||||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
fixture = createTestComponent(template);
|
||||||
const cutTemplate =
|
getComponent().items = ['a', 'b', 'c'];
|
||||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
|
||||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
|
||||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
|
||||||
|
|
||||||
const testComponent = fixture.debugElement.children[0];
|
|
||||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
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(() => {
|
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"
|
const template =
|
||||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`;
|
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
|
||||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
|
||||||
const cutTemplate =
|
fixture = createTestComponent(template);
|
||||||
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
|
getComponent().items = ['a', 'b', 'c'];
|
||||||
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
|
|
||||||
fixture = TestBed.createComponent(ComponentUsingTestComponent);
|
|
||||||
|
|
||||||
const testComponent = fixture.debugElement.children[0];
|
|
||||||
testComponent.componentInstance.items = ['a', 'b', 'c'];
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
detectChangesAndExpectText('0: a;1: b;2: c;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('track by', () => {
|
describe('track by', () => {
|
||||||
it('should throw if trackBy is not a function', async(() => {
|
it('should console.warn if trackBy is not a function', async(() => {
|
||||||
const template =
|
// TODO(vicb): expect a warning message when we have a proper log service
|
||||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`;
|
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
fixture.componentInstance.value = 0;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
getComponent().items = [{id: 1}, {id: 2}];
|
it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
|
||||||
expect(() => fixture.detectChanges())
|
// TODO(vicb): expect no warning message when we have a proper log service
|
||||||
.toThrowError(/trackBy must be a function, but received null/);
|
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
|
||||||
|
fixture = createTestComponent(template);
|
||||||
|
fixture.componentInstance.items = ['a', 'b', 'c'];
|
||||||
|
fixture.componentInstance.value = null;
|
||||||
|
detectChangesAndExpectText('abc');
|
||||||
|
fixture.componentInstance.value = undefined;
|
||||||
|
detectChangesAndExpectText('abc');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should set the context to the component instance', async(() => {
|
it('should set the context to the component instance', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
thisArg = null;
|
thisArg = null;
|
||||||
@ -316,9 +301,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should not replace tracked items', async(() => {
|
it('should not replace tracked items', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
|
||||||
<p>{{items[i]}}</p>
|
|
||||||
</template>`;
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
const buildItemList = () => {
|
const buildItemList = () => {
|
||||||
@ -334,7 +317,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should update implicit local variable on view', async(() => {
|
it('should update implicit local variable on view', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<div><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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
||||||
@ -343,9 +326,10 @@ export function main() {
|
|||||||
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
getComponent().items = [{'id': 'a', 'color': 'red'}];
|
||||||
detectChangesAndExpectText('red');
|
detectChangesAndExpectText('red');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should move items around and keep them updated ', async(() => {
|
it('should move items around and keep them updated ', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<div><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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
|
||||||
@ -356,8 +340,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle added and removed items properly when tracking by index', async(() => {
|
it('should handle added and removed items properly when tracking by index', async(() => {
|
||||||
const template =
|
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
|
||||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().items = ['a', 'b', 'c', 'd'];
|
getComponent().items = ['a', 'b', 'c', 'd'];
|
||||||
@ -377,21 +360,16 @@ class Foo {
|
|||||||
|
|
||||||
@Component({selector: 'test-cmp', template: ''})
|
@Component({selector: 'test-cmp', template: ''})
|
||||||
class TestComponent {
|
class TestComponent {
|
||||||
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
|
value: any;
|
||||||
items: any[] = [1, 2];
|
items: any[] = [1, 2];
|
||||||
trackById(index: number, item: any): string { return item['id']; }
|
trackById(index: number, item: any): string { return item['id']; }
|
||||||
trackByIndex(index: number, item: any): number { return index; }
|
trackByIndex(index: number, item: any): number { return index; }
|
||||||
trackByContext(): void { thisArg = this; }
|
trackByContext(): void { thisArg = this; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'outer-cmp', template: ''})
|
const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
|
||||||
class ComponentUsingTestComponent {
|
|
||||||
items: any = [1, 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
|
|
||||||
|
|
||||||
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
||||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||||
.createComponent(TestComponent);
|
.createComponent(TestComponent);
|
||||||
}
|
}
|
@ -9,6 +9,7 @@
|
|||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
@ -28,131 +29,114 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should work in a template attribute', async(() => {
|
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 = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should work in a template element', async(() => {
|
it('should work on a template element', async(() => {
|
||||||
const template =
|
const template = '<template [ngIf]="booleanCondition">hello2</template>';
|
||||||
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
|
||||||
expect(fixture.nativeElement).toHaveText('hello2');
|
expect(fixture.nativeElement).toHaveText('hello2');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should toggle node when condition changes', async(() => {
|
it('should toggle node when condition changes', async(() => {
|
||||||
const template = '<div><span template="ngIf booleanCondition">hello</span></div>';
|
const template = '<span *ngIf="booleanCondition">hello</span>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
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('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().booleanCondition = true;
|
getComponent().booleanCondition = true;
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
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('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should handle nested if correctly', async(() => {
|
it('should handle nested if correctly', async(() => {
|
||||||
const template =
|
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);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
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('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().booleanCondition = true;
|
getComponent().booleanCondition = true;
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().nestedBooleanCondition = false;
|
getComponent().nestedBooleanCondition = false;
|
||||||
fixture.detectChanges();
|
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('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
|
|
||||||
getComponent().nestedBooleanCondition = true;
|
getComponent().nestedBooleanCondition = true;
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().booleanCondition = false;
|
getComponent().booleanCondition = false;
|
||||||
fixture.detectChanges();
|
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('');
|
expect(fixture.nativeElement).toHaveText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should update several nodes with if', async(() => {
|
it('should update several nodes with if', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
|
||||||
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' +
|
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
|
||||||
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' +
|
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
|
||||||
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
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))
|
expect(getDOM().getText(fixture.nativeElement))
|
||||||
.toEqual('helloNumberhelloStringhelloFunction');
|
.toEqual('helloNumberhelloStringhelloFunction');
|
||||||
|
|
||||||
getComponent().numberCondition = 0;
|
getComponent().numberCondition = 0;
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('helloString');
|
||||||
|
|
||||||
getComponent().numberCondition = 1;
|
getComponent().numberCondition = 1;
|
||||||
getComponent().stringCondition = 'bar';
|
getComponent().stringCondition = 'bar';
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('helloNumber');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not add the element twice if the condition goes from true to true (JS)',
|
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
|
||||||
async(() => {
|
const template = '<span *ngIf="numberCondition">hello</span>';
|
||||||
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
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');
|
expect(fixture.nativeElement).toHaveText('hello');
|
||||||
|
|
||||||
getComponent().numberCondition = 2;
|
getComponent().numberCondition = 2;
|
||||||
fixture.detectChanges();
|
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');
|
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';
|
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('switch', () => {
|
describe('ngPlural', () => {
|
||||||
let fixture: ComponentFixture<any>;
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||||
@ -33,10 +33,25 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display the template according to the exact value', async(() => {
|
it('should display the template according to the exact value', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngPlural]="switchValue">' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
|
||||||
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
|
||||||
|
'</ul>';
|
||||||
|
|
||||||
|
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>';
|
'</ul></div>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
@ -51,10 +66,9 @@ export function main() {
|
|||||||
// https://github.com/angular/angular/issues/9868
|
// https://github.com/angular/angular/issues/9868
|
||||||
// https://github.com/angular/angular/issues/9882
|
// https://github.com/angular/angular/issues/9882
|
||||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngPlural]="switchValue">' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
||||||
'</ul></div>';
|
'</ul>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -64,11 +78,10 @@ export function main() {
|
|||||||
|
|
||||||
|
|
||||||
it('should be applicable to <ng-container> elements', async(() => {
|
it('should be applicable to <ng-container> elements', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ng-container [ngPlural]="switchValue">' +
|
||||||
'<ng-container [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="=0">you have no messages.</template>' +
|
'<template ngPluralCase="=0">you have no messages.</template>' +
|
||||||
'<template ngPluralCase="=1">you have one message.</template>' +
|
'<template ngPluralCase="=1">you have one message.</template>' +
|
||||||
'</ng-container></div>';
|
'</ng-container>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -80,11 +93,10 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display the template according to the category', async(() => {
|
it('should display the template according to the category', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngPlural]="switchValue">' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||||
'</ul></div>';
|
'</ul>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -96,11 +108,10 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should default to other when no matches are found', async(() => {
|
it('should default to other when no matches are found', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngPlural]="switchValue">' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||||
'</ul></div>';
|
'</ul>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -109,11 +120,10 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should prioritize value matches over category matches', async(() => {
|
it('should prioritize value matches over category matches', async(() => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngPlural]="switchValue">' +
|
||||||
'<ul [ngPlural]="switchValue">' +
|
|
||||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||||
'</ul></div>';
|
'</ul>';
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
@ -29,22 +29,19 @@ export function main() {
|
|||||||
it('should add styles specified in an object literal', async(() => {
|
it('should add styles specified in an object literal', async(() => {
|
||||||
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should add and change styles specified in an object expression', async(() => {
|
it('should add and change styles specified in an object expression', async(() => {
|
||||||
const template = `<div [ngStyle]="expr"></div>`;
|
const template = `<div [ngStyle]="expr"></div>`;
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
let expr: {[k: string]: string};
|
|
||||||
|
|
||||||
getComponent().expr = {'max-width': '40px'};
|
getComponent().expr = {'max-width': '40px'};
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||||
|
|
||||||
expr = getComponent().expr;
|
let expr = getComponent().expr;
|
||||||
expr['max-width'] = '30%';
|
expr['max-width'] = '30%';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
||||||
|
@ -33,11 +33,10 @@ export function main() {
|
|||||||
|
|
||||||
describe('switch value changes', () => {
|
describe('switch value changes', () => {
|
||||||
it('should switch amongst when values', () => {
|
it('should switch amongst when values', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||||
'<template ngSwitchCase="a"><li>when a</li></template>' +
|
'<li *ngSwitchCase="\'b\'">when b</li>' +
|
||||||
'<template ngSwitchCase="b"><li>when b</li></template>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
@ -51,11 +50,10 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should switch amongst when values with fallback to default', () => {
|
it('should switch amongst when values with fallback to default', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||||
'<li template="ngSwitchCase \'a\'">when a</li>' +
|
'<li *ngSwitchDefault>when default</li>' +
|
||||||
'<li template="ngSwitchDefault">when default</li>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default');
|
detectChangesAndExpectText('when default');
|
||||||
@ -71,15 +69,14 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should support multiple whens with the same value', () => {
|
it('should support multiple whens with the same value', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
'<li *ngSwitchDefault>when default1;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
'<li *ngSwitchDefault>when default2;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
@ -94,12 +91,11 @@ export function main() {
|
|||||||
|
|
||||||
describe('when values changes', () => {
|
describe('when values changes', () => {
|
||||||
it('should switch amongst when values', () => {
|
it('should switch amongst when values', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchCase="when1">when 1;</li>' +
|
||||||
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
|
'<li *ngSwitchCase="when2">when 2;</li>' +
|
||||||
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
|
'<li *ngSwitchDefault>when default;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default;</li></template>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
getComponent().when1 = 'a';
|
getComponent().when1 = 'a';
|
||||||
@ -148,11 +144,10 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should create the default case if there is no other case', () => {
|
it('should create the default case if there is no other case', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchDefault>when default1;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
'<li *ngSwitchDefault>when default2;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
@ -160,15 +155,14 @@ export function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow defaults before cases', () => {
|
it('should allow defaults before cases', () => {
|
||||||
const template = '<div>' +
|
const template = '<ul [ngSwitch]="switchValue">' +
|
||||||
'<ul [ngSwitch]="switchValue">' +
|
'<li *ngSwitchDefault>when default1;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
'<li *ngSwitchDefault>when default2;</li>' +
|
||||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
'</ul>';
|
||||||
'</ul></div>';
|
|
||||||
|
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('when default1;when default2;');
|
detectChangesAndExpectText('when default1;when default2;');
|
||||||
|
@ -34,29 +34,22 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should do nothing if templateRef is null', async(() => {
|
it('should do nothing if templateRef is `null`', async(() => {
|
||||||
const template = `<template [ngTemplateOutlet]="null"></template>`;
|
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
detectChangesAndExpectText('');
|
detectChangesAndExpectText('');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should insert content specified by TemplateRef', async(() => {
|
it('should insert content specified by TemplateRef', async(() => {
|
||||||
const template =
|
const template = `<template #tpl>foo</template>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
detectChangesAndExpectText('');
|
|
||||||
|
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
|
||||||
|
|
||||||
setTplRef(refs.tplRefs.first);
|
|
||||||
detectChangesAndExpectText('foo');
|
detectChangesAndExpectText('foo');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should clear content if TemplateRef becomes null', async(() => {
|
it('should clear content if TemplateRef becomes `null`', async(() => {
|
||||||
const template =
|
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
const refs = fixture.debugElement.children[0].references['refs'];
|
||||||
@ -70,7 +63,8 @@ export function main() {
|
|||||||
|
|
||||||
it('should swap content if TemplateRef changes', async(() => {
|
it('should swap content if TemplateRef changes', async(() => {
|
||||||
const template =
|
const template =
|
||||||
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><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 = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -83,70 +77,47 @@ export function main() {
|
|||||||
detectChangesAndExpectText('bar');
|
detectChangesAndExpectText('bar');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display template if context is null', async(() => {
|
it('should display template if context is `null`', async(() => {
|
||||||
const template =
|
const template = `<template #tpl>foo</template>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
|
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
detectChangesAndExpectText('');
|
|
||||||
|
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
|
||||||
|
|
||||||
setTplRef(refs.tplRefs.first);
|
|
||||||
detectChangesAndExpectText('foo');
|
detectChangesAndExpectText('foo');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect initial context and changes', async(() => {
|
it('should reflect initial context and changes', async(() => {
|
||||||
const template =
|
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
|
||||||
setTplRef(refs.tplRefs.first);
|
|
||||||
|
|
||||||
detectChangesAndExpectText('bar');
|
detectChangesAndExpectText('bar');
|
||||||
|
|
||||||
fixture.componentInstance.context.foo = 'alter-bar';
|
fixture.componentInstance.context.foo = 'alter-bar';
|
||||||
|
|
||||||
detectChangesAndExpectText('alter-bar');
|
detectChangesAndExpectText('alter-bar');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect user defined $implicit property in the context', async(() => {
|
it('should reflect user defined `$implicit` property in the context', async(() => {
|
||||||
const template =
|
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
|
||||||
fixture.detectChanges();
|
detectChangesAndExpectText('bra');
|
||||||
|
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
|
||||||
setTplRef(refs.tplRefs.first);
|
|
||||||
|
|
||||||
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
|
|
||||||
detectChangesAndExpectText('bar');
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should reflect context re-binding', async(() => {
|
it('should reflect context re-binding', async(() => {
|
||||||
const template =
|
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
|
||||||
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const refs = fixture.debugElement.children[0].references['refs'];
|
|
||||||
setTplRef(refs.tplRefs.first);
|
|
||||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||||
|
|
||||||
detectChangesAndExpectText('brooks');
|
detectChangesAndExpectText('brooks');
|
||||||
|
|
||||||
fixture.componentInstance.context = {shawshank: 'was here'};
|
fixture.componentInstance.context = {shawshank: 'was here'};
|
||||||
|
|
||||||
detectChangesAndExpectText('was here');
|
detectChangesAndExpectText('was here');
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
||||||
class CaptureTplRefs {
|
class CaptureTplRefs {
|
||||||
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||||
@ -162,4 +133,4 @@ function createTestComponent(template: string): ComponentFixture<TestComponent>
|
|||||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||||
.createComponent(TestComponent);
|
.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 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',
|
it('should support ISO string without time',
|
||||||
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
|
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
|
||||||
|
|
||||||
it('should not support other objects',
|
it('should not support other objects',
|
||||||
() => { expect(() => pipe.transform({})).toThrowError(); });
|
() => expect(() => pipe.transform({})).toThrowError(/Invalid argument/));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('transform', () => {
|
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',
|
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({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild([
|
imports: [RouterModule.forChild([
|
||||||
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
||||||
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
|
{path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
|
||||||
|
path: 'nested-feature',
|
||||||
|
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
|
||||||
|
}
|
||||||
])],
|
])],
|
||||||
declarations: [LazyFeatureComponent]
|
declarations: [LazyFeatureComponent]
|
||||||
})
|
})
|
||||||
|
@ -191,6 +191,8 @@ function lazyRoutesTest() {
|
|||||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||||
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||||
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||||
|
'./feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||||
|
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
|
||||||
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||||
'./default.module': 'feature2/default.module.ts',
|
'./default.module': 'feature2/default.module.ts',
|
||||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "../node_modules/third_party"
|
"outDir": "../node_modules/third_party",
|
||||||
}
|
// Prevent scanning up the directory tree for types
|
||||||
|
"typeRoots": ["node_modules/@types"]
|
||||||
|
}
|
||||||
}
|
}
|
@ -14,7 +14,9 @@
|
|||||||
"rootDir": "",
|
"rootDir": "",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"lib": ["es6", "dom"],
|
"lib": ["es6", "dom"],
|
||||||
"baseUrl": "."
|
"baseUrl": ".",
|
||||||
|
// Prevent scanning up the directory tree for types
|
||||||
|
"typeRoots": ["node_modules/@types"]
|
||||||
},
|
},
|
||||||
|
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"ng-xi18n": "./src/extract_i18n.js"
|
"ng-xi18n": "./src/extract_i18n.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/tsc-wrapped": "0.5.0",
|
"@angular/tsc-wrapped": "0.5.2",
|
||||||
"reflect-metadata": "^0.1.2",
|
"reflect-metadata": "^0.1.2",
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
* Intended to be used in a build step.
|
* Intended to be used in a build step.
|
||||||
*/
|
*/
|
||||||
import * as compiler from '@angular/compiler';
|
import * as compiler from '@angular/compiler';
|
||||||
import {ViewEncapsulation} from '@angular/core';
|
|
||||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||||
import {readFileSync} from 'fs';
|
import {readFileSync} from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -19,7 +18,6 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||||
import {Console} from './private_import_core';
|
|
||||||
|
|
||||||
const GENERATED_META_FILES = /\.json$/;
|
const GENERATED_META_FILES = /\.json$/;
|
||||||
|
|
||||||
@ -38,29 +36,6 @@ export class CodeGenerator {
|
|||||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||||
private ngCompilerHost: CompilerHost) {}
|
private ngCompilerHost: CompilerHost) {}
|
||||||
|
|
||||||
// Write codegen in a directory structure matching the sources.
|
|
||||||
private calculateEmitPath(filePath: string): string {
|
|
||||||
let root = this.options.basePath;
|
|
||||||
for (const eachRootDir of this.options.rootDirs || []) {
|
|
||||||
if (this.options.trace) {
|
|
||||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
|
||||||
}
|
|
||||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
|
||||||
root = eachRootDir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transplant the codegen path to be inside the `genDir`
|
|
||||||
let relativePath: string = path.relative(root, filePath);
|
|
||||||
while (relativePath.startsWith('..' + path.sep)) {
|
|
||||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
|
||||||
// into `genDir`.
|
|
||||||
relativePath = relativePath.substr(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.join(this.options.genDir, relativePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
codegen(): Promise<any> {
|
codegen(): Promise<any> {
|
||||||
return this.compiler
|
return this.compiler
|
||||||
.compileAll(this.program.getSourceFiles().map(
|
.compileAll(this.program.getSourceFiles().map(
|
||||||
@ -68,7 +43,7 @@ export class CodeGenerator {
|
|||||||
.then(generatedModules => {
|
.then(generatedModules => {
|
||||||
generatedModules.forEach(generatedModule => {
|
generatedModules.forEach(generatedModule => {
|
||||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||||
PREAMBLE + generatedModule.source;
|
PREAMBLE + generatedModule.source;
|
||||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||||
|
@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
|
|||||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||||
return !excludeRegex.test(filePath);
|
return !excludeRegex.test(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateEmitPath(filePath: string): string {
|
||||||
|
// Write codegen in a directory structure matching the sources.
|
||||||
|
let root = this.options.basePath;
|
||||||
|
for (const eachRootDir of this.options.rootDirs || []) {
|
||||||
|
if (this.options.trace) {
|
||||||
|
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||||
|
}
|
||||||
|
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||||
|
root = eachRootDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transplant the codegen path to be inside the `genDir`
|
||||||
|
let relativePath: string = path.relative(root, filePath);
|
||||||
|
while (relativePath.startsWith('..' + path.sep)) {
|
||||||
|
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||||
|
// into `genDir`.
|
||||||
|
relativePath = relativePath.substr(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(this.options.genDir, relativePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CompilerHostContextAdapter {
|
export class CompilerHostContextAdapter {
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// We cannot depend directly to @angular/router.
|
// We cannot depend directly to @angular/router.
|
||||||
type Route = any;
|
type Route = any;
|
||||||
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
||||||
@ -63,29 +61,37 @@ export function listLazyRoutesOfModule(
|
|||||||
const className = entryRouteDef.className;
|
const className = entryRouteDef.className;
|
||||||
|
|
||||||
// List loadChildren of this single module.
|
// 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 ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
|
||||||
const lazyRoutes: LazyRoute[] =
|
const lazyRoutes: LazyRoute[] =
|
||||||
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
|
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
|
||||||
const routes: LazyRouteMap = {};
|
|
||||||
|
|
||||||
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
const allLazyRoutes = lazyRoutes.reduce(
|
||||||
const route: string = lazyRoute.routeDef.toString();
|
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
|
||||||
_assertRoute(routes, lazyRoute);
|
LazyRouteMap {
|
||||||
routes[route] = lazyRoute;
|
const route: string = lazyRoute.routeDef.toString();
|
||||||
|
_assertRoute(allRoutes, lazyRoute);
|
||||||
|
allRoutes[route] = lazyRoute;
|
||||||
|
|
||||||
const lazyModuleSymbol = reflector.findDeclaration(
|
// StaticReflector does not support discovering annotations like `NgModule` on default
|
||||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
// exports
|
||||||
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
// 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.
|
const lazyModuleSymbol = reflector.findDeclaration(
|
||||||
subRoutes.forEach(subRoute => {
|
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||||
_assertRoute(routes, subRoute);
|
|
||||||
routes[subRoute.routeDef.toString()] = subRoute;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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[] {
|
function _collectLoadChildren(routes: Route[]): string[] {
|
||||||
return routes.reduce((m, r) => {
|
return routes.reduce((m, r) => {
|
||||||
if (r.loadChildren) {
|
if (r.loadChildren && typeof r.loadChildren === 'string') {
|
||||||
return m.concat(r.loadChildren);
|
return m.concat(r.loadChildren);
|
||||||
} else if (Array.isArray(r)) {
|
} else if (Array.isArray(r)) {
|
||||||
return m.concat(_collectLoadChildren(r));
|
return m.concat(_collectLoadChildren(r));
|
||||||
|
@ -9,10 +9,10 @@
|
|||||||
import {__core_private__ as r} from '@angular/core';
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
export type ReflectorReader = typeof r._ReflectorReader;
|
export type ReflectorReader = typeof r._ReflectorReader;
|
||||||
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
|
||||||
|
|
||||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
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 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);
|
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 {
|
getCurrentDirectory(): string { return this.context.currentDirectory; }
|
||||||
return this.context.currentDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||||
|
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
import {__core_private__ as r} from '@angular/core';
|
import {__core_private__ as r} from '@angular/core';
|
||||||
|
|
||||||
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
|
||||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
export var reflector: typeof r.reflector = r.reflector;
|
export const reflector: typeof r.reflector = r.reflector;
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {SchemaMetadata} from '@angular/core';
|
|
||||||
|
|
||||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||||
import {AnimationParser} from '../animation/animation_parser';
|
import {AnimationParser} from '../animation/animation_parser';
|
||||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||||
|
@ -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 {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||||
|
|
||||||
import {ReflectorReader} from '../private_import_core';
|
import {ReflectorReader} from '../private_import_core';
|
||||||
|
import {SyntaxError} from '../util';
|
||||||
|
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
import {StaticSymbol} from './static_symbol';
|
||||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
|
|
||||||
const ANGULAR_IMPORT_LOCATIONS = {
|
const ANGULAR_IMPORT_LOCATIONS = {
|
||||||
coreDecorators: '@angular/core/src/metadata',
|
coreDecorators: '@angular/core/src/metadata',
|
||||||
@ -310,6 +311,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||||
const parameters: string[] = targetFunction['parameters'];
|
const parameters: string[] = targetFunction['parameters'];
|
||||||
const defaults: any[] = targetFunction.defaults;
|
const defaults: any[] = targetFunction.defaults;
|
||||||
|
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||||
if (defaults && defaults.length > args.length) {
|
if (defaults && defaults.length > args.length) {
|
||||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||||
}
|
}
|
||||||
@ -499,15 +501,15 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
const argExpressions: any[] = expression['arguments'] || [];
|
const argExpressions: any[] = expression['arguments'] || [];
|
||||||
const args =
|
|
||||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
|
||||||
let converter = self.conversionMap.get(staticSymbol);
|
let converter = self.conversionMap.get(staticSymbol);
|
||||||
if (converter) {
|
if (converter) {
|
||||||
|
const args =
|
||||||
|
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||||
return converter(context, args);
|
return converter(context, args);
|
||||||
} else {
|
} else {
|
||||||
// Determine if the function is one we can simplify.
|
// Determine if the function is one we can simplify.
|
||||||
const targetFunction = resolveReferenceValue(staticSymbol);
|
const targetFunction = resolveReferenceValue(staticSymbol);
|
||||||
return simplifyCall(staticSymbol, targetFunction, args);
|
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -537,7 +539,7 @@ export class StaticReflector implements ReflectorReader {
|
|||||||
if (e.fileName) {
|
if (e.fileName) {
|
||||||
throw positionalError(message, e.fileName, e.line, e.column);
|
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 {
|
function shouldIgnore(value: any): boolean {
|
||||||
return value && value.__symbolic == 'ignore';
|
return value && value.__symbolic == 'ignore';
|
||||||
}
|
}
|
||||||
|
@ -273,7 +273,12 @@ export class StaticSymbolResolver {
|
|||||||
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
|
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
|
||||||
const filePath = this.resolveModule(module, containingFile);
|
const filePath = this.resolveModule(module, containingFile);
|
||||||
if (!filePath) {
|
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);
|
return this.getStaticSymbol(filePath, symbolName);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
|
||||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||||
|
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
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
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
import {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
||||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||||
import {ValueTransformer, visitValue} from '../util';
|
import {ValueTransformer, visitValue} from '../util';
|
||||||
|
|
||||||
import {GeneratedFile} from './generated_file';
|
|
||||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||||
|
|
||||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||||
|
|
||||||
|
|
||||||
export interface AotSummarySerializerHost {
|
export interface AotSummarySerializerHost {
|
||||||
/**
|
/**
|
||||||
* Returns the output file path of a source file.
|
* Returns the output file path of a source file.
|
||||||
@ -180,4 +180,4 @@ class Deserializer extends ValueTransformer {
|
|||||||
return super.visitStringMap(map, context);
|
return super.visitStringMap(map, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,6 @@ import {LifecycleHooks, reflector} from './private_import_core';
|
|||||||
import {CssSelector} from './selector';
|
import {CssSelector} from './selector';
|
||||||
import {splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
|
||||||
function unimplemented(): any {
|
|
||||||
throw new Error('unimplemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
// group 0: "[prop] or (event) or @trigger"
|
// group 0: "[prop] or (event) or @trigger"
|
||||||
// group 1: "prop" from "[prop]"
|
// group 1: "prop" from "[prop]"
|
||||||
// group 2: "event" from "(event)"
|
// group 2: "event" from "(event)"
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import {ClassBuilder} from '../output/class_builder';
|
import {ClassBuilder} from '../output/class_builder';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import * as cdAst from '../expression_parser/ast';
|
import * as cdAst from '../expression_parser/ast';
|
||||||
import {isBlank, isPresent} from '../facade/lang';
|
import {isBlank} from '../facade/lang';
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import {ClassBuilder} from '../output/class_builder';
|
import {ClassBuilder} from '../output/class_builder';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
|||||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||||
if (receiver === this._implicitReceiver) {
|
if (receiver === this._implicitReceiver) {
|
||||||
const varExpr = this._getLocal(ast.name);
|
const varExpr = this._getLocal(ast.name);
|
||||||
if (isPresent(varExpr)) {
|
if (varExpr) {
|
||||||
result = varExpr.callFn(args);
|
result = varExpr.callFn(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
|||||||
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||||
if (receiver === this._implicitReceiver) {
|
if (receiver === this._implicitReceiver) {
|
||||||
const varExpr = this._getLocal(ast.name);
|
const varExpr = this._getLocal(ast.name);
|
||||||
if (isPresent(varExpr)) {
|
if (varExpr) {
|
||||||
throw new Error('Cannot assign to a reference or variable!');
|
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 {CompileIdentifierMetadata} from './compile_metadata';
|
||||||
import {Identifiers, createIdentifier} from './identifiers';
|
import {Identifiers, createIdentifier} from './identifiers';
|
||||||
|
|
||||||
function unimplemented(): any {
|
|
||||||
throw new Error('unimplemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CompilerConfig {
|
export class CompilerConfig {
|
||||||
public renderTypes: RenderTypes;
|
public renderTypes: RenderTypes;
|
||||||
public defaultEncapsulation: ViewEncapsulation;
|
public defaultEncapsulation: ViewEncapsulation;
|
||||||
@ -52,12 +48,12 @@ export class CompilerConfig {
|
|||||||
* to help tree shaking.
|
* to help tree shaking.
|
||||||
*/
|
*/
|
||||||
export abstract class RenderTypes {
|
export abstract class RenderTypes {
|
||||||
get renderer(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderer(): CompileIdentifierMetadata;
|
||||||
get renderText(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderText(): CompileIdentifierMetadata;
|
||||||
get renderElement(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderElement(): CompileIdentifierMetadata;
|
||||||
get renderComment(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderComment(): CompileIdentifierMetadata;
|
||||||
get renderNode(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderNode(): CompileIdentifierMetadata;
|
||||||
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); }
|
abstract get renderEvent(): CompileIdentifierMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DefaultRenderTypes implements RenderTypes {
|
export class DefaultRenderTypes implements RenderTypes {
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {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 {CompilerConfig} from './config';
|
||||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
import {CompilerInjectable} from './injectable';
|
||||||
@ -102,11 +102,12 @@ export class DirectiveNormalizer {
|
|||||||
templateAbsUrl: string): CompileTemplateMetadata {
|
templateAbsUrl: string): CompileTemplateMetadata {
|
||||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||||
const rootNodesAndErrors = this._htmlParser.parse(
|
const rootNodesAndErrors = this._htmlParser.parse(
|
||||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
template, stringify(prenomData.componentType), true, interpolationConfig);
|
||||||
if (rootNodesAndErrors.errors.length > 0) {
|
if (rootNodesAndErrors.errors.length > 0) {
|
||||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||||
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||||
styles: prenomData.styles,
|
styles: prenomData.styles,
|
||||||
styleUrls: prenomData.styleUrls,
|
styleUrls: prenomData.styleUrls,
|
||||||
@ -228,9 +229,13 @@ class TemplatePreparseVisitor implements html.Visitor {
|
|||||||
return null;
|
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; }
|
visitComment(ast: html.Comment, context: any): any { return null; }
|
||||||
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
||||||
visitText(ast: html.Text, 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 {ReflectorReader, reflector} from './private_import_core';
|
||||||
import {splitAtColon} from './util';
|
import {splitAtColon} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolve a `Type` for {@link Directive}.
|
* Resolve a `Type` for {@link Directive}.
|
||||||
*
|
*
|
||||||
|
@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
|||||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||||
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
import {Console, LifecycleHooks} from './private_import_core';
|
||||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||||
import {BindingParser} from './template_parser/binding_parser';
|
import {BindingParser} from './template_parser/binding_parser';
|
||||||
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
||||||
@ -128,7 +128,6 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
|||||||
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
const fields: o.ClassField[] = [
|
const fields: o.ClassField[] = [
|
||||||
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
||||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as chars from '../chars';
|
import * as chars from '../chars';
|
||||||
import {NumberWrapper, isPresent} from '../facade/lang';
|
import {NumberWrapper} from '../facade/lang';
|
||||||
import {CompilerInjectable} from '../injectable';
|
import {CompilerInjectable} from '../injectable';
|
||||||
|
|
||||||
export enum TokenType {
|
export enum TokenType {
|
||||||
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
|
|||||||
return new Token(index, TokenType.Error, 0, message);
|
return new Token(index, TokenType.Error, 0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
|
||||||
|
|
||||||
class _Scanner {
|
class _Scanner {
|
||||||
length: number;
|
length: number;
|
||||||
@ -241,7 +241,7 @@ class _Scanner {
|
|||||||
this.advance();
|
this.advance();
|
||||||
str += two;
|
str += two;
|
||||||
}
|
}
|
||||||
if (isPresent(threeCode) && this.peek == threeCode) {
|
if (threeCode != null && this.peek == threeCode) {
|
||||||
this.advance();
|
this.advance();
|
||||||
str += three;
|
str += three;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export class Extractor {
|
|||||||
|
|
||||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
||||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
const {files, ngModules} =
|
||||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
||||||
return Promise
|
return Promise
|
||||||
.all(ngModules.map(
|
.all(ngModules.map(
|
||||||
|
@ -53,20 +53,22 @@ enum _VisitorMode {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class _Visitor implements html.Visitor {
|
class _Visitor implements html.Visitor {
|
||||||
|
private _depth: number;
|
||||||
|
|
||||||
// <el i18n>...</el>
|
// <el i18n>...</el>
|
||||||
private _inI18nNode: boolean;
|
private _inI18nNode: boolean;
|
||||||
private _depth: number;
|
|
||||||
private _inImplicitNode: boolean;
|
private _inImplicitNode: boolean;
|
||||||
|
|
||||||
// <!--i18n-->...<!--/i18n-->
|
// <!--i18n-->...<!--/i18n-->
|
||||||
|
private _inI18nBlock: boolean;
|
||||||
private _blockMeaningAndDesc: string;
|
private _blockMeaningAndDesc: string;
|
||||||
private _blockChildren: html.Node[];
|
private _blockChildren: html.Node[];
|
||||||
private _blockStartDepth: number;
|
private _blockStartDepth: number;
|
||||||
private _inI18nBlock: boolean;
|
|
||||||
|
|
||||||
// {<icu message>}
|
// {<icu message>}
|
||||||
private _inIcu: boolean;
|
private _inIcu: boolean;
|
||||||
|
|
||||||
|
// set to void 0 when not in a section
|
||||||
private _msgCountAtSectionStart: number;
|
private _msgCountAtSectionStart: number;
|
||||||
private _errors: I18nError[];
|
private _errors: I18nError[];
|
||||||
private _mode: _VisitorMode;
|
private _mode: _VisitorMode;
|
||||||
@ -208,50 +210,31 @@ class _Visitor implements html.Visitor {
|
|||||||
this._depth++;
|
this._depth++;
|
||||||
const wasInI18nNode = this._inI18nNode;
|
const wasInI18nNode = this._inI18nNode;
|
||||||
const wasInImplicitNode = this._inImplicitNode;
|
const wasInImplicitNode = this._inImplicitNode;
|
||||||
let childNodes: html.Node[];
|
let childNodes: html.Node[] = [];
|
||||||
|
let translatedChildNodes: html.Node[];
|
||||||
|
|
||||||
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
|
// Extract:
|
||||||
// message
|
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
|
||||||
|
// - ICU messages
|
||||||
const i18nAttr = _getI18nAttr(el);
|
const i18nAttr = _getI18nAttr(el);
|
||||||
|
const i18nMeta = i18nAttr ? i18nAttr.value : '';
|
||||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||||
!this._isInTranslatableSection;
|
!this._isInTranslatableSection;
|
||||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||||
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
this._inImplicitNode = wasInImplicitNode || isImplicit;
|
||||||
|
|
||||||
if (!this._isInTranslatableSection && !this._inIcu) {
|
if (!this._isInTranslatableSection && !this._inIcu) {
|
||||||
if (i18nAttr) {
|
if (i18nAttr || isTopLevelImplicit) {
|
||||||
// explicit translation
|
|
||||||
this._inI18nNode = true;
|
this._inI18nNode = true;
|
||||||
const message = this._addMessage(el.children, i18nAttr.value);
|
const message = this._addMessage(el.children, i18nMeta);
|
||||||
childNodes = this._translateMessage(el, message);
|
translatedChildNodes = this._translateMessage(el, message);
|
||||||
} else if (isTopLevelImplicit) {
|
|
||||||
// implicit translation
|
|
||||||
this._inI18nNode = true;
|
|
||||||
const message = this._addMessage(el.children);
|
|
||||||
childNodes = this._translateMessage(el, message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._mode == _VisitorMode.Extract) {
|
if (this._mode == _VisitorMode.Extract) {
|
||||||
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
||||||
if (isTranslatable) {
|
if (isTranslatable) this._openTranslatableSection(el);
|
||||||
this._openTranslatableSection(el);
|
|
||||||
}
|
|
||||||
html.visitAll(this, el.children);
|
html.visitAll(this, el.children);
|
||||||
if (isTranslatable) {
|
if (isTranslatable) this._closeTranslatableSection(el, el.children);
|
||||||
this._closeTranslatableSection(el, el.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
|
|
||||||
childNodes = [];
|
|
||||||
el.children.forEach(child => {
|
|
||||||
const visited = child.visit(this, context);
|
|
||||||
if (visited && !this._isInTranslatableSection) {
|
|
||||||
// Do not add the children from translatable sections (= i18n blocks here)
|
|
||||||
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
|
||||||
childNodes = childNodes.concat(visited);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i18nAttr || isTopLevelImplicit) {
|
if (i18nAttr || isTopLevelImplicit) {
|
||||||
@ -263,19 +246,18 @@ class _Visitor implements html.Visitor {
|
|||||||
// Descend into child nodes for extraction
|
// Descend into child nodes for extraction
|
||||||
html.visitAll(this, el.children);
|
html.visitAll(this, el.children);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this._mode == _VisitorMode.Merge) {
|
if (this._mode === _VisitorMode.Merge) {
|
||||||
// Translate attributes in ICU messages
|
const visitNodes = translatedChildNodes || el.children;
|
||||||
childNodes = [];
|
visitNodes.forEach(child => {
|
||||||
el.children.forEach(child => {
|
const visited = child.visit(this, context);
|
||||||
const visited = child.visit(this, context);
|
if (visited && !this._isInTranslatableSection) {
|
||||||
if (visited && !this._isInTranslatableSection) {
|
// Do not add the children from translatable sections (= i18n blocks here)
|
||||||
// Do not add the children from translatable sections (= i18n blocks here)
|
// They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
|
||||||
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
|
childNodes = childNodes.concat(visited);
|
||||||
childNodes = childNodes.concat(visited);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._visitAttributesOf(el);
|
this._visitAttributesOf(el);
|
||||||
@ -285,7 +267,6 @@ class _Visitor implements html.Visitor {
|
|||||||
this._inImplicitNode = wasInImplicitNode;
|
this._inImplicitNode = wasInImplicitNode;
|
||||||
|
|
||||||
if (this._mode === _VisitorMode.Merge) {
|
if (this._mode === _VisitorMode.Merge) {
|
||||||
// There are no childNodes in translatable sections - those nodes will be replace anyway
|
|
||||||
const translatedAttrs = this._translateAttributes(el);
|
const translatedAttrs = this._translateAttributes(el);
|
||||||
return new html.Element(
|
return new html.Element(
|
||||||
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
||||||
@ -344,6 +325,7 @@ class _Visitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Translates the given message given the `TranslationBundle`
|
// 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 [])
|
// no-op when called in extraction mode (returns [])
|
||||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||||
if (message && this._mode === _VisitorMode.Merge) {
|
if (message && this._mode === _VisitorMode.Merge) {
|
||||||
@ -385,7 +367,9 @@ class _Visitor implements html.Visitor {
|
|||||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||||
const nodes = this._translations.get(message);
|
const nodes = this._translations.get(message);
|
||||||
if (nodes) {
|
if (nodes) {
|
||||||
if (nodes[0] instanceof html.Text) {
|
if (nodes.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;
|
const value = (nodes[0] as html.Text).value;
|
||||||
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
|
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
|
||||||
} else {
|
} 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 {
|
private _openTranslatableSection(node: html.Node): void {
|
||||||
if (this._isInTranslatableSection) {
|
if (this._isInTranslatableSection) {
|
||||||
@ -432,7 +416,7 @@ class _Visitor implements html.Visitor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A translatable section could be:
|
* A translatable section could be:
|
||||||
* - a translatable element,
|
* - the content of translatable element,
|
||||||
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
||||||
*/
|
*/
|
||||||
private get _isInTranslatableSection(): boolean {
|
private get _isInTranslatableSection(): boolean {
|
||||||
|
@ -111,8 +111,14 @@ export class PlaceholderRegistry {
|
|||||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||||
|
|
||||||
private _generateUniqueName(base: string): string {
|
private _generateUniqueName(base: string): string {
|
||||||
const next = this._placeHolderNameCounts[base];
|
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
||||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
if (!seen) {
|
||||||
return next ? `${base}_${next}` : base;
|
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';
|
import * as i18n from '../i18n_ast';
|
||||||
|
|
||||||
export interface Serializer {
|
export abstract class Serializer {
|
||||||
write(messages: i18n.Message[]): string;
|
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/os/xliff-core.html
|
||||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.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 {
|
write(messages: i18n.Message[]): string {
|
||||||
const visitor = new _WriteVisitor();
|
const visitor = new _WriteVisitor();
|
||||||
const visited: {[id: string]: boolean} = {};
|
const visited: {[id: string]: boolean} = {};
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {decimalDigest} from '../digest';
|
import {decimalDigest} from '../digest';
|
||||||
import * as i18n from '../i18n_ast';
|
import * as i18n from '../i18n_ast';
|
||||||
|
|
||||||
import {Serializer} from './serializer';
|
import {PlaceholderMapper, Serializer} from './serializer';
|
||||||
import * as xml from './xml_helper';
|
import * as xml from './xml_helper';
|
||||||
|
|
||||||
const _MESSAGES_TAG = 'messagebundle';
|
const _MESSAGES_TAG = 'messagebundle';
|
||||||
@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
|||||||
|
|
||||||
<!ELEMENT ex (#PCDATA)>`;
|
<!ELEMENT ex (#PCDATA)>`;
|
||||||
|
|
||||||
export class Xmb implements Serializer {
|
export class Xmb extends Serializer {
|
||||||
write(messages: i18n.Message[]): string {
|
write(messages: i18n.Message[]): string {
|
||||||
const exampleVisitor = new ExampleVisitor();
|
const exampleVisitor = new ExampleVisitor();
|
||||||
const visitor = new _Visitor();
|
const visitor = new _Visitor();
|
||||||
@ -51,6 +51,8 @@ export class Xmb implements Serializer {
|
|||||||
if (visited[id]) return;
|
if (visited[id]) return;
|
||||||
visited[id] = true;
|
visited[id] = true;
|
||||||
|
|
||||||
|
const mapper = this.createNameMapper(message);
|
||||||
|
|
||||||
const attrs: {[k: string]: string} = {id};
|
const attrs: {[k: string]: string} = {id};
|
||||||
|
|
||||||
if (message.description) {
|
if (message.description) {
|
||||||
@ -62,7 +64,8 @@ export class Xmb implements Serializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootNode.children.push(
|
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());
|
rootNode.children.push(new xml.CR());
|
||||||
@ -82,22 +85,29 @@ export class Xmb implements Serializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
digest(message: i18n.Message): string { return digest(message); }
|
digest(message: i18n.Message): string { return digest(message); }
|
||||||
|
|
||||||
|
|
||||||
|
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||||
|
return new XmbPlaceholderMapper(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Visitor implements i18n.Visitor {
|
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[] = [];
|
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;
|
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}, `)];
|
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||||
|
|
||||||
Object.keys(icu.cases).forEach((c: string) => {
|
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(`}`));
|
nodes.push(new xml.Text(`}`));
|
||||||
@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
|
|||||||
return nodes;
|
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 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) {
|
if (ph.isVoid) {
|
||||||
// void tags have no children nor closing tags
|
// void tags have no children nor closing tags
|
||||||
return [startTagPh];
|
return [startTagPh];
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
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[] {
|
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
const name = ctx.mapper.toPublicName(ph.name);
|
||||||
|
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||||
}
|
}
|
||||||
|
|
||||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
const name = ctx.mapper.toPublicName(ph.name);
|
||||||
|
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||||
return [].concat(...nodes.map(node => node.visit(this)));
|
return [].concat(...nodes.map(node => node.visit(this, ctx)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
|
|||||||
visitDeclaration(decl: xml.Declaration): void {}
|
visitDeclaration(decl: xml.Declaration): void {}
|
||||||
visitDoctype(doctype: xml.Doctype): 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 * as i18n from '../i18n_ast';
|
||||||
import {I18nError} from '../parse_util';
|
import {I18nError} from '../parse_util';
|
||||||
|
|
||||||
import {Serializer} from './serializer';
|
import {PlaceholderMapper, Serializer} from './serializer';
|
||||||
import {digest} from './xmb';
|
import {XmbPlaceholderMapper, digest} from './xmb';
|
||||||
|
|
||||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||||
const _TRANSLATION_TAG = 'translation';
|
const _TRANSLATION_TAG = 'translation';
|
||||||
const _PLACEHOLDER_TAG = 'ph';
|
const _PLACEHOLDER_TAG = 'ph';
|
||||||
|
|
||||||
export class Xtb implements Serializer {
|
export class Xtb extends Serializer {
|
||||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||||
|
|
||||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
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); }
|
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
|
// 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 * as i18n from './i18n_ast';
|
||||||
import {I18nError} from './parse_util';
|
import {I18nError} from './parse_util';
|
||||||
import {Serializer} from './serializers/serializer';
|
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A container for translated messages
|
* A container for translated messages
|
||||||
@ -21,16 +21,20 @@ export class TranslationBundle {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||||
public digest: (m: i18n.Message) => string) {
|
public digest: (m: i18n.Message) => string,
|
||||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
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 {
|
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||||
const i18nNodesByMsgId = serializer.load(content, url);
|
const i18nNodesByMsgId = serializer.load(content, url);
|
||||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
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[] {
|
get(srcMsg: i18n.Message): html.Node[] {
|
||||||
const html = this._i18nToHtml.convert(srcMsg);
|
const html = this._i18nToHtml.convert(srcMsg);
|
||||||
|
|
||||||
@ -46,15 +50,17 @@ export class TranslationBundle {
|
|||||||
|
|
||||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||||
private _srcMsg: i18n.Message;
|
private _srcMsg: i18n.Message;
|
||||||
private _srcMsgStack: i18n.Message[] = [];
|
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
|
||||||
private _errors: I18nError[] = [];
|
private _errors: I18nError[] = [];
|
||||||
|
private _mapper: (name: string) => string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
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[]} {
|
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||||
this._srcMsgStack.length = 0;
|
this._contextStack.length = 0;
|
||||||
this._errors.length = 0;
|
this._errors.length = 0;
|
||||||
// i18n to text
|
// i18n to text
|
||||||
const text = this._convertToText(srcMsg);
|
const text = this._convertToText(srcMsg);
|
||||||
@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||||
const phName = ph.name;
|
const phName = this._mapper(ph.name);
|
||||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||||
return this._srcMsg.placeholders[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'; }
|
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 {
|
private _convertToText(srcMsg: i18n.Message): string {
|
||||||
const digest = this._digest(srcMsg);
|
const digest = this._digest(srcMsg);
|
||||||
|
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
|
||||||
|
|
||||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||||
this._srcMsgStack.push(this._srcMsg);
|
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
|
||||||
this._srcMsg = srcMsg;
|
this._srcMsg = srcMsg;
|
||||||
|
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
|
||||||
|
|
||||||
const nodes = this._i18nNodesByMsgId[digest];
|
const nodes = this._i18nNodesByMsgId[digest];
|
||||||
const text = nodes.map(node => node.visit(this)).join('');
|
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;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, 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 {StaticSymbol} from './aot/static_symbol';
|
||||||
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
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
|
// Note: the loadingPromise for a module only includes the loading of the exported directives
|
||||||
// of imported modules.
|
// of imported modules.
|
||||||
// However, for runtime compilation, we want to transitively compile all 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) => {
|
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||||
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
|
||||||
localModuleMeta.reference, isSync));
|
localModuleMeta.reference, isSync));
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, 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 {AnimationParser} from '../animation/animation_parser';
|
||||||
import {CompilerConfig} from '../config';
|
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}"`);}
|
`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
|
* A set of providers that provide `JitCompiler` and its dependencies to use for
|
||||||
* template compilation.
|
* template compilation.
|
||||||
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
|||||||
Console,
|
Console,
|
||||||
Lexer,
|
Lexer,
|
||||||
Parser,
|
Parser,
|
||||||
HtmlParser,
|
{
|
||||||
|
provide: baseHtmlParser,
|
||||||
|
useClass: HtmlParser,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: i18n.I18NHtmlParser,
|
provide: i18n.I18NHtmlParser,
|
||||||
useFactory: (parser: HtmlParser, translations: string, format: string) =>
|
useFactory: (parser: HtmlParser, translations: string, format: string) =>
|
||||||
new i18n.I18NHtmlParser(parser, translations, format),
|
new i18n.I18NHtmlParser(parser, translations, format),
|
||||||
deps: [
|
deps: [
|
||||||
HtmlParser,
|
baseHtmlParser,
|
||||||
[new Optional(), new Inject(TRANSLATIONS)],
|
[new Optional(), new Inject(TRANSLATIONS)],
|
||||||
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
|
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: HtmlParser,
|
||||||
|
useExisting: i18n.I18NHtmlParser,
|
||||||
|
},
|
||||||
TemplateParser,
|
TemplateParser,
|
||||||
DirectiveNormalizer,
|
DirectiveNormalizer,
|
||||||
CompileMetadataResolver,
|
CompileMetadataResolver,
|
||||||
|
@ -294,14 +294,14 @@ export class CompileMetadataResolver {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the metadata for the given directive.
|
* 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 {
|
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||||
const dirMeta = this._directiveCache.get(directiveType);
|
const dirMeta = this._directiveCache.get(directiveType);
|
||||||
if (!dirMeta) {
|
if (!dirMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
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);
|
directiveType);
|
||||||
}
|
}
|
||||||
return dirMeta;
|
return dirMeta;
|
||||||
@ -648,14 +648,14 @@ export class CompileMetadataResolver {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the metadata for the given pipe.
|
* 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 {
|
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||||
const pipeMeta = this._pipeCache.get(pipeType);
|
const pipeMeta = this._pipeCache.get(pipeType);
|
||||||
if (!pipeMeta) {
|
if (!pipeMeta) {
|
||||||
this._reportError(
|
this._reportError(
|
||||||
new SyntaxError(
|
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);
|
pipeType);
|
||||||
}
|
}
|
||||||
return pipeMeta;
|
return pipeMeta;
|
||||||
|
@ -283,7 +283,7 @@ class _TreeBuilder {
|
|||||||
const tagDef = this.getTagDefinition(el.name);
|
const tagDef = this.getTagDefinition(el.name);
|
||||||
const {parent, container} = this._getParentElementSkippingContainers();
|
const {parent, container} = this._getParentElementSkippingContainers();
|
||||||
|
|
||||||
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) {
|
if (parent && tagDef.requireExtraParent(parent.name)) {
|
||||||
const newParent = new html.Element(
|
const newParent = new html.Element(
|
||||||
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
||||||
this._insertBeforeContainer(parent, container, newParent);
|
this._insertBeforeContainer(parent, container, newParent);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
||||||
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
||||||
import {isPresent} from './facade/lang';
|
import {isPresent} from './facade/lang';
|
||||||
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
|
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
|
||||||
import {CompilerInjectable} from './injectable';
|
import {CompilerInjectable} from './injectable';
|
||||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||||
import * as o from './output/output_ast';
|
import * as o from './output/output_ast';
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {NgModule, Type} from '@angular/core';
|
import {NgModule, Type} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from './facade/collection';
|
||||||
import {isPresent, stringify} from './facade/lang';
|
import {stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
import {CompilerInjectable} from './injectable';
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export class NgModuleResolver {
|
|||||||
const ngModuleMeta: NgModule =
|
const ngModuleMeta: NgModule =
|
||||||
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||||
|
|
||||||
if (isPresent(ngModuleMeta)) {
|
if (ngModuleMeta) {
|
||||||
return ngModuleMeta;
|
return ngModuleMeta;
|
||||||
} else {
|
} else {
|
||||||
if (throwIfNotFound) {
|
if (throwIfNotFound) {
|
||||||
|
@ -68,13 +68,13 @@ export class MapType extends Type {
|
|||||||
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
|
||||||
}
|
}
|
||||||
|
|
||||||
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
||||||
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
||||||
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
||||||
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
||||||
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
||||||
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
||||||
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
|
||||||
|
|
||||||
export interface TypeVisitor {
|
export interface TypeVisitor {
|
||||||
visitBuiltintType(type: BuiltinType, context: any): any;
|
visitBuiltintType(type: BuiltinType, context: any): any;
|
||||||
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
|
|||||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
|
||||||
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
|
||||||
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
|
||||||
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
|
||||||
export var NULL_EXPR = new LiteralExpr(null, null);
|
export const NULL_EXPR = new LiteralExpr(null, null);
|
||||||
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
|
||||||
|
|
||||||
//// Statements
|
//// Statements
|
||||||
export enum StmtModifier {
|
export enum StmtModifier {
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
|
||||||
import {ValueTransformer, visitValue} from '../util';
|
import {ValueTransformer, visitValue} from '../util';
|
||||||
|
|
||||||
import * as o from './output_ast';
|
import * as o from './output_ast';
|
||||||
|
@ -48,6 +48,51 @@ export class ParseLocation {
|
|||||||
}
|
}
|
||||||
return new ParseLocation(this.file, offset, line, col);
|
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 {
|
export class ParseSourceFile {
|
||||||
@ -74,47 +119,9 @@ export class ParseError {
|
|||||||
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
|
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
const source = this.span.start.file.content;
|
const ctx = this.span.start.getContext(100, 3);
|
||||||
let ctxStart = this.span.start.offset;
|
const contextStr = ctx ? ` ("${ctx.before}[ERROR ->]${ctx.after}")` : '';
|
||||||
let contextStr = '';
|
const details = this.span.details ? `, ${this.span.details}` : '';
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
return `${this.msg}${contextStr}: ${this.span.start}${details}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {Pipe, Type, resolveForwardRef} from '@angular/core';
|
import {Pipe, Type, resolveForwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper} from './facade/collection';
|
import {ListWrapper} from './facade/collection';
|
||||||
import {isPresent, stringify} from './facade/lang';
|
import {stringify} from './facade/lang';
|
||||||
import {CompilerInjectable} from './injectable';
|
import {CompilerInjectable} from './injectable';
|
||||||
import {ReflectorReader, reflector} from './private_import_core';
|
import {ReflectorReader, reflector} from './private_import_core';
|
||||||
|
|
||||||
@ -38,9 +38,9 @@ export class PipeResolver {
|
|||||||
*/
|
*/
|
||||||
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
||||||
const metas = this._reflector.annotations(resolveForwardRef(type));
|
const metas = this._reflector.annotations(resolveForwardRef(type));
|
||||||
if (isPresent(metas)) {
|
if (metas) {
|
||||||
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
||||||
if (isPresent(annotation)) {
|
if (annotation) {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
||||||
import {isBlank, isPresent} from './facade/lang';
|
import {isBlank, isPresent} from './facade/lang';
|
||||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ export class ProviderElementContext {
|
|||||||
let queries: CompileQueryMetadata[];
|
let queries: CompileQueryMetadata[];
|
||||||
while (currentEl !== null) {
|
while (currentEl !== null) {
|
||||||
queries = currentEl._contentQueries.get(tokenReference(token));
|
queries = currentEl._contentQueries.get(tokenReference(token));
|
||||||
if (isPresent(queries)) {
|
if (queries) {
|
||||||
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
||||||
}
|
}
|
||||||
if (currentEl._directiveAsts.length > 0) {
|
if (currentEl._directiveAsts.length > 0) {
|
||||||
@ -123,7 +123,7 @@ export class ProviderElementContext {
|
|||||||
currentEl = currentEl._parent;
|
currentEl = currentEl._parent;
|
||||||
}
|
}
|
||||||
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
||||||
if (isPresent(queries)) {
|
if (queries) {
|
||||||
result.push(...queries);
|
result.push(...queries);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -143,7 +143,7 @@ export class ProviderElementContext {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||||
if (isPresent(transformedProviderAst)) {
|
if (transformedProviderAst) {
|
||||||
return transformedProviderAst;
|
return transformedProviderAst;
|
||||||
}
|
}
|
||||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||||
@ -165,11 +165,11 @@ export class ProviderElementContext {
|
|||||||
transformedUseExisting = null;
|
transformedUseExisting = null;
|
||||||
transformedUseValue = existingDiDep.value;
|
transformedUseValue = existingDiDep.value;
|
||||||
}
|
}
|
||||||
} else if (isPresent(provider.useFactory)) {
|
} else if (provider.useFactory) {
|
||||||
const deps = provider.deps || provider.useFactory.diDeps;
|
const deps = provider.deps || provider.useFactory.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||||
} else if (isPresent(provider.useClass)) {
|
} else if (provider.useClass) {
|
||||||
const deps = provider.deps || provider.useClass.diDeps;
|
const deps = provider.deps || provider.useClass.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||||
@ -235,7 +235,7 @@ export class ProviderElementContext {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check parent elements
|
// check parent elements
|
||||||
while (!result && isPresent(currElement._parent)) {
|
while (!result && currElement._parent) {
|
||||||
const prevElement = currElement;
|
const prevElement = currElement;
|
||||||
currElement = currElement._parent;
|
currElement = currElement._parent;
|
||||||
if (prevElement._isViewRoot) {
|
if (prevElement._isViewRoot) {
|
||||||
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||||
if (isPresent(transformedProviderAst)) {
|
if (transformedProviderAst) {
|
||||||
return transformedProviderAst;
|
return transformedProviderAst;
|
||||||
}
|
}
|
||||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||||
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
|
|||||||
transformedUseExisting = null;
|
transformedUseExisting = null;
|
||||||
transformedUseValue = existingDiDep.value;
|
transformedUseValue = existingDiDep.value;
|
||||||
}
|
}
|
||||||
} else if (isPresent(provider.useFactory)) {
|
} else if (provider.useFactory) {
|
||||||
const deps = provider.deps || provider.useFactory.diDeps;
|
const deps = provider.deps || provider.useFactory.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||||
} else if (isPresent(provider.useClass)) {
|
} else if (provider.useClass) {
|
||||||
const deps = provider.deps || provider.useClass.diDeps;
|
const deps = provider.deps || provider.useClass.diDeps;
|
||||||
transformedDeps =
|
transformedDeps =
|
||||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||||
@ -454,7 +454,7 @@ function _resolveProviders(
|
|||||||
|
|
||||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
||||||
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
||||||
if (isPresent(component.viewQueries)) {
|
if (component.viewQueries) {
|
||||||
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
||||||
}
|
}
|
||||||
return viewQueries;
|
return viewQueries;
|
||||||
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
|
|||||||
Map<any, CompileQueryMetadata[]> {
|
Map<any, CompileQueryMetadata[]> {
|
||||||
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
||||||
directives.forEach(directive => {
|
directives.forEach(directive => {
|
||||||
if (isPresent(directive.queries)) {
|
if (directive.queries) {
|
||||||
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -9,12 +9,13 @@
|
|||||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||||
|
|
||||||
const _SELECTOR_REGEXP = new RegExp(
|
const _SELECTOR_REGEXP = new RegExp(
|
||||||
'(\\:not\\()|' + //":not("
|
'(\\:not\\()|' + //":not("
|
||||||
'([-\\w]+)|' + // "tag"
|
'([-\\w]+)|' + // "tag"
|
||||||
'(?:\\.([-\\w]+))|' + // ".class"
|
'(?:\\.([-\\w]+))|' + // ".class"
|
||||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||||
'(\\))|' + // ")"
|
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||||
'(\\s*,\\s*)', // ","
|
'(\\))|' + // ")"
|
||||||
|
'(\\s*,\\s*)', // ","
|
||||||
'g');
|
'g');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,6 @@ import {SecurityContext} from '@angular/core';
|
|||||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||||
import {Parser} from '../expression_parser/parser';
|
import {Parser} from '../expression_parser/parser';
|
||||||
import {isPresent} from '../facade/lang';
|
|
||||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||||
import {mergeNsAndName} from '../ml_parser/tags';
|
import {mergeNsAndName} from '../ml_parser/tags';
|
||||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||||
@ -111,14 +110,14 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseInlineTemplateBinding(
|
parseInlineTemplateBinding(
|
||||||
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||||
for (let i = 0; i < bindings.length; i++) {
|
for (let i = 0; i < bindings.length; i++) {
|
||||||
const binding = bindings[i];
|
const binding = bindings[i];
|
||||||
if (binding.keyIsVar) {
|
if (binding.keyIsVar) {
|
||||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||||
} else if (isPresent(binding.expression)) {
|
} else if (binding.expression) {
|
||||||
this._parsePropertyAst(
|
this._parsePropertyAst(
|
||||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||||
} else {
|
} else {
|
||||||
@ -136,7 +135,7 @@ export class BindingParser {
|
|||||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||||
bindingsResult.templateBindings.forEach((binding) => {
|
bindingsResult.templateBindings.forEach((binding) => {
|
||||||
if (isPresent(binding.expression)) {
|
if (binding.expression) {
|
||||||
this._checkPipes(binding.expression, sourceSpan);
|
this._checkPipes(binding.expression, sourceSpan);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -193,7 +192,7 @@ export class BindingParser {
|
|||||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||||
targetProps: BoundProperty[]): boolean {
|
targetProps: BoundProperty[]): boolean {
|
||||||
const expr = this.parseInterpolation(value, sourceSpan);
|
const expr = this.parseInterpolation(value, sourceSpan);
|
||||||
if (isPresent(expr)) {
|
if (expr) {
|
||||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -374,7 +373,7 @@ export class BindingParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||||
if (isPresent(ast)) {
|
if (ast) {
|
||||||
const collector = new PipeCollector();
|
const collector = new PipeCollector();
|
||||||
ast.visit(collector);
|
ast.visit(collector);
|
||||||
collector.pipes.forEach((ast, pipeName) => {
|
collector.pipes.forEach((ast, pipeName) => {
|
||||||
|
@ -149,7 +149,7 @@ export class TemplateParser {
|
|||||||
return new TemplateParseResult(result, errors);
|
return new TemplateParseResult(result, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(this.transforms)) {
|
if (this.transforms) {
|
||||||
this.transforms.forEach(
|
this.transforms.forEach(
|
||||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
visitText(text: html.Text, parent: ElementContext): any {
|
visitText(text: html.Text, parent: ElementContext): any {
|
||||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||||
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
|
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
|
||||||
if (isPresent(expr)) {
|
if (expr) {
|
||||||
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
|
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
|
||||||
} else {
|
} else {
|
||||||
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
|
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
|
||||||
@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchableAttrs: string[][] = [];
|
const matchableAttrs: [string, string][] = [];
|
||||||
const elementOrDirectiveProps: BoundProperty[] = [];
|
const elementOrDirectiveProps: BoundProperty[] = [];
|
||||||
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||||
const elementVars: VariableAst[] = [];
|
const elementVars: VariableAst[] = [];
|
||||||
const events: BoundEventAst[] = [];
|
const events: BoundEventAst[] = [];
|
||||||
|
|
||||||
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
||||||
const templateMatchableAttrs: string[][] = [];
|
const templateMatchableAttrs: [string, string][] = [];
|
||||||
const templateElementVars: VariableAst[] = [];
|
const templateElementVars: VariableAst[] = [];
|
||||||
|
|
||||||
let hasInlineTemplates = false;
|
let hasInlineTemplates = false;
|
||||||
@ -268,14 +268,17 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||||
elementOrDirectiveRefs, elementVars);
|
elementOrDirectiveRefs, elementVars);
|
||||||
|
|
||||||
let templateBindingsSource: string|undefined = undefined;
|
let templateBindingsSource: string|undefined;
|
||||||
let prefixToken: string|undefined = undefined;
|
let prefixToken: string|undefined;
|
||||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
let normalizedName = this._normalizeAttributeName(attr.name);
|
||||||
|
|
||||||
|
if (normalizedName == TEMPLATE_ATTR) {
|
||||||
templateBindingsSource = attr.value;
|
templateBindingsSource = attr.value;
|
||||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||||
templateBindingsSource = attr.value;
|
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);
|
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||||
if (hasTemplateBinding) {
|
if (hasTemplateBinding) {
|
||||||
if (hasInlineTemplates) {
|
if (hasInlineTemplates) {
|
||||||
@ -285,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
hasInlineTemplates = true;
|
hasInlineTemplates = true;
|
||||||
this._bindingParser.parseInlineTemplateBinding(
|
this._bindingParser.parseInlineTemplateBinding(
|
||||||
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||||
templateElementOrDirectiveProps, templateElementVars);
|
templateElementOrDirectiveProps, templateElementVars);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,9 +309,11 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
const elementProps: BoundElementPropertyAst[] =
|
const elementProps: BoundElementPropertyAst[] =
|
||||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
||||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||||
|
|
||||||
const providerContext = new ProviderElementContext(
|
const providerContext = new ProviderElementContext(
|
||||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||||
references, element.sourceSpan);
|
references, element.sourceSpan);
|
||||||
|
|
||||||
const children = html.visitAll(
|
const children = html.visitAll(
|
||||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||||
ElementContext.create(
|
ElementContext.create(
|
||||||
@ -541,10 +546,12 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
||||||
const matchedReferences = new Set<string>();
|
const matchedReferences = new Set<string>();
|
||||||
let component: CompileDirectiveSummary = null;
|
let component: CompileDirectiveSummary = null;
|
||||||
|
|
||||||
const directiveAsts = directives.map((directive) => {
|
const directiveAsts = directives.map((directive) => {
|
||||||
const sourceSpan = new ParseSourceSpan(
|
const sourceSpan = new ParseSourceSpan(
|
||||||
elementSourceSpan.start, elementSourceSpan.end,
|
elementSourceSpan.start, elementSourceSpan.end,
|
||||||
`Directive ${identifierName(directive.type)}`);
|
`Directive ${identifierName(directive.type)}`);
|
||||||
|
|
||||||
if (directive.isComponent) {
|
if (directive.isComponent) {
|
||||||
component = directive;
|
component = directive;
|
||||||
}
|
}
|
||||||
@ -567,6 +574,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
return new DirectiveAst(
|
return new DirectiveAst(
|
||||||
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
|
||||||
});
|
});
|
||||||
|
|
||||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||||
if (elOrDirRef.value.length > 0) {
|
if (elOrDirRef.value.length > 0) {
|
||||||
if (!matchedReferences.has(elOrDirRef.name)) {
|
if (!matchedReferences.has(elOrDirRef.name)) {
|
||||||
@ -581,7 +589,7 @@ class TemplateParseVisitor implements html.Visitor {
|
|||||||
}
|
}
|
||||||
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
|
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
|
||||||
}
|
}
|
||||||
}); // fix syntax highlighting issue: `
|
});
|
||||||
return directiveAsts;
|
return directiveAsts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -742,7 +750,7 @@ class NonBindableVisitor implements html.Visitor {
|
|||||||
return null;
|
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 selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||||
@ -811,16 +819,16 @@ class ElementContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createElementCssSelector(
|
export function createElementCssSelector(
|
||||||
elementName: string, matchableAttrs: string[][]): CssSelector {
|
elementName: string, attributes: [string, string][]): CssSelector {
|
||||||
const cssSelector = new CssSelector();
|
const cssSelector = new CssSelector();
|
||||||
const elNameNoNs = splitNsName(elementName)[1];
|
const elNameNoNs = splitNsName(elementName)[1];
|
||||||
|
|
||||||
cssSelector.setElement(elNameNoNs);
|
cssSelector.setElement(elNameNoNs);
|
||||||
|
|
||||||
for (let i = 0; i < matchableAttrs.length; i++) {
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
const attrName = matchableAttrs[i][0];
|
const attrName = attributes[i][0];
|
||||||
const attrNameNoNs = splitNsName(attrName)[1];
|
const attrNameNoNs = splitNsName(attrName)[1];
|
||||||
const attrValue = matchableAttrs[i][1];
|
const attrValue = attributes[i][1];
|
||||||
|
|
||||||
cssSelector.addAttribute(attrNameNoNs, attrValue);
|
cssSelector.addAttribute(attrNameNoNs, attrValue);
|
||||||
if (attrName.toLowerCase() == CLASS_ATTR) {
|
if (attrName.toLowerCase() == CLASS_ATTR) {
|
||||||
|
@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
|
|||||||
/**
|
/**
|
||||||
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
|
* 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,
|
provide: PACKAGE_ROOT_URL,
|
||||||
useValue: '/'
|
useValue: '/'
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
|
|||||||
import {CompileMethod} from './compile_method';
|
import {CompileMethod} from './compile_method';
|
||||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||||
import {CompileView, CompileViewRootNode} from './compile_view';
|
import {CompileView, CompileViewRootNode} from './compile_view';
|
||||||
import {InjectMethodVars, ViewProperties} from './constants';
|
import {InjectMethodVars} from './constants';
|
||||||
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
|
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
|
||||||
import {getPropertyInView, injectFromViewParentInjector} from './util';
|
import {getPropertyInView, injectFromViewParentInjector} from './util';
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
|
|||||||
const propName =
|
const propName =
|
||||||
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
|
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
|
||||||
const instance = createProviderProperty(
|
const instance = createProviderProperty(
|
||||||
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
|
propName, providerValueExpressions, resolvedProvider.multiProvider,
|
||||||
resolvedProvider.eager, this);
|
resolvedProvider.eager, this);
|
||||||
if (isDirectiveWrapper) {
|
if (isDirectiveWrapper) {
|
||||||
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
|
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)));
|
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
|
||||||
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
|
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 => {
|
Object.keys(this.referenceTokens).forEach(varName => {
|
||||||
const token = this.referenceTokens[varName];
|
const token = this.referenceTokens[varName];
|
||||||
let varValue: o.Expression;
|
let varValue: o.Expression;
|
||||||
@ -226,27 +221,6 @@ export class CompileElement extends CompileNode {
|
|||||||
varValue = this.renderNode;
|
varValue = this.renderNode;
|
||||||
}
|
}
|
||||||
this.view.locals.set(varName, varValue);
|
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.view.injectorGetMethod.addStmt(createInjectInternalCondition(
|
||||||
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
Array.from(this._queries.values())
|
Array.from(this._queries.values())
|
||||||
.forEach(
|
.forEach(
|
||||||
queries => queries.forEach(
|
queries => queries.forEach(
|
||||||
q =>
|
q => q.generateStatements(
|
||||||
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod)));
|
this.view.createMethod, this.view.updateContentQueriesMethod)));
|
||||||
}
|
}
|
||||||
|
|
||||||
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
|
||||||
@ -283,12 +259,11 @@ export class CompileElement extends CompileNode {
|
|||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderTokens(): o.Expression[] {
|
getProviderTokens(): CompileTokenMetadata[] {
|
||||||
return Array.from(this._resolvedProviders.values())
|
return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
|
||||||
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
|
||||||
const result: CompileQuery[] = [];
|
const result: CompileQuery[] = [];
|
||||||
let currentEl: CompileElement = this;
|
let currentEl: CompileElement = this;
|
||||||
let distance = 0;
|
let distance = 0;
|
||||||
@ -314,7 +289,7 @@ export class CompileElement extends CompileNode {
|
|||||||
CompileQuery {
|
CompileQuery {
|
||||||
const propName =
|
const propName =
|
||||||
`_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`;
|
`_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);
|
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
|
||||||
addQueryToTokenMap(this._queries, query);
|
addQueryToTokenMap(this._queries, query);
|
||||||
return query;
|
return query;
|
||||||
@ -394,8 +369,8 @@ function createInjectInternalCondition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createProviderProperty(
|
function createProviderProperty(
|
||||||
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[],
|
propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
|
||||||
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression {
|
compileElement: CompileElement): o.Expression {
|
||||||
const view = compileElement.view;
|
const view = compileElement.view;
|
||||||
let resolvedProviderValueExpr: o.Expression;
|
let resolvedProviderValueExpr: o.Expression;
|
||||||
let type: o.Type;
|
let type: o.Type;
|
||||||
@ -426,10 +401,3 @@ function createProviderProperty(
|
|||||||
}
|
}
|
||||||
return o.THIS_EXPR.prop(propName);
|
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {isPresent} from '../facade/lang';
|
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {TemplateAst} from '../template_parser/template_ast';
|
import {TemplateAst} from '../template_parser/template_ast';
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ export class CompileMethod {
|
|||||||
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
|
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
|
||||||
this._newState.sourceAst !== this._currState.sourceAst) {
|
this._newState.sourceAst !== this._currState.sourceAst) {
|
||||||
const expr = this._updateDebugContext(this._newState);
|
const expr = this._updateDebugContext(this._newState);
|
||||||
if (isPresent(expr)) {
|
if (expr) {
|
||||||
this._bodyStatements.push(expr.toStmt());
|
this._bodyStatements.push(expr.toStmt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,13 +42,12 @@ export class CompileMethod {
|
|||||||
private _updateDebugContext(newState: _DebugState): o.Expression {
|
private _updateDebugContext(newState: _DebugState): o.Expression {
|
||||||
this._currState = this._newState = newState;
|
this._currState = this._newState = newState;
|
||||||
if (this._debugEnabled) {
|
if (this._debugEnabled) {
|
||||||
const sourceLocation =
|
const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
|
||||||
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
|
|
||||||
|
|
||||||
return o.THIS_EXPR.callMethod('debug', [
|
return o.THIS_EXPR.callMethod('debug', [
|
||||||
o.literal(newState.nodeIndex),
|
o.literal(newState.nodeIndex),
|
||||||
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
||||||
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
|
import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
|
||||||
import {ListWrapper} from '../facade/collection';
|
import {ListWrapper} from '../facade/collection';
|
||||||
import {isPresent} from '../facade/lang';
|
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
import {Identifiers, createIdentifier} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ export class CompileQuery {
|
|||||||
addValue(value: o.Expression, view: CompileView) {
|
addValue(value: o.Expression, view: CompileView) {
|
||||||
let currentView = view;
|
let currentView = view;
|
||||||
const elPath: CompileElement[] = [];
|
const elPath: CompileElement[] = [];
|
||||||
while (isPresent(currentView) && currentView !== this.view) {
|
while (currentView && currentView !== this.view) {
|
||||||
const parentEl = currentView.declarationElement;
|
const parentEl = currentView.declarationElement;
|
||||||
elPath.unshift(parentEl);
|
elPath.unshift(parentEl);
|
||||||
currentView = parentEl.view;
|
currentView = parentEl.view;
|
||||||
@ -64,10 +63,10 @@ export class CompileQuery {
|
|||||||
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
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 values = createQueryValues(this._values);
|
||||||
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
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;
|
const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
|
||||||
updateStmts.push(
|
updateStmts.push(
|
||||||
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
|
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
|
||||||
@ -110,9 +109,7 @@ function mapNestedViews(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createQueryList(
|
export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
|
||||||
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
|
|
||||||
compileView: CompileView): o.Expression {
|
|
||||||
compileView.fields.push(new o.ClassField(
|
compileView.fields.push(new o.ClassField(
|
||||||
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
|
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
|
||||||
const expr = o.THIS_EXPR.prop(propertyName);
|
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 {createPureProxy} from '../compiler_util/identifier_util';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {Identifiers, createIdentifier} from '../identifiers';
|
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ViewType} from '../private_import_core';
|
import {ViewType} from '../private_import_core';
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ export class CompileView implements NameResolver {
|
|||||||
const directiveInstance = o.THIS_EXPR.prop('context');
|
const directiveInstance = o.THIS_EXPR.prop('context');
|
||||||
this.component.viewQueries.forEach((queryMeta, queryIndex) => {
|
this.component.viewQueries.forEach((queryMeta, queryIndex) => {
|
||||||
const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${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);
|
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
|
||||||
addQueryToTokenMap(viewQueries, query);
|
addQueryToTokenMap(viewQueries, query);
|
||||||
});
|
});
|
||||||
@ -154,11 +153,11 @@ export class CompileView implements NameResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterNodes() {
|
finish() {
|
||||||
Array.from(this.viewQueries.values())
|
Array.from(this.viewQueries.values())
|
||||||
.forEach(
|
.forEach(
|
||||||
queries => queries.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 {createEnumExpression} from '../compiler_util/identifier_util';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
|
import {ViewType} from '../private_import_core';
|
||||||
|
|
||||||
export class ViewTypeEnum {
|
export class ViewTypeEnum {
|
||||||
static fromValue(value: ViewType): o.Expression {
|
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 {
|
export class ChangeDetectorStatusEnum {
|
||||||
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
|
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
|
||||||
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
|
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
|
||||||
|
@ -15,7 +15,6 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
|||||||
|
|
||||||
import {CompileElement} from './compile_element';
|
import {CompileElement} from './compile_element';
|
||||||
import {CompileMethod} from './compile_method';
|
import {CompileMethod} from './compile_method';
|
||||||
import {ViewProperties} from './constants';
|
|
||||||
import {getHandleEventMethodName} from './util';
|
import {getHandleEventMethodName} from './util';
|
||||||
|
|
||||||
export function bindOutputs(
|
export function bindOutputs(
|
||||||
@ -133,4 +132,4 @@ type EventSummary = {
|
|||||||
name: string,
|
name: string,
|
||||||
target: string,
|
target: string,
|
||||||
phase: 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 {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
|
||||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
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 * as o from '../output/output_ast';
|
||||||
import {ViewType} from '../private_import_core';
|
import {ViewType} from '../private_import_core';
|
||||||
|
|
||||||
@ -23,7 +21,7 @@ export function getPropertyInView(
|
|||||||
} else {
|
} else {
|
||||||
let viewProp: o.Expression = o.THIS_EXPR;
|
let viewProp: o.Expression = o.THIS_EXPR;
|
||||||
let currView: CompileView = callingView;
|
let currView: CompileView = callingView;
|
||||||
while (currView !== definedView && isPresent(currView.declarationElement.view)) {
|
while (currView !== definedView && currView.declarationElement.view) {
|
||||||
currView = currView.declarationElement.view;
|
currView = currView.declarationElement.view;
|
||||||
viewProp = viewProp.prop('parentView');
|
viewProp = viewProp.prop('parentView');
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
|
|||||||
import {bindOutputs} from './event_binder';
|
import {bindOutputs} from './event_binder';
|
||||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||||
|
import {bindQueryValues} from './query_binder';
|
||||||
|
|
||||||
export function bindView(
|
export function bindView(
|
||||||
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
||||||
@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||||||
|
|
||||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||||
|
bindQueryValues(compileElement);
|
||||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||||
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
||||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||||
@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||||||
|
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||||
|
bindQueryValues(compileElement);
|
||||||
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
bindOutputs(ast.outputs, ast.directives, compileElement, false);
|
||||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||||
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
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 {Identifiers, createIdentifier, identifierToken} from '../identifiers';
|
||||||
import {createClassStmt} from '../output/class_builder';
|
import {createClassStmt} from '../output/class_builder';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {ParseSourceSpan} from '../parse_util';
|
|
||||||
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
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';
|
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[]) {
|
export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
||||||
view.afterNodes();
|
|
||||||
createViewTopLevelStmts(view, targetStatements);
|
|
||||||
view.nodes.forEach((node) => {
|
view.nodes.forEach((node) => {
|
||||||
if (node instanceof CompileElement && node.hasEmbeddedView) {
|
if (node instanceof CompileElement) {
|
||||||
finishView(node.embeddedView, targetStatements);
|
node.finish();
|
||||||
|
if (node.hasEmbeddedView) {
|
||||||
|
finishView(node.embeddedView, targetStatements);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
view.finish();
|
||||||
|
createViewTopLevelStmts(view, targetStatements);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||||
@ -402,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
|||||||
o.literal(view.component.template.ngContentSelectors.length),
|
o.literal(view.component.template.ngContentSelectors.length),
|
||||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||||
view.styles,
|
view.styles,
|
||||||
o.literalMap(view.animations.map(
|
o.literalMap(
|
||||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||||
|
null, true),
|
||||||
]))
|
]))
|
||||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||||
}
|
}
|
||||||
@ -418,7 +421,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
|
|||||||
let componentToken: o.Expression = o.NULL_EXPR;
|
let componentToken: o.Expression = o.NULL_EXPR;
|
||||||
const varTokenEntries: any[] = [];
|
const varTokenEntries: any[] = [];
|
||||||
if (isPresent(compileElement)) {
|
if (isPresent(compileElement)) {
|
||||||
providerTokens = compileElement.getProviderTokens();
|
providerTokens =
|
||||||
|
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
|
||||||
|
|
||||||
if (isPresent(compileElement.component)) {
|
if (isPresent(compileElement.component)) {
|
||||||
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
|
||||||
}
|
}
|
||||||
@ -690,7 +695,6 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
|
|||||||
view.nodes.forEach((node) => {
|
view.nodes.forEach((node) => {
|
||||||
if (node instanceof CompileElement) {
|
if (node instanceof CompileElement) {
|
||||||
if (node.embeddedView) {
|
if (node.embeddedView) {
|
||||||
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
|
|
||||||
stmts.push(new o.IfStmt(
|
stmts.push(new o.IfStmt(
|
||||||
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
||||||
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([
|
[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
|
* 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 {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||||
|
|
||||||
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
|
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'));
|
'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', () => {
|
it('should record data about the error in the exception', () => {
|
||||||
let threw = false;
|
let threw = false;
|
||||||
try {
|
try {
|
||||||
@ -449,6 +463,40 @@ describe('StaticReflector', () => {
|
|||||||
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
|
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', () => {
|
describe('inheritance', () => {
|
||||||
class ClassDecorator {
|
class ClassDecorator {
|
||||||
constructor(public value: any) {}
|
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>`;
|
const HTML = `<div>before<p i18n="m|d">foo</p><!-- comment --></div>`;
|
||||||
expect(fakeTranslate(HTML)).toEqual('<div>before<p>**foo**</p></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', () => {
|
describe('blocks', () => {
|
||||||
@ -381,6 +399,25 @@ export function main() {
|
|||||||
const HTML = `<p i18n-title="m|d" title=""></p>`;
|
const HTML = `<p i18n-title="m|d" title=""></p>`;
|
||||||
expect(fakeTranslate(HTML)).toEqual('<p 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 translations = new TranslationBundle(i18nMsgMap, digest);
|
||||||
|
|
||||||
const translatedNodes =
|
const output = mergeTranslations(
|
||||||
mergeTranslations(
|
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs);
|
||||||
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs)
|
expect(output.errors).toEqual([]);
|
||||||
.rootNodes;
|
|
||||||
|
|
||||||
return serializeHtmlNodes(translatedNodes).join('');
|
return serializeHtmlNodes(output.rootNodes).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function extract(
|
function extract(
|
||||||
|
@ -141,12 +141,22 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
|||||||
<!-- /i18n -->
|
<!-- /i18n -->
|
||||||
|
|
||||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
<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 {
|
class I18nComponent {
|
||||||
count: number;
|
count: number;
|
||||||
sex: string;
|
sex: string;
|
||||||
sexB: string;
|
sexB: string;
|
||||||
|
response: any = {getItemsList: (): any[] => []};
|
||||||
}
|
}
|
||||||
|
|
||||||
class FrLocalization extends NgLocalization {
|
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>
|
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||||
</translation>
|
</translation>
|
||||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</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>`;
|
</translationbundle>`;
|
||||||
|
|
||||||
// unused, for reference only
|
// unused, for reference only
|
||||||
@ -197,18 +213,76 @@ const XMB = `
|
|||||||
<msg id="8670732454866344690">on translatable node</msg>
|
<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="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">
|
<msg id="1746565782635215">
|
||||||
<ph name="ICU"/>
|
<ph name="ICU"><ex>ICU</ex></ph>
|
||||||
</msg>
|
</msg>
|
||||||
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
||||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg>
|
<msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg>
|
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg>
|
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
|
||||||
<msg id="7685649297917455806">in a translatable section</msg>
|
<msg id="7685649297917455806">in a translatable section</msg>
|
||||||
<msg id="2387287228265107305">
|
<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_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"><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>
|
||||||
<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>
|
<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 {
|
write(messages: i18n.Message[]): string {
|
||||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||||
.join('//');
|
.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>
|
<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>
|
<note priority="1" from="description">ph names</note>
|
||||||
</trans-unit>
|
</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>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
@ -88,6 +93,7 @@ export function main(): void {
|
|||||||
|
|
||||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||||
const i18nNodesByMsgId = serializer.load(xliff, 'url');
|
const i18nNodesByMsgId = serializer.load(xliff, 'url');
|
||||||
|
|
||||||
const msgMap: {[id: string]: string} = {};
|
const msgMap: {[id: string]: string} = {};
|
||||||
Object.keys(i18nNodesByMsgId)
|
Object.keys(i18nNodesByMsgId)
|
||||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||||
@ -109,6 +115,7 @@ export function main(): void {
|
|||||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
'<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
|
runtime: BaseError
|
||||||
};
|
};
|
||||||
|
|
||||||
export var codegenExportsVars = [
|
export const codegenExportsVars = [
|
||||||
'getExpressions',
|
'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.CommentStmt('This is a comment'),
|
||||||
|
|
||||||
new o.ClassStmt(
|
new o.ClassStmt(
|
||||||
|
@ -30,14 +30,14 @@ export function main() {
|
|||||||
it('should select by element name case sensitive', () => {
|
it('should select by element name case sensitive', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector))
|
expect(matcher.match(getSelectorFor({tag: 'SOMEOTHERTAG'}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(false);
|
expect(matcher.match(getSelectorFor({tag: 'SOMETAG'}), selectableCollector)).toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('someTag')[0], selectableCollector)).toEqual(true);
|
expect(matcher.match(getSelectorFor({tag: 'someTag'}), selectableCollector)).toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,21 +45,22 @@ export function main() {
|
|||||||
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
|
||||||
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
|
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector))
|
expect(matcher.match(getSelectorFor({classes: 'SOMEOTHERCLASS'}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true);
|
expect(matcher.match(getSelectorFor({classes: 'SOMECLASS'}), selectableCollector))
|
||||||
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector))
|
expect(matcher.match(getSelectorFor({classes: 'someClass class2'}), selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw for class name "constructor"', () => {
|
it('should not throw for class name "constructor"', () => {
|
||||||
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
|
expect(matcher.match(getSelectorFor({classes: 'constructor'}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
});
|
});
|
||||||
@ -68,36 +69,43 @@ export function main() {
|
|||||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||||
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector))
|
expect(matcher.match(getSelectorFor({attrs: [['SOMEOTHERATTR', '']]}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(false);
|
expect(matcher.match(getSelectorFor({attrs: [['SOMEATTR', '']]}), selectableCollector))
|
||||||
expect(matched).toEqual([]);
|
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector))
|
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector))
|
expect(
|
||||||
|
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'someValue']]}), selectableCollector))
|
||||||
|
.toEqual(false);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
matcher.match(
|
||||||
|
getSelectorFor({attrs: [['someAttr', ''], ['someAttr2', '']]}), selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(
|
expect(matcher.match(
|
||||||
CssSelector.parse('[someAttr=someValue][someAttr2]')[0], selectableCollector))
|
getSelectorFor({attrs: [['someAttr', 'someValue'], ['someAttr2', '']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(
|
expect(matcher.match(
|
||||||
CssSelector.parse('[someAttr2][someAttr=someValue]')[0], selectableCollector))
|
getSelectorFor({attrs: [['someAttr2', ''], ['someAttr', 'someValue']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(
|
expect(matcher.match(
|
||||||
CssSelector.parse('[someAttr2=someValue][someAttr]')[0], selectableCollector))
|
getSelectorFor({attrs: [['someAttr2', 'someValue'], ['someAttr', '']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||||
});
|
});
|
||||||
@ -105,11 +113,13 @@ export function main() {
|
|||||||
it('should support "." in attribute names', () => {
|
it('should support "." in attribute names', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false);
|
expect(matcher.match(getSelectorFor({attrs: [['barfoo', '']]}), selectableCollector))
|
||||||
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true);
|
expect(matcher.match(getSelectorFor({attrs: [['foo.bar', '']]}), selectableCollector))
|
||||||
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,15 +137,18 @@ export function main() {
|
|||||||
it('should select by attr name case sensitive and value case insensitive', () => {
|
it('should select by attr name case sensitive and value case insensitive', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector))
|
expect(matcher.match(
|
||||||
|
getSelectorFor({attrs: [['SOMEATTR', 'SOMEOTHERATTR']]}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector))
|
expect(
|
||||||
|
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'SOMEVALUE']]}), selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('[someAttr=SOMEVALUE]')[0], selectableCollector))
|
expect(
|
||||||
|
matcher.match(getSelectorFor({attrs: [['someAttr', 'SOMEVALUE']]}), selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
});
|
});
|
||||||
@ -143,31 +156,38 @@ export function main() {
|
|||||||
it('should select by element name, class name and attribute name with value', () => {
|
it('should select by element name, class name and attribute name with value', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
matcher.match(
|
||||||
|
getSelectorFor(
|
||||||
|
{tag: 'someOtherTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
|
||||||
|
selectableCollector))
|
||||||
|
.toEqual(false);
|
||||||
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(
|
expect(matcher.match(
|
||||||
CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0],
|
getSelectorFor(
|
||||||
|
{tag: 'someTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
|
||||||
selectableCollector))
|
selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(
|
expect(matcher.match(
|
||||||
matcher.match(
|
getSelectorFor(
|
||||||
CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector))
|
{tag: 'someTag', classes: 'someClass', attrs: [['someOtherAttr', '']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(matcher.match(
|
expect(matcher.match(
|
||||||
CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector))
|
getSelectorFor({tag: 'someTag', classes: 'someClass', attrs: [['someAttr', '']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
|
|
||||||
expect(
|
expect(matcher.match(
|
||||||
matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector))
|
getSelectorFor(
|
||||||
.toEqual(false);
|
{tag: 'someTag', classes: 'someClass', attrs: [['someAttr', 'someValue']]}),
|
||||||
expect(matched).toEqual([]);
|
selectableCollector))
|
||||||
|
|
||||||
expect(
|
|
||||||
matcher.match(
|
|
||||||
CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector))
|
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
});
|
});
|
||||||
@ -217,7 +237,9 @@ export function main() {
|
|||||||
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
|
matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
|
||||||
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
|
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector))
|
expect(matcher.match(
|
||||||
|
getSelectorFor({tag: 'p', classes: 'someClass', attrs: [['someAttr', '']]}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(false);
|
.toEqual(false);
|
||||||
expect(matched).toEqual([]);
|
expect(matched).toEqual([]);
|
||||||
});
|
});
|
||||||
@ -228,32 +250,38 @@ export function main() {
|
|||||||
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
|
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
|
||||||
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
|
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
|
||||||
|
|
||||||
expect(matcher.match(
|
expect(
|
||||||
CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector))
|
matcher.match(
|
||||||
|
getSelectorFor({tag: 'p', attrs: [['someOtherAttr', '']], classes: 'someOtherClass'}),
|
||||||
|
selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match * with :not selector', () => {
|
it('should match * with :not selector', () => {
|
||||||
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
|
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
|
||||||
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true);
|
expect(matcher.match(getSelectorFor({tag: 'div'}), () => {})).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match with multiple :not selectors', () => {
|
it('should match with multiple :not selectors', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
|
||||||
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
|
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['a', '']]}), selectableCollector))
|
||||||
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
|
.toBe(false);
|
||||||
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
|
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['b', '']]}), selectableCollector))
|
||||||
|
.toBe(false);
|
||||||
|
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['c', '']]}), selectableCollector))
|
||||||
|
.toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select with one match in a list', () => {
|
it('should select with one match in a list', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true);
|
expect(matcher.match(getSelectorFor({tag: 'textbox'}), selectableCollector)).toEqual(true);
|
||||||
expect(matched).toEqual([s1[1], 1]);
|
expect(matched).toEqual([s1[1], 1]);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector))
|
expect(matcher.match(
|
||||||
|
getSelectorFor({tag: 'input', attrs: [['type', 'text']]}), selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
});
|
});
|
||||||
@ -261,7 +289,8 @@ export function main() {
|
|||||||
it('should not select twice with two matches in a list', () => {
|
it('should not select twice with two matches in a list', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
|
||||||
|
|
||||||
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector))
|
expect(
|
||||||
|
matcher.match(getSelectorFor({tag: 'input', classes: 'someclass'}), selectableCollector))
|
||||||
.toEqual(true);
|
.toEqual(true);
|
||||||
expect(matched.length).toEqual(2);
|
expect(matched.length).toEqual(2);
|
||||||
expect(matched).toEqual([s1[0], 1]);
|
expect(matched).toEqual([s1[0], 1]);
|
||||||
@ -404,3 +433,16 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectorFor(
|
||||||
|
{tag = '', attrs = [], classes = ''}: {tag?: string, attrs?: any[], classes?: string} = {}):
|
||||||
|
CssSelector {
|
||||||
|
const selector = new CssSelector();
|
||||||
|
selector.setElement(tag);
|
||||||
|
|
||||||
|
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
|
||||||
|
|
||||||
|
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
|
||||||
|
|
||||||
|
return selector;
|
||||||
|
}
|
@ -623,6 +623,23 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should parse directive dotted properties', () => {
|
||||||
|
const dirA =
|
||||||
|
CompileDirectiveMetadata
|
||||||
|
.create({
|
||||||
|
selector: '[dot.name]',
|
||||||
|
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
|
||||||
|
inputs: ['localName: dot.name'],
|
||||||
|
})
|
||||||
|
.toSummary();
|
||||||
|
|
||||||
|
expect(humanizeTplAst(parse('<div [dot.name]="expr"></div>', [dirA]))).toEqual([
|
||||||
|
[ElementAst, 'div'],
|
||||||
|
[DirectiveAst, dirA],
|
||||||
|
[BoundDirectivePropertyAst, 'localName', 'expr'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should locate directives in property bindings', () => {
|
it('should locate directives in property bindings', () => {
|
||||||
const dirA =
|
const dirA =
|
||||||
CompileDirectiveMetadata
|
CompileDirectiveMetadata
|
||||||
@ -1244,8 +1261,15 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should parse variables via let ...', () => {
|
it('should parse variables via let ...', () => {
|
||||||
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', [
|
const targetAst = [
|
||||||
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]);
|
[EmbeddedTemplateAst],
|
||||||
|
[VariableAst, 'a', 'b'],
|
||||||
|
[ElementAst, 'div'],
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', []))).toEqual(targetAst);
|
||||||
|
|
||||||
|
expect(humanizeTplAst(parse('<div data-*ngIf="let a=b">', []))).toEqual(targetAst);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('directives', () => {
|
describe('directives', () => {
|
||||||
@ -1299,17 +1323,30 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should work with *... and use the attribute name as property binding name', () => {
|
it('should work with *... and use the attribute name as property binding name', () => {
|
||||||
expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([
|
expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([
|
||||||
[EmbeddedTemplateAst], [DirectiveAst, ngIf],
|
[EmbeddedTemplateAst],
|
||||||
[BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div']
|
[DirectiveAst, ngIf],
|
||||||
|
[BoundDirectivePropertyAst, 'ngIf', 'test'],
|
||||||
|
[ElementAst, 'div'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// https://github.com/angular/angular/issues/13800
|
||||||
|
expect(humanizeTplAst(parse('<div *ngIf="-1">', [ngIf]))).toEqual([
|
||||||
|
[EmbeddedTemplateAst],
|
||||||
|
[DirectiveAst, ngIf],
|
||||||
|
[BoundDirectivePropertyAst, 'ngIf', '0 - 1'],
|
||||||
|
[ElementAst, 'div'],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with *... and empty value', () => {
|
it('should work with *... and empty value', () => {
|
||||||
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
|
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
|
||||||
[EmbeddedTemplateAst], [DirectiveAst, ngIf],
|
[EmbeddedTemplateAst],
|
||||||
[BoundDirectivePropertyAst, 'ngIf', 'null'], [ElementAst, 'div']
|
[DirectiveAst, ngIf],
|
||||||
|
[BoundDirectivePropertyAst, 'ngIf', 'null'],
|
||||||
|
[ElementAst, 'div'],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -108,9 +108,12 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
|
|||||||
provide: COMPILER_OPTIONS,
|
provide: COMPILER_OPTIONS,
|
||||||
useValue: {
|
useValue: {
|
||||||
providers: [
|
providers: [
|
||||||
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver},
|
MockPipeResolver,
|
||||||
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
{provide: PipeResolver, useExisting: MockPipeResolver},
|
||||||
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver}
|
MockDirectiveResolver,
|
||||||
|
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||||
|
MockNgModuleResolver,
|
||||||
|
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
multi: true
|
multi: true
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user