Compare commits
258 Commits
6.1.0-beta
...
6.0.5
Author | SHA1 | Date | |
---|---|---|---|
b91b9efc91 | |||
03718c95ce | |||
0834710c18 | |||
9a9a7de9a4 | |||
ff51691221 | |||
2ad90ab0d3 | |||
c5872e6782 | |||
d20877bf3f | |||
b3089b4963 | |||
880b03101e | |||
451d996414 | |||
ea3669e334 | |||
ea2987c7af | |||
5b823dedcc | |||
80f7c83bdd | |||
53072b10da | |||
30c2f560b1 | |||
18c4976f3b | |||
0139173c7c | |||
f3c9954a68 | |||
e8765352e8 | |||
9e61ad897e | |||
0d81151cbc | |||
70319722a1 | |||
cfa5b6567d | |||
41698bf5fd | |||
23c50e27fc | |||
0ae8ea254a | |||
19deca159b | |||
dc3e8aa0fb | |||
a9222c0ade | |||
ea3127e3f9 | |||
8e30f7b1aa | |||
46d81b48ac | |||
3e51a2cb26 | |||
faf60d9310 | |||
0639cb9de1 | |||
810d025488 | |||
efe49c141a | |||
a3d9878c9d | |||
10213d0ca0 | |||
0e1919c2db | |||
d579b8ce05 | |||
d69ba735ee | |||
2991b1b217 | |||
b18cf21e99 | |||
c17098dae6 | |||
43e3073687 | |||
f28878f92f | |||
a10bdefa8b | |||
2f377dbcdd | |||
e2e7b4943e | |||
b8a1363bb2 | |||
e9e6a58dd0 | |||
5932a79713 | |||
7c6a082afd | |||
8a2fe5b7c9 | |||
c9eb4910eb | |||
a634a5abbc | |||
41225026e4 | |||
e9f2203347 | |||
906b3ec8e7 | |||
1cb0c4644a | |||
9d28a27215 | |||
f04aef48f2 | |||
d249852f4a | |||
707dd59767 | |||
4d2570576a | |||
76e58e6cca | |||
a27066b9d2 | |||
29dfc8f3ac | |||
b5533e0ee5 | |||
b275d378df | |||
96655f7aac | |||
253f509493 | |||
5a02ae2f84 | |||
f860752902 | |||
efa126f157 | |||
715135b117 | |||
c30fc52d99 | |||
434eb971e4 | |||
6e31e22d41 | |||
8fef926cd2 | |||
7698afedb1 | |||
a583d12660 | |||
a867d71ece | |||
84b43daf65 | |||
9b3423b50d | |||
d6041d83ec | |||
a638f504eb | |||
a29c756251 | |||
f206eb94bb | |||
3d6e82eccd | |||
154289305e | |||
469b1e4a9a | |||
7fee1fd442 | |||
0ee5b7efa5 | |||
afff84c03f | |||
23f2a7069f | |||
42260702f7 | |||
eec231d21e | |||
95344d6039 | |||
1c9839e91d | |||
bc1a66e907 | |||
2a83dbb0d8 | |||
4007d00403 | |||
61e32ac3c4 | |||
43ee10fbbd | |||
eb90039ea1 | |||
5d318ff234 | |||
f3361abdd7 | |||
52cbe894e9 | |||
41c2030534 | |||
daee41a40a | |||
68bd45ba87 | |||
0f6407750b | |||
95fca24fd8 | |||
a8f6542115 | |||
c6b618d020 | |||
ad6052e397 | |||
e9d1709156 | |||
b5d3de50cc | |||
734d37b231 | |||
bc2063807c | |||
2a528fcb15 | |||
752b83ac81 | |||
56be3375ec | |||
1b83b3fb15 | |||
14d4625ede | |||
fd880a8de4 | |||
bd3dddce4b | |||
1336a9451f | |||
d280077412 | |||
2b578f5c61 | |||
12dcb313af | |||
f576851ecc | |||
ca6cb66c32 | |||
484233f6c1 | |||
3d8799b3a2 | |||
8733843c11 | |||
f1097914c5 | |||
06776d1d10 | |||
2b31b6dc3f | |||
2254ac23e4 | |||
38c678fdcd | |||
1a655836cb | |||
2e466f4bea | |||
7b06fa88ab | |||
3d712894ae | |||
75b8edae03 | |||
fe7f48c1d5 | |||
29600cbb19 | |||
5581e97d2a | |||
19262d9f90 | |||
1eb1c6315d | |||
3807599a4d | |||
2ed41d9e38 | |||
5eb9c01bb6 | |||
09d9662386 | |||
844cbd9774 | |||
373a47dda9 | |||
83f12f3047 | |||
bbc416cdcd | |||
07f2098655 | |||
9b53a6e779 | |||
65f8505fb6 | |||
5a5ea45c40 | |||
52a3657b48 | |||
3824e3f858 | |||
05aa5e0179 | |||
4ddeb030e7 | |||
afe6380429 | |||
947ea17a09 | |||
a190c45a64 | |||
902781803f | |||
0d480ac0dc | |||
6934bf4863 | |||
7e7ea33fb0 | |||
5bd8c6887e | |||
d28ab372c8 | |||
d0ccf5f169 | |||
ecde15298a | |||
983e5f2d7e | |||
5fc4299e0a | |||
1823d5dd1c | |||
91d4da0d2f | |||
22eb8e26fc | |||
f6002c1702 | |||
14138f6382 | |||
f11daa2031 | |||
31a435ef5b | |||
3e92b22258 | |||
2e5457c824 | |||
1ab5fba92e | |||
e1e57ddaa7 | |||
ee7cb48877 | |||
a30c57090a | |||
8a49ec4f27 | |||
697b6c040c | |||
4008e36e80 | |||
e47bb52084 | |||
ac2b530f4b | |||
64bf6edf00 | |||
adf6235479 | |||
04c18ac1aa | |||
1b26dd8cdb | |||
f721b06bde | |||
0bc8443e12 | |||
db17231597 | |||
540626a3a6 | |||
391bfcede5 | |||
d8de6488dd | |||
151fb66848 | |||
02424ff0d0 | |||
f0925d9705 | |||
212b806eda | |||
b9431e88fb | |||
7790cfa0d0 | |||
41b5149509 | |||
06f865640d | |||
824f74f27b | |||
5301c43eed | |||
f280d1aef1 | |||
5e741d42a6 | |||
0035d41030 | |||
08f447ceec | |||
8953f123e3 | |||
6274007e3b | |||
ee76be7783 | |||
a8c720bc3a | |||
ac47a3cd93 | |||
041458a3d2 | |||
2cb74b748f | |||
cb0bfe7a43 | |||
8ee11aeaa6 | |||
bf89fcb361 | |||
4f70c5b6f7 | |||
ad3ebec2a5 | |||
3f5d61f2dd | |||
11ada7f78b | |||
0b96bc7456 | |||
fefadadff3 | |||
946057ae29 | |||
d4293aaaaa | |||
e3dcc227f6 | |||
c9230dd90e | |||
234af9ba59 | |||
7204028d3e | |||
f7c55952bf | |||
e38e3bd135 | |||
cd20c01ba1 | |||
12a191ef3f | |||
65e67b3c3a | |||
32e57f6197 | |||
7de69ba29b | |||
44193c0b94 | |||
6db8241ffa | |||
33c594516c |
@ -85,7 +85,7 @@ jobs:
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||
- run: bazel query --output=label //... | xargs bazel test
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
@ -111,42 +111,6 @@ jobs:
|
||||
paths:
|
||||
- "node_modules"
|
||||
- "~/bazel_repository_cache"
|
||||
# Temporary job to test what will happen when we flip the Ivy flag to true
|
||||
test_ivy_jit:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
|
||||
|
||||
test_ivy_aot:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
@ -176,8 +140,6 @@ jobs:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
- packages-dist-ivy-jit
|
||||
- packages-dist-ivy-local
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# They are a separate workflow job so that they can be easily re-run.
|
||||
@ -238,8 +200,6 @@ workflows:
|
||||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
@ -252,8 +212,6 @@ workflows:
|
||||
requires:
|
||||
# Only publish if tests and integration tests pass
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- integration_test
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
|
@ -163,7 +163,7 @@ groups:
|
||||
files:
|
||||
- "packages/compiler/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- chuckjaz #primary
|
||||
- vicb
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
|
77
CHANGELOG.md
77
CHANGELOG.md
@ -1,37 +1,9 @@
|
||||
<a name="6.1.0-beta.1"></a>
|
||||
# [6.1.0-beta.1](https://github.com/angular/angular/compare/6.1.0-beta.0...6.1.0-beta.1) (2018-06-13)
|
||||
<a name="6.0.5"></a>
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0))
|
||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351))
|
||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([8be6892](https://github.com/angular/angular/commit/8be6892)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||
* **ivy:** compute transitive scopes from NgModuleDef only ([#24334](https://github.com/angular/angular/issues/24334)) ([1135563](https://github.com/angular/angular/commit/1135563))
|
||||
* **ivy:** correctly handle queries with embedded views ([#24418](https://github.com/angular/angular/issues/24418)) ([014949f](https://github.com/angular/angular/commit/014949f))
|
||||
* **ivy:** remove debugger statement ([#24480](https://github.com/angular/angular/issues/24480)) ([70ef061](https://github.com/angular/angular/commit/70ef061))
|
||||
* **ivy:** special case [style] and [class] bindings for future use ([#23232](https://github.com/angular/angular/issues/23232)) ([1b253e1](https://github.com/angular/angular/commit/1b253e1))
|
||||
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
|
||||
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7))
|
||||
* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c))
|
||||
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
|
||||
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
|
||||
* **ivy:** a generic visitor which allows prefixing nodes for ngtsc ([#24230](https://github.com/angular/angular/issues/24230)) ([ca79e11](https://github.com/angular/angular/commit/ca79e11))
|
||||
* **ivy:** add support of ApplicationRef.bootstrapModuleFactory ([#23811](https://github.com/angular/angular/issues/23811)) ([e3759f7](https://github.com/angular/angular/commit/e3759f7))
|
||||
* **ivy:** namespaced attributes added to output instructions ([#24386](https://github.com/angular/angular/issues/24386)) ([82c5313](https://github.com/angular/angular/commit/82c5313))
|
||||
* **ivy:** now supports SVG and MathML elements ([#24377](https://github.com/angular/angular/issues/24377)) ([8c1ac28](https://github.com/angular/angular/commit/8c1ac28))
|
||||
* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595)
|
||||
|
||||
|
||||
|
||||
<a name="6.0.5"></a>
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
|
||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
|
||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||
@ -40,51 +12,6 @@
|
||||
|
||||
|
||||
|
||||
<a name="6.1.0-beta.0"></a>
|
||||
## [6.1.0-beta.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0-beta.0) (2018-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687))
|
||||
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e))
|
||||
* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25))
|
||||
* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4))
|
||||
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d))
|
||||
* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41))
|
||||
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
|
||||
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
|
||||
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
|
||||
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
|
||||
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
|
||||
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
|
||||
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
|
||||
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36))
|
||||
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
|
||||
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8))
|
||||
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
|
||||
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
|
||||
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
|
||||
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
|
||||
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
|
||||
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526)
|
||||
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e))
|
||||
* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67))
|
||||
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
|
||||
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
|
||||
* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb))
|
||||
* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202)
|
||||
|
||||
|
||||
<a name="6.0.4"></a>
|
||||
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
# Supported Public API Surface of Angular
|
||||
|
||||
Our semver, timed-release cycle and deprecation policy currently applies to these npm packages:
|
||||
Our SemVer, timed-release cycle and deprecation policy currently applies to these npm packages:
|
||||
|
||||
- `@angular/animations`
|
||||
- `@angular/core`
|
||||
- `@angular/common`
|
||||
- `@angular/elements`
|
||||
- `@angular/forms`
|
||||
- `@angular/http`
|
||||
- `@angular/platform-browser`
|
||||
- `@angular/platform-browser-dynamic`
|
||||
- `@angular/platform-server`
|
||||
@ -15,13 +12,12 @@ Our semver, timed-release cycle and deprecation policy currently applies to thes
|
||||
- `@angular/platform-webworker-dynamic`
|
||||
- `@angular/upgrade`
|
||||
- `@angular/router`
|
||||
- `@angular/service-worker`
|
||||
- `@angular/forms`
|
||||
- `@angular/http`
|
||||
|
||||
|
||||
One intentional omission from this list is `@angular/compiler`, which is currently considered a low level api and is subject to internal changes. These changes will not affect any applications or libraries using the higher-level apis (the command line interface or JIT compilation via `@angular/platform-browser-dynamic`). Only very specific use-cases require direct access to the compiler API (mostly tooling integration for IDEs, linters, etc). If you are working on this kind of integration, please reach out to us first.
|
||||
|
||||
Package `@angular/bazel` is currently an Angular Labs project and not covered by the public API guarantees.
|
||||
|
||||
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, will be covered by these or similar guarantees in the future as they mature.
|
||||
@ -35,7 +31,7 @@ Within the supported packages, we provide guarantees for:
|
||||
|
||||
We explicitly don't consider the following to be our public API surface:
|
||||
|
||||
- any file/import paths within our package except for the `/`, `/testing` and `/bundles/*` and other documented package entry-points.
|
||||
- any file/import paths within our package except for the `/`, `/testing` and `/bundles/*`
|
||||
- constructors of injectable classes (services and directives) - please use DI to obtain instances of these classes
|
||||
- any class members or symbols marked as `private`, or prefixed with underscore (`_`) and [barred latin o](https://en.wikipedia.org/wiki/%C6%9F) (`ɵ`)
|
||||
- extending any of our classes unless the support for this is specifically documented in the API docs
|
||||
|
@ -3,13 +3,13 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod --progress false",
|
||||
"e2e": "ng e2e",
|
||||
"test": "ng test && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
|
||||
"start": "ng serve",
|
||||
"test": "ng test && ng e2e --webdriver-update=false && ng e2e --prod --webdriver-update=false"
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 2.7 can type-check an Angular application",
|
||||
"description": "Assert that users with TypeScript 2.6 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as core from '@angular/core';
|
||||
import * as coreTesting from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as http from '@angular/http';
|
||||
import * as httpTesting from '@angular/http/testing';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
|
||||
export default {
|
||||
compiler,
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
forms,
|
||||
http,
|
||||
httpTesting,
|
||||
platformBrowser,
|
||||
platformBrowserTesting,
|
||||
platformBrowserDynamic,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 2.8 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/http": "file:../../dist/packages-dist/http",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"typescript": "2.8.x",
|
||||
"zone.js": "file:../../node_modules/zone.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tsc"
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../dist/typings_test_ts28/",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.iterable",
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
@ -65,8 +65,6 @@ module.exports = function(config) {
|
||||
'dist/all/@angular/**/*node_only_spec.js',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler-cli/src/ngtsc/**',
|
||||
'dist/all/@angular/compiler-cli/test/ngtsc/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/compiler/test/render3/**',
|
||||
'dist/all/@angular/core/test/bundling/**',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "6.1.0-beta.1",
|
||||
"version": "6.0.5",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -114,7 +114,7 @@
|
||||
"tslint": "5.7.0",
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"tsutils": "2.20.0",
|
||||
"typescript": "2.8.x",
|
||||
"typescript": "2.7.x",
|
||||
"uglify-es": "^3.3.9",
|
||||
"universal-analytics": "0.4.15",
|
||||
"vlq": "0.2.2",
|
||||
|
@ -24,10 +24,7 @@ ng_package(
|
||||
"//packages/animations/browser/testing:package.json",
|
||||
],
|
||||
entry_point = "packages/animations/index.js",
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [
|
||||
":animations",
|
||||
"//packages/animations/browser",
|
||||
|
@ -15,7 +15,12 @@ const DEFAULT_FILL_MODE = 'forwards';
|
||||
const DEFAULT_EASING = 'linear';
|
||||
const ANIMATION_END_EVENT = 'animationend';
|
||||
|
||||
export const enum AnimatorControlState {INITIALIZED = 1, STARTED = 2, FINISHED = 3, DESTROYED = 4}
|
||||
export enum AnimatorControlState {
|
||||
INITIALIZED = 1,
|
||||
STARTED = 2,
|
||||
FINISHED = 3,
|
||||
DESTROYED = 4
|
||||
}
|
||||
|
||||
export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
@ -29,8 +34,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
public readonly totalTime: number;
|
||||
public readonly easing: string;
|
||||
public currentSnapshot: {[key: string]: string} = {};
|
||||
|
||||
private _state: AnimatorControlState = 0;
|
||||
public state = 0;
|
||||
|
||||
constructor(
|
||||
public readonly element: any, public readonly keyframes: {[key: string]: string | number}[],
|
||||
@ -50,8 +54,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
destroy() {
|
||||
this.init();
|
||||
if (this._state >= AnimatorControlState.DESTROYED) return;
|
||||
this._state = AnimatorControlState.DESTROYED;
|
||||
if (this.state >= AnimatorControlState.DESTROYED) return;
|
||||
this.state = AnimatorControlState.DESTROYED;
|
||||
this._styler.destroy();
|
||||
this._flushStartFns();
|
||||
this._flushDoneFns();
|
||||
@ -71,8 +75,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
finish() {
|
||||
this.init();
|
||||
if (this._state >= AnimatorControlState.FINISHED) return;
|
||||
this._state = AnimatorControlState.FINISHED;
|
||||
if (this.state >= AnimatorControlState.FINISHED) return;
|
||||
this.state = AnimatorControlState.FINISHED;
|
||||
this._styler.finish();
|
||||
this._flushStartFns();
|
||||
this._flushDoneFns();
|
||||
@ -82,10 +86,10 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
getPosition(): number { return this._styler.getPosition(); }
|
||||
|
||||
hasStarted(): boolean { return this._state >= AnimatorControlState.STARTED; }
|
||||
hasStarted(): boolean { return this.state >= AnimatorControlState.STARTED; }
|
||||
init(): void {
|
||||
if (this._state >= AnimatorControlState.INITIALIZED) return;
|
||||
this._state = AnimatorControlState.INITIALIZED;
|
||||
if (this.state >= AnimatorControlState.INITIALIZED) return;
|
||||
this.state = AnimatorControlState.INITIALIZED;
|
||||
const elm = this.element;
|
||||
this._styler.apply();
|
||||
if (this._delay) {
|
||||
@ -97,7 +101,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this.init();
|
||||
if (!this.hasStarted()) {
|
||||
this._flushStartFns();
|
||||
this._state = AnimatorControlState.STARTED;
|
||||
this.state = AnimatorControlState.STARTED;
|
||||
}
|
||||
this._styler.resume();
|
||||
}
|
||||
@ -133,7 +137,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this.init();
|
||||
const styles: {[key: string]: string} = {};
|
||||
if (this.hasStarted()) {
|
||||
const finished = this._state >= AnimatorControlState.FINISHED;
|
||||
const finished = this.state >= AnimatorControlState.FINISHED;
|
||||
Object.keys(this._finalStyles).forEach(prop => {
|
||||
if (prop != 'offset') {
|
||||
styles[prop] = finished ? this._finalStyles[prop] : computeStyle(this.element, prop);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler-cli": "0.0.0-PLACEHOLDER",
|
||||
"typescript": ">=2.7.2 <2.9"
|
||||
"typescript": ">=2.7.2 <2.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -14,90 +14,6 @@ load(":rules_typescript.bzl",
|
||||
"ts_providers_dict_to_struct",
|
||||
)
|
||||
|
||||
def _compile_strategy(ctx):
|
||||
"""Detect which strategy should be used to implement ng_module.
|
||||
|
||||
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
|
||||
can be implemented in various ways. This function reads the configuration passed by the user and
|
||||
determines which mode is active.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx
|
||||
"""
|
||||
|
||||
strategy = 'legacy'
|
||||
if 'compile' in ctx.var:
|
||||
strategy = ctx.var['compile']
|
||||
|
||||
if strategy not in ['legacy', 'local', 'jit']:
|
||||
fail("Unknown --define=compile value '%s'" % strategy)
|
||||
|
||||
if strategy == 'legacy' and hasattr(ctx.attr, '_global_mode') and ctx.attr._global_mode:
|
||||
strategy = 'global'
|
||||
|
||||
return strategy
|
||||
|
||||
def _compiler_name(ctx):
|
||||
"""Selects a user-visible name depending on the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the name of the current compiler to be displayed in build output
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return 'ngc'
|
||||
elif strategy == 'global':
|
||||
return 'ngc.ivy'
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
|
||||
def _enable_ivy_value(ctx):
|
||||
"""Determines the value of the enableIvy option in the generated tsconfig.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return False
|
||||
elif strategy == 'global':
|
||||
return True
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
|
||||
def _include_ng_files(ctx):
|
||||
"""Determines whether Angular outputs will be produced by the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the current compilation strategy will produce View Engine compilation outputs (such as
|
||||
factory files), false otherwise
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
return strategy == 'legacy' or strategy == 'global'
|
||||
|
||||
def _basename_of(ctx, file):
|
||||
ext_len = len(".ts")
|
||||
if file.short_path.endswith(".ng.html"):
|
||||
@ -145,8 +61,6 @@ def _should_produce_flat_module_outs(ctx):
|
||||
# in the library. Most of these will be produced as empty files but it is
|
||||
# unknown, without parsing, which will be empty.
|
||||
def _expected_outs(ctx):
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
devmode_js_files = []
|
||||
closure_js_files = []
|
||||
declaration_files = []
|
||||
@ -164,7 +78,7 @@ def _expected_outs(ctx):
|
||||
|
||||
if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
|
||||
basename = short_path[len(package_prefix):-len(".ts")]
|
||||
if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set):
|
||||
if len(factory_basename_set) == 0 or basename in factory_basename_set:
|
||||
devmode_js = [
|
||||
".ngfactory.js",
|
||||
".ngsummary.js",
|
||||
@ -176,7 +90,7 @@ def _expected_outs(ctx):
|
||||
devmode_js = [".js"]
|
||||
summaries = []
|
||||
metadata = []
|
||||
elif include_ng_files and short_path.endswith(".css"):
|
||||
elif short_path.endswith(".css"):
|
||||
basename = short_path[len(package_prefix):-len(".css")]
|
||||
devmode_js = [
|
||||
".css.shim.ngstyle.js",
|
||||
@ -199,7 +113,7 @@ def _expected_outs(ctx):
|
||||
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]
|
||||
|
||||
# We do this just when producing a flat module index for a publishable ng_module
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
flat_module_out = _flat_module_out_file(ctx)
|
||||
devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
|
||||
closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out))
|
||||
@ -209,12 +123,7 @@ def _expected_outs(ctx):
|
||||
else:
|
||||
bundle_index_typings = None
|
||||
|
||||
# TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
|
||||
# when ngtsc can extract messages
|
||||
if include_ng_files:
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
else:
|
||||
i18n_messages_files = []
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
|
||||
return struct(
|
||||
closure_js = closure_js_files,
|
||||
@ -226,9 +135,14 @@ def _expected_outs(ctx):
|
||||
i18n_messages = i18n_messages_files,
|
||||
)
|
||||
|
||||
def _ivy_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs)
|
||||
|
||||
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs)
|
||||
|
||||
def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs):
|
||||
outs = _expected_outs(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
if "devmode_manifest" in kwargs:
|
||||
expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
else:
|
||||
@ -238,9 +152,8 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
"enableResourceInlining": ctx.attr.inline_resources,
|
||||
"generateCodeForLibraries": False,
|
||||
"allowEmptyCodegenFiles": True,
|
||||
# Summaries are only enabled if Angular outputs are to be produced.
|
||||
"enableSummariesForJit": include_ng_files,
|
||||
"enableIvy": _enable_ivy_value(ctx),
|
||||
"enableSummariesForJit": True,
|
||||
"enableIvy": enable_ivy,
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||
@ -303,10 +216,8 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
|
||||
the parameters of the compilation which will be used to replay the ngc action for i18N.
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
mnemonic = "AngularTemplateCompile"
|
||||
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
|
||||
progress_message = "Compiling Angular templates (ngc) %s" % label
|
||||
|
||||
if locale:
|
||||
mnemonic = "AngularI18NMerging"
|
||||
@ -340,7 +251,7 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
|
||||
},
|
||||
)
|
||||
|
||||
if include_ng_files and messages_out != None:
|
||||
if messages_out != None:
|
||||
ctx.actions.run(
|
||||
inputs = list(inputs),
|
||||
outputs = messages_out,
|
||||
@ -402,7 +313,7 @@ def _ts_expected_outs(ctx, label):
|
||||
_ignored = [label]
|
||||
return _expected_outs(ctx)
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
def ng_module_impl(ctx, ts_compile_actions, ivy = False):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
This is exposed so that google3 can have its own entry point that re-uses this
|
||||
@ -411,30 +322,29 @@ def ng_module_impl(ctx, ts_compile_actions):
|
||||
Args:
|
||||
ctx: the skylark rule context
|
||||
ts_compile_actions: generates all the actions to run an ngc compilation
|
||||
ivy: if True, run the compiler in Ivy mode (internal only)
|
||||
|
||||
Returns:
|
||||
the result of the ng_module rule as a dict, suitable for
|
||||
conversion by ts_providers_dict_to_struct
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
|
||||
|
||||
providers = ts_compile_actions(
|
||||
ctx, is_library=True, compile_action=_prodmode_compile_action,
|
||||
devmode_compile_action=_devmode_compile_action,
|
||||
tsc_wrapped_tsconfig=_ngc_tsconfig,
|
||||
tsc_wrapped_tsconfig=tsconfig,
|
||||
outputs = _ts_expected_outs)
|
||||
|
||||
outs = _expected_outs(ctx)
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files:
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
if len(outs.metadata) > 1:
|
||||
fail("expecting exactly one metadata output for " + str(ctx.label))
|
||||
|
||||
@ -450,6 +360,9 @@ def ng_module_impl(ctx, ts_compile_actions):
|
||||
def _ng_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
|
||||
|
||||
def _ivy_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True))
|
||||
|
||||
NG_MODULE_ATTRIBUTES = {
|
||||
"srcs": attr.label_list(allow_files = [".ts"]),
|
||||
|
||||
@ -516,16 +429,11 @@ ng_module = rule(
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
||||
|
||||
# TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information.
|
||||
# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used
|
||||
# instead, and should be removed once ngtsc is capable of fulfilling the same requirements.
|
||||
internal_global_ng_module = rule(
|
||||
implementation = _ng_module_impl,
|
||||
attrs = dict(NG_MODULE_RULE_ATTRS, **{
|
||||
"_global_mode": attr.bool(
|
||||
default = True,
|
||||
),
|
||||
}),
|
||||
# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
|
||||
# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
|
||||
# removed and its usages refactored to use ng_module() directly.
|
||||
internal_ivy_ng_module = rule(
|
||||
implementation = _ivy_module_impl,
|
||||
attrs = NG_MODULE_RULE_ATTRS,
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
|
@ -27,29 +27,6 @@ PLUGIN_CONFIG="{sideEffectFreeModules: [\n%s]}" % ",\n".join(
|
||||
BO_ROLLUP="angular_devkit/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js"
|
||||
BO_PLUGIN="require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG)
|
||||
|
||||
def _use_plain_rollup(ctx):
|
||||
"""Determine whether to use the Angular or upstream versions of the rollup_bundle rule.
|
||||
|
||||
In legacy mode, the Angular version of rollup is used. This runs build optimizer as part of its
|
||||
processing, which affects decorators and annotations.
|
||||
|
||||
In other modes, an emulation of the upstream rollup_bundle rule is used. This avoids running
|
||||
build optimizer on code which isn't designed to be optimized by it.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the Angular version of rollup with build optimizer should be used, false otherwise
|
||||
"""
|
||||
|
||||
if 'compile' not in ctx.var:
|
||||
return False
|
||||
|
||||
strategy = ctx.var['compile']
|
||||
return strategy != 'legacy'
|
||||
|
||||
|
||||
def run_brotli(ctx, input, output):
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._brotli,
|
||||
@ -58,41 +35,7 @@ def run_brotli(ctx, input, output):
|
||||
arguments = ["--output=%s" % output.path, input.path],
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs
|
||||
def _run_tsc(ctx, input, output):
|
||||
args = ctx.actions.args()
|
||||
args.add("--target", "es5")
|
||||
args.add("--allowJS")
|
||||
args.add(input)
|
||||
args.add("--outFile", output)
|
||||
|
||||
ctx.action(
|
||||
executable = ctx.executable._tsc,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = [args]
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs, with the addition of brotli compression output
|
||||
def _plain_rollup_bundle(ctx):
|
||||
rollup_config = write_rollup_config(ctx)
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es6)
|
||||
_run_tsc(ctx, ctx.outputs.build_es6, ctx.outputs.build_es5)
|
||||
source_map = run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min)
|
||||
run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True)
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html)
|
||||
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
files = [ctx.outputs.build_es5_min, source_map]
|
||||
return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files))
|
||||
|
||||
def _ng_rollup_bundle(ctx):
|
||||
# Escape and use the plain rollup rule if the compilation strategy requires it
|
||||
if _use_plain_rollup(ctx):
|
||||
return _plain_rollup_bundle(ctx)
|
||||
|
||||
# We don't expect anyone to make use of this bundle yet, but it makes this rule
|
||||
# compatible with rollup_bundle which allows them to be easily swapped back and
|
||||
# forth.
|
||||
|
@ -119,15 +119,6 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
compilerOpts.annotationsAs = 'static fields';
|
||||
}
|
||||
|
||||
// Detect from compilerOpts whether the entrypoint is being invoked in Ivy mode.
|
||||
const isInIvyMode = compilerOpts.enableIvy === 'ngtsc' || compilerOpts.enableIvy === 'tsc';
|
||||
|
||||
// Disable downleveling and Closure annotation if in Ivy mode.
|
||||
if (isInIvyMode) {
|
||||
compilerOpts.annotateForClosureCompiler = false;
|
||||
compilerOpts.annotationsAs = 'decorators';
|
||||
}
|
||||
|
||||
if (!compilerOpts.rootDirs) {
|
||||
throw new Error('rootDirs is not set!');
|
||||
}
|
||||
@ -181,12 +172,6 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
const bazelHost = new CompilerHost(
|
||||
files, compilerOpts, bazelOpts, tsHost, fileLoader, allowNonHermeticReads,
|
||||
generatedFileModuleResolver);
|
||||
|
||||
// Also need to disable decorator downleveling in the BazelHost in Ivy mode.
|
||||
if (isInIvyMode) {
|
||||
bazelHost.transformDecorators = false;
|
||||
}
|
||||
|
||||
// Prevent tsickle adding any types at all if we don't want closure compiler annotations.
|
||||
bazelHost.transformTypesToClosure = compilerOpts.annotateForClosureCompiler;
|
||||
const origBazelHostFileExist = bazelHost.fileExists;
|
||||
|
@ -51,12 +51,12 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
|
||||
gc() { return this._driver.executeScript('window.gc()'); }
|
||||
|
||||
async timeBegin(name: string): Promise<any> {
|
||||
timeBegin(name: string): Promise<any> {
|
||||
if (this._firstRun) {
|
||||
this._firstRun = false;
|
||||
// Before the first run, read out the existing performance logs
|
||||
// so that the chrome buffer does not fill up.
|
||||
await this._driver.logs('performance');
|
||||
this._driver.logs('performance');
|
||||
}
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
}
|
||||
|
@ -27,10 +27,7 @@ ng_package(
|
||||
],
|
||||
entry_point = "packages/common/index.js",
|
||||
packages = ["//packages/common/locales:package"],
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [
|
||||
"//packages/common",
|
||||
"//packages/common/http",
|
||||
|
@ -21,8 +21,7 @@ export {parseCookieValue as ɵparseCookieValue} from './cookie';
|
||||
export {CommonModule, DeprecatedI18NPipesModule} from './common_module';
|
||||
export {NgClass, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
||||
export {DOCUMENT} from './dom_tokens';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||
export {DeprecatedDatePipe, DeprecatedCurrencyPipe, DeprecatedDecimalPipe, DeprecatedPercentPipe} from './pipes/deprecated/index';
|
||||
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
|
||||
export {VERSION} from './version';
|
||||
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
|
||||
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, OnChanges, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
|
||||
|
||||
export class NgForOfContext<T> {
|
||||
constructor(
|
||||
@ -93,12 +93,8 @@ export class NgForOfContext<T> {
|
||||
*
|
||||
*/
|
||||
@Directive({selector: '[ngFor][ngForOf]'})
|
||||
export class NgForOf<T> implements DoCheck {
|
||||
@Input()
|
||||
set ngForOf(ngForOf: NgIterable<T>) {
|
||||
this._ngForOf = ngForOf;
|
||||
this._ngForOfDirty = true;
|
||||
}
|
||||
export class NgForOf<T> implements DoCheck, OnChanges {
|
||||
@Input() ngForOf: NgIterable<T>;
|
||||
@Input()
|
||||
set ngForTrackBy(fn: TrackByFunction<T>) {
|
||||
if (isDevMode() && fn != null && typeof fn !== 'function') {
|
||||
@ -114,8 +110,6 @@ export class NgForOf<T> implements DoCheck {
|
||||
|
||||
get ngForTrackBy(): TrackByFunction<T> { return this._trackByFn; }
|
||||
|
||||
private _ngForOf: NgIterable<T>;
|
||||
private _ngForOfDirty: boolean = true;
|
||||
private _differ: IterableDiffer<T>|null = null;
|
||||
private _trackByFn: TrackByFunction<T>;
|
||||
|
||||
@ -133,11 +127,10 @@ export class NgForOf<T> implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (this._ngForOfDirty) {
|
||||
this._ngForOfDirty = false;
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if ('ngForOf' in changes) {
|
||||
// React on ngForOf changes only once all inputs have been initialized
|
||||
const value = this._ngForOf;
|
||||
const value = changes['ngForOf'].currentValue;
|
||||
if (!this._differ && value) {
|
||||
try {
|
||||
this._differ = this._differs.find(value).create(this.ngForTrackBy);
|
||||
@ -147,8 +140,11 @@ export class NgForOf<T> implements DoCheck {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (this._differ) {
|
||||
const changes = this._differ.diff(this._ngForOf);
|
||||
const changes = this._differ.diff(this.ngForOf);
|
||||
if (changes) this._applyChanges(changes);
|
||||
}
|
||||
}
|
||||
@ -159,7 +155,7 @@ export class NgForOf<T> implements DoCheck {
|
||||
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (item.previousIndex == null) {
|
||||
const view = this._viewContainer.createEmbeddedView(
|
||||
this._template, new NgForOfContext<T>(null !, this._ngForOf, -1, -1), currentIndex);
|
||||
this._template, new NgForOfContext<T>(null !, this.ngForOf, -1, -1), currentIndex);
|
||||
const tuple = new RecordViewTuple<T>(item, view);
|
||||
insertTuples.push(tuple);
|
||||
} else if (currentIndex == null) {
|
||||
|
@ -17,7 +17,6 @@ import {DatePipe} from './date_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {KeyValue, KeyValuePipe} from './keyvalue_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
|
||||
@ -26,8 +25,6 @@ export {
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
KeyValue,
|
||||
KeyValuePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe,
|
||||
JsonPipe,
|
||||
@ -35,7 +32,7 @@ export {
|
||||
PercentPipe,
|
||||
SlicePipe,
|
||||
TitleCasePipe,
|
||||
UpperCasePipe,
|
||||
UpperCasePipe
|
||||
};
|
||||
|
||||
|
||||
@ -55,5 +52,4 @@ export const COMMON_PIPES = [
|
||||
DatePipe,
|
||||
I18nPluralPipe,
|
||||
I18nSelectPipe,
|
||||
KeyValuePipe,
|
||||
];
|
||||
|
@ -1,110 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
function makeKeyValuePair<K, V>(key: K, value: V): KeyValue<K, V> {
|
||||
return {key: key, value: value};
|
||||
}
|
||||
|
||||
/**
|
||||
* A key value pair.
|
||||
* Usually used to represent the key value pairs from a Map or Object.
|
||||
*/
|
||||
export interface KeyValue<K, V> {
|
||||
key: K;
|
||||
value: V;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @description
|
||||
*
|
||||
* Transforms Object or Map into an array of key value pairs.
|
||||
*
|
||||
* The output array will be ordered by keys.
|
||||
* By default the comparator will be by Unicode point value.
|
||||
* You can optionally pass a compareFn if your keys are complex types.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* This examples show how an Object or a Map and be iterated by ngFor with the use of this keyvalue
|
||||
* pipe.
|
||||
*
|
||||
* {@example common/pipes/ts/keyvalue_pipe.ts region='KeyValuePipe'}
|
||||
*/
|
||||
@Pipe({name: 'keyvalue', pure: false})
|
||||
export class KeyValuePipe implements PipeTransform {
|
||||
constructor(private readonly differs: KeyValueDiffers) {}
|
||||
|
||||
private differ: KeyValueDiffer<any, any>;
|
||||
private keyValues: Array<KeyValue<any, any>>;
|
||||
|
||||
transform<K, V>(input: null, compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number): null;
|
||||
transform<V>(
|
||||
input: {[key: string]: V}|Map<string, V>,
|
||||
compareFn?: (a: KeyValue<string, V>, b: KeyValue<string, V>) => number):
|
||||
Array<KeyValue<string, V>>;
|
||||
transform<V>(
|
||||
input: {[key: number]: V}|Map<number, V>,
|
||||
compareFn?: (a: KeyValue<number, V>, b: KeyValue<number, V>) => number):
|
||||
Array<KeyValue<number, V>>;
|
||||
transform<K, V>(input: Map<K, V>, compareFn?: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number):
|
||||
Array<KeyValue<K, V>>;
|
||||
transform<K, V>(
|
||||
input: null|{[key: string]: V, [key: number]: V}|Map<K, V>,
|
||||
compareFn: (a: KeyValue<K, V>, b: KeyValue<K, V>) => number = defaultComparator):
|
||||
Array<KeyValue<K, V>>|null {
|
||||
if (!input || (!(input instanceof Map) && typeof input !== 'object')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.differ) {
|
||||
// make a differ for whatever type we've been passed in
|
||||
this.differ = this.differs.find(input).create();
|
||||
}
|
||||
|
||||
const differChanges: KeyValueChanges<K, V>|null = this.differ.diff(input as any);
|
||||
|
||||
if (differChanges) {
|
||||
this.keyValues = [];
|
||||
differChanges.forEachItem((r: KeyValueChangeRecord<K, V>) => {
|
||||
this.keyValues.push(makeKeyValuePair(r.key, r.currentValue !));
|
||||
});
|
||||
this.keyValues.sort(compareFn);
|
||||
}
|
||||
return this.keyValues;
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultComparator<K, V>(
|
||||
keyValueA: KeyValue<K, V>, keyValueB: KeyValue<K, V>): number {
|
||||
const a = keyValueA.key;
|
||||
const b = keyValueB.key;
|
||||
// if same exit with 0;
|
||||
if (a === b) return 0;
|
||||
// make sure that undefined are at the end of the sort.
|
||||
if (a === undefined) return 1;
|
||||
if (b === undefined) return -1;
|
||||
// make sure that nulls are at the end of the sort.
|
||||
if (a === null) return 1;
|
||||
if (b === null) return -1;
|
||||
if (typeof a == 'string' && typeof b == 'string') {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
if (typeof a == 'number' && typeof b == 'number') {
|
||||
return a - b;
|
||||
}
|
||||
if (typeof a == 'boolean' && typeof b == 'boolean') {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
// `a` and `b` are of different types. Compare their string values.
|
||||
const aString = String(a);
|
||||
const bString = String(b);
|
||||
return aString == bString ? 0 : aString < bString ? -1 : 1;
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {defineInjectable, inject} from '@angular/core';
|
||||
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
|
||||
/**
|
||||
* @whatItDoes Manages the scroll position.
|
||||
*/
|
||||
export abstract class ViewportScroller {
|
||||
// De-sugared tree-shakable injection
|
||||
// See #23917
|
||||
/** @nocollapse */
|
||||
static ngInjectableDef = defineInjectable(
|
||||
{providedIn: 'root', factory: () => new BrowserViewportScroller(inject(DOCUMENT), window)});
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the top offset used when scrolling to an anchor.
|
||||
*
|
||||
* When given a tuple with two number, the service will always use the numbers.
|
||||
* When given a function, the service will invoke the function every time it restores scroll
|
||||
* position.
|
||||
*/
|
||||
abstract setOffset(offset: [number, number]|(() => [number, number])): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Returns the current scroll position.
|
||||
*/
|
||||
abstract getScrollPosition(): [number, number];
|
||||
|
||||
/**
|
||||
* @whatItDoes Sets the scroll position.
|
||||
*/
|
||||
abstract scrollToPosition(position: [number, number]): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Scrolls to the provided anchor.
|
||||
*/
|
||||
abstract scrollToAnchor(anchor: string): void;
|
||||
|
||||
/**
|
||||
* @whatItDoes Disables automatic scroll restoration provided by the browser.
|
||||
* See also [window.history.scrollRestoration
|
||||
* info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration)
|
||||
*/
|
||||
abstract setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Manages the scroll position.
|
||||
*/
|
||||
export class BrowserViewportScroller implements ViewportScroller {
|
||||
private offset: () => [number, number] = () => [0, 0];
|
||||
|
||||
constructor(private document: any, private window: any) {}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the top offset used when scrolling to an anchor.
|
||||
*
|
||||
* * When given a number, the service will always use the number.
|
||||
* * When given a function, the service will invoke the function every time it restores scroll
|
||||
* position.
|
||||
*/
|
||||
setOffset(offset: [number, number]|(() => [number, number])): void {
|
||||
if (Array.isArray(offset)) {
|
||||
this.offset = () => offset;
|
||||
} else {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Returns the current scroll position.
|
||||
*/
|
||||
getScrollPosition(): [number, number] {
|
||||
if (this.supportScrollRestoration()) {
|
||||
return [this.window.scrollX, this.window.scrollY];
|
||||
} else {
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Sets the scroll position.
|
||||
*/
|
||||
scrollToPosition(position: [number, number]): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
this.window.scrollTo(position[0], position[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Scrolls to the provided anchor.
|
||||
*/
|
||||
scrollToAnchor(anchor: string): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
const elSelectedById = this.document.querySelector(`#${anchor}`);
|
||||
if (elSelectedById) {
|
||||
this.scrollToElement(elSelectedById);
|
||||
return;
|
||||
}
|
||||
const elSelectedByName = this.document.querySelector(`[name='${anchor}']`);
|
||||
if (elSelectedByName) {
|
||||
this.scrollToElement(elSelectedByName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Disables automatic scroll restoration provided by the browser.
|
||||
*/
|
||||
setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {
|
||||
if (this.supportScrollRestoration()) {
|
||||
const history = this.window.history;
|
||||
if (history && history.scrollRestoration) {
|
||||
history.scrollRestoration = scrollRestoration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private scrollToElement(el: any): void {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const left = rect.left + this.window.pageXOffset;
|
||||
const top = rect.top + this.window.pageYOffset;
|
||||
const offset = this.offset();
|
||||
this.window.scrollTo(left - offset[0], top - offset[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* We only support scroll restoration when we can get a hold of window.
|
||||
* This means that we do not support this behavior when running in a web worker.
|
||||
*
|
||||
* Lifting this restriction right now would require more changes in the dom adapter.
|
||||
* Since webworkers aren't widely used, we will lift it once RouterScroller is
|
||||
* battle-tested.
|
||||
*/
|
||||
private supportScrollRestoration(): boolean {
|
||||
try {
|
||||
return !!this.window && !!this.window.scrollTo;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Provides an empty implementation of the viewport scroller. This will
|
||||
* live in @angular/common as it will be used by both platform-server and platform-webworker.
|
||||
*/
|
||||
export class NullViewportScroller implements ViewportScroller {
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
setOffset(offset: [number, number]|(() => [number, number])): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
getScrollPosition(): [number, number] { return [0, 0]; }
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
scrollToPosition(position: [number, number]): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
scrollToAnchor(anchor: string): void {}
|
||||
|
||||
/**
|
||||
* @whatItDoes empty implementation
|
||||
*/
|
||||
setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {}
|
||||
}
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {CommonModule, NgForOf} from '@angular/common';
|
||||
import {Component, Directive} 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/src/matchers';
|
||||
|
@ -1,152 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {KeyValuePipe} from '@angular/common';
|
||||
import {EventEmitter, KeyValueDiffers, WrappedValue, ɵdefaultKeyValueDiffers as defaultKeyValueDiffers} from '@angular/core';
|
||||
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/src/browser_util';
|
||||
|
||||
import {defaultComparator} from '../../src/pipes/keyvalue_pipe';
|
||||
import {SpyChangeDetectorRef} from '../spies';
|
||||
|
||||
describe('KeyValuePipe', () => {
|
||||
it('should return null when given null', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(null)).toEqual(null);
|
||||
});
|
||||
it('should return null when given undefined', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(undefined as any)).toEqual(null);
|
||||
});
|
||||
it('should return null for an unsupported type', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const fn = () => {};
|
||||
expect(pipe.transform(fn as any)).toEqual(null);
|
||||
});
|
||||
describe('object dictionary', () => {
|
||||
it('should transform a basic dictionary', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({1: 2})).toEqual([{key: '1', value: 2}]);
|
||||
});
|
||||
it('should order by alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({'b': 1, 'a': 1})).toEqual([
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform({2: 1, 1: 1})).toEqual([{key: '1', value: 1}, {key: '2', value: 1}]);
|
||||
});
|
||||
it('should order by numerical and alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = {2: 1, 1: 1, 'b': 1, 0: 1, 3: 1, 'a': 1};
|
||||
expect(pipe.transform(input)).toEqual([
|
||||
{key: '0', value: 1}, {key: '1', value: 1}, {key: '2', value: 1}, {key: '3', value: 1},
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should return the same ref if nothing changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform({1: 2});
|
||||
const transform2 = pipe.transform({1: 2});
|
||||
expect(transform1 === transform2).toEqual(true);
|
||||
});
|
||||
it('should return a new ref if something changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform({1: 2});
|
||||
const transform2 = pipe.transform({1: 3});
|
||||
expect(transform1 !== transform2).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Map', () => {
|
||||
it('should transform a basic Map', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([[1, 2]]))).toEqual([{key: 1, value: 2}]);
|
||||
});
|
||||
it('should order by alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([['b', 1], ['a', 1]]))).toEqual([
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
expect(pipe.transform(new Map([[2, 1], [1, 1]]))).toEqual([
|
||||
{key: 1, value: 1}, {key: 2, value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by numerical and alpha', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = [[2, 1], [1, 1], ['b', 1], [0, 1], [3, 1], ['a', 1]];
|
||||
expect(pipe.transform(new Map(input as any))).toEqual([
|
||||
{key: 0, value: 1}, {key: 1, value: 1}, {key: 2, value: 1}, {key: 3, value: 1},
|
||||
{key: 'a', value: 1}, {key: 'b', value: 1}
|
||||
]);
|
||||
});
|
||||
it('should order by complex types with compareFn', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const input = new Map([[{id: 1}, 1], [{id: 0}, 1]]);
|
||||
expect(pipe.transform<{id: number}, number>(input, (a, b) => a.key.id > b.key.id ? 1 : -1))
|
||||
.toEqual([
|
||||
{key: {id: 0}, value: 1},
|
||||
{key: {id: 1}, value: 1},
|
||||
]);
|
||||
});
|
||||
it('should return the same ref if nothing changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform(new Map([[1, 2]]));
|
||||
const transform2 = pipe.transform(new Map([[1, 2]]));
|
||||
expect(transform1 === transform2).toEqual(true);
|
||||
});
|
||||
it('should return a new ref if something changes', () => {
|
||||
const pipe = new KeyValuePipe(defaultKeyValueDiffers);
|
||||
const transform1 = pipe.transform(new Map([[1, 2]]));
|
||||
const transform2 = pipe.transform(new Map([[1, 3]]));
|
||||
expect(transform1 !== transform2).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultComparator', () => {
|
||||
it('should remain the same order when keys are equal', () => {
|
||||
const key = 1;
|
||||
const values = [{key, value: 2}, {key, value: 1}];
|
||||
expect(values.sort(defaultComparator)).toEqual(values);
|
||||
});
|
||||
it('should sort undefined keys to the end', () => {
|
||||
const values = [{key: 3, value: 1}, {key: undefined, value: 3}, {key: 1, value: 2}];
|
||||
expect(values.sort(defaultComparator)).toEqual([
|
||||
{key: 1, value: 2}, {key: 3, value: 1}, {key: undefined, value: 3}
|
||||
]);
|
||||
});
|
||||
it('should sort null keys to the end', () => {
|
||||
const values = [{key: 3, value: 1}, {key: null, value: 3}, {key: 1, value: 2}];
|
||||
expect(values.sort(defaultComparator)).toEqual([
|
||||
{key: 1, value: 2}, {key: 3, value: 1}, {key: null, value: 3}
|
||||
]);
|
||||
});
|
||||
it('should sort strings in alpha ascending', () => {
|
||||
const values = [{key: 'b', value: 1}, {key: 'a', value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 'a', value: 3}, {key: 'b', value: 1}]);
|
||||
});
|
||||
it('should sort numbers in numerical ascending', () => {
|
||||
const values = [{key: 2, value: 1}, {key: 1, value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 1, value: 3}, {key: 2, value: 1}]);
|
||||
});
|
||||
it('should sort boolean in false (0) -> true (1)', () => {
|
||||
const values = [{key: true, value: 3}, {key: false, value: 1}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: false, value: 1}, {key: true, value: 3}]);
|
||||
});
|
||||
it('should sort numbers as strings in numerical ascending', () => {
|
||||
const values = [{key: '2', value: 1}, {key: 1, value: 3}];
|
||||
expect(values.sort(defaultComparator)).toEqual([{key: 1, value: 3}, {key: '2', value: 1}]);
|
||||
});
|
||||
});
|
@ -25,7 +25,6 @@ ts_library(
|
||||
tsconfig = ":tsconfig",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
||||
|
||||
@ -34,9 +33,6 @@ npm_package(
|
||||
srcs = [
|
||||
"package.json",
|
||||
],
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [":compiler-cli"],
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ivy_ng_module")
|
||||
load("//tools:defaults.bzl", "ivy_ng_module", "ts_library")
|
||||
load("//packages/bazel/src:ng_rollup_bundle.bzl", "ng_rollup_bundle")
|
||||
|
||||
ivy_ng_module(
|
||||
|
@ -15,7 +15,7 @@
|
||||
"chokidar": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.7.2 <2.9",
|
||||
"typescript": ">=2.7.2 <2.8",
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"engines" : {
|
||||
|
@ -40,8 +40,7 @@ export function main(
|
||||
|
||||
|
||||
function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined {
|
||||
const transformDecorators = options.enableIvy !== 'ngtsc' && options.enableIvy !== 'tsc' &&
|
||||
options.annotationsAs !== 'decorators';
|
||||
const transformDecorators = options.annotationsAs !== 'decorators';
|
||||
const transformTypesToClosure = options.annotateForClosureCompiler;
|
||||
if (!transformDecorators && !transformTypesToClosure) {
|
||||
return undefined;
|
||||
|
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* The TypeScript compiler host used by `ngtsc`.
|
||||
*
|
||||
* It's mostly identical to the native `CompilerHost`, but also includes the ability to
|
||||
* asynchronously resolve resources.
|
||||
*/
|
||||
export interface CompilerHost extends ts.CompilerHost {
|
||||
/**
|
||||
* Begin processing a resource file.
|
||||
*
|
||||
* When the returned Promise resolves, `loadResource` should be able to synchronously produce a
|
||||
* `string` for the given file.
|
||||
*/
|
||||
preloadResource(file: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Like `readFile`, but reads the contents of a resource file which may have been pre-processed
|
||||
* by `preloadResource`.
|
||||
*/
|
||||
loadResource(file: string): string|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of `CompilerHost` which delegates to a native TypeScript host in most cases.
|
||||
*/
|
||||
export class NgtscCompilerHost implements CompilerHost {
|
||||
constructor(private delegate: ts.CompilerHost) {}
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
return this.delegate.getSourceFile(
|
||||
fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return this.delegate.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile(
|
||||
fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void)|undefined,
|
||||
sourceFiles: ReadonlyArray<ts.SourceFile>): void {
|
||||
return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
|
||||
|
||||
getDirectories(path: string): string[] { return this.delegate.getDirectories(path); }
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return this.delegate.getCanonicalFileName(fileName);
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
|
||||
|
||||
getNewLine(): string { return this.delegate.getNewLine(); }
|
||||
|
||||
fileExists(fileName: string): boolean { return this.delegate.fileExists(fileName); }
|
||||
|
||||
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
|
||||
|
||||
loadResource(file: string): string|undefined { throw new Error('Method not implemented.'); }
|
||||
|
||||
preloadResource(file: string): Promise<void> { throw new Error('Method not implemented.'); }
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "metadata",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/metadata",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
],
|
||||
)
|
@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {Decorator, Parameter, reflectConstructorParameters, reflectDecorator} from './src/reflector';
|
||||
export {Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
|
@ -1,238 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* reflector.ts implements static reflection of declarations using the TypeScript `ts.TypeChecker`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A reflected parameter of a function, method, or constructor, indicating the name, any
|
||||
* decorators, and an expression representing a reference to the value side of the parameter's
|
||||
* declared type, if applicable.
|
||||
*/
|
||||
export interface Parameter {
|
||||
/**
|
||||
* Name of the parameter as a `ts.BindingName`, which allows the parameter name to be identified
|
||||
* via sourcemaps.
|
||||
*/
|
||||
name: ts.BindingName;
|
||||
|
||||
/**
|
||||
* A `ts.Expression` which represents a reference to the value side of the parameter's type.
|
||||
*/
|
||||
typeValueExpr: ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Array of decorators present on the parameter.
|
||||
*/
|
||||
decorators: Decorator[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A reflected decorator, indicating the name, where it was imported from, and any arguments if the
|
||||
* decorator is a call expression.
|
||||
*/
|
||||
export interface Decorator {
|
||||
/**
|
||||
* Name of the decorator, extracted from the decoration expression.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Import path (relative to the decorator's file) of the decorator itself.
|
||||
*/
|
||||
from: string;
|
||||
|
||||
/**
|
||||
* The decorator node itself (useful for printing sourcemap based references to the decorator).
|
||||
*/
|
||||
node: ts.Decorator;
|
||||
|
||||
/**
|
||||
* Any arguments of a call expression, if one is present. If the decorator was not a call
|
||||
* expression, then this will be an empty array.
|
||||
*/
|
||||
args: ts.Expression[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a `ts.ClassDeclaration` and determine the list of parameters.
|
||||
*
|
||||
* Note that this only reflects the referenced class and not any potential parent class - that must
|
||||
* be handled by the caller.
|
||||
*
|
||||
* @param node the `ts.ClassDeclaration` to reflect
|
||||
* @param checker a `ts.TypeChecker` used for reflection
|
||||
* @returns a `Parameter` instance for each argument of the constructor, or `null` if no constructor
|
||||
*/
|
||||
export function reflectConstructorParameters(
|
||||
node: ts.ClassDeclaration, checker: ts.TypeChecker): Parameter[]|null {
|
||||
// Firstly, look for a constructor.
|
||||
// clang-format off
|
||||
const maybeCtor: ts.ConstructorDeclaration[] = node
|
||||
.members
|
||||
.filter(element => ts.isConstructorDeclaration(element)) as ts.ConstructorDeclaration[];
|
||||
// clang-format on
|
||||
|
||||
if (maybeCtor.length !== 1) {
|
||||
// No constructor.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reflect each parameter.
|
||||
return maybeCtor[0].parameters.map(param => reflectParameter(param, checker));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a `ts.ParameterDeclaration` and determine its name, a token which refers to the value
|
||||
* declaration of its type (if possible to statically determine), and its decorators, if any.
|
||||
*/
|
||||
function reflectParameter(node: ts.ParameterDeclaration, checker: ts.TypeChecker): Parameter {
|
||||
// The name of the parameter is easy.
|
||||
const name = node.name;
|
||||
|
||||
const decorators = node.decorators &&
|
||||
node.decorators.map(decorator => reflectDecorator(decorator, checker))
|
||||
.filter(decorator => decorator !== null) as Decorator[] ||
|
||||
[];
|
||||
|
||||
// It may or may not be possible to write an expression that refers to the value side of the
|
||||
// type named for the parameter.
|
||||
let typeValueExpr: ts.Expression|null = null;
|
||||
|
||||
// It's not possible to get a value expression if the parameter doesn't even have a type.
|
||||
if (node.type !== undefined) {
|
||||
// It's only valid to convert a type reference to a value reference if the type actually has a
|
||||
// value declaration associated with it.
|
||||
const type = checker.getTypeFromTypeNode(node.type);
|
||||
if (type.symbol !== undefined && type.symbol.valueDeclaration !== undefined) {
|
||||
// The type points to a valid value declaration. Rewrite the TypeReference into an Expression
|
||||
// which references the value pointed to by the TypeReference, if possible.
|
||||
typeValueExpr = typeNodeToValueExpr(node.type);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name, typeValueExpr, decorators,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflect a decorator and return a structure describing where it comes from and any arguments.
|
||||
*
|
||||
* Only imported decorators are considered, not locally defined decorators.
|
||||
*/
|
||||
export function reflectDecorator(decorator: ts.Decorator, checker: ts.TypeChecker): Decorator|null {
|
||||
// Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
|
||||
// expression may contain a call to a function which returns the decorator function, in which
|
||||
// case we want to return the arguments.
|
||||
let decoratorOfInterest: ts.Expression = decorator.expression;
|
||||
let args: ts.Expression[] = [];
|
||||
|
||||
// Check for call expressions.
|
||||
if (ts.isCallExpression(decoratorOfInterest)) {
|
||||
args = Array.from(decoratorOfInterest.arguments);
|
||||
decoratorOfInterest = decoratorOfInterest.expression;
|
||||
}
|
||||
|
||||
// The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
|
||||
// wrong and the decorator can't be resolved statically.
|
||||
if (!ts.isIdentifier(decoratorOfInterest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const importDecl = reflectImportedIdentifier(decoratorOfInterest, checker);
|
||||
if (importDecl === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...importDecl,
|
||||
node: decorator, args,
|
||||
};
|
||||
}
|
||||
|
||||
function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
|
||||
if (ts.isTypeReferenceNode(node)) {
|
||||
return entityNameToValue(node.typeName);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function entityNameToValue(node: ts.EntityName): ts.Expression|null {
|
||||
if (ts.isQualifiedName(node)) {
|
||||
const left = entityNameToValue(node.left);
|
||||
return left !== null ? ts.createPropertyAccess(left, node.right) : null;
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
return ts.getMutableClone(node);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function propertyNameToValue(node: ts.PropertyName): string|null {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||
return node.text;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function reflectObjectLiteral(node: ts.ObjectLiteralExpression): Map<string, ts.Expression> {
|
||||
const map = new Map<string, ts.Expression>();
|
||||
node.properties.forEach(prop => {
|
||||
if (ts.isPropertyAssignment(prop)) {
|
||||
const name = propertyNameToValue(prop.name);
|
||||
if (name === null) {
|
||||
return;
|
||||
}
|
||||
map.set(name, prop.initializer);
|
||||
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
||||
map.set(prop.name.text, prop.name);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export function reflectImportedIdentifier(
|
||||
id: ts.Identifier, checker: ts.TypeChecker): {name: string, from: string}|null {
|
||||
const symbol = checker.getSymbolAtLocation(id);
|
||||
|
||||
if (symbol === undefined || symbol.declarations === undefined ||
|
||||
symbol.declarations.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ignore decorators that are defined locally (not imported).
|
||||
const decl: ts.Declaration = symbol.declarations[0];
|
||||
if (!ts.isImportSpecifier(decl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Walk back from the specifier to find the declaration, which carries the module specifier.
|
||||
const importDecl = decl.parent !.parent !.parent !;
|
||||
|
||||
// The module specifier is guaranteed to be a string literal, so this should always pass.
|
||||
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
||||
// Not allowed to happen in TypeScript ASTs.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the module specifier.
|
||||
const from = importDecl.moduleSpecifier.text;
|
||||
|
||||
// Compute the name by which the decorator was exported, not imported.
|
||||
const name = (decl.propertyName !== undefined ? decl.propertyName : decl.name).text;
|
||||
|
||||
return {from, name};
|
||||
}
|
@ -1,514 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* resolver.ts implements partial computation of expressions, resolving expressions to static
|
||||
* values where possible and returning a `DynamicValue` signal when not.
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* Represents a value which cannot be determined statically.
|
||||
*
|
||||
* Use `isDynamicValue` to determine whether a `ResolvedValue` is a `DynamicValue`.
|
||||
*/
|
||||
export class DynamicValue {
|
||||
/**
|
||||
* This is needed so the "is DynamicValue" assertion of `isDynamicValue` actually has meaning.
|
||||
*
|
||||
* Otherwise, "is DynamicValue" is akin to "is {}" which doesn't trigger narrowing.
|
||||
*/
|
||||
private _isDynamic = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal flyweight for `DynamicValue`. Eventually the dynamic value will carry information
|
||||
* on the location of the node that could not be statically computed.
|
||||
*/
|
||||
const DYNAMIC_VALUE: DynamicValue = new DynamicValue();
|
||||
|
||||
/**
|
||||
* Used to test whether a `ResolvedValue` is a `DynamicValue`.
|
||||
*/
|
||||
export function isDynamicValue(value: any): value is DynamicValue {
|
||||
return value === DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A value resulting from static resolution.
|
||||
*
|
||||
* This could be a primitive, collection type, reference to a `ts.Node` that declares a
|
||||
* non-primitive value, or a special `DynamicValue` type which indicates the value was not
|
||||
* available statically.
|
||||
*/
|
||||
export type ResolvedValue = number | boolean | string | null | undefined | Reference |
|
||||
ResolvedValueArray | ResolvedValueMap | DynamicValue;
|
||||
|
||||
/**
|
||||
* An array of `ResolvedValue`s.
|
||||
*
|
||||
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueArray`
|
||||
* ->
|
||||
* `ResolvedValue`.
|
||||
*/
|
||||
export interface ResolvedValueArray extends Array<ResolvedValue> {}
|
||||
|
||||
/**
|
||||
* A map of strings to `ResolvedValue`s.
|
||||
*
|
||||
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueMap` ->
|
||||
* `ResolvedValue`.
|
||||
*/ export interface ResolvedValueMap extends Map<string, ResolvedValue> {}
|
||||
|
||||
/**
|
||||
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
|
||||
* body.
|
||||
*/
|
||||
type Scope = Map<ts.ParameterDeclaration, ResolvedValue>;
|
||||
|
||||
/**
|
||||
* Whether or not to allow references during resolution.
|
||||
*
|
||||
* See `StaticInterpreter` for details.
|
||||
*/
|
||||
const enum AllowReferences {
|
||||
No = 0,
|
||||
Yes = 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `ts.Node`.
|
||||
*
|
||||
* For example, if an expression evaluates to a function or class definition, it will be returned
|
||||
* as a `Reference` (assuming references are allowed in evaluation).
|
||||
*/
|
||||
export class Reference {
|
||||
constructor(readonly node: ts.Node) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Statically resolve the given `ts.Expression` into a `ResolvedValue`.
|
||||
*
|
||||
* @param node the expression to statically resolve if possible
|
||||
* @param checker a `ts.TypeChecker` used to understand the expression
|
||||
* @returns a `ResolvedValue` representing the resolved value
|
||||
*/
|
||||
export function staticallyResolve(node: ts.Expression, checker: ts.TypeChecker): ResolvedValue {
|
||||
return new StaticInterpreter(
|
||||
checker, new Map<ts.ParameterDeclaration, ResolvedValue>(), AllowReferences.No)
|
||||
.visit(node);
|
||||
}
|
||||
|
||||
interface BinaryOperatorDef {
|
||||
literal: boolean;
|
||||
op: (a: any, b: any) => ResolvedValue;
|
||||
}
|
||||
|
||||
function literalBinaryOp(op: (a: any, b: any) => any): BinaryOperatorDef {
|
||||
return {op, literal: true};
|
||||
}
|
||||
|
||||
function referenceBinaryOp(op: (a: any, b: any) => any): BinaryOperatorDef {
|
||||
return {op, literal: false};
|
||||
}
|
||||
|
||||
const BINARY_OPERATORS = new Map<ts.SyntaxKind, BinaryOperatorDef>([
|
||||
[ts.SyntaxKind.PlusToken, literalBinaryOp((a, b) => a + b)],
|
||||
[ts.SyntaxKind.MinusToken, literalBinaryOp((a, b) => a - b)],
|
||||
[ts.SyntaxKind.AsteriskToken, literalBinaryOp((a, b) => a * b)],
|
||||
[ts.SyntaxKind.SlashToken, literalBinaryOp((a, b) => a / b)],
|
||||
[ts.SyntaxKind.PercentToken, literalBinaryOp((a, b) => a % b)],
|
||||
[ts.SyntaxKind.AmpersandToken, literalBinaryOp((a, b) => a & b)],
|
||||
[ts.SyntaxKind.BarToken, literalBinaryOp((a, b) => a | b)],
|
||||
[ts.SyntaxKind.CaretToken, literalBinaryOp((a, b) => a ^ b)],
|
||||
[ts.SyntaxKind.LessThanToken, literalBinaryOp((a, b) => a < b)],
|
||||
[ts.SyntaxKind.LessThanEqualsToken, literalBinaryOp((a, b) => a <= b)],
|
||||
[ts.SyntaxKind.GreaterThanToken, literalBinaryOp((a, b) => a > b)],
|
||||
[ts.SyntaxKind.GreaterThanEqualsToken, literalBinaryOp((a, b) => a >= b)],
|
||||
[ts.SyntaxKind.LessThanLessThanToken, literalBinaryOp((a, b) => a << b)],
|
||||
[ts.SyntaxKind.GreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >> b)],
|
||||
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >>> b)],
|
||||
[ts.SyntaxKind.AsteriskAsteriskToken, literalBinaryOp((a, b) => Math.pow(a, b))],
|
||||
[ts.SyntaxKind.AmpersandAmpersandToken, referenceBinaryOp((a, b) => a && b)],
|
||||
[ts.SyntaxKind.BarBarToken, referenceBinaryOp((a, b) => a || b)]
|
||||
]);
|
||||
|
||||
const UNARY_OPERATORS = new Map<ts.SyntaxKind, (a: any) => any>([
|
||||
[ts.SyntaxKind.TildeToken, a => ~a], [ts.SyntaxKind.MinusToken, a => -a],
|
||||
[ts.SyntaxKind.PlusToken, a => +a], [ts.SyntaxKind.ExclamationToken, a => !a]
|
||||
]);
|
||||
|
||||
class StaticInterpreter {
|
||||
constructor(
|
||||
private checker: ts.TypeChecker, private scope: Scope,
|
||||
private allowReferences: AllowReferences) {}
|
||||
|
||||
visit(node: ts.Expression): ResolvedValue { return this.visitExpression(node); }
|
||||
|
||||
private visitExpression(node: ts.Expression): ResolvedValue {
|
||||
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
||||
return true;
|
||||
} else if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
||||
return false;
|
||||
} else if (ts.isStringLiteral(node)) {
|
||||
return node.text;
|
||||
} else if (ts.isNumericLiteral(node)) {
|
||||
return parseFloat(node.text);
|
||||
} else if (ts.isObjectLiteralExpression(node)) {
|
||||
return this.visitObjectLiteralExpression(node);
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
return this.visitIdentifier(node);
|
||||
} else if (ts.isPropertyAccessExpression(node)) {
|
||||
return this.visitPropertyAccessExpression(node);
|
||||
} else if (ts.isCallExpression(node)) {
|
||||
return this.visitCallExpression(node);
|
||||
} else if (ts.isConditionalExpression(node)) {
|
||||
return this.visitConditionalExpression(node);
|
||||
} else if (ts.isPrefixUnaryExpression(node)) {
|
||||
return this.visitPrefixUnaryExpression(node);
|
||||
} else if (ts.isBinaryExpression(node)) {
|
||||
return this.visitBinaryExpression(node);
|
||||
} else if (ts.isArrayLiteralExpression(node)) {
|
||||
return this.visitArrayLiteralExpression(node);
|
||||
} else if (ts.isParenthesizedExpression(node)) {
|
||||
return this.visitParenthesizedExpression(node);
|
||||
} else if (ts.isElementAccessExpression(node)) {
|
||||
return this.visitElementAccessExpression(node);
|
||||
} else if (ts.isAsExpression(node)) {
|
||||
return this.visitExpression(node.expression);
|
||||
} else if (ts.isNonNullExpression(node)) {
|
||||
return this.visitExpression(node.expression);
|
||||
} else if (ts.isClassDeclaration(node)) {
|
||||
return this.visitDeclaration(node);
|
||||
} else {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): ResolvedValue {
|
||||
const array: ResolvedValueArray = [];
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
const element = node.elements[i];
|
||||
if (ts.isSpreadElement(element)) {
|
||||
const spread = this.visitExpression(element.expression);
|
||||
if (isDynamicValue(spread)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
if (!Array.isArray(spread)) {
|
||||
throw new Error(`Unexpected value in spread expression: ${spread}`);
|
||||
}
|
||||
|
||||
array.push(...spread);
|
||||
} else {
|
||||
const result = this.visitExpression(element);
|
||||
if (isDynamicValue(result)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
array.push(result);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): ResolvedValue {
|
||||
const map: ResolvedValueMap = new Map<string, ResolvedValue>();
|
||||
for (let i = 0; i < node.properties.length; i++) {
|
||||
const property = node.properties[i];
|
||||
if (ts.isPropertyAssignment(property)) {
|
||||
const name = this.stringNameFromPropertyName(property.name);
|
||||
|
||||
// Check whether the name can be determined statically.
|
||||
if (name === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
map.set(name, this.visitExpression(property.initializer));
|
||||
} else if (ts.isShorthandPropertyAssignment(property)) {
|
||||
const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
|
||||
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration));
|
||||
} else if (ts.isSpreadAssignment(property)) {
|
||||
const spread = this.visitExpression(property.expression);
|
||||
if (isDynamicValue(spread)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
if (!(spread instanceof Map)) {
|
||||
throw new Error(`Unexpected value in spread assignment: ${spread}`);
|
||||
}
|
||||
spread.forEach((value, key) => map.set(key, value));
|
||||
} else {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private visitIdentifier(node: ts.Identifier): ResolvedValue {
|
||||
let symbol: ts.Symbol|undefined = this.checker.getSymbolAtLocation(node);
|
||||
if (symbol === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
const result = this.visitSymbol(symbol);
|
||||
if (this.allowReferences === AllowReferences.Yes && isDynamicValue(result)) {
|
||||
return new Reference(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private visitSymbol(symbol: ts.Symbol): ResolvedValue {
|
||||
while (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = this.checker.getAliasedSymbol(symbol);
|
||||
}
|
||||
|
||||
if (symbol.declarations === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
if (symbol.valueDeclaration !== undefined) {
|
||||
return this.visitDeclaration(symbol.valueDeclaration);
|
||||
}
|
||||
|
||||
return symbol.declarations.reduce<ResolvedValue>((prev, decl) => {
|
||||
if (!(isDynamicValue(prev) || prev instanceof Reference)) {
|
||||
return prev;
|
||||
}
|
||||
return this.visitDeclaration(decl);
|
||||
}, DYNAMIC_VALUE);
|
||||
}
|
||||
|
||||
private visitDeclaration(node: ts.Declaration): ResolvedValue {
|
||||
if (ts.isVariableDeclaration(node)) {
|
||||
if (!node.initializer) {
|
||||
return undefined;
|
||||
}
|
||||
return this.visitExpression(node.initializer);
|
||||
} else if (ts.isParameter(node) && this.scope.has(node)) {
|
||||
return this.scope.get(node) !;
|
||||
} else if (ts.isExportAssignment(node)) {
|
||||
return this.visitExpression(node.expression);
|
||||
} else if (ts.isSourceFile(node)) {
|
||||
return this.visitSourceFile(node);
|
||||
}
|
||||
return this.allowReferences === AllowReferences.Yes ? new Reference(node) : DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
private visitElementAccessExpression(node: ts.ElementAccessExpression): ResolvedValue {
|
||||
const lhs = this.withReferences.visitExpression(node.expression);
|
||||
if (node.argumentExpression === undefined) {
|
||||
throw new Error(`Expected argument in ElementAccessExpression`);
|
||||
}
|
||||
if (isDynamicValue(lhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
const rhs = this.withNoReferences.visitExpression(node.argumentExpression);
|
||||
if (isDynamicValue(rhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
if (typeof rhs !== 'string' && typeof rhs !== 'number') {
|
||||
throw new Error(
|
||||
`ElementAccessExpression index should be string or number, got ${typeof rhs}: ${rhs}`);
|
||||
}
|
||||
|
||||
return this.accessHelper(lhs, rhs);
|
||||
}
|
||||
|
||||
private visitPropertyAccessExpression(node: ts.PropertyAccessExpression): ResolvedValue {
|
||||
const lhs = this.withReferences.visitExpression(node.expression);
|
||||
const rhs = node.name.text;
|
||||
// TODO: handle reference to class declaration.
|
||||
if (isDynamicValue(lhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
return this.accessHelper(lhs, rhs);
|
||||
}
|
||||
|
||||
private visitSourceFile(node: ts.SourceFile): ResolvedValue {
|
||||
const map = new Map<string, ResolvedValue>();
|
||||
const symbol = this.checker.getSymbolAtLocation(node);
|
||||
if (symbol === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
const exports = this.checker.getExportsOfModule(symbol);
|
||||
exports.forEach(symbol => map.set(symbol.name, this.visitSymbol(symbol)));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private accessHelper(lhs: ResolvedValue, rhs: string|number): ResolvedValue {
|
||||
const strIndex = `${rhs}`;
|
||||
if (lhs instanceof Map) {
|
||||
if (lhs.has(strIndex)) {
|
||||
return lhs.get(strIndex) !;
|
||||
} else {
|
||||
throw new Error(`Invalid map access: [${Array.from(lhs.keys())}] dot ${rhs}`);
|
||||
}
|
||||
} else if (Array.isArray(lhs)) {
|
||||
if (rhs === 'length') {
|
||||
return rhs.length;
|
||||
}
|
||||
if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
if (rhs < 0 || rhs >= lhs.length) {
|
||||
throw new Error(`Index out of bounds: ${rhs} vs ${lhs.length}`);
|
||||
}
|
||||
return lhs[rhs];
|
||||
} else if (lhs instanceof Reference) {
|
||||
const ref = lhs.node;
|
||||
if (ts.isClassDeclaration(ref)) {
|
||||
let value: ResolvedValue = undefined;
|
||||
const member = ref.members.filter(member => isStatic(member))
|
||||
.find(
|
||||
member => member.name !== undefined &&
|
||||
this.stringNameFromPropertyName(member.name) === strIndex);
|
||||
if (member !== undefined) {
|
||||
if (ts.isPropertyDeclaration(member) && member.initializer !== undefined) {
|
||||
value = this.visitExpression(member.initializer);
|
||||
} else if (ts.isMethodDeclaration(member)) {
|
||||
value = this.allowReferences === AllowReferences.Yes ? new Reference(member) :
|
||||
DYNAMIC_VALUE;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new Error(`Invalid dot property access: ${lhs} dot ${rhs}`);
|
||||
}
|
||||
|
||||
private visitCallExpression(node: ts.CallExpression): ResolvedValue {
|
||||
const lhs = this.withReferences.visitExpression(node.expression);
|
||||
if (!(lhs instanceof Reference)) {
|
||||
throw new Error(`attempting to call something that is not a function: ${lhs}`);
|
||||
} else if (!isFunctionOrMethodDeclaration(lhs.node) || !lhs.node.body) {
|
||||
throw new Error(
|
||||
`calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]}`);
|
||||
}
|
||||
|
||||
const fn = lhs.node;
|
||||
const body = fn.body as ts.Block;
|
||||
if (body.statements.length !== 1 || !ts.isReturnStatement(body.statements[0])) {
|
||||
throw new Error('Function body must have a single return statement only.');
|
||||
}
|
||||
const ret = body.statements[0] as ts.ReturnStatement;
|
||||
|
||||
const newScope: Scope = new Map<ts.ParameterDeclaration, ResolvedValue>();
|
||||
fn.parameters.forEach((param, index) => {
|
||||
let value: ResolvedValue = undefined;
|
||||
if (index < node.arguments.length) {
|
||||
const arg = node.arguments[index];
|
||||
value = this.visitExpression(arg);
|
||||
}
|
||||
if (value === undefined && param.initializer !== undefined) {
|
||||
value = this.visitExpression(param.initializer);
|
||||
}
|
||||
newScope.set(param, value);
|
||||
});
|
||||
|
||||
return ret.expression !== undefined ? this.withScope(newScope).visitExpression(ret.expression) :
|
||||
undefined;
|
||||
}
|
||||
|
||||
private visitConditionalExpression(node: ts.ConditionalExpression): ResolvedValue {
|
||||
const condition = this.withNoReferences.visitExpression(node.condition);
|
||||
if (isDynamicValue(condition)) {
|
||||
return condition;
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
return this.visitExpression(node.whenTrue);
|
||||
} else {
|
||||
return this.visitExpression(node.whenFalse);
|
||||
}
|
||||
}
|
||||
|
||||
private visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ResolvedValue {
|
||||
const operatorKind = node.operator;
|
||||
if (!UNARY_OPERATORS.has(operatorKind)) {
|
||||
throw new Error(`Unsupported prefix unary operator: ${ts.SyntaxKind[operatorKind]}`);
|
||||
}
|
||||
|
||||
const op = UNARY_OPERATORS.get(operatorKind) !;
|
||||
const value = this.visitExpression(node.operand);
|
||||
return isDynamicValue(value) ? DYNAMIC_VALUE : op(value);
|
||||
}
|
||||
|
||||
private visitBinaryExpression(node: ts.BinaryExpression): ResolvedValue {
|
||||
const tokenKind = node.operatorToken.kind;
|
||||
if (!BINARY_OPERATORS.has(tokenKind)) {
|
||||
throw new Error(`Unsupported binary operator: ${ts.SyntaxKind[tokenKind]}`);
|
||||
}
|
||||
|
||||
const opRecord = BINARY_OPERATORS.get(tokenKind) !;
|
||||
let lhs: ResolvedValue, rhs: ResolvedValue;
|
||||
if (opRecord.literal) {
|
||||
const withNoReferences = this.withNoReferences;
|
||||
lhs = literal(withNoReferences.visitExpression(node.left));
|
||||
rhs = literal(withNoReferences.visitExpression(node.right));
|
||||
} else {
|
||||
lhs = this.visitExpression(node.left);
|
||||
rhs = this.visitExpression(node.right);
|
||||
}
|
||||
|
||||
return isDynamicValue(lhs) || isDynamicValue(rhs) ? DYNAMIC_VALUE : opRecord.op(lhs, rhs);
|
||||
}
|
||||
|
||||
private visitParenthesizedExpression(node: ts.ParenthesizedExpression): ResolvedValue {
|
||||
return this.visitExpression(node.expression);
|
||||
}
|
||||
|
||||
private stringNameFromPropertyName(node: ts.PropertyName): string|undefined {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||
return node.text;
|
||||
} else { // ts.ComputedPropertyName
|
||||
const literal = this.withNoReferences.visitExpression(node.expression);
|
||||
return typeof literal === 'string' ? literal : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private get withReferences(): StaticInterpreter {
|
||||
return this.allowReferences === AllowReferences.Yes ?
|
||||
this :
|
||||
new StaticInterpreter(this.checker, this.scope, AllowReferences.Yes);
|
||||
}
|
||||
|
||||
private get withNoReferences(): StaticInterpreter {
|
||||
return this.allowReferences === AllowReferences.No ?
|
||||
this :
|
||||
new StaticInterpreter(this.checker, this.scope, AllowReferences.No);
|
||||
}
|
||||
|
||||
private withScope(scope: Scope): StaticInterpreter {
|
||||
return new StaticInterpreter(this.checker, scope, this.allowReferences);
|
||||
}
|
||||
}
|
||||
|
||||
function isStatic(element: ts.ClassElement): boolean {
|
||||
return element.modifiers !== undefined &&
|
||||
element.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
||||
}
|
||||
|
||||
function isFunctionOrMethodDeclaration(node: ts.Node): node is ts.FunctionDeclaration|
|
||||
ts.MethodDeclaration {
|
||||
return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node);
|
||||
}
|
||||
|
||||
function literal(value: ResolvedValue): any {
|
||||
if (value === null || value === undefined || typeof value === 'string' ||
|
||||
typeof value === 'number' || typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (isDynamicValue(value)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
throw new Error(`Value ${value} is not literal and cannot be used in this context.`);
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = 1,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
"//tools/testing:node_no_angular",
|
||||
],
|
||||
)
|
@ -1,144 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {Parameter, reflectConstructorParameters} from '../src/reflector';
|
||||
|
||||
describe('reflector', () => {
|
||||
describe('ctor params', () => {
|
||||
it('should reflect a single argument', () => {
|
||||
const {program} = makeProgram([{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
class Bar {}
|
||||
|
||||
class Foo {
|
||||
constructor(bar: Bar) {}
|
||||
}
|
||||
`
|
||||
}]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: 'dec.ts',
|
||||
contents: `
|
||||
export function dec(target: any, key: string, index: number) {
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {dec} from './dec';
|
||||
class Bar {}
|
||||
|
||||
class Foo {
|
||||
constructor(@dec bar: Bar) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument with a call', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: 'dec.ts',
|
||||
contents: `
|
||||
export function dec(target: any, key: string, index: number) {
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {dec} from './dec';
|
||||
class Bar {}
|
||||
|
||||
class Foo {
|
||||
constructor(@dec bar: Bar) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
expect(args.length).toBe(1);
|
||||
expectArgument(args[0], 'bar', 'Bar', 'dec', './dec');
|
||||
});
|
||||
|
||||
it('should reflect a decorated argument with an indirection', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: 'bar.ts',
|
||||
contents: `
|
||||
export class Bar {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {Bar} from './bar';
|
||||
import * as star from './bar';
|
||||
|
||||
class Foo {
|
||||
constructor(bar: Bar, otherBar: star.Bar) {}
|
||||
}
|
||||
`
|
||||
}
|
||||
]);
|
||||
const clazz = getDeclaration(program, 'entry.ts', 'Foo', ts.isClassDeclaration);
|
||||
const checker = program.getTypeChecker();
|
||||
const args = reflectConstructorParameters(clazz, checker) !;
|
||||
expect(args.length).toBe(2);
|
||||
expectArgument(args[0], 'bar', 'Bar');
|
||||
expectArgument(args[1], 'otherBar', 'star.Bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function expectArgument(
|
||||
arg: Parameter, name: string, type?: string, decorator?: string, decoratorFrom?: string): void {
|
||||
expect(argExpressionToString(arg.name)).toEqual(name);
|
||||
if (type === undefined) {
|
||||
expect(arg.typeValueExpr).toBeNull();
|
||||
} else {
|
||||
expect(arg.typeValueExpr).not.toBeNull();
|
||||
expect(argExpressionToString(arg.typeValueExpr !)).toEqual(type);
|
||||
}
|
||||
if (decorator !== undefined) {
|
||||
expect(arg.decorators.length).toBeGreaterThan(0);
|
||||
expect(arg.decorators.some(dec => dec.name === decorator && dec.from === decoratorFrom))
|
||||
.toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
function argExpressionToString(name: ts.Node): string {
|
||||
if (ts.isIdentifier(name)) {
|
||||
return name.text;
|
||||
} else if (ts.isPropertyAccessExpression(name)) {
|
||||
return `${argExpressionToString(name.expression)}.${name.name.text}`;
|
||||
} else {
|
||||
throw new Error(`Unexpected node in arg expression: ${ts.SyntaxKind[name.kind]}.`);
|
||||
}
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {ResolvedValue, staticallyResolve} from '../src/resolver';
|
||||
|
||||
function makeSimpleProgram(contents: string): ts.Program {
|
||||
return makeProgram([{name: 'entry.ts', contents}]).program;
|
||||
}
|
||||
|
||||
function makeExpression(
|
||||
code: string, expr: string): {expression: ts.Expression, checker: ts.TypeChecker} {
|
||||
const {program} =
|
||||
makeProgram([{name: 'entry.ts', contents: `${code}; const target$ = ${expr};`}]);
|
||||
const checker = program.getTypeChecker();
|
||||
const decl = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
return {
|
||||
expression: decl.initializer !,
|
||||
checker,
|
||||
};
|
||||
}
|
||||
|
||||
function evaluate<T extends ResolvedValue>(code: string, expr: string): T {
|
||||
const {expression, checker} = makeExpression(code, expr);
|
||||
return staticallyResolve(expression, checker) as T;
|
||||
}
|
||||
|
||||
describe('ngtsc metadata', () => {
|
||||
it('reads a file correctly', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {Y} from './other';
|
||||
const A = Y;
|
||||
export const X = A;
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'other.ts',
|
||||
contents: `
|
||||
export const Y = 'test';
|
||||
`
|
||||
}
|
||||
]);
|
||||
const decl = getDeclaration(program, 'entry.ts', 'X', ts.isVariableDeclaration);
|
||||
|
||||
const value = staticallyResolve(decl.initializer !, program.getTypeChecker());
|
||||
expect(value).toEqual('test');
|
||||
});
|
||||
|
||||
it('map access works',
|
||||
() => { expect(evaluate('const obj = {a: "test"};', 'obj.a')).toEqual('test'); });
|
||||
|
||||
it('function calls work', () => {
|
||||
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
|
||||
});
|
||||
|
||||
it('conditionals work', () => {
|
||||
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
|
||||
});
|
||||
|
||||
it('addition works', () => { expect(evaluate(`const x = 1 + 2;`, 'x')).toEqual(3); });
|
||||
|
||||
it('static property on class works',
|
||||
() => { expect(evaluate(`class Foo { static bar = 'test'; }`, 'Foo.bar')).toEqual('test'); });
|
||||
|
||||
it('static property call works', () => {
|
||||
expect(evaluate(`class Foo { static bar(test) { return test; } }`, 'Foo.bar("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
|
||||
it('indirected static property call works', () => {
|
||||
expect(
|
||||
evaluate(
|
||||
`class Foo { static bar(test) { return test; } }; const fn = Foo.bar;`, 'fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
|
||||
it('array works', () => {
|
||||
expect(evaluate(`const x = 'test'; const y = [1, x, 2];`, 'y')).toEqual([1, 'test', 2]);
|
||||
});
|
||||
|
||||
it('array spread works', () => {
|
||||
expect(evaluate(`const a = [1, 2]; const b = [4, 5]; const c = [...a, 3, ...b];`, 'c'))
|
||||
.toEqual([1, 2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('&& operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a && b')).toEqual('world');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a && b')).toEqual(false);
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a && b')).toEqual(0);
|
||||
});
|
||||
|
||||
it('|| operations work', () => {
|
||||
expect(evaluate(`const a = 'hello', b = 'world';`, 'a || b')).toEqual('hello');
|
||||
expect(evaluate(`const a = false, b = 'world';`, 'a || b')).toEqual('world');
|
||||
expect(evaluate(`const a = 'hello', b = 0;`, 'a || b')).toEqual('hello');
|
||||
});
|
||||
|
||||
it('parentheticals work',
|
||||
() => { expect(evaluate(`const a = 3, b = 4;`, 'a * (a + b)')).toEqual(21); });
|
||||
|
||||
it('array access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[1] + a[0]')).toEqual(3); });
|
||||
|
||||
it('negation works', () => {
|
||||
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
||||
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
||||
});
|
||||
|
||||
it('reads values from default exports', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'second.ts', contents: 'export default {property: "test"}'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import mod from './second';
|
||||
const target$ = mod.property;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
debugger;
|
||||
expect(staticallyResolve(expr, checker)).toEqual('test');
|
||||
});
|
||||
|
||||
it('reads values from named exports', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'second.ts', contents: 'export const a = {property: "test"};'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import * as mod from './second';
|
||||
const target$ = mod.a.property;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
expect(staticallyResolve(expr, checker)).toEqual('test');
|
||||
});
|
||||
|
||||
it('chain of re-exports works', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'const.ts', contents: 'export const value = {property: "test"};'},
|
||||
{name: 'def.ts', contents: `import {value} from './const'; export default value;`},
|
||||
{name: 'indirect-reexport.ts', contents: `import value from './def'; export {value};`},
|
||||
{name: 'direct-reexport.ts', contents: `export {value} from './indirect-reexport';`},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `import * as mod from './direct-reexport'; const target$ = mod.value.property;`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
expect(staticallyResolve(expr, checker)).toEqual('test');
|
||||
});
|
||||
|
||||
it('map spread works', () => {
|
||||
const map: Map<string, number> = evaluate<Map<string, number>>(
|
||||
`const a = {a: 1}; const b = {b: 2, c: 1}; const c = {...a, ...b, c: 3};`, 'c');
|
||||
|
||||
const obj: {[key: string]: number} = {};
|
||||
map.forEach((value, key) => obj[key] = value);
|
||||
expect(obj).toEqual({
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it('indirected-via-object function call works', () => {
|
||||
expect(evaluate(
|
||||
`
|
||||
function fn(res) { return res; }
|
||||
const obj = {fn};
|
||||
`,
|
||||
'obj.fn("test")'))
|
||||
.toEqual('test');
|
||||
});
|
||||
});
|
@ -1,145 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {GeneratedFile} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import * as api from '../transformers/api';
|
||||
|
||||
import {CompilerHost} from './compiler_host';
|
||||
import {InjectableCompilerAdapter, IvyCompilation, ivyTransformFactory} from './transform';
|
||||
|
||||
export class NgtscProgram implements api.Program {
|
||||
private tsProgram: ts.Program;
|
||||
|
||||
constructor(
|
||||
rootNames: ReadonlyArray<string>, private options: api.CompilerOptions,
|
||||
private host: api.CompilerHost, oldProgram?: api.Program) {
|
||||
this.tsProgram =
|
||||
ts.createProgram(rootNames, options, host, oldProgram && oldProgram.getTsProgram());
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program { return this.tsProgram; }
|
||||
|
||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
||||
}
|
||||
|
||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getTsSyntacticDiagnostics(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getTsSemanticDiagnostics(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
getNgSemanticDiagnostics(
|
||||
fileName?: string|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
loadNgStructureAsync(): Promise<void> { return Promise.resolve(); }
|
||||
|
||||
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
emit(opts?: {
|
||||
emitFlags?: api.EmitFlags,
|
||||
cancellationToken?: ts.CancellationToken,
|
||||
customTransformers?: api.CustomTransformers,
|
||||
emitCallback?: api.TsEmitCallback,
|
||||
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback
|
||||
}): ts.EmitResult {
|
||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||
const mergeEmitResultsCallback = opts && opts.mergeEmitResultsCallback || mergeEmitResults;
|
||||
|
||||
const checker = this.tsProgram.getTypeChecker();
|
||||
|
||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||
const adapters = [new InjectableCompilerAdapter(checker)];
|
||||
const compilation = new IvyCompilation(adapters, checker);
|
||||
|
||||
// Analyze every source file in the program.
|
||||
this.tsProgram.getSourceFiles()
|
||||
.filter(file => !file.fileName.endsWith('.d.ts'))
|
||||
.forEach(file => compilation.analyze(file));
|
||||
|
||||
// Since there is no .d.ts transformation API, .d.ts files are transformed during write.
|
||||
const writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError: ((message: string) => void) | undefined,
|
||||
sourceFiles: ReadonlyArray<ts.SourceFile>) => {
|
||||
if (fileName.endsWith('.d.ts')) {
|
||||
data = sourceFiles.reduce(
|
||||
(data, sf) => compilation.transformedDtsFor(sf.fileName, data), data);
|
||||
}
|
||||
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
};
|
||||
|
||||
|
||||
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
emitOnlyDtsFiles: false, writeFile,
|
||||
customTransformers: {
|
||||
before: [ivyTransformFactory(compilation)],
|
||||
},
|
||||
});
|
||||
return emitResult;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultEmitCallback: api.TsEmitCallback =
|
||||
({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
|
||||
customTransformers}) =>
|
||||
program.emit(
|
||||
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||
|
||||
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
let emitSkipped = false;
|
||||
const emittedFiles: string[] = [];
|
||||
for (const er of emitResults) {
|
||||
diagnostics.push(...er.diagnostics);
|
||||
emitSkipped = emitSkipped || er.emitSkipped;
|
||||
emittedFiles.push(...(er.emittedFiles || []));
|
||||
}
|
||||
return {diagnostics, emitSkipped, emittedFiles};
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "testing",
|
||||
testonly = 1,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
],
|
||||
)
|
@ -1,130 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export function makeProgram(files: {name: string, contents: string}[]):
|
||||
{program: ts.Program, host: ts.CompilerHost} {
|
||||
const host = new InMemoryHost();
|
||||
files.forEach(file => host.writeFile(file.name, file.contents));
|
||||
|
||||
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
|
||||
const program = ts.createProgram(rootNames, {noLib: true, experimentalDecorators: true}, host);
|
||||
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
|
||||
if (diags.length > 0) {
|
||||
throw new Error(
|
||||
`Typescript diagnostics failed! ${diags.map(diag => diag.messageText).join(', ')}`);
|
||||
}
|
||||
return {program, host};
|
||||
}
|
||||
|
||||
export class InMemoryHost implements ts.CompilerHost {
|
||||
private fileSystem = new Map<string, string>();
|
||||
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
const contents = this.fileSystem.get(this.getCanonicalFileName(fileName));
|
||||
if (contents === undefined) {
|
||||
onError && onError(`File does not exist: ${this.getCanonicalFileName(fileName)})`);
|
||||
return undefined;
|
||||
}
|
||||
return ts.createSourceFile(fileName, contents, languageVersion, undefined, ts.ScriptKind.TS);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string { return '/lib.d.ts'; }
|
||||
|
||||
writeFile(
|
||||
fileName: string, data: string, writeByteOrderMark?: boolean,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>): void {
|
||||
this.fileSystem.set(this.getCanonicalFileName(fileName), data);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string { return '/'; }
|
||||
|
||||
getDirectories(dir: string): string[] {
|
||||
const fullDir = this.getCanonicalFileName(dir) + '/';
|
||||
const dirSet = new Set(Array
|
||||
// Look at all paths known to the host.
|
||||
.from(this.fileSystem.keys())
|
||||
// Filter out those that aren't under the requested directory.
|
||||
.filter(candidate => candidate.startsWith(fullDir))
|
||||
// Relativize the rest by the requested directory.
|
||||
.map(candidate => candidate.substr(fullDir.length))
|
||||
// What's left are dir/.../file.txt entries, and file.txt entries.
|
||||
// Get the dirname, which
|
||||
// yields '.' for the latter and dir/... for the former.
|
||||
.map(candidate => path.dirname(candidate))
|
||||
// Filter out the '.' entries, which were files.
|
||||
.filter(candidate => candidate !== '.')
|
||||
// Finally, split on / and grab the first entry.
|
||||
.map(candidate => candidate.split('/', 1)[0]));
|
||||
|
||||
// Get the resulting values out of the Set.
|
||||
return Array.from(dirSet);
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return path.posix.normalize(`${this.getCurrentDirectory()}/${fileName}`);
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames(): boolean { return true; }
|
||||
|
||||
getNewLine(): string { return '\n'; }
|
||||
|
||||
fileExists(fileName: string): boolean { return this.fileSystem.has(fileName); }
|
||||
|
||||
readFile(fileName: string): string|undefined { return this.fileSystem.get(fileName); }
|
||||
}
|
||||
|
||||
function bindingNameEquals(node: ts.BindingName, name: string): boolean {
|
||||
if (ts.isIdentifier(node)) {
|
||||
return node.text === name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getDeclaration<T extends ts.Declaration>(
|
||||
program: ts.Program, fileName: string, name: string, assert: (value: any) => value is T): T {
|
||||
const sf = program.getSourceFile(fileName);
|
||||
if (!sf) {
|
||||
throw new Error(`No such file: ${fileName}`);
|
||||
}
|
||||
|
||||
let chosenDecl: ts.Declaration|null = null;
|
||||
|
||||
sf.statements.forEach(stmt => {
|
||||
if (chosenDecl !== null) {
|
||||
return;
|
||||
} else if (ts.isVariableStatement(stmt)) {
|
||||
stmt.declarationList.declarations.forEach(decl => {
|
||||
if (bindingNameEquals(decl.name, name)) {
|
||||
chosenDecl = decl;
|
||||
}
|
||||
});
|
||||
} else if (ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) {
|
||||
if (stmt.name !== undefined && stmt.name.text === name) {
|
||||
chosenDecl = stmt;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chosenDecl = chosenDecl as ts.Declaration | null;
|
||||
|
||||
if (chosenDecl === null) {
|
||||
throw new Error(`No such symbol: ${name} in ${fileName}`);
|
||||
}
|
||||
if (!assert(chosenDecl)) {
|
||||
throw new Error(`Symbol ${name} from ${fileName} is a ${ts.SyntaxKind[chosenDecl.kind]}`);
|
||||
}
|
||||
|
||||
return chosenDecl;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "transform",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/transform",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
],
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {IvyCompilation} from './src/compilation';
|
||||
export {InjectableCompilerAdapter} from './src/injectable';
|
||||
export {ivyTransformFactory} from './src/transform';
|
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
|
||||
/**
|
||||
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
|
||||
* compiler/transform.
|
||||
*
|
||||
* The decorator compilers in @angular/compiler do not depend on Typescript. The adapter is
|
||||
* responsible for extracting the information required to perform compilation from the decorators
|
||||
* and Typescript source, invoking the decorator compiler, and returning the result.
|
||||
*/
|
||||
export interface CompilerAdapter<A> {
|
||||
/**
|
||||
* Scan a set of reflected decorators and determine if this adapter is responsible for compilation
|
||||
* of one of them.
|
||||
*/
|
||||
detect(decorator: Decorator[]): Decorator|undefined;
|
||||
|
||||
/**
|
||||
* Perform analysis on the decorator/class combination, producing instructions for compilation
|
||||
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
|
||||
* isn't valid.
|
||||
*/
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<A>;
|
||||
|
||||
/**
|
||||
* Generate a description of the field which should be added to the class, including any
|
||||
* initialization code to be generated.
|
||||
*/
|
||||
compile(node: ts.ClassDeclaration, analysis: A): AddStaticFieldInstruction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The output of an analysis operation, consisting of possibly an arbitrary analysis object (used as
|
||||
* the input to code generation) and potentially diagnostics if there were errors uncovered during
|
||||
* analysis.
|
||||
*/
|
||||
export interface AnalysisOutput<A> {
|
||||
analysis?: A;
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of the static field to add to a class, including an initialization expression
|
||||
* and a type for the .d.ts file.
|
||||
*/
|
||||
export interface AddStaticFieldInstruction {
|
||||
field: string;
|
||||
initializer: Expression;
|
||||
type: Type;
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, reflectDecorator} from '../../metadata';
|
||||
|
||||
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
|
||||
import {DtsFileTransformer} from './declaration';
|
||||
import {ImportManager, translateType} from './translator';
|
||||
|
||||
|
||||
/**
|
||||
* Record of an adapter which decided to emit a static field, and the analysis it performed to
|
||||
* prepare for that operation.
|
||||
*/
|
||||
interface EmitFieldOperation<T> {
|
||||
adapter: CompilerAdapter<T>;
|
||||
analysis: AnalysisOutput<T>;
|
||||
decorator: ts.Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a compilation of Ivy decorators into static fields across an entire ts.Program.
|
||||
*
|
||||
* The compilation is stateful - source files are analyzed and records of the operations that need
|
||||
* to be performed during the transform/emit process are maintained internally.
|
||||
*/
|
||||
export class IvyCompilation {
|
||||
/**
|
||||
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the
|
||||
* information recorded about them for later compilation.
|
||||
*/
|
||||
private analysis = new Map<ts.ClassDeclaration, EmitFieldOperation<any>>();
|
||||
|
||||
/**
|
||||
* Tracks the `DtsFileTransformer`s for each TS file that needs .d.ts transformations.
|
||||
*/
|
||||
private dtsMap = new Map<string, DtsFileTransformer>();
|
||||
|
||||
constructor(private adapters: CompilerAdapter<any>[], private checker: ts.TypeChecker) {}
|
||||
|
||||
/**
|
||||
* Analyze a source file and produce diagnostics for it (if any).
|
||||
*/
|
||||
analyze(sf: ts.SourceFile): ts.Diagnostic[] {
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
const visit = (node: ts.Node) => {
|
||||
// Process nodes recursively, and look for class declarations with decorators.
|
||||
if (ts.isClassDeclaration(node) && node.decorators !== undefined) {
|
||||
// The first step is to reflect the decorators, which will identify decorators
|
||||
// that are imported from another module.
|
||||
const decorators =
|
||||
node.decorators.map(decorator => reflectDecorator(decorator, this.checker))
|
||||
.filter(decorator => decorator !== null) as Decorator[];
|
||||
|
||||
// Look through the CompilerAdapters to see if any are relevant.
|
||||
this.adapters.forEach(adapter => {
|
||||
// An adapter is relevant if it matches one of the decorators on the class.
|
||||
const decorator = adapter.detect(decorators);
|
||||
if (decorator === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for multiple decorators on the same node. Technically speaking this
|
||||
// could be supported, but right now it's an error.
|
||||
if (this.analysis.has(node)) {
|
||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||
}
|
||||
|
||||
// Run analysis on the decorator. This will produce either diagnostics, an
|
||||
// analysis result, or both.
|
||||
const analysis = adapter.analyze(node, decorator);
|
||||
if (analysis.diagnostics !== undefined) {
|
||||
diagnostics.push(...analysis.diagnostics);
|
||||
}
|
||||
if (analysis.analysis !== undefined) {
|
||||
this.analysis.set(node, {
|
||||
adapter,
|
||||
analysis: analysis.analysis,
|
||||
decorator: decorator.node,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
|
||||
visit(sf);
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a compilation operation on the given class declaration and return instructions to an
|
||||
* AST transformer if any are available.
|
||||
*/
|
||||
compileIvyFieldFor(node: ts.ClassDeclaration): AddStaticFieldInstruction|undefined {
|
||||
// Look to see whether the original node was analyzed. If not, there's nothing to do.
|
||||
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
return undefined;
|
||||
}
|
||||
const op = this.analysis.get(original) !;
|
||||
|
||||
// Run the actual compilation, which generates an Expression for the Ivy field.
|
||||
const res = op.adapter.compile(node, op.analysis);
|
||||
|
||||
// Look up the .d.ts transformer for the input file and record that a field was generated,
|
||||
// which will allow the .d.ts to be transformed later.
|
||||
const fileName = node.getSourceFile().fileName;
|
||||
const dtsTransformer = this.getDtsTransformer(fileName);
|
||||
dtsTransformer.recordStaticField(node.name !.text, res);
|
||||
|
||||
// Return the instruction to the transformer so the field will be added.
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
|
||||
*/
|
||||
ivyDecoratorFor(node: ts.ClassDeclaration): ts.Decorator|undefined {
|
||||
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
if (!this.analysis.has(original)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.analysis.get(original) !.decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a .d.ts source string and return a transformed version that incorporates the changes
|
||||
* made to the source file.
|
||||
*/
|
||||
transformedDtsFor(tsFileName: string, dtsOriginalSource: string): string {
|
||||
// No need to transform if no changes have been requested to the input file.
|
||||
if (!this.dtsMap.has(tsFileName)) {
|
||||
return dtsOriginalSource;
|
||||
}
|
||||
|
||||
// Return the transformed .d.ts source.
|
||||
return this.dtsMap.get(tsFileName) !.transform(dtsOriginalSource);
|
||||
}
|
||||
|
||||
private getDtsTransformer(tsFileName: string): DtsFileTransformer {
|
||||
if (!this.dtsMap.has(tsFileName)) {
|
||||
this.dtsMap.set(tsFileName, new DtsFileTransformer());
|
||||
}
|
||||
return this.dtsMap.get(tsFileName) !;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AddStaticFieldInstruction} from './api';
|
||||
import {ImportManager, translateType} from './translator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Processes .d.ts file text and adds static field declarations, with types.
|
||||
*/
|
||||
export class DtsFileTransformer {
|
||||
private ivyFields = new Map<string, AddStaticFieldInstruction>();
|
||||
private imports = new ImportManager();
|
||||
|
||||
/**
|
||||
* Track that a static field was added to the code for a class.
|
||||
*/
|
||||
recordStaticField(name: string, decl: AddStaticFieldInstruction): void {
|
||||
this.ivyFields.set(name, decl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the .d.ts text for a file and add any declarations which were recorded.
|
||||
*/
|
||||
transform(dts: string): string {
|
||||
const dtsFile =
|
||||
ts.createSourceFile('out.d.ts', dts, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
||||
|
||||
for (let i = dtsFile.statements.length - 1; i >= 0; i--) {
|
||||
const stmt = dtsFile.statements[i];
|
||||
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined &&
|
||||
this.ivyFields.has(stmt.name.text)) {
|
||||
const desc = this.ivyFields.get(stmt.name.text) !;
|
||||
const before = dts.substring(0, stmt.end - 1);
|
||||
const after = dts.substring(stmt.end - 1);
|
||||
const type = translateType(desc.type, this.imports);
|
||||
dts = before + ` static ${desc.field}: ${type};\n` + after;
|
||||
}
|
||||
}
|
||||
|
||||
const imports = this.imports.getAllImports();
|
||||
if (imports.length !== 0) {
|
||||
dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join() + dts;
|
||||
}
|
||||
|
||||
return dts;
|
||||
}
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../metadata';
|
||||
import {reflectConstructorParameters, reflectImportedIdentifier, reflectObjectLiteral} from '../../metadata/src/reflector';
|
||||
|
||||
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
|
||||
|
||||
|
||||
/**
|
||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
*/
|
||||
export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMetadata> {
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
|
||||
detect(decorator: Decorator[]): Decorator|undefined {
|
||||
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
|
||||
return {
|
||||
analysis: extractInjectableMetadata(node, decorator, this.checker),
|
||||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction {
|
||||
const res = compileIvyInjectable(analysis);
|
||||
return {
|
||||
field: 'ngInjectableDef',
|
||||
initializer: res.expression,
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the input
|
||||
* metadata needed to run `compileIvyInjectable`.
|
||||
*/
|
||||
function extractInjectableMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator,
|
||||
checker: ts.TypeChecker): R3InjectableMetadata {
|
||||
if (clazz.name === undefined) {
|
||||
throw new Error(`@Injectables must have names`);
|
||||
}
|
||||
const name = clazz.name.text;
|
||||
const type = new WrappedNodeExpr(clazz.name);
|
||||
if (decorator.args.length === 0) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
providedIn: new LiteralExpr(null),
|
||||
deps: getConstructorDependencies(clazz, checker),
|
||||
};
|
||||
} else if (decorator.args.length === 1) {
|
||||
const metaNode = decorator.args[0];
|
||||
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
||||
// transport references from one location to another. This is the problem that lowering
|
||||
// used to solve - if this restriction proves too undesirable we can re-implement lowering.
|
||||
if (!ts.isObjectLiteralExpression(metaNode)) {
|
||||
throw new Error(`In Ivy, decorator metadata must be inline.`);
|
||||
}
|
||||
|
||||
// Resolve the fields of the literal into a map of field name to expression.
|
||||
const meta = reflectObjectLiteral(metaNode);
|
||||
let providedIn: Expression = new LiteralExpr(null);
|
||||
if (meta.has('providedIn')) {
|
||||
providedIn = new WrappedNodeExpr(meta.get('providedIn') !);
|
||||
}
|
||||
if (meta.has('useValue')) {
|
||||
return {name, type, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !)};
|
||||
} else if (meta.has('useExisting')) {
|
||||
return {name, type, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !)};
|
||||
} else if (meta.has('useClass')) {
|
||||
return {name, type, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !)};
|
||||
} else if (meta.has('useFactory')) {
|
||||
// useFactory is special - the 'deps' property must be analyzed.
|
||||
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
|
||||
const deps: R3DependencyMetadata[] = [];
|
||||
if (meta.has('deps')) {
|
||||
const depsExpr = meta.get('deps') !;
|
||||
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
||||
throw new Error(`In Ivy, deps metadata must be inline.`);
|
||||
}
|
||||
if (depsExpr.elements.length > 0) {
|
||||
throw new Error(`deps not yet supported`);
|
||||
}
|
||||
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
|
||||
}
|
||||
return {name, type, providedIn, useFactory: factory, deps};
|
||||
} else {
|
||||
const deps = getConstructorDependencies(clazz, checker);
|
||||
return {name, type, providedIn, deps};
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Too many arguments to @Injectable`);
|
||||
}
|
||||
}
|
||||
|
||||
function getConstructorDependencies(
|
||||
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
|
||||
const useType: R3DependencyMetadata[] = [];
|
||||
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
|
||||
ctorParams.forEach(param => {
|
||||
let tokenExpr = param.typeValueExpr;
|
||||
let optional = false, self = false, skipSelf = false;
|
||||
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
|
||||
if (dec.name === 'Inject') {
|
||||
if (dec.args.length !== 1) {
|
||||
throw new Error(`Unexpected number of arguments to @Inject().`);
|
||||
}
|
||||
tokenExpr = dec.args[0];
|
||||
} else if (dec.name === 'Optional') {
|
||||
optional = true;
|
||||
} else if (dec.name === 'SkipSelf') {
|
||||
skipSelf = true;
|
||||
} else if (dec.name === 'Self') {
|
||||
self = true;
|
||||
} else {
|
||||
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
|
||||
}
|
||||
if (tokenExpr === null) {
|
||||
throw new Error(`No suitable token for parameter!`);
|
||||
}
|
||||
});
|
||||
const token = new WrappedNodeExpr(tokenExpr);
|
||||
useType.push(
|
||||
{token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token});
|
||||
});
|
||||
return useType;
|
||||
}
|
||||
|
||||
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
|
||||
const meta: R3DependencyMetadata = {
|
||||
token: new WrappedNodeExpr(dep),
|
||||
host: false,
|
||||
resolved: R3ResolvedDependencyType.Token,
|
||||
optional: false,
|
||||
self: false,
|
||||
skipSelf: false,
|
||||
};
|
||||
|
||||
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
|
||||
const source = reflectImportedIdentifier(dec, checker);
|
||||
if (source === null || source.from !== '@angular/core') {
|
||||
return;
|
||||
}
|
||||
switch (source.name) {
|
||||
case 'Inject':
|
||||
if (token !== undefined) {
|
||||
meta.token = new WrappedNodeExpr(token);
|
||||
}
|
||||
break;
|
||||
case 'Optional':
|
||||
meta.optional = true;
|
||||
break;
|
||||
case 'SkipSelf':
|
||||
meta.skipSelf = true;
|
||||
break;
|
||||
case 'Self':
|
||||
meta.self = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ts.isArrayLiteralExpression(dep)) {
|
||||
dep.elements.forEach(el => {
|
||||
if (ts.isIdentifier(el)) {
|
||||
maybeUpdateDecorator(el);
|
||||
} else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) {
|
||||
const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined;
|
||||
maybeUpdateDecorator(el.expression, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
return meta;
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {IvyCompilation} from './compilation';
|
||||
import {ImportManager, translateExpression} from './translator';
|
||||
|
||||
export function ivyTransformFactory(compilation: IvyCompilation):
|
||||
ts.TransformerFactory<ts.SourceFile> {
|
||||
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
|
||||
return (file: ts.SourceFile): ts.SourceFile => {
|
||||
return transformIvySourceFile(compilation, context, file);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
|
||||
*/
|
||||
function transformIvySourceFile(
|
||||
compilation: IvyCompilation, context: ts.TransformationContext,
|
||||
file: ts.SourceFile): ts.SourceFile {
|
||||
const importManager = new ImportManager();
|
||||
|
||||
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
|
||||
const sf = visitNode(file);
|
||||
|
||||
// Generate the import statements to prepend.
|
||||
const imports = importManager.getAllImports().map(
|
||||
i => ts.createImportDeclaration(
|
||||
undefined, undefined,
|
||||
ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))),
|
||||
ts.createLiteral(i.name)));
|
||||
|
||||
// Prepend imports if needed.
|
||||
if (imports.length > 0) {
|
||||
sf.statements = ts.createNodeArray([...imports, ...sf.statements]);
|
||||
}
|
||||
return sf;
|
||||
|
||||
// Helper function to process a class declaration.
|
||||
function visitClassDeclaration(node: ts.ClassDeclaration): ts.ClassDeclaration {
|
||||
// Determine if this class has an Ivy field that needs to be added, and compile the field
|
||||
// to an expression if so.
|
||||
const res = compilation.compileIvyFieldFor(node);
|
||||
if (res !== undefined) {
|
||||
// There is a field to add. Translate the initializer for the field into TS nodes.
|
||||
const exprNode = translateExpression(res.initializer, importManager);
|
||||
|
||||
// Create a static property declaration for the new field.
|
||||
const property = ts.createProperty(
|
||||
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
|
||||
exprNode);
|
||||
|
||||
// Replace the class declaration with an updated version.
|
||||
node = ts.updateClassDeclaration(
|
||||
node,
|
||||
// Remove the decorator which triggered this compilation, leaving the others alone.
|
||||
maybeFilterDecorator(node.decorators, compilation.ivyDecoratorFor(node) !),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
[...node.members, property]);
|
||||
}
|
||||
|
||||
// Recurse into the class declaration in case there are nested class declarations.
|
||||
return ts.visitEachChild(node, child => visitNode(child), context);
|
||||
}
|
||||
|
||||
// Helper function that recurses through the nodes and processes each one.
|
||||
function visitNode<T extends ts.Node>(node: T): T;
|
||||
function visitNode(node: ts.Node): ts.Node {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
return visitClassDeclaration(node);
|
||||
} else {
|
||||
return ts.visitEachChild(node, child => visitNode(child), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
function maybeFilterDecorator(
|
||||
decorators: ts.NodeArray<ts.Decorator>| undefined,
|
||||
toRemove: ts.Decorator): ts.NodeArray<ts.Decorator>|undefined {
|
||||
if (decorators === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const filtered = decorators.filter(dec => ts.getOriginalNode(dec) !== toRemove);
|
||||
if (filtered.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ts.createNodeArray(filtered);
|
||||
}
|
@ -1,319 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ArrayType, AssertNotNull, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export class ImportManager {
|
||||
private moduleToIndex = new Map<string, string>();
|
||||
private nextIndex = 0;
|
||||
|
||||
generateNamedImport(moduleName: string): string {
|
||||
if (!this.moduleToIndex.has(moduleName)) {
|
||||
this.moduleToIndex.set(moduleName, `i${this.nextIndex++}`);
|
||||
}
|
||||
return this.moduleToIndex.get(moduleName) !;
|
||||
}
|
||||
|
||||
getAllImports(): {name: string, as: string}[] {
|
||||
return Array.from(this.moduleToIndex.keys()).map(name => {
|
||||
const as = this.moduleToIndex.get(name) !;
|
||||
return {name, as};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function translateExpression(expression: Expression, imports: ImportManager): ts.Expression {
|
||||
return expression.visitExpression(new ExpressionTranslatorVisitor(imports), null);
|
||||
}
|
||||
|
||||
export function translateType(type: Type, imports: ImportManager): string {
|
||||
return type.visitType(new TypeTranslatorVisitor(imports), null);
|
||||
}
|
||||
|
||||
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
|
||||
constructor(private imports: ImportManager) {}
|
||||
|
||||
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitExpressionStmt(stmt: ExpressionStatement, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitReturnStmt(stmt: ReturnStatement, context: any): ts.ReturnStatement {
|
||||
return ts.createReturn(stmt.value.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitDeclareClassStmt(stmt: ClassStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitIfStmt(stmt: IfStmt, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitTryCatchStmt(stmt: TryCatchStmt, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitThrowStmt(stmt: ThrowStmt, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitCommentStmt(stmt: CommentStmt, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitJSDocCommentStmt(stmt: JSDocCommentStmt, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitReadVarExpr(ast: ReadVarExpr, context: any): ts.Identifier {
|
||||
return ts.createIdentifier(ast.name !);
|
||||
}
|
||||
|
||||
visitWriteVarExpr(expr: WriteVarExpr, context: any): ts.BinaryExpression {
|
||||
return ts.createBinary(
|
||||
ts.createIdentifier(expr.name), ts.SyntaxKind.EqualsToken,
|
||||
expr.value.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitWritePropExpr(expr: WritePropExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): ts.CallExpression {
|
||||
return ts.createCall(
|
||||
ast.fn.visitExpression(this, context), undefined,
|
||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
visitInstantiateExpr(ast: InstantiateExpr, context: any): ts.NewExpression {
|
||||
return ts.createNew(
|
||||
ast.classExpr.visitExpression(this, context), undefined,
|
||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
visitLiteralExpr(ast: LiteralExpr, context: any): ts.Expression {
|
||||
if (ast.value === undefined) {
|
||||
return ts.createIdentifier('undefined');
|
||||
} else if (ast.value === null) {
|
||||
return ts.createNull();
|
||||
} else {
|
||||
return ts.createLiteral(ast.value);
|
||||
}
|
||||
}
|
||||
|
||||
visitExternalExpr(ast: ExternalExpr, context: any): ts.PropertyAccessExpression {
|
||||
if (ast.value.moduleName === null || ast.value.name === null) {
|
||||
throw new Error(`Import unknown module or symbol ${ast.value}`);
|
||||
}
|
||||
return ts.createPropertyAccess(
|
||||
ts.createIdentifier(this.imports.generateNamedImport(ast.value.moduleName)),
|
||||
ts.createIdentifier(ast.value.name));
|
||||
}
|
||||
|
||||
visitConditionalExpr(ast: ConditionalExpr, context: any): ts.ParenthesizedExpression {
|
||||
return ts.createParen(ts.createConditional(
|
||||
ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context),
|
||||
ast.falseCase !.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
visitNotExpr(ast: NotExpr, context: any): ts.PrefixUnaryExpression {
|
||||
return ts.createPrefix(
|
||||
ts.SyntaxKind.ExclamationToken, ast.condition.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitAssertNotNullExpr(ast: AssertNotNull, context: any): ts.NonNullExpression {
|
||||
return ts.createNonNullExpression(ast.condition.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitCastExpr(ast: CastExpr, context: any): ts.Expression {
|
||||
return ast.value.visitExpression(this, context);
|
||||
}
|
||||
|
||||
visitFunctionExpr(ast: FunctionExpr, context: any): ts.FunctionExpression {
|
||||
return ts.createFunctionExpression(
|
||||
undefined, undefined, ast.name || undefined, undefined,
|
||||
ast.params.map(
|
||||
param => ts.createParameter(
|
||||
undefined, undefined, undefined, param.name, undefined, undefined, undefined)),
|
||||
undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context))));
|
||||
}
|
||||
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitReadPropExpr(ast: ReadPropExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): ts.ObjectLiteralExpression {
|
||||
const entries = ast.entries.map(
|
||||
entry => ts.createPropertyAssignment(
|
||||
entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key),
|
||||
entry.value.visitExpression(this, context)));
|
||||
return ts.createObjectLiteral(entries);
|
||||
}
|
||||
|
||||
visitCommaExpr(ast: CommaExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any { return ast.node; }
|
||||
}
|
||||
|
||||
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
|
||||
constructor(private imports: ImportManager) {}
|
||||
|
||||
visitBuiltinType(type: BuiltinType, context: any): string {
|
||||
switch (type.name) {
|
||||
case BuiltinTypeName.Bool:
|
||||
return 'boolean';
|
||||
case BuiltinTypeName.Dynamic:
|
||||
return 'any';
|
||||
case BuiltinTypeName.Int:
|
||||
case BuiltinTypeName.Number:
|
||||
return 'number';
|
||||
case BuiltinTypeName.String:
|
||||
return 'string';
|
||||
default:
|
||||
throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
|
||||
}
|
||||
}
|
||||
|
||||
visitExpressionType(type: ExpressionType, context: any): any {
|
||||
return type.value.visitExpression(this, context);
|
||||
}
|
||||
|
||||
visitArrayType(type: ArrayType, context: any): string {
|
||||
return `Array<${type.visitType(this, context)}>`;
|
||||
}
|
||||
|
||||
visitMapType(type: MapType, context: any): string {
|
||||
if (type.valueType !== null) {
|
||||
return `{[key: string]: ${type.valueType.visitType(this, context)}}`;
|
||||
} else {
|
||||
return '{[key: string]: any}';
|
||||
}
|
||||
}
|
||||
|
||||
visitReadVarExpr(ast: ReadVarExpr, context: any): string {
|
||||
if (ast.name === null) {
|
||||
throw new Error(`ReadVarExpr with no variable name in type`);
|
||||
}
|
||||
return ast.name;
|
||||
}
|
||||
|
||||
visitWriteVarExpr(expr: WriteVarExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitWritePropExpr(expr: WritePropExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitInstantiateExpr(ast: InstantiateExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralExpr(ast: LiteralExpr, context: any): string {
|
||||
if (typeof ast.value === 'string') {
|
||||
const escaped = ast.value.replace(/\'/g, '\\\'');
|
||||
return `'${escaped}'`;
|
||||
} else {
|
||||
return `${ast.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
visitExternalExpr(ast: ExternalExpr, context: any): string {
|
||||
if (ast.value.moduleName === null || ast.value.name === null) {
|
||||
throw new Error(`Import unknown module or symbol`);
|
||||
}
|
||||
const base = `${this.imports.generateNamedImport(ast.value.moduleName)}.${ast.value.name}`;
|
||||
if (ast.typeParams !== null) {
|
||||
const generics = ast.typeParams.map(type => type.visitType(this, context)).join(', ');
|
||||
return `${base}<${generics}>`;
|
||||
} else {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
visitConditionalExpr(ast: ConditionalExpr, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitNotExpr(ast: NotExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitAssertNotNullExpr(ast: AssertNotNull, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitCastExpr(ast: CastExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitFunctionExpr(ast: FunctionExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitReadPropExpr(ast: ReadPropExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitCommaExpr(ast: CommaExpr, context: any) { throw new Error('Method not implemented.'); }
|
||||
|
||||
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any) {
|
||||
const node: ts.Node = ast.node;
|
||||
if (ts.isIdentifier(node)) {
|
||||
return node.text;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "util",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/util",
|
||||
)
|
@ -1,118 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
/**
|
||||
* Result type of visiting a node that's typically an entry in a list, which allows specifying that
|
||||
* nodes should be added before the visited node in the output.
|
||||
*/
|
||||
export type VisitListEntryResult<B extends ts.Node, T extends B> = {
|
||||
node: T,
|
||||
before?: B[]
|
||||
};
|
||||
|
||||
/**
|
||||
* Visit a node with the given visitor and return a transformed copy.
|
||||
*/
|
||||
export function visit<T extends ts.Node>(
|
||||
node: T, visitor: Visitor, context: ts.TransformationContext): T {
|
||||
return visitor._visit(node, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for visitors, which processes certain nodes specially to allow insertion
|
||||
* of other nodes before them.
|
||||
*/
|
||||
export abstract class Visitor {
|
||||
/**
|
||||
* Maps statements to an array of statements that should be inserted before them.
|
||||
*/
|
||||
private _before = new Map<ts.Statement, ts.Statement[]>();
|
||||
|
||||
/**
|
||||
* Visit a class declaration, returning at least the transformed declaration and optionally other
|
||||
* nodes to insert before the declaration.
|
||||
*/
|
||||
visitClassDeclaration(node: ts.ClassDeclaration):
|
||||
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
|
||||
return {node};
|
||||
}
|
||||
|
||||
private _visitClassDeclaration(node: ts.ClassDeclaration, context: ts.TransformationContext):
|
||||
ts.ClassDeclaration {
|
||||
const result = this.visitClassDeclaration(node);
|
||||
const visited = ts.visitEachChild(result.node, child => this._visit(child, context), context);
|
||||
if (result.before !== undefined) {
|
||||
// Record that some nodes should be inserted before the given declaration. The declaration's
|
||||
// parent's _visit call is responsible for performing this insertion.
|
||||
this._before.set(visited, result.before);
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit types of nodes which don't have their own explicit visitor.
|
||||
*/
|
||||
visitOtherNode<T extends ts.Node>(node: T): T { return node; }
|
||||
|
||||
private _visitOtherNode<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
|
||||
return ts.visitEachChild(
|
||||
this.visitOtherNode(node), child => this._visit(child, context), context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_visit<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
|
||||
// First, visit the node. visitedNode starts off as `null` but should be set after visiting
|
||||
// is completed.
|
||||
let visitedNode: T|null = null;
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
visitedNode = this._visitClassDeclaration(node, context) as typeof node;
|
||||
} else {
|
||||
visitedNode = this._visitOtherNode(node, context);
|
||||
}
|
||||
|
||||
// If the visited node has a `statements` array then process them, maybe replacing the visited
|
||||
// node and adding additional statements.
|
||||
if (hasStatements(visitedNode)) {
|
||||
visitedNode = this._maybeProcessStatements(visitedNode);
|
||||
}
|
||||
|
||||
return visitedNode;
|
||||
}
|
||||
|
||||
private _maybeProcessStatements<T extends ts.Node&{statements: ts.NodeArray<ts.Statement>}>(
|
||||
node: T): T {
|
||||
// Shortcut - if every statement doesn't require nodes to be prepended, this is a no-op.
|
||||
if (node.statements.every(stmt => !this._before.has(stmt))) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// There are statements to prepend, so clone the original node.
|
||||
const clone = ts.getMutableClone(node);
|
||||
|
||||
// Build a new list of statements and patch it onto the clone.
|
||||
const newStatements: ts.Statement[] = [];
|
||||
clone.statements.forEach(stmt => {
|
||||
if (this._before.has(stmt)) {
|
||||
newStatements.push(...(this._before.get(stmt) !as ts.Statement[]));
|
||||
this._before.delete(stmt);
|
||||
}
|
||||
newStatements.push(stmt);
|
||||
});
|
||||
clone.statements = ts.createNodeArray(newStatements, node.statements.hasTrailingComma);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
function hasStatements(node: ts.Node): node is ts.Node&{statements: ts.NodeArray<ts.Statement>} {
|
||||
const block = node as{statements?: any};
|
||||
return block.statements !== undefined && Array.isArray(block.statements);
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = 1,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
|
||||
deps = [
|
||||
":test_lib",
|
||||
"//tools/testing:node_no_angular",
|
||||
],
|
||||
)
|
@ -1,94 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {VisitListEntryResult, Visitor, visit} from '../src/visitor';
|
||||
|
||||
class TestAstVisitor extends Visitor {
|
||||
visitClassDeclaration(node: ts.ClassDeclaration):
|
||||
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
|
||||
const name = node.name !.text;
|
||||
const statics =
|
||||
node.members.filter(member => (member.modifiers as ReadonlyArray<ts.Modifier>|| [
|
||||
]).some(mod => mod.kind === ts.SyntaxKind.StaticKeyword));
|
||||
const idStatic = statics
|
||||
.find(
|
||||
el => ts.isPropertyDeclaration(el) && ts.isIdentifier(el.name) &&
|
||||
el.name.text === 'id') as ts.PropertyDeclaration |
|
||||
undefined;
|
||||
if (idStatic !== undefined) {
|
||||
return {
|
||||
node,
|
||||
before: [
|
||||
ts.createVariableStatement(
|
||||
undefined,
|
||||
[
|
||||
ts.createVariableDeclaration(`${name}_id`, undefined, idStatic.initializer),
|
||||
]),
|
||||
],
|
||||
};
|
||||
}
|
||||
return {node};
|
||||
}
|
||||
}
|
||||
|
||||
function testTransformerFactory(context: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
||||
return (file: ts.SourceFile) => visit(file, new TestAstVisitor(), context);
|
||||
}
|
||||
|
||||
describe('AST Visitor', () => {
|
||||
it('should add a statement before class in plain file', () => {
|
||||
const {program, host} =
|
||||
makeProgram([{name: 'main.ts', contents: `class A { static id = 3; }`}]);
|
||||
const sf = program.getSourceFile('main.ts') !;
|
||||
program.emit(sf, undefined, undefined, undefined, {before: [testTransformerFactory]});
|
||||
const main = host.readFile('/main.js');
|
||||
expect(main).toMatch(/^var A_id = 3;/);
|
||||
});
|
||||
|
||||
it('should add a statement before class inside function definition', () => {
|
||||
const {program, host} = makeProgram([{
|
||||
name: 'main.ts',
|
||||
contents: `
|
||||
export function foo() {
|
||||
var x = 3;
|
||||
class A { static id = 2; }
|
||||
return A;
|
||||
}
|
||||
`
|
||||
}]);
|
||||
const sf = program.getSourceFile('main.ts') !;
|
||||
program.emit(sf, undefined, undefined, undefined, {before: [testTransformerFactory]});
|
||||
const main = host.readFile('/main.js');
|
||||
expect(main).toMatch(/var x = 3;\s+var A_id = 2;\s+var A =/);
|
||||
});
|
||||
|
||||
it('handles nested statements', () => {
|
||||
const {program, host} = makeProgram([{
|
||||
name: 'main.ts',
|
||||
contents: `
|
||||
export class A {
|
||||
static id = 3;
|
||||
|
||||
foo() {
|
||||
class B {
|
||||
static id = 4;
|
||||
}
|
||||
return B;
|
||||
}
|
||||
}`
|
||||
}]);
|
||||
const sf = program.getSourceFile('main.ts') !;
|
||||
program.emit(sf, undefined, undefined, undefined, {before: [testTransformerFactory]});
|
||||
const main = host.readFile('/main.js');
|
||||
expect(main).toMatch(/var A_id = 3;\s+var A = /);
|
||||
expect(main).toMatch(/var B_id = 4;\s+var B = /);
|
||||
});
|
||||
});
|
@ -182,17 +182,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
||||
* Not all features are supported with this option enabled. It is only supported
|
||||
* for experimentation and testing of Render3 style code generation.
|
||||
*
|
||||
* Acceptable values are as follows:
|
||||
*
|
||||
* `false` - run ngc normally
|
||||
* `true` - run ngc with its usual global analysis, but compile decorators to Ivy fields instead
|
||||
* of running the View Engine compilers
|
||||
* `ngtsc` - run the ngtsc compiler instead of the normal ngc compiler
|
||||
* `tsc` - behave like plain tsc as much as possible (used for testing JIT code)
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
enableIvy?: boolean|'ngtsc'|'tsc';
|
||||
enableIvy?: boolean;
|
||||
|
||||
/** @internal */
|
||||
collectAllErrors?: boolean;
|
||||
|
@ -12,7 +12,6 @@ import * as ts from 'typescript';
|
||||
|
||||
import {TypeCheckHost} from '../diagnostics/translate_diagnostics';
|
||||
import {METADATA_VERSION, ModuleMetadata} from '../metadata/index';
|
||||
import {NgtscCompilerHost} from '../ngtsc/compiler_host';
|
||||
|
||||
import {CompilerHost, CompilerOptions, LibrarySummary} from './api';
|
||||
import {MetadataReaderHost, createMetadataReaderCache, readMetadata} from './metadata_reader';
|
||||
@ -24,9 +23,6 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
export function createCompilerHost(
|
||||
{options, tsHost = ts.createCompilerHost(options, true)}:
|
||||
{options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost {
|
||||
if (options.enableIvy === 'ngtsc' || options.enableIvy === 'tsc') {
|
||||
return new NgtscCompilerHost(tsHost);
|
||||
}
|
||||
return tsHost;
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {error} from './util';
|
||||
|
||||
export interface Node { sourceSpan: ParseSourceSpan|null; }
|
||||
@ -21,7 +20,7 @@ const _VALID_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i;
|
||||
export class TypeScriptNodeEmitter {
|
||||
updateSourceFile(sourceFile: ts.SourceFile, stmts: Statement[], preamble?: string):
|
||||
[ts.SourceFile, Map<ts.Node, Node>] {
|
||||
const converter = new NodeEmitterVisitor();
|
||||
const converter = new _NodeEmitterVisitor();
|
||||
// [].concat flattens the result so that each `visit...` method can also return an array of
|
||||
// stmts.
|
||||
const statements: any[] = [].concat(
|
||||
@ -63,7 +62,7 @@ export class TypeScriptNodeEmitter {
|
||||
export function updateSourceFile(
|
||||
sourceFile: ts.SourceFile, module: PartialModule,
|
||||
context: ts.TransformationContext): [ts.SourceFile, Map<ts.Node, Node>] {
|
||||
const converter = new NodeEmitterVisitor();
|
||||
const converter = new _NodeEmitterVisitor();
|
||||
converter.loadExportedVariableIdentifiers(sourceFile);
|
||||
|
||||
const prefixStatements = module.statements.filter(statement => !(statement instanceof ClassStmt));
|
||||
@ -149,7 +148,7 @@ function firstAfter<T>(a: T[], predicate: (value: T) => boolean) {
|
||||
// A recorded node is a subtype of the node that is marked as being recorded. This is used
|
||||
// to ensure that NodeEmitterVisitor.record has been called on all nodes returned by the
|
||||
// NodeEmitterVisitor
|
||||
export type RecordedNode<T extends ts.Node = ts.Node> = (T & { __recorded: any;}) | null;
|
||||
type RecordedNode<T extends ts.Node = ts.Node> = (T & { __recorded: any; }) | null;
|
||||
|
||||
function escapeLiteral(value: string): string {
|
||||
return value.replace(/(\"|\\)/g, '\\$1').replace(/(\n)|(\r)/g, function(v, n, r) {
|
||||
@ -184,7 +183,7 @@ function isExportTypeStatement(statement: ts.Statement): boolean {
|
||||
/**
|
||||
* Visits an output ast and produces the corresponding TypeScript synthetic nodes.
|
||||
*/
|
||||
export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
||||
class _NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
||||
private _nodeMap = new Map<ts.Node, Node>();
|
||||
private _importsWithPrefixes = new Map<string, string>();
|
||||
private _reexports = new Map<string, {name: string, as: string}[]>();
|
||||
@ -462,9 +461,6 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
|
||||
return commentStmt;
|
||||
}
|
||||
|
||||
// ExpressionVisitor
|
||||
visitWrappedNodeExpr(expr: WrappedNodeExpr<any>) { return this.record(expr, expr.node); }
|
||||
|
||||
// ExpressionVisitor
|
||||
visitReadVarExpr(expr: ReadVarExpr) {
|
||||
switch (expr.builtin) {
|
||||
|
@ -15,7 +15,6 @@ import * as ts from 'typescript';
|
||||
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
|
||||
import {compareVersions} from '../diagnostics/typescript_version';
|
||||
import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata/index';
|
||||
import {NgtscProgram} from '../ngtsc/program';
|
||||
|
||||
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback, TsMergeEmitResultsCallback} from './api';
|
||||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||
@ -26,7 +25,6 @@ import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
|
||||
import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators';
|
||||
import {getAngularClassTransformerFactory} from './r3_transform';
|
||||
import {TscPassThroughProgram} from './tsc_pass_through';
|
||||
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
|
||||
|
||||
|
||||
@ -109,7 +107,7 @@ const MIN_TS_VERSION = '2.7.2';
|
||||
* ∀ supported typescript version v, v < MAX_TS_VERSION
|
||||
* MAX_TS_VERSION is not considered as a supported TypeScript version
|
||||
*/
|
||||
const MAX_TS_VERSION = '2.9.0';
|
||||
const MAX_TS_VERSION = '2.8.0';
|
||||
|
||||
class AngularCompilerProgram implements Program {
|
||||
private rootNames: string[];
|
||||
@ -288,9 +286,6 @@ class AngularCompilerProgram implements Program {
|
||||
emitCallback?: TsEmitCallback,
|
||||
mergeEmitResultsCallback?: TsMergeEmitResultsCallback,
|
||||
} = {}): ts.EmitResult {
|
||||
if (this.options.enableIvy === 'ngtsc' || this.options.enableIvy === 'tsc') {
|
||||
throw new Error('Cannot run legacy compiler in ngtsc mode');
|
||||
}
|
||||
return this.options.enableIvy === true ? this._emitRender3(parameters) :
|
||||
this._emitRender2(parameters);
|
||||
}
|
||||
@ -338,34 +333,14 @@ class AngularCompilerProgram implements Program {
|
||||
/* genFiles */ undefined, /* partialModules */ modules,
|
||||
/* stripDecorators */ this.reifiedDecorators, customTransformers);
|
||||
|
||||
|
||||
// Restore the original references before we emit so TypeScript doesn't emit
|
||||
// a reference to the .d.ts file.
|
||||
const augmentedReferences = new Map<ts.SourceFile, ReadonlyArray<ts.FileReference>>();
|
||||
for (const sourceFile of this.tsProgram.getSourceFiles()) {
|
||||
const originalReferences = getOriginalReferences(sourceFile);
|
||||
if (originalReferences) {
|
||||
augmentedReferences.set(sourceFile, sourceFile.referencedFiles);
|
||||
sourceFile.referencedFiles = originalReferences;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
writeFile: writeTsFile, emitOnlyDtsFiles,
|
||||
customTransformers: tsCustomTransformers
|
||||
});
|
||||
} finally {
|
||||
// Restore the references back to the augmented value to ensure that the
|
||||
// checks that TypeScript makes for project structure reuse will succeed.
|
||||
for (const [sourceFile, references] of Array.from(augmentedReferences)) {
|
||||
// TODO(chuckj): Remove any cast after updating build to 2.6
|
||||
(sourceFile as any).referencedFiles = references;
|
||||
}
|
||||
}
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
writeFile: writeTsFile, emitOnlyDtsFiles,
|
||||
customTransformers: tsCustomTransformers
|
||||
});
|
||||
return emitResult;
|
||||
}
|
||||
|
||||
private _emitRender2(
|
||||
@ -928,11 +903,6 @@ export function createProgram({rootNames, options, host, oldProgram}: {
|
||||
options: CompilerOptions,
|
||||
host: CompilerHost, oldProgram?: Program
|
||||
}): Program {
|
||||
if (options.enableIvy === 'ngtsc') {
|
||||
return new NgtscProgram(rootNames, options, host, oldProgram);
|
||||
} else if (options.enableIvy === 'tsc') {
|
||||
return new TscPassThroughProgram(rootNames, options, host, oldProgram);
|
||||
}
|
||||
return new AngularCompilerProgram(rootNames, options, host, oldProgram);
|
||||
}
|
||||
|
||||
|
@ -1,107 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {GeneratedFile} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import * as api from '../transformers/api';
|
||||
|
||||
/**
|
||||
* An implementation of the `Program` API which behaves like plain `tsc` and does not include any
|
||||
* Angular-specific behavior whatsoever.
|
||||
*
|
||||
* This allows `ngc` to behave like `tsc` in cases where JIT code needs to be tested.
|
||||
*/
|
||||
export class TscPassThroughProgram implements api.Program {
|
||||
private tsProgram: ts.Program;
|
||||
|
||||
constructor(
|
||||
rootNames: ReadonlyArray<string>, private options: api.CompilerOptions,
|
||||
private host: api.CompilerHost, oldProgram?: api.Program) {
|
||||
this.tsProgram =
|
||||
ts.createProgram(rootNames, options, host, oldProgram && oldProgram.getTsProgram());
|
||||
}
|
||||
|
||||
getTsProgram(): ts.Program { return this.tsProgram; }
|
||||
|
||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
||||
}
|
||||
|
||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getTsSyntacticDiagnostics(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken|
|
||||
undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
getTsSemanticDiagnostics(
|
||||
sourceFile?: ts.SourceFile|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
||||
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||
}
|
||||
|
||||
getNgSemanticDiagnostics(
|
||||
fileName?: string|undefined,
|
||||
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<api.Diagnostic> {
|
||||
return [];
|
||||
}
|
||||
|
||||
loadNgStructureAsync(): Promise<void> { return Promise.resolve(); }
|
||||
|
||||
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
emit(opts?: {
|
||||
emitFlags?: api.EmitFlags,
|
||||
cancellationToken?: ts.CancellationToken,
|
||||
customTransformers?: api.CustomTransformers,
|
||||
emitCallback?: api.TsEmitCallback,
|
||||
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback
|
||||
}): ts.EmitResult {
|
||||
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
||||
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
emitOnlyDtsFiles: false,
|
||||
});
|
||||
return emitResult;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultEmitCallback: api.TsEmitCallback =
|
||||
({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
|
||||
customTransformers}) =>
|
||||
program.emit(
|
||||
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
@ -2021,7 +2021,6 @@ describe('ngc transformer command-line', () => {
|
||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
||||
expect(exitCode).toBe(0, 'Compile failed');
|
||||
expect(emittedFile('hello-world.js')).toContain('ngComponentDef');
|
||||
expect(emittedFile('hello-world.js')).toContain('HelloWorldComponent_Factory');
|
||||
});
|
||||
|
||||
it('should emit an injection of a string token', () => {
|
||||
|
@ -1,27 +0,0 @@
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "ngtsc_lib",
|
||||
testonly = 1,
|
||||
srcs = [
|
||||
"ngtsc_spec.ts",
|
||||
],
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/test:test_utils",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "ngtsc",
|
||||
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
|
||||
data = [
|
||||
"//packages/compiler-cli/test/ngtsc/fake_core:npm_package",
|
||||
],
|
||||
deps = [
|
||||
":ngtsc_lib",
|
||||
"//tools/testing:node_no_angular",
|
||||
],
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ng_package", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "fake_core",
|
||||
srcs = [
|
||||
"index.ts",
|
||||
],
|
||||
module_name = "@angular/core",
|
||||
)
|
||||
|
||||
ng_package(
|
||||
name = "npm_package",
|
||||
srcs = [
|
||||
"package.json",
|
||||
],
|
||||
entry_point = "packages/fake_core/index.js",
|
||||
deps = [
|
||||
":fake_core",
|
||||
],
|
||||
)
|
@ -1,13 +0,0 @@
|
||||
`fake_core` is a library designed to expose some of the same symbols as `@angular/core`, without
|
||||
requiring compilation of the whole of `@angular/core`. This enables unit tests for the compiler to
|
||||
be written without incurring long rebuilds for every change.
|
||||
|
||||
* `@angular/core` is compiled with `@angular/compiler-cli`, and therefore has an implicit dependency
|
||||
on it. Therefore core must be rebuilt if the compiler changes.
|
||||
* Tests for the compiler which intend to build code that depends on `@angular/core` must have
|
||||
a data dependency on `@angular/core`. Therefore core must be built to run the compiler tests, and
|
||||
thus rebuilt if the compiler changes.
|
||||
|
||||
This rebuild cycle is expensive and slow. `fake_core` avoids this by exposing a subset of the
|
||||
`@angular/core` API, which enables applications to be built by the ngtsc compiler without
|
||||
needing a full version of core present at compile time.
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
type FnWithArg<T> = (arg?: any) => T;
|
||||
|
||||
function callableClassDecorator(): FnWithArg<(clazz: any) => any> {
|
||||
return null !;
|
||||
}
|
||||
|
||||
function callableParamDecorator(): FnWithArg<(a: any, b: any, c: any) => void> {
|
||||
return null !;
|
||||
}
|
||||
|
||||
export const Injectable = callableClassDecorator();
|
||||
export const NgModule = callableClassDecorator();
|
||||
|
||||
export const Inject = callableParamDecorator();
|
||||
export const Self = callableParamDecorator();
|
||||
export const SkipSelf = callableParamDecorator();
|
||||
export const Optional = callableParamDecorator();
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "@angular/core",
|
||||
"version": "0.0.0-FAKE-FOR-TESTS",
|
||||
"description": "Fake version of core for ngtsc tests",
|
||||
"main": "./bundles/fake_core.umd.js",
|
||||
"module": "./fesm5/fake_core.js",
|
||||
"es2015": "./fesm2015/fake_core.js",
|
||||
"esm5": "./esm5/index.js",
|
||||
"esm2015": "./esm2015/index.js",
|
||||
"fesm5": "./fesm5/fake_core.js",
|
||||
"fesm2015": "./fesm2015/fake_core.js",
|
||||
"typings": "./index.d.ts"
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {main, readCommandLineAndConfiguration, watchMode} from '../../src/main';
|
||||
import {TestSupport, isInBazel, makeTempDir, setup} from '../test_support';
|
||||
|
||||
function setupFakeCore(support: TestSupport): void {
|
||||
const fakeCore = path.join(
|
||||
process.env.TEST_SRCDIR, 'angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package');
|
||||
|
||||
const nodeModulesPath = path.join(support.basePath, 'node_modules');
|
||||
const angularCoreDirectory = path.join(nodeModulesPath, '@angular/core');
|
||||
|
||||
fs.symlinkSync(fakeCore, angularCoreDirectory);
|
||||
}
|
||||
|
||||
function getNgRootDir() {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
const distIndex = moduleFilename.indexOf('/dist/all');
|
||||
return moduleFilename.substr(0, distIndex);
|
||||
}
|
||||
|
||||
describe('ngtsc behavioral tests', () => {
|
||||
if (!isInBazel()) {
|
||||
// These tests should be excluded from the non-Bazel build.
|
||||
return;
|
||||
}
|
||||
|
||||
let basePath: string;
|
||||
let outDir: string;
|
||||
let write: (fileName: string, content: string) => void;
|
||||
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||
|
||||
function shouldExist(fileName: string) {
|
||||
if (!fs.existsSync(path.resolve(outDir, fileName))) {
|
||||
throw new Error(`Expected ${fileName} to be emitted (outDir: ${outDir})`);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldNotExist(fileName: string) {
|
||||
if (fs.existsSync(path.resolve(outDir, fileName))) {
|
||||
throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`);
|
||||
}
|
||||
}
|
||||
|
||||
function getContents(fileName: string): string {
|
||||
shouldExist(fileName);
|
||||
const modulePath = path.resolve(outDir, fileName);
|
||||
return fs.readFileSync(modulePath, 'utf8');
|
||||
}
|
||||
|
||||
function writeConfig(
|
||||
tsconfig: string =
|
||||
'{"extends": "./tsconfig-base.json", "angularCompilerOptions": {"enableIvy": "ngtsc"}}') {
|
||||
write('tsconfig.json', tsconfig);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
|
||||
const support = setup();
|
||||
basePath = support.basePath;
|
||||
outDir = path.join(basePath, 'built');
|
||||
process.chdir(basePath);
|
||||
write = (fileName: string, content: string) => { support.write(fileName, content); };
|
||||
|
||||
setupFakeCore(support);
|
||||
write('tsconfig-base.json', `{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": true,
|
||||
"types": [],
|
||||
"outDir": "built",
|
||||
"rootDir": ".",
|
||||
"baseUrl": ".",
|
||||
"declaration": true,
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es6", "dom"],
|
||||
"typeRoots": ["node_modules/@types"]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": "ngtsc"
|
||||
}
|
||||
}`);
|
||||
});
|
||||
|
||||
it('should compile without errors', () => {
|
||||
writeConfig();
|
||||
write('test.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class Dep {}
|
||||
|
||||
@Injectable()
|
||||
export class Service {
|
||||
constructor(dep: Dep) {}
|
||||
}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
|
||||
const jsContents = getContents('test.js');
|
||||
expect(jsContents).toContain('Dep.ngInjectableDef =');
|
||||
expect(jsContents).toContain('Service.ngInjectableDef =');
|
||||
expect(jsContents).not.toContain('__decorate');
|
||||
const dtsContents = getContents('test.d.ts');
|
||||
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Dep>;');
|
||||
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Service>;');
|
||||
});
|
||||
});
|
@ -21,10 +21,7 @@ ng_package(
|
||||
],
|
||||
entry_point = "packages/compiler/compiler.js",
|
||||
include_devmode_srcs = True,
|
||||
tags = [
|
||||
"ivy-jit",
|
||||
"release-with-framework",
|
||||
],
|
||||
tags = ["release-with-framework"],
|
||||
deps = [
|
||||
":compiler",
|
||||
"//packages/compiler/testing",
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, templateSourceUrl, tokenReference} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import {ViewEncapsulation} from '../core';
|
||||
@ -14,19 +14,16 @@ import {MessageBundle} from '../i18n/message_bundle';
|
||||
import {Identifiers, createTokenForExternalReference} from '../identifiers';
|
||||
import {InjectableCompiler} from '../injectable_compiler';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {removeWhitespaces} from '../ml_parser/html_whitespaces';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {OutputEmitter} from '../output/abstract_emitter';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {compileNgModuleFromRender2 as compileR3Module} from '../render3/r3_module_compiler';
|
||||
import {compilePipe as compileR3Pipe} from '../render3/r3_pipe_compiler';
|
||||
import {htmlAstToRender3Ast} from '../render3/r3_template_transform';
|
||||
import {compileComponentFromRender2 as compileR3Component, compileDirectiveFromRender2 as compileR3Directive} from '../render3/view/compiler';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {compileNgModule as compileIvyModule} from '../render3/r3_module_compiler';
|
||||
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
|
||||
import {OutputMode} from '../render3/r3_types';
|
||||
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
@ -43,11 +40,15 @@ import {LazyRoute, listLazyRoutes, parseLazyRoute} from './lazy_routes';
|
||||
import {PartialModule} from './partial_module';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {createForJitStub, serializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName} from './util';
|
||||
import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
|
||||
|
||||
const enum StubEmitFlags { Basic = 1 << 0, TypeCheck = 1 << 1, All = TypeCheck | Basic }
|
||||
enum StubEmitFlags {
|
||||
Basic = 1 << 0,
|
||||
TypeCheck = 1 << 1,
|
||||
All = TypeCheck | Basic
|
||||
}
|
||||
|
||||
export class AotCompiler {
|
||||
private _templateAstCache =
|
||||
@ -362,19 +363,18 @@ export class AotCompiler {
|
||||
private _compileShallowModules(
|
||||
fileName: string, shallowModules: CompileShallowModuleMetadata[],
|
||||
context: OutputContext): void {
|
||||
shallowModules.forEach(module => compileR3Module(context, module, this._injectableCompiler));
|
||||
shallowModules.forEach(module => compileIvyModule(context, module, this._injectableCompiler));
|
||||
}
|
||||
|
||||
private _compilePartialModule(
|
||||
fileName: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
|
||||
injectables: CompileInjectableMetadata[], context: OutputContext): void {
|
||||
const classes: o.ClassStmt[] = [];
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
const schemaRegistry = new DomElementSchemaRegistry();
|
||||
const hostBindingParser = new BindingParser(
|
||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [],
|
||||
errors);
|
||||
this._templateParser.expressionParser, DEFAULT_INTERPOLATION_CONFIG, null !, [], errors);
|
||||
|
||||
// Process all components and directives
|
||||
directives.forEach(directiveType => {
|
||||
@ -385,46 +385,21 @@ export class AotCompiler {
|
||||
error(
|
||||
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
|
||||
|
||||
let htmlAst = directiveMetadata.template !.htmlAst !;
|
||||
const preserveWhitespaces = directiveMetadata !.template !.preserveWhitespaces;
|
||||
|
||||
if (!preserveWhitespaces) {
|
||||
htmlAst = removeWhitespaces(htmlAst);
|
||||
}
|
||||
const render3Ast = htmlAstToRender3Ast(htmlAst.rootNodes, hostBindingParser);
|
||||
|
||||
// Map of StaticType by directive selectors
|
||||
const directiveTypeBySel = new Map<string, any>();
|
||||
|
||||
const directives = module.transitiveModule.directives.map(
|
||||
dir => this._metadataResolver.getDirectiveSummary(dir.reference));
|
||||
|
||||
directives.forEach(directive => {
|
||||
if (directive.selector) {
|
||||
directiveTypeBySel.set(directive.selector, directive.type.reference);
|
||||
}
|
||||
});
|
||||
|
||||
// Map of StaticType by pipe names
|
||||
const pipeTypeByName = new Map<string, any>();
|
||||
|
||||
const pipes = module.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
|
||||
|
||||
pipes.forEach(pipe => { pipeTypeByName.set(pipe.name, pipe.type.reference); });
|
||||
|
||||
compileR3Component(
|
||||
context, directiveMetadata, render3Ast, this.reflector, hostBindingParser,
|
||||
directiveTypeBySel, pipeTypeByName);
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
|
||||
compileIvyComponent(
|
||||
context, directiveMetadata, parsedPipes, parsedTemplate, this.reflector,
|
||||
hostBindingParser, OutputMode.PartialClass);
|
||||
} else {
|
||||
compileR3Directive(context, directiveMetadata, this.reflector, hostBindingParser);
|
||||
compileIvyDirective(
|
||||
context, directiveMetadata, this.reflector, hostBindingParser, OutputMode.PartialClass);
|
||||
}
|
||||
});
|
||||
|
||||
pipes.forEach(pipeType => {
|
||||
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
|
||||
if (pipeMetadata) {
|
||||
compileR3Pipe(context, pipeMetadata, this.reflector);
|
||||
compileIvyPipe(context, pipeMetadata, this.reflector, OutputMode.PartialClass);
|
||||
}
|
||||
});
|
||||
|
||||
@ -918,13 +893,16 @@ export function analyzeFileForInjectables(
|
||||
if (!symbolMeta || symbolMeta.__symbolic === 'error') {
|
||||
return;
|
||||
}
|
||||
let isNgSymbol = false;
|
||||
if (symbolMeta.__symbolic === 'class') {
|
||||
if (metadataResolver.isInjectable(symbol)) {
|
||||
isNgSymbol = true;
|
||||
const injectable = metadataResolver.getInjectableMetadata(symbol, null, false);
|
||||
if (injectable) {
|
||||
injectables.push(injectable);
|
||||
}
|
||||
} else if (metadataResolver.isNgModule(symbol)) {
|
||||
isNgSymbol = true;
|
||||
const module = metadataResolver.getShallowModuleMetadata(symbol);
|
||||
if (module) {
|
||||
shallowModules.push(module);
|
||||
|
@ -18,5 +18,5 @@ export interface AotCompilerOptions {
|
||||
fullTemplateTypeCheck?: boolean;
|
||||
allowEmptyCodegenFiles?: boolean;
|
||||
strictInjectionParameters?: boolean;
|
||||
enableIvy?: boolean|'ngtsc'|'tsc';
|
||||
enableIvy?: boolean;
|
||||
}
|
||||
|
@ -50,7 +50,6 @@ export {JitCompiler} from './jit/compiler';
|
||||
export * from './compile_reflector';
|
||||
export * from './url_resolver';
|
||||
export * from './resource_loader';
|
||||
export {ConstantPool} from './constant_pool';
|
||||
export {DirectiveResolver} from './directive_resolver';
|
||||
export {PipeResolver} from './pipe_resolver';
|
||||
export {NgModuleResolver} from './ng_module_resolver';
|
||||
@ -68,7 +67,7 @@ export * from './ml_parser/html_tags';
|
||||
export * from './ml_parser/interpolation_config';
|
||||
export * from './ml_parser/tags';
|
||||
export {NgModuleCompiler} from './ng_module_compiler';
|
||||
export {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinType, BuiltinTypeName, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
|
||||
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
|
||||
export {EmitterVisitorContext} from './output/abstract_emitter';
|
||||
export * from './output/ts_emitter';
|
||||
export * from './parse_util';
|
||||
@ -79,11 +78,4 @@ export * from './template_parser/template_parser';
|
||||
export {ViewCompiler} from './view_compiler/view_compiler';
|
||||
export {getParseErrors, isSyntaxError, syntaxError, Version} from './util';
|
||||
export {SourceMap} from './output/source_map';
|
||||
export * from './injectable_compiler_2';
|
||||
export * from './render3/view/api';
|
||||
export {jitExpression} from './render3/r3_jit';
|
||||
export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory';
|
||||
export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler';
|
||||
export {makeBindingParser, parseTemplate} from './render3/view/template';
|
||||
export {compileComponentFromMetadata, compileDirectiveFromMetadata} from './render3/view/compiler';
|
||||
// This file only reexports content of the `src` folder. Keep it that way.
|
@ -295,9 +295,7 @@ class KeyVisitor implements o.ExpressionVisitor {
|
||||
`EX:${ast.value.runtime.name}`;
|
||||
}
|
||||
|
||||
visitReadVarExpr(node: o.ReadVarExpr) { return `VAR:${node.name}`; }
|
||||
|
||||
visitWrappedNodeExpr = invalid;
|
||||
visitReadVarExpr = invalid;
|
||||
visitWriteVarExpr = invalid;
|
||||
visitWriteKeyExpr = invalid;
|
||||
visitWritePropExpr = invalid;
|
||||
|
@ -12,8 +12,6 @@
|
||||
// This is important to prevent a build cycle, as @angular/core needs to
|
||||
// be compiled with the compiler.
|
||||
|
||||
import {CssSelector} from './selector';
|
||||
|
||||
export interface Inject { token: any; }
|
||||
export const createInject = makeMetadataFactory<Inject>('Inject', (token: any) => ({token}));
|
||||
export const createInjectionToken = makeMetadataFactory<object>(
|
||||
@ -297,84 +295,3 @@ export interface Route {
|
||||
children?: Route[];
|
||||
loadChildren?: string|Type|any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags used to generate R3-style CSS Selectors. They are pasted from
|
||||
* core/src/render3/projection.ts because they cannot be referenced directly.
|
||||
*/
|
||||
export const enum SelectorFlags {
|
||||
/** Indicates this is the beginning of a new negative selector */
|
||||
NOT = 0b0001,
|
||||
|
||||
/** Mode for matching attributes */
|
||||
ATTRIBUTE = 0b0010,
|
||||
|
||||
/** Mode for matching tag names */
|
||||
ELEMENT = 0b0100,
|
||||
|
||||
/** Mode for matching class names */
|
||||
CLASS = 0b1000,
|
||||
}
|
||||
|
||||
// These are a copy the CSS types from core/src/render3/interfaces/projection.ts
|
||||
// They are duplicated here as they cannot be directly referenced from core.
|
||||
export type R3CssSelector = (string | SelectorFlags)[];
|
||||
export type R3CssSelectorList = R3CssSelector[];
|
||||
|
||||
function parserSelectorToSimpleSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
const elementName = selector.element && selector.element !== '*' ? selector.element : '';
|
||||
return [elementName, ...selector.attrs, ...classes];
|
||||
}
|
||||
|
||||
function parserSelectorToNegativeSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
|
||||
if (selector.element) {
|
||||
return [
|
||||
SelectorFlags.NOT | SelectorFlags.ELEMENT, selector.element, ...selector.attrs, ...classes
|
||||
];
|
||||
} else if (selector.attrs.length) {
|
||||
return [SelectorFlags.NOT | SelectorFlags.ATTRIBUTE, ...selector.attrs, ...classes];
|
||||
} else {
|
||||
return selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.NOT | SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
}
|
||||
}
|
||||
|
||||
function parserSelectorToR3Selector(selector: CssSelector): R3CssSelector {
|
||||
const positive = parserSelectorToSimpleSelector(selector);
|
||||
|
||||
const negative: R3CssSelectorList = selector.notSelectors && selector.notSelectors.length ?
|
||||
selector.notSelectors.map(notSelector => parserSelectorToNegativeSelector(notSelector)) :
|
||||
[];
|
||||
|
||||
return positive.concat(...negative);
|
||||
}
|
||||
|
||||
export function parseSelectorToR3Selector(selector: string): R3CssSelectorList {
|
||||
const selectors = CssSelector.parse(selector);
|
||||
return selectors.map(parserSelectorToR3Selector);
|
||||
}
|
||||
|
||||
// Pasted from render3/interfaces/definition since it cannot be referenced directly
|
||||
/**
|
||||
* Flags passed into template functions to determine which blocks (i.e. creation, update)
|
||||
* should be executed.
|
||||
*
|
||||
* Typically, a template runs both the creation block and the update block on initialization and
|
||||
* subsequent runs only execute the update block. However, dynamically created views require that
|
||||
* the creation block be executed separately from the update block (for backwards compat).
|
||||
*/
|
||||
export const enum RenderFlags {
|
||||
/* Whether to run the creation block (e.g. create elements and directives) */
|
||||
Create = 0b01,
|
||||
|
||||
/* Whether to run the update block (e.g. refresh bindings) */
|
||||
Update = 0b10
|
||||
}
|
||||
|
@ -6,8 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '../core';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
|
||||
export class ParserError {
|
||||
public message: string;
|
||||
@ -216,7 +215,7 @@ export class ASTWithSource extends AST {
|
||||
export class TemplateBinding {
|
||||
constructor(
|
||||
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
|
||||
public expression: ASTWithSource|null) {}
|
||||
public expression: ASTWithSource) {}
|
||||
}
|
||||
|
||||
export interface AstVisitor {
|
||||
@ -664,62 +663,3 @@ export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||
visitSafePropertyRead(ast) { visit(ast.receiver); },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Bindings
|
||||
|
||||
export class ParsedProperty {
|
||||
public readonly isLiteral: boolean;
|
||||
public readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public expression: ASTWithSource, public type: ParsedPropertyType,
|
||||
public sourceSpan: ParseSourceSpan) {
|
||||
this.isLiteral = this.type === ParsedPropertyType.LITERAL_ATTR;
|
||||
this.isAnimation = this.type === ParsedPropertyType.ANIMATION;
|
||||
}
|
||||
}
|
||||
|
||||
export enum ParsedPropertyType {
|
||||
DEFAULT,
|
||||
LITERAL_ATTR,
|
||||
ANIMATION
|
||||
}
|
||||
|
||||
export const enum ParsedEventType {
|
||||
// DOM or Directive event
|
||||
Regular,
|
||||
// Animation specific event
|
||||
Animation,
|
||||
}
|
||||
|
||||
export class ParsedEvent {
|
||||
// Regular events have a target
|
||||
// Animation events have a phase
|
||||
constructor(
|
||||
public name: string, public targetOrPhase: string, public type: ParsedEventType,
|
||||
public handler: AST, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
||||
export class ParsedVariable {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
||||
export const enum BindingType {
|
||||
// A regular binding to a property (e.g. `[property]="expression"`).
|
||||
Property,
|
||||
// A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
Attribute,
|
||||
// A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
Class,
|
||||
// A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
Style,
|
||||
// A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
Animation,
|
||||
}
|
||||
|
||||
export class BoundElementProperty {
|
||||
constructor(
|
||||
public name: string, public type: BindingType, public securityContext: SecurityContext,
|
||||
public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
@ -98,11 +98,19 @@ export class Parser {
|
||||
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(tplKey: string, tplValue: string, location: any):
|
||||
parseTemplateBindings(prefixToken: string|null, input: string, location: any):
|
||||
TemplateBindingParseResult {
|
||||
const tokens = this._lexer.tokenize(tplValue);
|
||||
return new _ParseAST(tplValue, location, tokens, tplValue.length, false, this.errors, 0)
|
||||
.parseTemplateBindings(tplKey);
|
||||
const tokens = this._lexer.tokenize(input);
|
||||
if (prefixToken) {
|
||||
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
|
||||
const prefixTokens = this._lexer.tokenize(prefixToken).map(t => {
|
||||
t.index = 0;
|
||||
return t;
|
||||
});
|
||||
tokens.unshift(...prefixTokens);
|
||||
}
|
||||
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
||||
.parseTemplateBindings();
|
||||
}
|
||||
|
||||
parseInterpolation(
|
||||
@ -678,49 +686,48 @@ export class _ParseAST {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Parses the AST for `<some-tag *tplKey=AST>`
|
||||
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
|
||||
let firstBinding = true;
|
||||
parseTemplateBindings(): TemplateBindingParseResult {
|
||||
const bindings: TemplateBinding[] = [];
|
||||
let prefix: string = null !;
|
||||
const warnings: string[] = [];
|
||||
do {
|
||||
while (this.index < this.tokens.length) {
|
||||
const start = this.inputIndex;
|
||||
let rawKey: string;
|
||||
let key: string;
|
||||
let isVar: boolean = false;
|
||||
if (firstBinding) {
|
||||
rawKey = key = tplKey;
|
||||
firstBinding = false;
|
||||
} else {
|
||||
isVar = this.peekKeywordLet();
|
||||
if (isVar) this.advance();
|
||||
rawKey = this.expectTemplateBindingKey();
|
||||
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
|
||||
this.optionalCharacter(chars.$COLON);
|
||||
let keyIsVar: boolean = this.peekKeywordLet();
|
||||
if (keyIsVar) {
|
||||
this.advance();
|
||||
}
|
||||
|
||||
let rawKey = this.expectTemplateBindingKey();
|
||||
let key = rawKey;
|
||||
if (!keyIsVar) {
|
||||
if (prefix == null) {
|
||||
prefix = key;
|
||||
} else {
|
||||
key = prefix + key[0].toUpperCase() + key.substring(1);
|
||||
}
|
||||
}
|
||||
this.optionalCharacter(chars.$COLON);
|
||||
let name: string = null !;
|
||||
let expression: ASTWithSource|null = null;
|
||||
if (isVar) {
|
||||
let expression: ASTWithSource = null !;
|
||||
if (keyIsVar) {
|
||||
if (this.optionalOperator('=')) {
|
||||
name = this.expectTemplateBindingKey();
|
||||
} else {
|
||||
name = '\$implicit';
|
||||
}
|
||||
} else if (this.peekKeywordAs()) {
|
||||
const letStart = this.inputIndex;
|
||||
this.advance(); // consume `as`
|
||||
name = rawKey;
|
||||
key = this.expectTemplateBindingKey(); // read local var name
|
||||
isVar = true;
|
||||
keyIsVar = true;
|
||||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
||||
const start = this.inputIndex;
|
||||
const ast = this.parsePipe();
|
||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
||||
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
||||
}
|
||||
|
||||
bindings.push(new TemplateBinding(this.span(start), key, isVar, name, expression));
|
||||
if (this.peekKeywordAs() && !isVar) {
|
||||
bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression));
|
||||
if (this.peekKeywordAs() && !keyIsVar) {
|
||||
const letStart = this.inputIndex;
|
||||
this.advance(); // consume `as`
|
||||
const letName = this.expectTemplateBindingKey(); // read local var name
|
||||
@ -729,8 +736,7 @@ export class _ParseAST {
|
||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
this.optionalCharacter(chars.$COMMA);
|
||||
}
|
||||
} while (this.index < this.tokens.length);
|
||||
|
||||
}
|
||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,6 @@ export class Identifiers {
|
||||
static INJECTOR: o.ExternalReference = {name: 'INJECTOR', moduleName: CORE};
|
||||
static Injector: o.ExternalReference = {name: 'Injector', moduleName: CORE};
|
||||
static defineInjectable: o.ExternalReference = {name: 'defineInjectable', moduleName: CORE};
|
||||
static InjectableDef: o.ExternalReference = {name: 'InjectableDef', moduleName: CORE};
|
||||
static ViewEncapsulation: o.ExternalReference = {
|
||||
name: 'ViewEncapsulation',
|
||||
moduleName: CORE,
|
||||
|
@ -1,113 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectFlags} from './core';
|
||||
import {Identifiers} from './identifiers';
|
||||
import * as o from './output/output_ast';
|
||||
import {R3DependencyMetadata, compileFactoryFunction} from './render3/r3_factory';
|
||||
import {mapToMapExpression} from './render3/util';
|
||||
|
||||
export interface InjectableDef {
|
||||
expression: o.Expression;
|
||||
type: o.Type;
|
||||
}
|
||||
|
||||
export interface R3InjectableMetadata {
|
||||
name: string;
|
||||
type: o.Expression;
|
||||
providedIn: o.Expression;
|
||||
useClass?: o.Expression;
|
||||
useFactory?: o.Expression;
|
||||
useExisting?: o.Expression;
|
||||
useValue?: o.Expression;
|
||||
deps?: R3DependencyMetadata[];
|
||||
}
|
||||
|
||||
export function compileInjectable(meta: R3InjectableMetadata): InjectableDef {
|
||||
let factory: o.Expression = o.NULL_EXPR;
|
||||
|
||||
function makeFn(ret: o.Expression): o.Expression {
|
||||
return o.fn([], [new o.ReturnStatement(ret)], undefined, undefined, `${meta.name}_Factory`);
|
||||
}
|
||||
|
||||
if (meta.useClass !== undefined || meta.useFactory !== undefined) {
|
||||
// First, handle useClass and useFactory together, since both involve a similar call to
|
||||
// `compileFactoryFunction`. Either dependencies are explicitly specified, in which case
|
||||
// a factory function call is generated, or they're not specified and the calls are special-
|
||||
// cased.
|
||||
if (meta.deps !== undefined) {
|
||||
// Either call `new meta.useClass(...)` or `meta.useFactory(...)`.
|
||||
const fnOrClass: o.Expression = meta.useClass || meta.useFactory !;
|
||||
|
||||
// useNew: true if meta.useClass, false for meta.useFactory.
|
||||
const useNew = meta.useClass !== undefined;
|
||||
|
||||
factory = compileFactoryFunction({
|
||||
name: meta.name,
|
||||
fnOrClass,
|
||||
useNew,
|
||||
injectFn: Identifiers.inject,
|
||||
useOptionalParam: true,
|
||||
deps: meta.deps,
|
||||
});
|
||||
} else if (meta.useClass !== undefined) {
|
||||
// Special case for useClass where the factory from the class's ngInjectableDef is used.
|
||||
if (meta.useClass.isEquivalent(meta.type)) {
|
||||
// For the injectable compiler, useClass represents a foreign type that should be
|
||||
// instantiated to satisfy construction of the given type. It's not valid to specify
|
||||
// useClass === type, since the useClass type is expected to already be compiled.
|
||||
throw new Error(
|
||||
`useClass is the same as the type, but no deps specified, which is invalid.`);
|
||||
}
|
||||
factory =
|
||||
makeFn(new o.ReadPropExpr(new o.ReadPropExpr(meta.useClass, 'ngInjectableDef'), 'factory')
|
||||
.callFn([]));
|
||||
} else if (meta.useFactory !== undefined) {
|
||||
// Special case for useFactory where no arguments are passed.
|
||||
factory = meta.useFactory.callFn([]);
|
||||
} else {
|
||||
// Can't happen - outer conditional guards against both useClass and useFactory being
|
||||
// undefined.
|
||||
throw new Error('Reached unreachable block in injectable compiler.');
|
||||
}
|
||||
} else if (meta.useValue !== undefined) {
|
||||
// Note: it's safe to use `meta.useValue` instead of the `USE_VALUE in meta` check used for
|
||||
// client code because meta.useValue is an Expression which will be defined even if the actual
|
||||
// value is undefined.
|
||||
factory = makeFn(meta.useValue);
|
||||
} else if (meta.useExisting !== undefined) {
|
||||
// useExisting is an `inject` call on the existing token.
|
||||
factory = makeFn(o.importExpr(Identifiers.inject).callFn([meta.useExisting]));
|
||||
} else {
|
||||
// A strict type is compiled according to useClass semantics, except the dependencies are
|
||||
// required.
|
||||
if (meta.deps === undefined) {
|
||||
throw new Error(`Type compilation of an injectable requires dependencies.`);
|
||||
}
|
||||
factory = compileFactoryFunction({
|
||||
name: meta.name,
|
||||
fnOrClass: meta.type,
|
||||
useNew: true,
|
||||
injectFn: Identifiers.inject,
|
||||
useOptionalParam: true,
|
||||
deps: meta.deps,
|
||||
});
|
||||
}
|
||||
|
||||
const token = meta.type;
|
||||
const providedIn = meta.providedIn;
|
||||
|
||||
const expression = o.importExpr(Identifiers.defineInjectable).callFn([mapToMapExpression(
|
||||
{token, factory, providedIn})]);
|
||||
const type = new o.ExpressionType(
|
||||
o.importExpr(Identifiers.InjectableDef, [new o.ExpressionType(meta.type)]));
|
||||
|
||||
return {
|
||||
expression, type,
|
||||
};
|
||||
}
|
@ -49,7 +49,7 @@ export function replaceNgsp(value: string): string {
|
||||
* whitespace removal. The default option for whitespace removal will be revisited in Angular 6
|
||||
* and might be changed to "on" by default.
|
||||
*/
|
||||
export class WhitespaceVisitor implements html.Visitor {
|
||||
class WhitespaceVisitor implements html.Visitor {
|
||||
visitElement(element: html.Element, context: any): any {
|
||||
if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
|
||||
// don't descent into elements where we need to preserve whitespaces
|
||||
|
@ -312,9 +312,6 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
ctx.print(expr, `)`);
|
||||
return null;
|
||||
}
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||
throw new Error('Abstract emitter cannot visit WrappedNodeExpr.');
|
||||
}
|
||||
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any {
|
||||
let varName = ast.name !;
|
||||
if (ast.builtin != null) {
|
||||
|
@ -70,10 +70,6 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
|
||||
ctx.println(stmt, `};`);
|
||||
}
|
||||
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||
throw new Error('Cannot emit a WrappedNodeExpr in Javascript.');
|
||||
}
|
||||
|
||||
visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): string|null {
|
||||
if (ast.builtin === o.BuiltinVar.This) {
|
||||
ctx.print(ast, 'self');
|
||||
|
@ -279,21 +279,6 @@ export class ReadVarExpr extends Expression {
|
||||
}
|
||||
}
|
||||
|
||||
export class WrappedNodeExpr<T> extends Expression {
|
||||
constructor(public node: T, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
|
||||
super(type, sourceSpan);
|
||||
}
|
||||
|
||||
isEquivalent(e: Expression): boolean {
|
||||
return e instanceof WrappedNodeExpr && this.node === e.node;
|
||||
}
|
||||
|
||||
isConstant() { return false; }
|
||||
|
||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||
return visitor.visitWrappedNodeExpr(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class WriteVarExpr extends Expression {
|
||||
public value: Expression;
|
||||
@ -737,7 +722,6 @@ export interface ExpressionVisitor {
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any;
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
|
||||
visitCommaExpr(ast: CommaExpr, context: any): any;
|
||||
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any;
|
||||
}
|
||||
|
||||
export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This, null, null);
|
||||
@ -989,10 +973,6 @@ export class AstTransformer implements StatementVisitor, ExpressionVisitor {
|
||||
|
||||
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return this.transformExpr(ast, context); }
|
||||
|
||||
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any {
|
||||
return this.transformExpr(ast, context);
|
||||
}
|
||||
|
||||
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
|
||||
return this.transformExpr(
|
||||
new WriteVarExpr(
|
||||
@ -1219,7 +1199,6 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
|
||||
}
|
||||
visitArrayType(type: ArrayType, context: any): any { return this.visitType(type, context); }
|
||||
visitMapType(type: MapType, context: any): any { return this.visitType(type, context); }
|
||||
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any { return ast; }
|
||||
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
|
||||
return this.visitExpression(ast, context);
|
||||
}
|
||||
|
@ -114,9 +114,6 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
||||
}
|
||||
throw new Error(`Not declared variable ${expr.name}`);
|
||||
}
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: _ExecutionContext): never {
|
||||
throw new Error('Cannot interpret a WrappedNodeExpr.');
|
||||
}
|
||||
visitReadVarExpr(ast: o.ReadVarExpr, ctx: _ExecutionContext): any {
|
||||
let varName = ast.name !;
|
||||
if (ast.builtin != null) {
|
||||
|
@ -68,12 +68,15 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
}
|
||||
|
||||
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
||||
this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||
this._emitReferenceToExternal(ast, ast.node, ctx);
|
||||
const value = this.reflector.resolveExternalReference(ast.value);
|
||||
let id = this._evalArgValues.indexOf(value);
|
||||
if (id === -1) {
|
||||
id = this._evalArgValues.length;
|
||||
this._evalArgValues.push(value);
|
||||
const name = identifierName({reference: value}) || 'val';
|
||||
this._evalArgNames.push(`jit_${name}_${id}`);
|
||||
}
|
||||
ctx.print(ast, this._evalArgNames[id]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -97,16 +100,4 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
}
|
||||
return super.visitDeclareClassStmt(stmt, ctx);
|
||||
}
|
||||
|
||||
private _emitReferenceToExternal(ast: o.Expression, value: any, ctx: EmitterVisitorContext):
|
||||
void {
|
||||
let id = this._evalArgValues.indexOf(value);
|
||||
if (id === -1) {
|
||||
id = this._evalArgValues.length;
|
||||
this._evalArgValues.push(value);
|
||||
const name = identifierName({reference: value}) || 'val';
|
||||
this._evalArgNames.push(`jit_${name}_${id}`);
|
||||
}
|
||||
ctx.print(ast, this._evalArgNames[id]);
|
||||
}
|
||||
}
|
||||
|
@ -169,10 +169,6 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
return null;
|
||||
}
|
||||
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): never {
|
||||
throw new Error('Cannot visit a WrappedNodeExpr when outputting Typescript.');
|
||||
}
|
||||
|
||||
visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any {
|
||||
ctx.print(ast, `(<`);
|
||||
ast.type !.visitType(this, ctx);
|
||||
|
@ -1,223 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '../core';
|
||||
import {AST, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
export interface Node {
|
||||
sourceSpan: ParseSourceSpan;
|
||||
visit<Result>(visitor: Visitor<Result>): Result;
|
||||
}
|
||||
|
||||
export class Text implements Node {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitText(this); }
|
||||
}
|
||||
|
||||
export class BoundText implements Node {
|
||||
constructor(public value: AST, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundText(this); }
|
||||
}
|
||||
|
||||
export class TextAttribute implements Node {
|
||||
constructor(
|
||||
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
||||
public valueSpan?: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTextAttribute(this); }
|
||||
}
|
||||
|
||||
export class BoundAttribute implements Node {
|
||||
constructor(
|
||||
public name: string, public type: BindingType, public securityContext: SecurityContext,
|
||||
public value: AST, public unit: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
static fromBoundElementProperty(prop: BoundElementProperty) {
|
||||
return new BoundAttribute(
|
||||
prop.name, prop.type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan);
|
||||
}
|
||||
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundAttribute(this); }
|
||||
}
|
||||
|
||||
export class BoundEvent implements Node {
|
||||
constructor(
|
||||
public name: string, public handler: AST, public target: string|null,
|
||||
public phase: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
static fromParsedEvent(event: ParsedEvent) {
|
||||
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
|
||||
const phase: string|null =
|
||||
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
|
||||
return new BoundEvent(event.name, event.handler, target, phase, event.sourceSpan);
|
||||
}
|
||||
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
|
||||
}
|
||||
|
||||
export class Element implements Node {
|
||||
constructor(
|
||||
public name: string, public attributes: TextAttribute[], public inputs: BoundAttribute[],
|
||||
public outputs: BoundEvent[], public children: Node[], public references: Reference[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||
public endSourceSpan: ParseSourceSpan|null) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitElement(this); }
|
||||
}
|
||||
|
||||
export class Template implements Node {
|
||||
constructor(
|
||||
public attributes: TextAttribute[], public inputs: BoundAttribute[], public children: Node[],
|
||||
public references: Reference[], public variables: Variable[],
|
||||
public sourceSpan: ParseSourceSpan, public startSourceSpan: ParseSourceSpan|null,
|
||||
public endSourceSpan: ParseSourceSpan|null) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitTemplate(this); }
|
||||
}
|
||||
|
||||
export class Content implements Node {
|
||||
constructor(
|
||||
public selectorIndex: number, public attributes: TextAttribute[],
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitContent(this); }
|
||||
}
|
||||
|
||||
export class Variable implements Node {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitVariable(this); }
|
||||
}
|
||||
|
||||
export class Reference implements Node {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitReference(this); }
|
||||
}
|
||||
|
||||
export interface Visitor<Result = any> {
|
||||
// Returning a truthy value from `visit()` will prevent `visitAll()` from the call to the typed
|
||||
// method and result returned will become the result included in `visitAll()`s result array.
|
||||
visit?(node: Node): Result;
|
||||
|
||||
visitElement(element: Element): Result;
|
||||
visitTemplate(template: Template): Result;
|
||||
visitContent(content: Content): Result;
|
||||
visitVariable(variable: Variable): Result;
|
||||
visitReference(reference: Reference): Result;
|
||||
visitTextAttribute(attribute: TextAttribute): Result;
|
||||
visitBoundAttribute(attribute: BoundAttribute): Result;
|
||||
visitBoundEvent(attribute: BoundEvent): Result;
|
||||
visitText(text: Text): Result;
|
||||
visitBoundText(text: BoundText): Result;
|
||||
}
|
||||
|
||||
export class NullVisitor implements Visitor<void> {
|
||||
visitElement(element: Element): void {}
|
||||
visitTemplate(template: Template): void {}
|
||||
visitContent(content: Content): void {}
|
||||
visitVariable(variable: Variable): void {}
|
||||
visitReference(reference: Reference): void {}
|
||||
visitTextAttribute(attribute: TextAttribute): void {}
|
||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||
visitBoundEvent(attribute: BoundEvent): void {}
|
||||
visitText(text: Text): void {}
|
||||
visitBoundText(text: BoundText): void {}
|
||||
}
|
||||
|
||||
export class RecursiveVisitor implements Visitor<void> {
|
||||
visitElement(element: Element): void {
|
||||
visitAll(this, element.attributes);
|
||||
visitAll(this, element.children);
|
||||
visitAll(this, element.references);
|
||||
}
|
||||
visitTemplate(template: Template): void {
|
||||
visitAll(this, template.attributes);
|
||||
visitAll(this, template.children);
|
||||
visitAll(this, template.references);
|
||||
visitAll(this, template.variables);
|
||||
}
|
||||
visitContent(content: Content): void {}
|
||||
visitVariable(variable: Variable): void {}
|
||||
visitReference(reference: Reference): void {}
|
||||
visitTextAttribute(attribute: TextAttribute): void {}
|
||||
visitBoundAttribute(attribute: BoundAttribute): void {}
|
||||
visitBoundEvent(attribute: BoundEvent): void {}
|
||||
visitText(text: Text): void {}
|
||||
visitBoundText(text: BoundText): void {}
|
||||
}
|
||||
|
||||
export class TransformVisitor implements Visitor<Node> {
|
||||
visitElement(element: Element): Node {
|
||||
const newAttributes = transformAll(this, element.attributes);
|
||||
const newInputs = transformAll(this, element.inputs);
|
||||
const newOutputs = transformAll(this, element.outputs);
|
||||
const newChildren = transformAll(this, element.children);
|
||||
const newReferences = transformAll(this, element.references);
|
||||
if (newAttributes != element.attributes || newInputs != element.inputs ||
|
||||
newOutputs != element.outputs || newChildren != element.children ||
|
||||
newReferences != element.references) {
|
||||
return new Element(
|
||||
element.name, newAttributes, newInputs, newOutputs, newChildren, newReferences,
|
||||
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
visitTemplate(template: Template): Node {
|
||||
const newAttributes = transformAll(this, template.attributes);
|
||||
const newInputs = transformAll(this, template.inputs);
|
||||
const newChildren = transformAll(this, template.children);
|
||||
const newReferences = transformAll(this, template.references);
|
||||
const newVariables = transformAll(this, template.variables);
|
||||
if (newAttributes != template.attributes || newInputs != template.inputs ||
|
||||
newChildren != template.children || newVariables != template.variables ||
|
||||
newReferences != template.references) {
|
||||
return new Template(
|
||||
newAttributes, newInputs, newChildren, newReferences, newVariables, template.sourceSpan,
|
||||
template.startSourceSpan, template.endSourceSpan);
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
visitContent(content: Content): Node { return content; }
|
||||
|
||||
visitVariable(variable: Variable): Node { return variable; }
|
||||
visitReference(reference: Reference): Node { return reference; }
|
||||
visitTextAttribute(attribute: TextAttribute): Node { return attribute; }
|
||||
visitBoundAttribute(attribute: BoundAttribute): Node { return attribute; }
|
||||
visitBoundEvent(attribute: BoundEvent): Node { return attribute; }
|
||||
visitText(text: Text): Node { return text; }
|
||||
visitBoundText(text: BoundText): Node { return text; }
|
||||
}
|
||||
|
||||
export function visitAll<Result>(visitor: Visitor<Result>, nodes: Node[]): Result[] {
|
||||
const result: Result[] = [];
|
||||
if (visitor.visit) {
|
||||
for (const node of nodes) {
|
||||
const newNode = visitor.visit(node) || node.visit(visitor);
|
||||
}
|
||||
} else {
|
||||
for (const node of nodes) {
|
||||
const newNode = node.visit(visitor);
|
||||
if (newNode) {
|
||||
result.push(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function transformAll<Result extends Node>(
|
||||
visitor: Visitor<Node>, nodes: Result[]): Result[] {
|
||||
const result: Result[] = [];
|
||||
let changed = false;
|
||||
for (const node of nodes) {
|
||||
const newNode = node.visit(visitor);
|
||||
if (newNode) {
|
||||
result.push(newNode as Result);
|
||||
}
|
||||
changed = changed || newNode != node;
|
||||
}
|
||||
return changed ? result : nodes;
|
||||
}
|
106
packages/compiler/src/render3/r3_back_patch_compiler.ts
Normal file
106
packages/compiler/src/render3/r3_back_patch_compiler.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @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 {StaticReflector} from '../aot/static_reflector';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata} from '../compile_metadata';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Lexer, ParseError, Parser} from '../compiler';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
import {compilePipe} from './r3_pipe_compiler';
|
||||
import {BUILD_OPTIMIZER_REMOVE, OutputMode} from './r3_types';
|
||||
import {compileComponent, compileDirective} from './r3_view_compiler';
|
||||
|
||||
export const enum ModuleKind {
|
||||
Renderer2,
|
||||
Renderer3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the back-patching function for the given module to the output context.
|
||||
*/
|
||||
export function compileModuleBackPatch(
|
||||
outputCtx: OutputContext, name: string, module: CompileNgModuleMetadata, kind: ModuleKind,
|
||||
backPatchReferenceOf: (module: CompileTypeMetadata) => o.Expression,
|
||||
parseTemplate: (
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||
directiveIdentifiers: CompileIdentifierMetadata[]) => {
|
||||
template: TemplateAst[],
|
||||
pipes: CompilePipeSummary[]
|
||||
},
|
||||
reflector: StaticReflector, resolver: CompileMetadataResolver) {
|
||||
const imports: o.Statement[] = [];
|
||||
let statements: o.Statement[] = [];
|
||||
|
||||
// Call dependent back patching
|
||||
for (const importedModule of module.importedModules) {
|
||||
const importBackPatchFunction = backPatchReferenceOf(importedModule.type);
|
||||
|
||||
// e.g. // @BUILD_OPTIMIZER_REMOVE
|
||||
imports.push(new o.CommentStmt(BUILD_OPTIMIZER_REMOVE));
|
||||
|
||||
// e.g. ngBackPatch_some_other_module_Module();
|
||||
imports.push(importBackPatchFunction.callFn([]).toStmt());
|
||||
}
|
||||
|
||||
// The local output context allows collecting the back-patch statements that
|
||||
// are generated by the various compilers which allows putting placing them
|
||||
// into the body of a function instead of at global scope.
|
||||
const localCtx: OutputContext = {
|
||||
statements,
|
||||
constantPool: outputCtx.constantPool,
|
||||
genFilePath: outputCtx.genFilePath,
|
||||
importExpr: outputCtx.importExpr
|
||||
};
|
||||
|
||||
// e.g. export function ngBackPatch_some_module_Lib1Module()
|
||||
if (kind === ModuleKind.Renderer2) {
|
||||
// For all Renderer2 modules generate back-patching code for all the components, directives,
|
||||
// pipes, and injectables as well as the injector def for the module itself.
|
||||
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const errors: ParseError[] = [];
|
||||
const hostBindingParser = new BindingParser(
|
||||
expressionParser, DEFAULT_INTERPOLATION_CONFIG, elementSchemaRegistry, [], errors);
|
||||
|
||||
// Back-patch all declared directive and components
|
||||
for (const declaredDirective of module.declaredDirectives) {
|
||||
const declaredDirectiveMetadata = resolver.getDirectiveMetadata(declaredDirective.reference);
|
||||
if (declaredDirectiveMetadata.isComponent) {
|
||||
const {template: parsedTemplate, pipes: parsedPipes} =
|
||||
parseTemplate(declaredDirectiveMetadata, module, module.transitiveModule.directives);
|
||||
compileComponent(
|
||||
localCtx, declaredDirectiveMetadata, parsedPipes, parsedTemplate, reflector,
|
||||
hostBindingParser, OutputMode.BackPatch);
|
||||
} else {
|
||||
compileDirective(
|
||||
localCtx, declaredDirectiveMetadata, reflector, hostBindingParser,
|
||||
OutputMode.BackPatch);
|
||||
}
|
||||
}
|
||||
|
||||
// Back-patch all pipes declared in the module.
|
||||
for (const pipeType of module.declaredPipes) {
|
||||
const pipeMetadata = resolver.getPipeMetadata(pipeType.reference);
|
||||
if (pipeMetadata) {
|
||||
compilePipe(localCtx, pipeMetadata, reflector, OutputMode.BackPatch);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
outputCtx.statements.push(new o.DeclareFunctionStmt(
|
||||
name, [], [...imports, ...statements], o.INFERRED_TYPE, [o.StmtModifier.Exported]));
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileTypeMetadata, tokenReference} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {InjectFlags} from '../core';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {Identifiers as R3} from '../render3/r3_identifiers';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
import {unsupported} from './view/util';
|
||||
|
||||
/**
|
||||
* Metadata required by the factory generator to generate a `factory` function for a type.
|
||||
*/
|
||||
export interface R3FactoryMetadata {
|
||||
/**
|
||||
* String name of the type being generated (used to name the factory function).
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* An expression representing the function (or constructor) which will instantiate the requested
|
||||
* type.
|
||||
*
|
||||
* This could be a reference to a constructor type, or to a user-defined factory function. The
|
||||
* `useNew` property determines whether it will be called as a constructor or not.
|
||||
*/
|
||||
fnOrClass: o.Expression;
|
||||
|
||||
/**
|
||||
* Regardless of whether `fnOrClass` is a constructor function or a user-defined factory, it
|
||||
* may have 0 or more parameters, which will be injected according to the `R3DependencyMetadata`
|
||||
* for those parameters.
|
||||
*/
|
||||
deps: R3DependencyMetadata[];
|
||||
|
||||
/**
|
||||
* Whether to interpret `fnOrClass` as a constructor function (`useNew: true`) or as a factory
|
||||
* (`useNew: false`).
|
||||
*/
|
||||
useNew: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* An expression for the function which will be used to inject dependencies. The API of this
|
||||
* function could be different, and other options control how it will be invoked.
|
||||
*/
|
||||
injectFn: o.ExternalReference;
|
||||
|
||||
/**
|
||||
* Whether the `injectFn` given above accepts a 2nd parameter indicating the default value to
|
||||
* be used to resolve missing @Optional dependencies.
|
||||
*
|
||||
* If the optional parameter is used, injectFn for an optional dependency will be invoked as:
|
||||
* `injectFn(token, null, flags)`.
|
||||
*
|
||||
* If it's not used, injectFn for an optional dependency will be invoked as:
|
||||
* `injectFn(token, flags)`. The Optional flag will indicate that injectFn should select a default
|
||||
* value if it cannot satisfy the injection request for the token.
|
||||
*/
|
||||
useOptionalParam: boolean;
|
||||
|
||||
/**
|
||||
* If present, the return of the factory function will be an array with the injected value in the
|
||||
* 0th position and the extra results included in subsequent positions.
|
||||
*
|
||||
* Occasionally APIs want to construct additional values when the factory function is called. The
|
||||
* paradigm there is to have the factory function return an array, with the DI-created value as
|
||||
* well as other values. Specifying `extraResults` enables this functionality.
|
||||
*/
|
||||
extraResults?: o.Expression[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved type of a dependency.
|
||||
*
|
||||
* Occasionally, dependencies will have special significance which is known statically. In that
|
||||
* case the `R3ResolvedDependencyType` informs the factory generator that a particular dependency
|
||||
* should be generated specially (usually by calling a special injection function instead of the
|
||||
* standard one).
|
||||
*/
|
||||
export enum R3ResolvedDependencyType {
|
||||
/**
|
||||
* A normal token dependency.
|
||||
*/
|
||||
Token = 0,
|
||||
|
||||
/**
|
||||
* The dependency is for an attribute.
|
||||
*
|
||||
* The token expression is a string representing the attribute name.
|
||||
*/
|
||||
Attribute = 1,
|
||||
|
||||
/**
|
||||
* The dependency is for the `Injector` type itself.
|
||||
*/
|
||||
Injector = 2,
|
||||
|
||||
/**
|
||||
* The dependency is for `ElementRef`.
|
||||
*/
|
||||
ElementRef = 3,
|
||||
|
||||
/**
|
||||
* The dependency is for `TemplateRef`.
|
||||
*/
|
||||
TemplateRef = 4,
|
||||
|
||||
/**
|
||||
* The dependency is for `ViewContainerRef`.
|
||||
*/
|
||||
ViewContainerRef = 5,
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata representing a single dependency to be injected into a constructor or function call.
|
||||
*/
|
||||
export interface R3DependencyMetadata {
|
||||
/**
|
||||
* An expression representing the token or value to be injected.
|
||||
*/
|
||||
token: o.Expression;
|
||||
|
||||
/**
|
||||
* An enum indicating whether this dependency has special meaning to Angular and needs to be
|
||||
* injected specially.
|
||||
*/
|
||||
resolved: R3ResolvedDependencyType;
|
||||
|
||||
/**
|
||||
* Whether the dependency has an @Host qualifier.
|
||||
*/
|
||||
host: boolean;
|
||||
|
||||
/**
|
||||
* Whether the dependency has an @Optional qualifier.
|
||||
*/
|
||||
optional: boolean;
|
||||
|
||||
/**
|
||||
* Whether the dependency has an @Self qualifier.
|
||||
*/
|
||||
self: boolean;
|
||||
|
||||
/**
|
||||
* Whether the dependency has an @SkipSelf qualifier.
|
||||
*/
|
||||
skipSelf: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a factory function expression for the given `R3FactoryMetadata`.
|
||||
*/
|
||||
export function compileFactoryFunction(meta: R3FactoryMetadata): o.Expression {
|
||||
// Each dependency becomes an invocation of an inject*() function.
|
||||
const args =
|
||||
meta.deps.map(dep => compileInjectDependency(dep, meta.injectFn, meta.useOptionalParam));
|
||||
|
||||
// The overall result depends on whether this is construction or function invocation.
|
||||
const expr = meta.useNew ? new o.InstantiateExpr(meta.fnOrClass, args) :
|
||||
new o.InvokeFunctionExpr(meta.fnOrClass, args);
|
||||
|
||||
// If `extraResults` is specified, then the result is an array consisting of the instantiated
|
||||
// value plus any extra results.
|
||||
const retExpr =
|
||||
meta.extraResults === undefined ? expr : o.literalArr([expr, ...meta.extraResults]);
|
||||
return o.fn(
|
||||
[], [new o.ReturnStatement(retExpr)], o.INFERRED_TYPE, undefined, `${meta.name}_Factory`);
|
||||
}
|
||||
|
||||
function compileInjectDependency(
|
||||
dep: R3DependencyMetadata, injectFn: o.ExternalReference,
|
||||
useOptionalParam: boolean): o.Expression {
|
||||
// Interpret the dependency according to its resolved type.
|
||||
switch (dep.resolved) {
|
||||
case R3ResolvedDependencyType.Token:
|
||||
case R3ResolvedDependencyType.Injector: {
|
||||
// Build up the injection flags according to the metadata.
|
||||
const flags = InjectFlags.Default | (dep.self ? InjectFlags.Self : 0) |
|
||||
(dep.skipSelf ? InjectFlags.SkipSelf : 0) | (dep.host ? InjectFlags.Host : 0) |
|
||||
(dep.optional ? InjectFlags.Optional : 0);
|
||||
// Determine the token used for injection. In almost all cases this is the given token, but
|
||||
// if the dependency is resolved to the `Injector` then the special `INJECTOR` token is used
|
||||
// instead.
|
||||
let token: o.Expression = dep.token;
|
||||
if (dep.resolved === R3ResolvedDependencyType.Injector) {
|
||||
token = o.importExpr(Identifiers.INJECTOR);
|
||||
}
|
||||
|
||||
// Build up the arguments to the injectFn call.
|
||||
const injectArgs = [dep.token];
|
||||
// If this dependency is optional or otherwise has non-default flags, then additional
|
||||
// parameters describing how to inject the dependency must be passed to the inject function
|
||||
// that's being used.
|
||||
if (flags !== InjectFlags.Default || dep.optional) {
|
||||
// Either the dependency is optional, or non-default flags are in use. Either of these cases
|
||||
// necessitates adding an argument for the default value if such an argument is required
|
||||
// by the inject function (useOptionalParam === true).
|
||||
if (useOptionalParam) {
|
||||
// The inject function requires a default value parameter.
|
||||
injectArgs.push(dep.optional ? o.NULL_EXPR : o.literal(undefined));
|
||||
}
|
||||
// The last parameter is always the InjectFlags, which only need to be specified if they're
|
||||
// non-default.
|
||||
if (flags !== InjectFlags.Default) {
|
||||
injectArgs.push(o.literal(flags));
|
||||
}
|
||||
}
|
||||
return o.importExpr(injectFn).callFn(injectArgs);
|
||||
}
|
||||
case R3ResolvedDependencyType.Attribute:
|
||||
// In the case of attributes, the attribute name in question is given as the token.
|
||||
return o.importExpr(R3.injectAttribute).callFn([dep.token]);
|
||||
case R3ResolvedDependencyType.ElementRef:
|
||||
return o.importExpr(R3.injectElementRef).callFn([]);
|
||||
case R3ResolvedDependencyType.TemplateRef:
|
||||
return o.importExpr(R3.injectTemplateRef).callFn([]);
|
||||
case R3ResolvedDependencyType.ViewContainerRef:
|
||||
return o.importExpr(R3.injectViewContainerRef).callFn([]);
|
||||
default:
|
||||
return unsupported(
|
||||
`Unknown R3ResolvedDependencyType: ${R3ResolvedDependencyType[dep.resolved]}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function useful for extracting `R3DependencyMetadata` from a Render2
|
||||
* `CompileTypeMetadata` instance.
|
||||
*/
|
||||
export function dependenciesFromGlobalMetadata(
|
||||
type: CompileTypeMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): R3DependencyMetadata[] {
|
||||
// Use the `CompileReflector` to look up references to some well-known Angular types. These will
|
||||
// be compared with the token to statically determine whether the token has significance to
|
||||
// Angular, and set the correct `R3ResolvedDependencyType` as a result.
|
||||
const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef);
|
||||
const templateRef = reflector.resolveExternalReference(Identifiers.TemplateRef);
|
||||
const viewContainerRef = reflector.resolveExternalReference(Identifiers.ViewContainerRef);
|
||||
const injectorRef = reflector.resolveExternalReference(Identifiers.Injector);
|
||||
|
||||
// Iterate through the type's DI dependencies and produce `R3DependencyMetadata` for each of them.
|
||||
const deps: R3DependencyMetadata[] = [];
|
||||
for (let dependency of type.diDeps) {
|
||||
if (dependency.token) {
|
||||
const tokenRef = tokenReference(dependency.token);
|
||||
let resolved: R3ResolvedDependencyType = R3ResolvedDependencyType.Token;
|
||||
if (tokenRef === elementRef) {
|
||||
resolved = R3ResolvedDependencyType.ElementRef;
|
||||
} else if (tokenRef === templateRef) {
|
||||
resolved = R3ResolvedDependencyType.TemplateRef;
|
||||
} else if (tokenRef === viewContainerRef) {
|
||||
resolved = R3ResolvedDependencyType.ViewContainerRef;
|
||||
} else if (tokenRef === injectorRef) {
|
||||
resolved = R3ResolvedDependencyType.Injector;
|
||||
} else if (dependency.isAttribute) {
|
||||
resolved = R3ResolvedDependencyType.Attribute;
|
||||
}
|
||||
|
||||
// In the case of most dependencies, the token will be a reference to a type. Sometimes,
|
||||
// however, it can be a string, in the case of older Angular code or @Attribute injection.
|
||||
const token =
|
||||
tokenRef instanceof StaticSymbol ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef);
|
||||
|
||||
// Construct the dependency.
|
||||
deps.push({
|
||||
token,
|
||||
resolved,
|
||||
host: !!dependency.isHost,
|
||||
optional: !!dependency.isOptional,
|
||||
self: !!dependency.isSelf,
|
||||
skipSelf: !!dependency.isSkipSelf,
|
||||
});
|
||||
} else {
|
||||
unsupported('dependency without a token');
|
||||
}
|
||||
}
|
||||
|
||||
return deps;
|
||||
}
|
@ -17,15 +17,7 @@ export class Identifiers {
|
||||
static PATCH_DEPS = 'patchedDeps';
|
||||
|
||||
/* Instructions */
|
||||
static namespaceHTML: o.ExternalReference = {name: 'ɵNH', moduleName: CORE};
|
||||
|
||||
static namespaceMathML: o.ExternalReference = {name: 'ɵNM', moduleName: CORE};
|
||||
|
||||
static namespaceSVG: o.ExternalReference = {name: 'ɵNS', moduleName: CORE};
|
||||
|
||||
static element: o.ExternalReference = {name: 'ɵEe', moduleName: CORE};
|
||||
|
||||
static elementStart: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
|
||||
|
||||
static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE};
|
||||
|
||||
@ -33,19 +25,21 @@ export class Identifiers {
|
||||
|
||||
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};
|
||||
|
||||
static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE};
|
||||
|
||||
static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE};
|
||||
|
||||
static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE};
|
||||
|
||||
static elementStyleNamed: o.ExternalReference = {name: 'ɵsn', moduleName: CORE};
|
||||
|
||||
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
||||
|
||||
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
|
||||
|
||||
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
|
||||
|
||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
||||
|
||||
static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||
static directiveInput: o.ExternalReference = {name: 'ɵi', moduleName: CORE};
|
||||
|
||||
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||
|
||||
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||
|
||||
@ -77,7 +71,6 @@ export class Identifiers {
|
||||
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
|
||||
|
||||
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
|
||||
static loadDirective: o.ExternalReference = {name: 'ɵd', moduleName: CORE};
|
||||
|
||||
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
|
||||
|
||||
@ -88,8 +81,6 @@ export class Identifiers {
|
||||
|
||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
||||
|
||||
static inject: o.ExternalReference = {name: 'inject', moduleName: CORE};
|
||||
|
||||
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
|
||||
|
||||
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
|
||||
@ -103,28 +94,16 @@ export class Identifiers {
|
||||
|
||||
static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE};
|
||||
|
||||
static ComponentDef: o.ExternalReference = {
|
||||
name: 'ComponentDef',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineDirective: o.ExternalReference = {
|
||||
name: 'ɵdefineDirective',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static DirectiveDef: o.ExternalReference = {
|
||||
name: 'DirectiveDef',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineInjector: o.ExternalReference = {
|
||||
name: 'defineInjector',
|
||||
moduleName: CORE,
|
||||
};
|
||||
|
||||
static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE};
|
||||
|
||||
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
|
||||
|
||||
static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE};
|
||||
@ -133,7 +112,4 @@ export class Identifiers {
|
||||
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
|
||||
|
||||
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
|
||||
|
||||
// Reserve slots for pure functions
|
||||
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
|
||||
}
|
||||
|
@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {ConstantPool} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {jitStatements} from '../output/output_jit';
|
||||
|
||||
/**
|
||||
* Implementation of `CompileReflector` which resolves references to @angular/core
|
||||
* symbols at runtime, according to a consumer-provided mapping.
|
||||
*
|
||||
* Only supports `resolveExternalReference`, all other methods throw.
|
||||
*/
|
||||
class R3JitReflector implements CompileReflector {
|
||||
constructor(private context: {[key: string]: any}) {}
|
||||
|
||||
resolveExternalReference(ref: o.ExternalReference): any {
|
||||
// This reflector only handles @angular/core imports.
|
||||
if (ref.moduleName !== '@angular/core') {
|
||||
throw new Error(
|
||||
`Cannot resolve external reference to ${ref.moduleName}, only references to @angular/core are supported.`);
|
||||
}
|
||||
if (!this.context.hasOwnProperty(ref.name !)) {
|
||||
throw new Error(`No value provided for @angular/core symbol '${ref.name!}'.`);
|
||||
}
|
||||
return this.context[ref.name !];
|
||||
}
|
||||
|
||||
parameters(typeOrFunc: any): any[][] { throw new Error('Not implemented.'); }
|
||||
|
||||
annotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
shallowAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
tryAnnotations(typeOrFunc: any): any[] { throw new Error('Not implemented.'); }
|
||||
|
||||
propMetadata(typeOrFunc: any): {[key: string]: any[];} { throw new Error('Not implemented.'); }
|
||||
|
||||
hasLifecycleHook(type: any, lcProperty: string): boolean { throw new Error('Not implemented.'); }
|
||||
|
||||
guards(typeOrFunc: any): {[key: string]: any;} { throw new Error('Not implemented.'); }
|
||||
|
||||
componentModuleUrl(type: any, cmpMetadata: any): string { throw new Error('Not implemented.'); }
|
||||
}
|
||||
|
||||
/**
|
||||
* JIT compiles an expression and returns the result of executing that expression.
|
||||
*
|
||||
* @param def the definition which will be compiled and executed to get the value to patch
|
||||
* @param context an object map of @angular/core symbol names to symbols which will be available in
|
||||
* the context of the compiled expression
|
||||
* @param sourceUrl a URL to use for the source map of the compiled expression
|
||||
* @param constantPool an optional `ConstantPool` which contains constants used in the expression
|
||||
*/
|
||||
export function jitExpression(
|
||||
def: o.Expression, context: {[key: string]: any}, sourceUrl: string,
|
||||
constantPool?: ConstantPool): any {
|
||||
// The ConstantPool may contain Statements which declare variables used in the final expression.
|
||||
// Therefore, its statements need to precede the actual JIT operation. The final statement is a
|
||||
// declaration of $def which is set to the expression being compiled.
|
||||
const statements: o.Statement[] = [
|
||||
...(constantPool !== undefined ? constantPool.statements : []),
|
||||
new o.DeclareVarStmt('$def', def, undefined, [o.StmtModifier.Exported]),
|
||||
];
|
||||
|
||||
const res = jitStatements(sourceUrl, statements, new R3JitReflector(context), false);
|
||||
return res['$def'];
|
||||
}
|
@ -14,72 +14,22 @@ import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
import {convertMetaToOutput, mapToMapExpression} from './util';
|
||||
|
||||
export interface R3NgModuleDef {
|
||||
expression: o.Expression;
|
||||
type: o.Type;
|
||||
additionalStatements: o.Statement[];
|
||||
const EMPTY_ARRAY = o.literalArr([]);
|
||||
|
||||
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||
if (Array.isArray(meta)) {
|
||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||
} else if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
} else if (meta == null) {
|
||||
return o.literal(meta);
|
||||
} else {
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata required by the module compiler to generate a `ngModuleDef` for a type.
|
||||
*/
|
||||
export interface R3NgModuleMetadata {
|
||||
/**
|
||||
* An expression representing the module type being compiled.
|
||||
*/
|
||||
type: o.Expression;
|
||||
|
||||
/**
|
||||
* An array of expressions representing the bootstrap components specified by the module.
|
||||
*/
|
||||
bootstrap: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the directives and pipes declared by the module.
|
||||
*/
|
||||
declarations: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the imports of the module.
|
||||
*/
|
||||
imports: o.Expression[];
|
||||
|
||||
/**
|
||||
* An array of expressions representing the exports of the module.
|
||||
*/
|
||||
exports: o.Expression[];
|
||||
|
||||
/**
|
||||
* Whether to emit the selector scope values (declarations, imports, exports) inline into the
|
||||
* module definition, or to generate additional statements which patch them on. Inline emission
|
||||
* does not allow components to be tree-shaken, but is useful for JIT mode.
|
||||
*/
|
||||
emitInline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
||||
*/
|
||||
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||
const {type: moduleType, bootstrap, declarations, imports, exports} = meta;
|
||||
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression({
|
||||
type: moduleType,
|
||||
bootstrap: o.literalArr(bootstrap),
|
||||
declarations: o.literalArr(declarations),
|
||||
imports: o.literalArr(imports),
|
||||
exports: o.literalArr(exports),
|
||||
})]);
|
||||
|
||||
// TODO(alxhub): write a proper type reference when AOT compilation of @NgModule is implemented.
|
||||
const type = new o.ExpressionType(o.NULL_EXPR);
|
||||
const additionalStatements: o.Statement[] = [];
|
||||
return {expression, type, additionalStatements};
|
||||
}
|
||||
|
||||
// TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations.
|
||||
export function compileNgModuleFromRender2(
|
||||
export function compileNgModule(
|
||||
ctx: OutputContext, ngModule: CompileShallowModuleMetadata,
|
||||
injectableCompiler: InjectableCompiler): void {
|
||||
const className = identifierName(ngModule.type) !;
|
||||
@ -108,8 +58,3 @@ export function compileNgModuleFromRender2(
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
}
|
||||
|
||||
function accessExportScope(module: o.Expression): o.Expression {
|
||||
const selectorScope = new o.ReadPropExpr(module, 'ngModuleDef');
|
||||
return new o.ReadPropExpr(selectorScope, 'exported');
|
||||
}
|
||||
|
@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileNgModuleMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {StaticReflector} from '../aot/static_reflector';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
@ -6,21 +6,22 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {DefinitionKind} from '../constant_pool';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext, error} from '../util';
|
||||
|
||||
import {compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory';
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
import {BUILD_OPTIMIZER_COLOCATE, OutputMode} from './r3_types';
|
||||
import {createFactory} from './r3_view_compiler';
|
||||
|
||||
/**
|
||||
* Write a pipe definition to the output context.
|
||||
*/
|
||||
export function compilePipe(
|
||||
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
|
||||
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector,
|
||||
mode: OutputMode) {
|
||||
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
// e.g. `name: 'myPipe'`
|
||||
@ -30,18 +31,11 @@ export function compilePipe(
|
||||
definitionMapValues.push(
|
||||
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
|
||||
|
||||
// e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }`
|
||||
const deps = dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector);
|
||||
const templateFactory = compileFactoryFunction({
|
||||
name: identifierName(pipe.type) !,
|
||||
fnOrClass: outputCtx.importExpr(pipe.type.reference), deps,
|
||||
useNew: true,
|
||||
injectFn: R3.directiveInject,
|
||||
useOptionalParam: false,
|
||||
});
|
||||
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
|
||||
const templateFactory = createFactory(pipe.type, outputCtx, reflector, []);
|
||||
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
|
||||
|
||||
// e.g. `pure: true`
|
||||
// e.g. pure: true
|
||||
if (pipe.pure) {
|
||||
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
|
||||
}
|
||||
@ -53,15 +47,25 @@ export function compilePipe(
|
||||
const definitionFunction =
|
||||
o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]);
|
||||
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ definitionField,
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ definitionFunction)],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
if (mode === OutputMode.PartialClass) {
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
/* name */ className,
|
||||
/* parent */ null,
|
||||
/* fields */[new o.ClassField(
|
||||
/* name */ definitionField,
|
||||
/* type */ o.INFERRED_TYPE,
|
||||
/* modifiers */[o.StmtModifier.Static],
|
||||
/* initializer */ definitionFunction)],
|
||||
/* getters */[],
|
||||
/* constructorMethod */ new o.ClassMethod(null, [], []),
|
||||
/* methods */[]));
|
||||
} else {
|
||||
// Create back-patch definition.
|
||||
const classReference = outputCtx.importExpr(pipe.type.reference);
|
||||
|
||||
// Create the back-patch statement
|
||||
outputCtx.statements.push(
|
||||
new o.CommentStmt(BUILD_OPTIMIZER_COLOCATE),
|
||||
classReference.prop(definitionField).set(definitionFunction).toStmt());
|
||||
}
|
||||
}
|
@ -1,386 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ParsedEvent, ParsedProperty, ParsedVariable, ParserError} from '../expression_parser/ast';
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {replaceNgsp} from '../ml_parser/html_whitespaces';
|
||||
import {isNgTemplate} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
|
||||
import {syntaxError} from '../util';
|
||||
import * as t from './r3_ast';
|
||||
|
||||
|
||||
const BIND_NAME_REGEXP =
|
||||
/^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/;
|
||||
|
||||
// Group 1 = "bind-"
|
||||
const KW_BIND_IDX = 1;
|
||||
// Group 2 = "let-"
|
||||
const KW_LET_IDX = 2;
|
||||
// Group 3 = "ref-/#"
|
||||
const KW_REF_IDX = 3;
|
||||
// Group 4 = "on-"
|
||||
const KW_ON_IDX = 4;
|
||||
// Group 5 = "bindon-"
|
||||
const KW_BINDON_IDX = 5;
|
||||
// Group 6 = "@"
|
||||
const KW_AT_IDX = 6;
|
||||
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
|
||||
const IDENT_KW_IDX = 7;
|
||||
// Group 8 = identifier inside [()]
|
||||
const IDENT_BANANA_BOX_IDX = 8;
|
||||
// Group 9 = identifier inside []
|
||||
const IDENT_PROPERTY_IDX = 9;
|
||||
// Group 10 = identifier inside ()
|
||||
const IDENT_EVENT_IDX = 10;
|
||||
|
||||
const TEMPLATE_ATTR_PREFIX = '*';
|
||||
const CLASS_ATTR = 'class';
|
||||
// Default selector used by `<ng-content>` if none specified
|
||||
const DEFAULT_CONTENT_SELECTOR = '*';
|
||||
|
||||
// Result of the html AST to Ivy AST transformation
|
||||
export type Render3ParseResult = {
|
||||
nodes: t.Node[]; errors: ParseError[];
|
||||
// Any non default (empty or '*') selector found in the template
|
||||
ngContentSelectors: string[];
|
||||
// Wether the template contains any `<ng-content>`
|
||||
hasNgContent: boolean;
|
||||
};
|
||||
|
||||
export function htmlAstToRender3Ast(
|
||||
htmlNodes: html.Node[], bindingParser: BindingParser): Render3ParseResult {
|
||||
const transformer = new HtmlAstToIvyAst(bindingParser);
|
||||
const ivyNodes = html.visitAll(transformer, htmlNodes);
|
||||
|
||||
// Errors might originate in either the binding parser or the html to ivy transformer
|
||||
const allErrors = bindingParser.errors.concat(transformer.errors);
|
||||
const errors: ParseError[] = allErrors.filter(e => e.level === ParseErrorLevel.ERROR);
|
||||
|
||||
if (errors.length > 0) {
|
||||
const errorString = errors.join('\n');
|
||||
throw syntaxError(`Template parse errors:\n${errorString}`, errors);
|
||||
}
|
||||
|
||||
return {
|
||||
nodes: ivyNodes,
|
||||
errors: allErrors,
|
||||
ngContentSelectors: transformer.ngContentSelectors,
|
||||
hasNgContent: transformer.hasNgContent,
|
||||
};
|
||||
}
|
||||
|
||||
class HtmlAstToIvyAst implements html.Visitor {
|
||||
errors: ParseError[] = [];
|
||||
// Selectors for the `ng-content` tags. Only non `*` selectors are recorded here
|
||||
ngContentSelectors: string[] = [];
|
||||
// Any `<ng-content>` in the template ?
|
||||
hasNgContent = false;
|
||||
|
||||
constructor(private bindingParser: BindingParser) {}
|
||||
|
||||
// HTML visitor
|
||||
visitElement(element: html.Element): t.Node|null {
|
||||
const preparsedElement = preparseElement(element);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
preparsedElement.type === PreparsedElementType.STYLE) {
|
||||
// Skipping <script> for security reasons
|
||||
// Skipping <style> as we already processed them
|
||||
// in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
|
||||
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
|
||||
// Skipping stylesheets with either relative urls or package scheme as we already processed
|
||||
// them in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
|
||||
// Whether the element is a `<ng-template>`
|
||||
const isTemplateElement = isNgTemplate(element.name);
|
||||
|
||||
const matchableAttributes: [string, string][] = [];
|
||||
const parsedProperties: ParsedProperty[] = [];
|
||||
const boundEvents: t.BoundEvent[] = [];
|
||||
const variables: t.Variable[] = [];
|
||||
const references: t.Reference[] = [];
|
||||
const attributes: t.TextAttribute[] = [];
|
||||
|
||||
const templateMatchableAttributes: [string, string][] = [];
|
||||
let inlineTemplateSourceSpan: ParseSourceSpan;
|
||||
const templateParsedProperties: ParsedProperty[] = [];
|
||||
const templateVariables: t.Variable[] = [];
|
||||
|
||||
// Whether the element has any *-attribute
|
||||
let elementHasInlineTemplate = false;
|
||||
|
||||
for (const attribute of element.attrs) {
|
||||
let hasBinding = false;
|
||||
const normalizedName = normalizeAttributeName(attribute.name);
|
||||
|
||||
// `*attr` defines template bindings
|
||||
let isTemplateBinding = false;
|
||||
|
||||
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
if (elementHasInlineTemplate) {
|
||||
this.reportError(
|
||||
`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`,
|
||||
attribute.sourceSpan);
|
||||
}
|
||||
isTemplateBinding = true;
|
||||
elementHasInlineTemplate = true;
|
||||
const templateValue = attribute.value;
|
||||
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||
|
||||
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||
|
||||
const parsedVariables: ParsedVariable[] = [];
|
||||
this.bindingParser.parseInlineTemplateBinding(
|
||||
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
|
||||
templateParsedProperties, parsedVariables);
|
||||
templateVariables.push(
|
||||
...parsedVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||
} else {
|
||||
// Check for variables, events, property bindings, interpolation
|
||||
hasBinding = this.parseAttribute(
|
||||
isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents,
|
||||
variables, references);
|
||||
}
|
||||
|
||||
if (!hasBinding && !isTemplateBinding) {
|
||||
// don't include the bindings as attributes as well in the AST
|
||||
attributes.push(this.visitAttribute(attribute) as t.TextAttribute);
|
||||
matchableAttributes.push([attribute.name, attribute.value]);
|
||||
}
|
||||
}
|
||||
|
||||
const children: t.Node[] =
|
||||
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
|
||||
|
||||
let parsedElement: t.Node|undefined;
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
// `<ng-content>`
|
||||
this.hasNgContent = true;
|
||||
|
||||
if (element.children && !element.children.every(isEmptyTextNode)) {
|
||||
this.reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
|
||||
}
|
||||
|
||||
const selector = preparsedElement.selectAttr;
|
||||
|
||||
let attributes: t.TextAttribute[] = element.attrs.map(attribute => {
|
||||
return new t.TextAttribute(
|
||||
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||
});
|
||||
|
||||
const selectorIndex =
|
||||
selector === DEFAULT_CONTENT_SELECTOR ? 0 : this.ngContentSelectors.push(selector);
|
||||
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
|
||||
} else if (isTemplateElement) {
|
||||
// `<ng-template>`
|
||||
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, children, references, variables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
} else {
|
||||
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
|
||||
|
||||
parsedElement = new t.Element(
|
||||
element.name, attributes, boundAttributes, boundEvents, children, references,
|
||||
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
|
||||
if (elementHasInlineTemplate) {
|
||||
const attributes: t.TextAttribute[] = [];
|
||||
|
||||
templateMatchableAttributes.forEach(
|
||||
([name, value]) =>
|
||||
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
|
||||
|
||||
const boundAttributes = this.createBoundAttributes('ng-template', templateParsedProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
}
|
||||
return parsedElement;
|
||||
}
|
||||
|
||||
visitAttribute(attribute: html.Attribute): t.Node {
|
||||
return new t.TextAttribute(
|
||||
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
|
||||
}
|
||||
|
||||
visitText(text: html.Text): t.Node {
|
||||
const valueNoNgsp = replaceNgsp(text.value);
|
||||
const expr = this.bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan);
|
||||
return expr ? new t.BoundText(expr, text.sourceSpan) : new t.Text(valueNoNgsp, text.sourceSpan);
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment): null { return null; }
|
||||
|
||||
visitExpansion(expansion: html.Expansion): null { return null; }
|
||||
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
|
||||
|
||||
private createBoundAttributes(elementName: string, properties: ParsedProperty[]):
|
||||
t.BoundAttribute[] {
|
||||
return properties.filter(prop => !prop.isLiteral)
|
||||
.map(prop => this.bindingParser.createBoundElementProperty(elementName, prop))
|
||||
.map(prop => t.BoundAttribute.fromBoundElementProperty(prop));
|
||||
}
|
||||
|
||||
private parseAttribute(
|
||||
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
|
||||
parsedProperties: ParsedProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
|
||||
references: t.Reference[]) {
|
||||
const name = normalizeAttributeName(attribute.name);
|
||||
const value = attribute.value;
|
||||
const srcSpan = attribute.sourceSpan;
|
||||
|
||||
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||
let hasBinding = false;
|
||||
|
||||
if (bindParts) {
|
||||
hasBinding = true;
|
||||
if (bindParts[KW_BIND_IDX] != null) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
|
||||
|
||||
} else if (bindParts[KW_LET_IDX]) {
|
||||
if (isTemplateElement) {
|
||||
const identifier = bindParts[IDENT_KW_IDX];
|
||||
this.parseVariable(identifier, value, srcSpan, variables);
|
||||
} else {
|
||||
this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
|
||||
}
|
||||
|
||||
} else if (bindParts[KW_REF_IDX]) {
|
||||
const identifier = bindParts[IDENT_KW_IDX];
|
||||
this.parseReference(identifier, value, srcSpan, references);
|
||||
|
||||
} else if (bindParts[KW_ON_IDX]) {
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
} else if (bindParts[KW_BINDON_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
} else if (bindParts[KW_AT_IDX]) {
|
||||
this.bindingParser.parseLiteralAttr(
|
||||
name, value, srcSpan, matchableAttributes, parsedProperties);
|
||||
|
||||
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
|
||||
parsedProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
|
||||
parsedProperties);
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
} else {
|
||||
hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, matchableAttributes, parsedProperties);
|
||||
}
|
||||
|
||||
return hasBinding;
|
||||
}
|
||||
|
||||
private parseVariable(
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
|
||||
if (identifier.indexOf('-') > -1) {
|
||||
this.reportError(`"-" is not allowed in variable names`, sourceSpan);
|
||||
}
|
||||
variables.push(new t.Variable(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private parseReference(
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, references: t.Reference[]) {
|
||||
if (identifier.indexOf('-') > -1) {
|
||||
this.reportError(`"-" is not allowed in reference names`, sourceSpan);
|
||||
}
|
||||
|
||||
references.push(new t.Reference(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private parseAssignmentEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
|
||||
private reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
|
||||
this.errors.push(new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
}
|
||||
|
||||
class NonBindableVisitor implements html.Visitor {
|
||||
visitElement(ast: html.Element): t.Element|null {
|
||||
const preparsedElement = preparseElement(ast);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
preparsedElement.type === PreparsedElementType.STYLE ||
|
||||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
|
||||
// Skipping <script> for security reasons
|
||||
// Skipping <style> and stylesheets as we already processed them
|
||||
// in the StyleCompiler
|
||||
return null;
|
||||
}
|
||||
|
||||
const children: t.Node[] = html.visitAll(this, ast.children, null);
|
||||
return new t.Element(
|
||||
ast.name, html.visitAll(this, ast.attrs) as t.TextAttribute[],
|
||||
/* inputs */[], /* outputs */[], children, /* references */[], ast.sourceSpan,
|
||||
ast.startSourceSpan, ast.endSourceSpan);
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment): any { return null; }
|
||||
|
||||
visitAttribute(attribute: html.Attribute): t.TextAttribute {
|
||||
return new t.TextAttribute(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
}
|
||||
|
||||
visitText(text: html.Text): t.Text { return new t.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitExpansion(expansion: html.Expansion): any { return null; }
|
||||
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase): any { return null; }
|
||||
}
|
||||
|
||||
const NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
||||
|
||||
function normalizeAttributeName(attrName: string): string {
|
||||
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
|
||||
}
|
||||
|
||||
function addEvents(events: ParsedEvent[], boundEvents: t.BoundEvent[]) {
|
||||
boundEvents.push(...events.map(e => t.BoundEvent.fromParsedEvent(e)));
|
||||
}
|
||||
|
||||
function isEmptyTextNode(node: html.Node): boolean {
|
||||
return node instanceof html.Text && node.value.trim().length == 0;
|
||||
}
|
@ -6,6 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* The statement mode for the render, either as a class back-patch or as a partial class
|
||||
*/
|
||||
export const enum OutputMode {
|
||||
PartialClass,
|
||||
BackPatch,
|
||||
}
|
||||
|
||||
/**
|
||||
* Comment to insert above back-patch
|
||||
*/
|
||||
|
1332
packages/compiler/src/render3/r3_view_compiler.ts
Normal file
1332
packages/compiler/src/render3/r3_view_compiler.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import * as o from '../output/output_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
/**
|
||||
* Convert an object map with `Expression` values into a `LiteralMapExpr`.
|
||||
*/
|
||||
export function mapToMapExpression(map: {[key: string]: o.Expression}): o.LiteralMapExpr {
|
||||
const result = Object.keys(map).map(key => ({key, value: map[key], quoted: false}));
|
||||
return o.literalMap(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert metadata into an `Expression` in the given `OutputContext`.
|
||||
*
|
||||
* This operation will handle arrays, references to symbols, or literal `null` or `undefined`.
|
||||
*/
|
||||
export function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
|
||||
if (Array.isArray(meta)) {
|
||||
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
|
||||
}
|
||||
if (meta instanceof StaticSymbol) {
|
||||
return ctx.importExpr(meta);
|
||||
}
|
||||
if (meta == null) {
|
||||
return o.literal(meta);
|
||||
}
|
||||
|
||||
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import * as t from '../r3_ast';
|
||||
import {R3DependencyMetadata} from '../r3_factory';
|
||||
|
||||
/**
|
||||
* Information needed to compile a directive for the render3 runtime.
|
||||
*/
|
||||
export interface R3DirectiveMetadata {
|
||||
/**
|
||||
* Name of the directive type.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* An expression representing a reference to the directive itself.
|
||||
*/
|
||||
type: o.Expression;
|
||||
|
||||
/**
|
||||
* A source span for the directive type.
|
||||
*/
|
||||
typeSourceSpan: ParseSourceSpan;
|
||||
|
||||
/**
|
||||
* Dependencies of the directive's constructor.
|
||||
*/
|
||||
deps: R3DependencyMetadata[];
|
||||
|
||||
/**
|
||||
* Unparsed selector of the directive, or `null` if there was no selector.
|
||||
*/
|
||||
selector: string|null;
|
||||
|
||||
/**
|
||||
* Information about the content queries made by the directive.
|
||||
*/
|
||||
queries: R3QueryMetadata[];
|
||||
|
||||
/**
|
||||
* Mappings indicating how the directive interacts with its host element (host bindings,
|
||||
* listeners, etc).
|
||||
*/
|
||||
host: {
|
||||
/**
|
||||
* A mapping of attribute binding keys to unparsed expressions.
|
||||
*/
|
||||
attributes: {[key: string]: string};
|
||||
|
||||
/**
|
||||
* A mapping of event binding keys to unparsed expressions.
|
||||
*/
|
||||
listeners: {[key: string]: string};
|
||||
|
||||
/**
|
||||
* A mapping of property binding keys to unparsed expressions.
|
||||
*/
|
||||
properties: {[key: string]: string};
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about usage of specific lifecycle events which require special treatment in the
|
||||
* code generator.
|
||||
*/
|
||||
lifecycle: {
|
||||
/**
|
||||
* Whether the directive uses NgOnChanges.
|
||||
*/
|
||||
usesOnChanges: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A mapping of input field names to the property names.
|
||||
*/
|
||||
inputs: {[field: string]: string};
|
||||
|
||||
/**
|
||||
* A mapping of output field names to the property names.
|
||||
*/
|
||||
outputs: {[field: string]: string};
|
||||
}
|
||||
|
||||
/**
|
||||
* Information needed to compile a component for the render3 runtime.
|
||||
*/
|
||||
export interface R3ComponentMetadata extends R3DirectiveMetadata {
|
||||
/**
|
||||
* Information about the component's template.
|
||||
*/
|
||||
template: {
|
||||
/**
|
||||
* Parsed nodes of the template.
|
||||
*/
|
||||
nodes: t.Node[];
|
||||
|
||||
/**
|
||||
* Whether the template includes <ng-content> tags.
|
||||
*/
|
||||
hasNgContent: boolean;
|
||||
|
||||
/**
|
||||
* Selectors found in the <ng-content> tags in the template.
|
||||
*/
|
||||
ngContentSelectors: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about the view queries made by the component.
|
||||
*/
|
||||
viewQueries: R3QueryMetadata[];
|
||||
|
||||
/**
|
||||
* A map of pipe names to an expression referencing the pipe type which are in the scope of the
|
||||
* compilation.
|
||||
*/
|
||||
pipes: Map<string, o.Expression>;
|
||||
|
||||
/**
|
||||
* A map of directive selectors to an expression referencing the directive type which are in the
|
||||
* scope of the compilation.
|
||||
*/
|
||||
directives: Map<string, o.Expression>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information needed to compile a query (view or content).
|
||||
*/
|
||||
export interface R3QueryMetadata {
|
||||
/**
|
||||
* Name of the property on the class to update with query results.
|
||||
*/
|
||||
propertyName: string;
|
||||
|
||||
/**
|
||||
* Whether to read only the first matching result, or an array of results.
|
||||
*/
|
||||
first: boolean;
|
||||
|
||||
/**
|
||||
* Either an expression representing a type for the query predicate, or a set of string selectors.
|
||||
*/
|
||||
predicate: o.Expression|string[];
|
||||
|
||||
/**
|
||||
* Whether to include only direct children or all descendants.
|
||||
*/
|
||||
descendants: boolean;
|
||||
|
||||
/**
|
||||
* An expression representing a type to read from each matched node, or null if the node itself
|
||||
* is to be returned.
|
||||
*/
|
||||
read: o.Expression|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output of render3 directive compilation.
|
||||
*/
|
||||
export interface R3DirectiveDef {
|
||||
expression: o.Expression;
|
||||
type: o.Type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output of render3 component compilation.
|
||||
*/
|
||||
export interface R3ComponentDef {
|
||||
expression: o.Expression;
|
||||
type: o.Type;
|
||||
}
|
@ -1,447 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '../../aot/static_symbol';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../../compile_metadata';
|
||||
import {CompileReflector} from '../../compile_reflector';
|
||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
||||
import {Identifiers} from '../../identifiers';
|
||||
import {LifecycleHooks} from '../../lifecycle_reflector';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan, typeSourceSpan} from '../../parse_util';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
|
||||
import * as t from '../r3_ast';
|
||||
import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {Render3ParseResult} from '../r3_template_transform';
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
|
||||
import {BindingScope, TemplateDefinitionBuilder} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util';
|
||||
|
||||
function baseDirectiveFields(
|
||||
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
||||
bindingParser: BindingParser): DefinitionMap {
|
||||
const definitionMap = new DefinitionMap();
|
||||
|
||||
// e.g. `type: MyDirective`
|
||||
definitionMap.set('type', meta.type);
|
||||
|
||||
// e.g. `selectors: [['', 'someDir', '']]`
|
||||
definitionMap.set('selectors', createDirectiveSelector(meta.selector !));
|
||||
|
||||
const queryDefinitions = createQueryDefinitions(meta.queries, constantPool);
|
||||
|
||||
// e.g. `factory: () => new MyApp(injectElementRef())`
|
||||
definitionMap.set('factory', compileFactoryFunction({
|
||||
name: meta.name,
|
||||
fnOrClass: meta.type,
|
||||
deps: meta.deps,
|
||||
useNew: true,
|
||||
injectFn: R3.directiveInject,
|
||||
useOptionalParam: false,
|
||||
extraResults: queryDefinitions,
|
||||
}));
|
||||
|
||||
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
|
||||
definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser));
|
||||
|
||||
// e.g. `attributes: ['role', 'listbox']`
|
||||
definitionMap.set('attributes', createHostAttributesArray(meta));
|
||||
|
||||
// e.g 'inputs: {a: 'a'}`
|
||||
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs));
|
||||
|
||||
// e.g 'outputs: {a: 'a'}`
|
||||
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
||||
|
||||
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
|
||||
const features: o.Expression[] = [];
|
||||
if (meta.lifecycle.usesOnChanges) {
|
||||
features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type]));
|
||||
}
|
||||
if (features.length) {
|
||||
definitionMap.set('features', o.literalArr(features));
|
||||
}
|
||||
|
||||
return definitionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
|
||||
*/
|
||||
export function compileDirectiveFromMetadata(
|
||||
meta: R3DirectiveMetadata, constantPool: ConstantPool,
|
||||
bindingParser: BindingParser): R3DirectiveDef {
|
||||
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
||||
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);
|
||||
const type =
|
||||
new o.ExpressionType(o.importExpr(R3.DirectiveDef, [new o.ExpressionType(meta.type)]));
|
||||
return {expression, type};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
|
||||
*/
|
||||
export function compileComponentFromMetadata(
|
||||
meta: R3ComponentMetadata, constantPool: ConstantPool,
|
||||
bindingParser: BindingParser): R3ComponentDef {
|
||||
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
||||
|
||||
const selector = meta.selector && CssSelector.parse(meta.selector);
|
||||
const firstSelector = selector && selector[0];
|
||||
|
||||
// e.g. `attr: ["class", ".my.app"]`
|
||||
// This is optional an only included if the first selector of a component specifies attributes.
|
||||
if (firstSelector) {
|
||||
const selectorAttributes = firstSelector.getAttrs();
|
||||
if (selectorAttributes.length) {
|
||||
definitionMap.set(
|
||||
'attrs', constantPool.getConstLiteral(
|
||||
o.literalArr(selectorAttributes.map(
|
||||
value => value != null ? o.literal(value) : o.literal(undefined))),
|
||||
/* forceShared */ true));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the CSS matcher that recognize directive
|
||||
let directiveMatcher: SelectorMatcher|null = null;
|
||||
|
||||
if (meta.directives.size) {
|
||||
const matcher = new SelectorMatcher();
|
||||
meta.directives.forEach((expression, selector: string) => {
|
||||
matcher.addSelectables(CssSelector.parse(selector), expression);
|
||||
});
|
||||
directiveMatcher = matcher;
|
||||
}
|
||||
|
||||
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
||||
const templateTypeName = meta.name;
|
||||
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
||||
|
||||
const directivesUsed = new Set<o.Expression>();
|
||||
const pipesUsed = new Set<o.Expression>();
|
||||
|
||||
const template = meta.template;
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(
|
||||
constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName,
|
||||
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed,
|
||||
R3.namespaceHTML)
|
||||
.buildTemplateFunction(
|
||||
template.nodes, [], template.hasNgContent, template.ngContentSelectors);
|
||||
|
||||
definitionMap.set('template', templateFunctionExpression);
|
||||
|
||||
// e.g. `directives: [MyDirective]`
|
||||
if (directivesUsed.size) {
|
||||
definitionMap.set('directives', o.literalArr(Array.from(directivesUsed)));
|
||||
}
|
||||
|
||||
// e.g. `pipes: [MyPipe]`
|
||||
if (pipesUsed.size) {
|
||||
definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed)));
|
||||
}
|
||||
|
||||
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
|
||||
const type =
|
||||
new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)]));
|
||||
|
||||
return {expression, type};
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around `compileDirective` which depends on render2 global analysis data as its input
|
||||
* instead of the `R3DirectiveMetadata`.
|
||||
*
|
||||
* `R3DirectiveMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
|
||||
* information.
|
||||
*/
|
||||
export function compileDirectiveFromRender2(
|
||||
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
|
||||
bindingParser: BindingParser) {
|
||||
const name = identifierName(directive.type) !;
|
||||
name || error(`Cannot resolver the name of ${directive.type}`);
|
||||
|
||||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive);
|
||||
|
||||
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
|
||||
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||
|
||||
// Create the partial class to be merged with the actual class.
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
name, null,
|
||||
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
|
||||
[], new o.ClassMethod(null, [], []), []));
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around `compileComponent` which depends on render2 global analysis data as its input
|
||||
* instead of the `R3DirectiveMetadata`.
|
||||
*
|
||||
* `R3ComponentMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
|
||||
* information.
|
||||
*/
|
||||
export function compileComponentFromRender2(
|
||||
outputCtx: OutputContext, component: CompileDirectiveMetadata, render3Ast: Render3ParseResult,
|
||||
reflector: CompileReflector, bindingParser: BindingParser, directiveTypeBySel: Map<string, any>,
|
||||
pipeTypeByName: Map<string, any>) {
|
||||
const name = identifierName(component.type) !;
|
||||
name || error(`Cannot resolver the name of ${component.type}`);
|
||||
|
||||
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
|
||||
|
||||
const summary = component.toSummary();
|
||||
|
||||
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
|
||||
const meta: R3ComponentMetadata = {
|
||||
...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector),
|
||||
selector: component.selector,
|
||||
template: {
|
||||
nodes: render3Ast.nodes,
|
||||
hasNgContent: render3Ast.hasNgContent,
|
||||
ngContentSelectors: render3Ast.ngContentSelectors,
|
||||
},
|
||||
directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx),
|
||||
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
|
||||
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
|
||||
};
|
||||
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
|
||||
|
||||
// Create the partial class to be merged with the actual class.
|
||||
outputCtx.statements.push(new o.ClassStmt(
|
||||
name, null,
|
||||
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
|
||||
[], new o.ClassMethod(null, [], []), []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute `R3DirectiveMetadata` given `CompileDirectiveMetadata` and a `CompileReflector`.
|
||||
*/
|
||||
function directiveMetadataFromGlobalMetadata(
|
||||
directive: CompileDirectiveMetadata, outputCtx: OutputContext,
|
||||
reflector: CompileReflector): R3DirectiveMetadata {
|
||||
const summary = directive.toSummary();
|
||||
const name = identifierName(directive.type) !;
|
||||
name || error(`Cannot resolver the name of ${directive.type}`);
|
||||
|
||||
return {
|
||||
name,
|
||||
type: outputCtx.importExpr(directive.type.reference),
|
||||
typeSourceSpan:
|
||||
typeSourceSpan(directive.isComponent ? 'Component' : 'Directive', directive.type),
|
||||
selector: directive.selector,
|
||||
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
|
||||
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
|
||||
host: {
|
||||
attributes: directive.hostAttributes,
|
||||
listeners: summary.hostListeners,
|
||||
properties: summary.hostProperties,
|
||||
},
|
||||
lifecycle: {
|
||||
usesOnChanges:
|
||||
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
|
||||
},
|
||||
inputs: directive.inputs,
|
||||
outputs: directive.outputs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `CompileQueryMetadata` into `R3QueryMetadata`.
|
||||
*/
|
||||
function queriesFromGlobalMetadata(
|
||||
queries: CompileQueryMetadata[], outputCtx: OutputContext): R3QueryMetadata[] {
|
||||
return queries.map(query => {
|
||||
let read: o.Expression|null = null;
|
||||
if (query.read && query.read.identifier) {
|
||||
read = outputCtx.importExpr(query.read.identifier.reference);
|
||||
}
|
||||
return {
|
||||
propertyName: query.propertyName,
|
||||
first: query.first,
|
||||
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
|
||||
descendants: query.descendants, read,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert `CompileTokenMetadata` for query selectors into either an expression for a predicate
|
||||
* type, or a list of string predicates.
|
||||
*/
|
||||
function selectorsFromGlobalMetadata(
|
||||
selectors: CompileTokenMetadata[], outputCtx: OutputContext): o.Expression|string[] {
|
||||
if (selectors.length > 1 || (selectors.length == 1 && selectors[0].value)) {
|
||||
const selectorStrings = selectors.map(value => value.value as string);
|
||||
selectorStrings.some(value => !value) &&
|
||||
error('Found a type among the string selectors expected');
|
||||
return outputCtx.constantPool.getConstLiteral(
|
||||
o.literalArr(selectorStrings.map(value => o.literal(value))));
|
||||
}
|
||||
|
||||
if (selectors.length == 1) {
|
||||
const first = selectors[0];
|
||||
if (first.identifier) {
|
||||
return outputCtx.importExpr(first.identifier.reference);
|
||||
}
|
||||
}
|
||||
|
||||
error('Unexpected query form');
|
||||
return o.NULL_EXPR;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param meta
|
||||
* @param constantPool
|
||||
*/
|
||||
function createQueryDefinitions(
|
||||
queries: R3QueryMetadata[], constantPool: ConstantPool): o.Expression[]|undefined {
|
||||
const queryDefinitions: o.Expression[] = [];
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i];
|
||||
const predicate = getQueryPredicate(query, constantPool);
|
||||
|
||||
// e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false)
|
||||
const parameters = [
|
||||
o.literal(null, o.INFERRED_TYPE),
|
||||
predicate,
|
||||
o.literal(query.descendants),
|
||||
];
|
||||
|
||||
if (query.read) {
|
||||
parameters.push(query.read);
|
||||
}
|
||||
|
||||
queryDefinitions.push(o.importExpr(R3.query).callFn(parameters));
|
||||
}
|
||||
return queryDefinitions.length > 0 ? queryDefinitions : undefined;
|
||||
}
|
||||
|
||||
// Turn a directive selector into an R3-compatible selector for directive def
|
||||
function createDirectiveSelector(selector: string): o.Expression {
|
||||
return asLiteral(core.parseSelectorToR3Selector(selector));
|
||||
}
|
||||
|
||||
function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null {
|
||||
const values: o.Expression[] = [];
|
||||
const attributes = meta.host.attributes;
|
||||
for (let key of Object.getOwnPropertyNames(attributes)) {
|
||||
const value = attributes[key];
|
||||
values.push(o.literal(key), o.literal(value));
|
||||
}
|
||||
if (values.length > 0) {
|
||||
return o.literalArr(values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return a host binding function or null if one is not necessary.
|
||||
function createHostBindingsFunction(
|
||||
meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null {
|
||||
const statements: o.Statement[] = [];
|
||||
|
||||
const temporary = temporaryAllocator(statements, TEMPORARY_NAME);
|
||||
|
||||
const hostBindingSourceSpan = meta.typeSourceSpan;
|
||||
|
||||
// Calculate the queries
|
||||
for (let index = 0; index < meta.queries.length; index++) {
|
||||
const query = meta.queries[index];
|
||||
|
||||
// e.g. r3.qR(tmp = r3.d(dirIndex)[1]) && (r3.d(dirIndex)[0].someDir = tmp);
|
||||
const getDirectiveMemory = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
|
||||
// The query list is at the query index + 1 because the directive itself is in slot 0.
|
||||
const getQueryList = getDirectiveMemory.key(o.literal(index + 1));
|
||||
const assignToTemporary = temporary().set(getQueryList);
|
||||
const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]);
|
||||
const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE))
|
||||
.prop(query.propertyName)
|
||||
.set(query.first ? temporary().prop('first') : temporary());
|
||||
const andExpression = callQueryRefresh.and(updateDirective);
|
||||
statements.push(andExpression.toStmt());
|
||||
}
|
||||
|
||||
const directiveSummary = metadataAsSummary(meta);
|
||||
|
||||
// Calculate the host property bindings
|
||||
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
||||
const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
|
||||
if (bindings) {
|
||||
for (const binding of bindings) {
|
||||
const bindingExpr = convertPropertyBinding(
|
||||
null, bindingContext, binding.expression, 'b', BindingForm.TrySimple,
|
||||
() => error('Unexpected interpolation'));
|
||||
statements.push(...bindingExpr.stmts);
|
||||
statements.push(o.importExpr(R3.elementProperty)
|
||||
.callFn([
|
||||
o.variable('elIndex'),
|
||||
o.literal(binding.name),
|
||||
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
|
||||
])
|
||||
.toStmt());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate host event bindings
|
||||
const eventBindings =
|
||||
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
|
||||
if (eventBindings) {
|
||||
for (const binding of eventBindings) {
|
||||
const bindingExpr = convertActionBinding(
|
||||
null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation'));
|
||||
const bindingName = binding.name && sanitizeIdentifier(binding.name);
|
||||
const typeName = meta.name;
|
||||
const functionName =
|
||||
typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null;
|
||||
const handler = o.fn(
|
||||
[new o.FnParam('$event', o.DYNAMIC_TYPE)],
|
||||
[...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE,
|
||||
null, functionName);
|
||||
statements.push(
|
||||
o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt());
|
||||
}
|
||||
}
|
||||
|
||||
if (statements.length > 0) {
|
||||
const typeName = meta.name;
|
||||
return o.fn(
|
||||
[
|
||||
new o.FnParam('dirIndex', o.NUMBER_TYPE),
|
||||
new o.FnParam('elIndex', o.NUMBER_TYPE),
|
||||
],
|
||||
statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function metadataAsSummary(meta: R3DirectiveMetadata): CompileDirectiveSummary {
|
||||
// clang-format off
|
||||
return {
|
||||
hostAttributes: meta.host.attributes,
|
||||
hostListeners: meta.host.listeners,
|
||||
hostProperties: meta.host.properties,
|
||||
} as CompileDirectiveSummary;
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
||||
function typeMapToExpressionMap(
|
||||
map: Map<string, StaticSymbol>, outputCtx: OutputContext): Map<string, o.Expression> {
|
||||
// Convert each map entry into another entry where the value is an expression importing the type.
|
||||
const entries = Array.from(map).map(
|
||||
([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]);
|
||||
return new Map(entries);
|
||||
}
|
@ -1,883 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {flatten, sanitizeIdentifier} from '../../compile_metadata';
|
||||
import {CompileReflector} from '../../compile_reflector';
|
||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import * as core from '../../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
|
||||
import {Lexer} from '../../expression_parser/lexer';
|
||||
import {Parser} from '../../expression_parser/parser';
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
|
||||
import {splitNsName} from '../../ml_parser/tags';
|
||||
import * as o from '../../output/output_ast';
|
||||
import {ParseError, ParseSourceSpan} from '../../parse_util';
|
||||
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
|
||||
import {CssSelector, SelectorMatcher} from '../../selector';
|
||||
import {BindingParser} from '../../template_parser/binding_parser';
|
||||
import {OutputContext, error} from '../../util';
|
||||
import * as t from '../r3_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {htmlAstToRender3Ast} from '../r3_template_transform';
|
||||
|
||||
import {R3QueryMetadata} from './api';
|
||||
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
|
||||
|
||||
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
|
||||
[BindingType.Property]: R3.elementProperty,
|
||||
[BindingType.Attribute]: R3.elementAttribute,
|
||||
[BindingType.Class]: R3.elementClassNamed,
|
||||
[BindingType.Style]: R3.elementStyleNamed,
|
||||
};
|
||||
|
||||
// `className` is used below instead of `class` because the interception
|
||||
// code (where this map is used) deals with DOM element property values
|
||||
// (like elm.propName) and not component bindining properties (like [propName]).
|
||||
const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP: {[index: string]: o.ExternalReference} = {
|
||||
'className': R3.elementClass,
|
||||
'style': R3.elementStyle
|
||||
};
|
||||
|
||||
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||
private _dataIndex = 0;
|
||||
private _bindingContext = 0;
|
||||
private _prefixCode: o.Statement[] = [];
|
||||
private _creationCode: o.Statement[] = [];
|
||||
private _variableCode: o.Statement[] = [];
|
||||
private _bindingCode: o.Statement[] = [];
|
||||
private _postfixCode: o.Statement[] = [];
|
||||
private _temporary = temporaryAllocator(this._prefixCode, TEMPORARY_NAME);
|
||||
private _projectionDefinitionIndex = -1;
|
||||
private _valueConverter: ValueConverter;
|
||||
private _unsupported = unsupported;
|
||||
private _bindingScope: BindingScope;
|
||||
|
||||
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
|
||||
private _inI18nSection: boolean = false;
|
||||
private _i18nSectionIndex = -1;
|
||||
// Maps of placeholder to node indexes for each of the i18n section
|
||||
private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}];
|
||||
|
||||
// Number of slots to reserve for pureFunctions
|
||||
private _pureFunctionSlots = 0;
|
||||
|
||||
constructor(
|
||||
private constantPool: ConstantPool, private contextParameter: string,
|
||||
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
|
||||
private templateName: string|null, private viewQueries: R3QueryMetadata[],
|
||||
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
||||
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
||||
private _namespace: o.ExternalReference) {
|
||||
this._bindingScope =
|
||||
parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => {
|
||||
this._bindingCode.push(
|
||||
lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
});
|
||||
this._valueConverter = new ValueConverter(
|
||||
constantPool, () => this.allocateDataSlot(),
|
||||
(numSlots: number): number => this._pureFunctionSlots += numSlots,
|
||||
(name, localName, slot, value: o.ReadVarExpr) => {
|
||||
const pipeType = pipeTypeByName.get(name);
|
||||
if (pipeType) {
|
||||
this.pipes.add(pipeType);
|
||||
}
|
||||
this._bindingScope.set(localName, value);
|
||||
this._creationCode.push(
|
||||
o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt());
|
||||
});
|
||||
}
|
||||
|
||||
buildTemplateFunction(
|
||||
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
|
||||
ngContentSelectors: string[] = []): o.FunctionExpr {
|
||||
if (this._namespace !== R3.namespaceHTML) {
|
||||
this.instruction(this._creationCode, null, this._namespace);
|
||||
}
|
||||
|
||||
// Create variable bindings
|
||||
for (const variable of variables) {
|
||||
const variableName = variable.name;
|
||||
const expression =
|
||||
o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
|
||||
const scopedName = this._bindingScope.freshReferenceName();
|
||||
// Add the reference to the local scope.
|
||||
this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression);
|
||||
}
|
||||
|
||||
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
|
||||
if (hasNgContent) {
|
||||
this._projectionDefinitionIndex = this.allocateDataSlot();
|
||||
const parameters: o.Expression[] = [o.literal(this._projectionDefinitionIndex)];
|
||||
|
||||
// Only selectors with a non-default value are generated
|
||||
if (ngContentSelectors.length > 1) {
|
||||
const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
|
||||
// `projectionDef` needs both the parsed and raw value of the selectors
|
||||
const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
|
||||
const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true);
|
||||
parameters.push(parsed, unParsed);
|
||||
}
|
||||
|
||||
this.instruction(this._creationCode, null, R3.projectionDef, ...parameters);
|
||||
}
|
||||
|
||||
// Define and update any view queries
|
||||
for (let query of this.viewQueries) {
|
||||
// e.g. r3.Q(0, somePredicate, true);
|
||||
const querySlot = this.allocateDataSlot();
|
||||
const predicate = getQueryPredicate(query, this.constantPool);
|
||||
const args: o.Expression[] = [
|
||||
o.literal(querySlot, o.INFERRED_TYPE),
|
||||
predicate,
|
||||
o.literal(query.descendants, o.INFERRED_TYPE),
|
||||
];
|
||||
|
||||
if (query.read) {
|
||||
args.push(query.read);
|
||||
}
|
||||
this.instruction(this._creationCode, null, R3.query, ...args);
|
||||
|
||||
// (r3.qR(tmp = r3.ɵld(0)) && (ctx.someDir = tmp));
|
||||
const temporary = this._temporary();
|
||||
const getQueryList = o.importExpr(R3.load).callFn([o.literal(querySlot)]);
|
||||
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
|
||||
const updateDirective = o.variable(CONTEXT_NAME)
|
||||
.prop(query.propertyName)
|
||||
.set(query.first ? temporary.prop('first') : temporary);
|
||||
this._bindingCode.push(refresh.and(updateDirective).toStmt());
|
||||
}
|
||||
|
||||
t.visitAll(this, nodes);
|
||||
|
||||
if (this._pureFunctionSlots > 0) {
|
||||
this.instruction(
|
||||
this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
|
||||
}
|
||||
|
||||
const creationCode = this._creationCode.length > 0 ?
|
||||
[o.ifStmt(
|
||||
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false),
|
||||
this._creationCode)] :
|
||||
[];
|
||||
|
||||
const updateCode = this._bindingCode.length > 0 ?
|
||||
[o.ifStmt(
|
||||
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Update), null, false),
|
||||
this._bindingCode)] :
|
||||
[];
|
||||
|
||||
// Generate maps of placeholder name to node indexes
|
||||
// TODO(vicb): This is a WIP, not fully supported yet
|
||||
for (const phToNodeIdx of this._phToNodeIdxes) {
|
||||
if (Object.keys(phToNodeIdx).length > 0) {
|
||||
const scopedName = this._bindingScope.freshReferenceName();
|
||||
const phMap = o.variable(scopedName)
|
||||
.set(mapToExpression(phToNodeIdx, true))
|
||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
|
||||
|
||||
this._prefixCode.push(phMap);
|
||||
}
|
||||
}
|
||||
|
||||
return o.fn(
|
||||
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(this.contextParameter, null)],
|
||||
[
|
||||
// Temporary variable declarations for query refresh (i.e. let _t: any;)
|
||||
...this._prefixCode,
|
||||
// Creating mode (i.e. if (rf & RenderFlags.Create) { ... })
|
||||
...creationCode,
|
||||
// Temporary variable declarations for local refs (i.e. const tmp = ld(1) as any)
|
||||
...this._variableCode,
|
||||
// Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...})
|
||||
...updateCode,
|
||||
// Nested templates (i.e. function CompTemplate() {})
|
||||
...this._postfixCode
|
||||
],
|
||||
o.INFERRED_TYPE, null, this.templateName);
|
||||
}
|
||||
|
||||
// LocalResolver
|
||||
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
|
||||
|
||||
visitContent(ngContent: t.Content) {
|
||||
const slot = this.allocateDataSlot();
|
||||
const selectorIndex = ngContent.selectorIndex;
|
||||
const parameters: o.Expression[] = [
|
||||
o.literal(slot),
|
||||
o.literal(this._projectionDefinitionIndex),
|
||||
];
|
||||
|
||||
const attributeAsList: string[] = [];
|
||||
|
||||
ngContent.attributes.forEach((attribute) => {
|
||||
const name = attribute.name;
|
||||
if (name !== 'select') {
|
||||
attributeAsList.push(name, attribute.value);
|
||||
}
|
||||
});
|
||||
|
||||
if (attributeAsList.length > 0) {
|
||||
parameters.push(o.literal(selectorIndex), asLiteral(attributeAsList));
|
||||
} else if (selectorIndex !== 0) {
|
||||
parameters.push(o.literal(selectorIndex));
|
||||
}
|
||||
|
||||
this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters);
|
||||
}
|
||||
|
||||
|
||||
getNamespaceInstruction(namespaceKey: string|null) {
|
||||
switch (namespaceKey) {
|
||||
case 'math':
|
||||
return R3.namespaceMathML;
|
||||
case 'svg':
|
||||
return R3.namespaceSVG;
|
||||
default:
|
||||
return R3.namespaceHTML;
|
||||
}
|
||||
}
|
||||
|
||||
addNamespaceInstruction(nsInstruction: o.ExternalReference, element: t.Element) {
|
||||
this._namespace = nsInstruction;
|
||||
this.instruction(this._creationCode, element.sourceSpan, nsInstruction);
|
||||
}
|
||||
|
||||
visitElement(element: t.Element) {
|
||||
const elementIndex = this.allocateDataSlot();
|
||||
const referenceDataSlots = new Map<string, number>();
|
||||
const wasInI18nSection = this._inI18nSection;
|
||||
|
||||
const outputAttrs: {[name: string]: string} = {};
|
||||
const attrI18nMetas: {[name: string]: string} = {};
|
||||
let i18nMeta: string = '';
|
||||
|
||||
const [namespaceKey, elementName] = splitNsName(element.name);
|
||||
|
||||
// Elements inside i18n sections are replaced with placeholders
|
||||
// TODO(vicb): nested elements are a WIP in this phase
|
||||
if (this._inI18nSection) {
|
||||
const phName = element.name.toLowerCase();
|
||||
if (!this._phToNodeIdxes[this._i18nSectionIndex][phName]) {
|
||||
this._phToNodeIdxes[this._i18nSectionIndex][phName] = [];
|
||||
}
|
||||
this._phToNodeIdxes[this._i18nSectionIndex][phName].push(elementIndex);
|
||||
}
|
||||
|
||||
// Handle i18n attributes
|
||||
for (const attr of element.attributes) {
|
||||
const name = attr.name;
|
||||
const value = attr.value;
|
||||
if (name === I18N_ATTR) {
|
||||
if (this._inI18nSection) {
|
||||
throw new Error(
|
||||
`Could not mark an element as translatable inside of a translatable section`);
|
||||
}
|
||||
this._inI18nSection = true;
|
||||
this._i18nSectionIndex++;
|
||||
this._phToNodeIdxes[this._i18nSectionIndex] = {};
|
||||
i18nMeta = value;
|
||||
} else if (name.startsWith(I18N_ATTR_PREFIX)) {
|
||||
attrI18nMetas[name.slice(I18N_ATTR_PREFIX.length)] = value;
|
||||
} else {
|
||||
outputAttrs[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Match directives on non i18n attributes
|
||||
if (this.directiveMatcher) {
|
||||
const selector = createCssSelector(element.name, outputAttrs);
|
||||
this.directiveMatcher.match(
|
||||
selector, (sel: CssSelector, staticType: any) => { this.directives.add(staticType); });
|
||||
}
|
||||
|
||||
// Element creation mode
|
||||
const parameters: o.Expression[] = [
|
||||
o.literal(elementIndex),
|
||||
o.literal(elementName),
|
||||
];
|
||||
|
||||
// Add the attributes
|
||||
const i18nMessages: o.Statement[] = [];
|
||||
const attributes: o.Expression[] = [];
|
||||
|
||||
Object.getOwnPropertyNames(outputAttrs).forEach(name => {
|
||||
const value = outputAttrs[name];
|
||||
attributes.push(o.literal(name));
|
||||
if (attrI18nMetas.hasOwnProperty(name)) {
|
||||
const meta = parseI18nMeta(attrI18nMetas[name]);
|
||||
const variable = this.constantPool.getTranslation(value, meta);
|
||||
attributes.push(variable);
|
||||
} else {
|
||||
attributes.push(o.literal(value));
|
||||
}
|
||||
});
|
||||
|
||||
const attrArg: o.Expression = attributes.length > 0 ?
|
||||
this.constantPool.getConstLiteral(o.literalArr(attributes), true) :
|
||||
o.TYPED_NULL_EXPR;
|
||||
parameters.push(attrArg);
|
||||
|
||||
if (element.references && element.references.length > 0) {
|
||||
const references = flatten(element.references.map(reference => {
|
||||
const slot = this.allocateDataSlot();
|
||||
referenceDataSlots.set(reference.name, slot);
|
||||
// Generate the update temporary.
|
||||
const variableName = this._bindingScope.freshReferenceName();
|
||||
this._variableCode.push(o.variable(variableName, o.INFERRED_TYPE)
|
||||
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
|
||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
this._bindingScope.set(reference.name, o.variable(variableName));
|
||||
return [reference.name, reference.value];
|
||||
}));
|
||||
parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true));
|
||||
} else {
|
||||
parameters.push(o.TYPED_NULL_EXPR);
|
||||
}
|
||||
|
||||
// Generate the instruction create element instruction
|
||||
if (i18nMessages.length > 0) {
|
||||
this._creationCode.push(...i18nMessages);
|
||||
}
|
||||
|
||||
const wasInNamespace = this._namespace;
|
||||
const currentNamespace = this.getNamespaceInstruction(namespaceKey);
|
||||
|
||||
// If the namespace is changing now, include an instruction to change it
|
||||
// during element creation.
|
||||
if (currentNamespace !== wasInNamespace) {
|
||||
this.addNamespaceInstruction(currentNamespace, element);
|
||||
}
|
||||
|
||||
const isEmptyElement = element.children.length === 0 && element.outputs.length === 0;
|
||||
|
||||
const implicit = o.variable(CONTEXT_NAME);
|
||||
|
||||
if (isEmptyElement) {
|
||||
this.instruction(
|
||||
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
|
||||
} else {
|
||||
// Generate the instruction create element instruction
|
||||
if (i18nMessages.length > 0) {
|
||||
this._creationCode.push(...i18nMessages);
|
||||
}
|
||||
this.instruction(
|
||||
this._creationCode, element.sourceSpan, R3.elementStart,
|
||||
...trimTrailingNulls(parameters));
|
||||
|
||||
// Generate Listeners (outputs)
|
||||
element.outputs.forEach((outputAst: t.BoundEvent) => {
|
||||
const elName = sanitizeIdentifier(element.name);
|
||||
const evName = sanitizeIdentifier(outputAst.name);
|
||||
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
|
||||
const localVars: o.Statement[] = [];
|
||||
const bindingScope =
|
||||
this._bindingScope.nestedScope((lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => {
|
||||
localVars.push(
|
||||
lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
});
|
||||
const bindingExpr = convertActionBinding(
|
||||
bindingScope, implicit, outputAst.handler, 'b',
|
||||
() => error('Unexpected interpolation'));
|
||||
const handler = o.fn(
|
||||
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts],
|
||||
o.INFERRED_TYPE, null, functionName);
|
||||
this.instruction(
|
||||
this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name),
|
||||
handler);
|
||||
});
|
||||
}
|
||||
|
||||
// Generate element input bindings
|
||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||
if (input.type === BindingType.Animation) {
|
||||
this._unsupported('animations');
|
||||
}
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name];
|
||||
if (specialInstruction) {
|
||||
// special case for [style] and [class] bindings since they are not handled as
|
||||
// standard properties within this implementation. Instead they are
|
||||
// handed off to special cased instruction handlers which will then
|
||||
// delegate them as animation sequences (or input bindings for dirs/cmps)
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, specialInstruction, o.literal(elementIndex),
|
||||
convertedBinding);
|
||||
return;
|
||||
}
|
||||
|
||||
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||
if (instruction) {
|
||||
// TODO(chuckj): runtime: security context?
|
||||
this.instruction(
|
||||
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), convertedBinding);
|
||||
} else {
|
||||
this._unsupported(`binding type ${input.type}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Traverse element child nodes
|
||||
if (this._inI18nSection && element.children.length == 1 &&
|
||||
element.children[0] instanceof t.Text) {
|
||||
const text = element.children[0] as t.Text;
|
||||
this.visitSingleI18nTextChild(text, i18nMeta);
|
||||
} else {
|
||||
t.visitAll(this, element.children);
|
||||
}
|
||||
|
||||
if (!isEmptyElement) {
|
||||
// Finish element construction mode.
|
||||
this.instruction(
|
||||
this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd);
|
||||
}
|
||||
|
||||
// Restore the state before exiting this node
|
||||
this._inI18nSection = wasInI18nSection;
|
||||
}
|
||||
|
||||
visitTemplate(template: t.Template) {
|
||||
const templateIndex = this.allocateDataSlot();
|
||||
|
||||
let elName = '';
|
||||
if (template.children.length === 1 && template.children[0] instanceof t.Element) {
|
||||
// When the template as a single child, derive the context name from the tag
|
||||
elName = sanitizeIdentifier((template.children[0] as t.Element).name);
|
||||
}
|
||||
|
||||
const contextName = elName ? `${this.contextName}_${elName}` : '';
|
||||
|
||||
const templateName =
|
||||
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
|
||||
|
||||
const templateContext = `ctx${this.level}`;
|
||||
|
||||
const parameters: o.Expression[] = [
|
||||
o.literal(templateIndex),
|
||||
o.variable(templateName),
|
||||
o.TYPED_NULL_EXPR,
|
||||
];
|
||||
|
||||
const attributeNames: o.Expression[] = [];
|
||||
const attributeMap: {[name: string]: string} = {};
|
||||
|
||||
template.attributes.forEach(a => {
|
||||
attributeNames.push(asLiteral(a.name), asLiteral(''));
|
||||
attributeMap[a.name] = a.value;
|
||||
});
|
||||
|
||||
// Match directives on template attributes
|
||||
if (this.directiveMatcher) {
|
||||
const selector = createCssSelector('ng-template', attributeMap);
|
||||
this.directiveMatcher.match(
|
||||
selector, (cssSelector, staticType) => { this.directives.add(staticType); });
|
||||
}
|
||||
|
||||
if (attributeNames.length) {
|
||||
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true));
|
||||
}
|
||||
|
||||
// e.g. C(1, C1Template)
|
||||
this.instruction(
|
||||
this._creationCode, template.sourceSpan, R3.containerCreate,
|
||||
...trimTrailingNulls(parameters));
|
||||
|
||||
// e.g. p(1, 'forOf', ɵb(ctx.items));
|
||||
const context = o.variable(CONTEXT_NAME);
|
||||
template.inputs.forEach(input => {
|
||||
const convertedBinding = this.convertPropertyBinding(context, input.value);
|
||||
this.instruction(
|
||||
this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex),
|
||||
o.literal(input.name), convertedBinding);
|
||||
});
|
||||
|
||||
// Create the template function
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName,
|
||||
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes,
|
||||
this._namespace);
|
||||
const templateFunctionExpr =
|
||||
templateVisitor.buildTemplateFunction(template.children, template.variables);
|
||||
this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
}
|
||||
|
||||
// These should be handled in the template or element directly.
|
||||
readonly visitReference = invalid;
|
||||
readonly visitVariable = invalid;
|
||||
readonly visitTextAttribute = invalid;
|
||||
readonly visitBoundAttribute = invalid;
|
||||
readonly visitBoundEvent = invalid;
|
||||
|
||||
visitBoundText(text: t.BoundText) {
|
||||
const nodeIndex = this.allocateDataSlot();
|
||||
|
||||
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex));
|
||||
|
||||
this.instruction(
|
||||
this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex),
|
||||
this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value));
|
||||
}
|
||||
|
||||
visitText(text: t.Text) {
|
||||
this.instruction(
|
||||
this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()),
|
||||
o.literal(text.value));
|
||||
}
|
||||
|
||||
// When the content of the element is a single text node the translation can be inlined:
|
||||
//
|
||||
// `<p i18n="desc|mean">some content</p>`
|
||||
// compiles to
|
||||
// ```
|
||||
// /**
|
||||
// * @desc desc
|
||||
// * @meaning mean
|
||||
// */
|
||||
// const MSG_XYZ = goog.getMsg('some content');
|
||||
// i0.ɵT(1, MSG_XYZ);
|
||||
// ```
|
||||
visitSingleI18nTextChild(text: t.Text, i18nMeta: string) {
|
||||
const meta = parseI18nMeta(i18nMeta);
|
||||
const variable = this.constantPool.getTranslation(text.value, meta);
|
||||
this.instruction(
|
||||
this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable);
|
||||
}
|
||||
|
||||
private allocateDataSlot() { return this._dataIndex++; }
|
||||
private bindingContext() { return `${this._bindingContext++}`; }
|
||||
|
||||
private instruction(
|
||||
statements: o.Statement[], span: ParseSourceSpan|null, reference: o.ExternalReference,
|
||||
...params: o.Expression[]) {
|
||||
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
|
||||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||
const pipesConvertedValue = value.visit(this._valueConverter);
|
||||
if (pipesConvertedValue instanceof Interpolation) {
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||
interpolate);
|
||||
this._bindingCode.push(...convertedPropertyBinding.stmts);
|
||||
return convertedPropertyBinding.currValExpr;
|
||||
} else {
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||
() => error('Unexpected interpolation'));
|
||||
this._bindingCode.push(...convertedPropertyBinding.stmts);
|
||||
return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ValueConverter extends AstMemoryEfficientTransformer {
|
||||
constructor(
|
||||
private constantPool: ConstantPool, private allocateSlot: () => number,
|
||||
private allocatePureFunctionSlots: (numSlots: number) => number,
|
||||
private definePipe:
|
||||
(name: string, localName: string, slot: number, value: o.Expression) => void) {
|
||||
super();
|
||||
}
|
||||
|
||||
// AstMemoryEfficientTransformer
|
||||
visitPipe(pipe: BindingPipe, context: any): AST {
|
||||
// Allocate a slot to create the pipe
|
||||
const slot = this.allocateSlot();
|
||||
const slotPseudoLocal = `PIPE:${slot}`;
|
||||
// Allocate one slot for the result plus one slot per pipe argument
|
||||
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
|
||||
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
|
||||
const {identifier, isVarLength} = pipeBindingCallInfo(pipe.args);
|
||||
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(identifier));
|
||||
const args: AST[] = [pipe.exp, ...pipe.args];
|
||||
const convertedArgs: AST[] =
|
||||
isVarLength ? this.visitAll([new LiteralArray(pipe.span, args)]) : this.visitAll(args);
|
||||
|
||||
return new FunctionCall(pipe.span, target, [
|
||||
new LiteralPrimitive(pipe.span, slot),
|
||||
new LiteralPrimitive(pipe.span, pureFunctionSlot),
|
||||
...convertedArgs,
|
||||
]);
|
||||
}
|
||||
|
||||
visitLiteralArray(array: LiteralArray, context: any): AST {
|
||||
return new BuiltinFunctionCall(array.span, this.visitAll(array.expressions), values => {
|
||||
// If the literal has calculated (non-literal) elements transform it into
|
||||
// calls to literal factories that compose the literal and will cache intermediate
|
||||
// values. Otherwise, just return an literal array that contains the values.
|
||||
const literal = o.literalArr(values);
|
||||
return values.every(a => a.isConstant()) ?
|
||||
this.constantPool.getConstLiteral(literal, true) :
|
||||
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
||||
});
|
||||
}
|
||||
|
||||
visitLiteralMap(map: LiteralMap, context: any): AST {
|
||||
return new BuiltinFunctionCall(map.span, this.visitAll(map.values), values => {
|
||||
// If the literal has calculated (non-literal) elements transform it into
|
||||
// calls to literal factories that compose the literal and will cache intermediate
|
||||
// values. Otherwise, just return an literal array that contains the values.
|
||||
const literal = o.literalMap(values.map(
|
||||
(value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted})));
|
||||
return values.every(a => a.isConstant()) ?
|
||||
this.constantPool.getConstLiteral(literal, true) :
|
||||
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Pipes always have at least one parameter, the value they operate on
|
||||
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
|
||||
|
||||
function pipeBindingCallInfo(args: o.Expression[]) {
|
||||
const identifier = pipeBindingIdentifiers[args.length];
|
||||
return {
|
||||
identifier: identifier || R3.pipeBindV,
|
||||
isVarLength: !identifier,
|
||||
};
|
||||
}
|
||||
|
||||
const pureFunctionIdentifiers = [
|
||||
R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4,
|
||||
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
|
||||
];
|
||||
|
||||
function pureFunctionCallInfo(args: o.Expression[]) {
|
||||
const identifier = pureFunctionIdentifiers[args.length];
|
||||
return {
|
||||
identifier: identifier || R3.pureFunctionV,
|
||||
isVarLength: !identifier,
|
||||
};
|
||||
}
|
||||
|
||||
function getLiteralFactory(
|
||||
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
|
||||
allocateSlots: (numSlots: number) => number): o.Expression {
|
||||
const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal);
|
||||
// Allocate 1 slot for the result plus 1 per argument
|
||||
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
|
||||
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
|
||||
const {identifier, isVarLength} = pureFunctionCallInfo(literalFactoryArguments);
|
||||
|
||||
// Literal factories are pure functions that only need to be re-invoked when the parameters
|
||||
// change.
|
||||
const args = [
|
||||
o.literal(startSlot),
|
||||
literalFactory,
|
||||
];
|
||||
|
||||
if (isVarLength) {
|
||||
args.push(o.literalArr(literalFactoryArguments));
|
||||
} else {
|
||||
args.push(...literalFactoryArguments);
|
||||
}
|
||||
|
||||
return o.importExpr(identifier).callFn(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which is executed whenever a variable is referenced for the first time in a given
|
||||
* scope.
|
||||
*
|
||||
* It is expected that the function creates the `const localName = expression`; statement.
|
||||
*/
|
||||
export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void;
|
||||
|
||||
export class BindingScope implements LocalResolver {
|
||||
/**
|
||||
* Keeps a map from local variables to their expressions.
|
||||
*
|
||||
* This is used when one refers to variable such as: 'let abc = a.b.c`.
|
||||
* - key to the map is the string literal `"abc"`.
|
||||
* - value `lhs` is the left hand side which is an AST representing `abc`.
|
||||
* - value `rhs` is the right hand side which is an AST representing `a.b.c`.
|
||||
* - value `declared` is true if the `declareLocalVarCallback` has been called for this scope
|
||||
* already.
|
||||
*/
|
||||
private map = new Map < string, {
|
||||
lhs: o.ReadVarExpr;
|
||||
rhs: o.Expression|undefined;
|
||||
declared: boolean;
|
||||
}
|
||||
> ();
|
||||
private referenceNameIndex = 0;
|
||||
|
||||
static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event'));
|
||||
|
||||
private constructor(
|
||||
private parent: BindingScope|null = null,
|
||||
private declareLocalVarCallback: DeclareLocalVarCallback = noop) {}
|
||||
|
||||
get(name: string): o.Expression|null {
|
||||
let current: BindingScope|null = this;
|
||||
while (current) {
|
||||
let value = current.map.get(name);
|
||||
if (value != null) {
|
||||
if (current !== this) {
|
||||
// make a local copy and reset the `declared` state.
|
||||
value = {lhs: value.lhs, rhs: value.rhs, declared: false};
|
||||
// Cache the value locally.
|
||||
this.map.set(name, value);
|
||||
}
|
||||
if (value.rhs && !value.declared) {
|
||||
// if it is first time we are referencing the variable in the scope
|
||||
// than invoke the callback to insert variable declaration.
|
||||
this.declareLocalVarCallback(value.lhs, value.rhs);
|
||||
value.declared = true;
|
||||
}
|
||||
return value.lhs;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a local variable for later reference.
|
||||
*
|
||||
* @param name Name of the variable.
|
||||
* @param lhs AST representing the left hand side of the `let lhs = rhs;`.
|
||||
* @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be
|
||||
* `undefined` for variable that are ambient such as `$event` and which don't have `rhs`
|
||||
* declaration.
|
||||
*/
|
||||
set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope {
|
||||
!this.map.has(name) ||
|
||||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
||||
this.map.set(name, {lhs: lhs, rhs: rhs, declared: false});
|
||||
return this;
|
||||
}
|
||||
|
||||
getLocal(name: string): (o.Expression|null) { return this.get(name); }
|
||||
|
||||
nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope {
|
||||
return new BindingScope(this, declareCallback);
|
||||
}
|
||||
|
||||
freshReferenceName(): string {
|
||||
let current: BindingScope = this;
|
||||
// Find the top scope as it maintains the global reference count
|
||||
while (current.parent) current = current.parent;
|
||||
const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`;
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `CssSelector` given a tag name and a map of attributes
|
||||
*/
|
||||
function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector {
|
||||
const cssSelector = new CssSelector();
|
||||
|
||||
cssSelector.setElement(tag);
|
||||
|
||||
Object.getOwnPropertyNames(attributes).forEach((name) => {
|
||||
const value = attributes[name];
|
||||
|
||||
cssSelector.addAttribute(name, value);
|
||||
if (name.toLowerCase() === 'class') {
|
||||
const classes = value.trim().split(/\s+/g);
|
||||
classes.forEach(className => cssSelector.addClassName(className));
|
||||
}
|
||||
});
|
||||
|
||||
return cssSelector;
|
||||
}
|
||||
|
||||
// Parse i18n metas like:
|
||||
// - "@@id",
|
||||
// - "description[@@id]",
|
||||
// - "meaning|description[@@id]"
|
||||
function parseI18nMeta(i18n?: string): {description?: string, id?: string, meaning?: string} {
|
||||
let meaning: string|undefined;
|
||||
let description: string|undefined;
|
||||
let id: string|undefined;
|
||||
|
||||
if (i18n) {
|
||||
// TODO(vicb): figure out how to force a message ID with closure ?
|
||||
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
||||
|
||||
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
||||
let meaningAndDesc: string;
|
||||
[meaningAndDesc, id] =
|
||||
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
||||
[meaning, description] = (descIndex > -1) ?
|
||||
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
||||
['', meaningAndDesc];
|
||||
}
|
||||
|
||||
return {description, id, meaning};
|
||||
}
|
||||
|
||||
function interpolate(args: o.Expression[]): o.Expression {
|
||||
args = args.slice(1); // Ignore the length prefix added for render2
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
return o.importExpr(R3.interpolation1).callFn(args);
|
||||
case 5:
|
||||
return o.importExpr(R3.interpolation2).callFn(args);
|
||||
case 7:
|
||||
return o.importExpr(R3.interpolation3).callFn(args);
|
||||
case 9:
|
||||
return o.importExpr(R3.interpolation4).callFn(args);
|
||||
case 11:
|
||||
return o.importExpr(R3.interpolation5).callFn(args);
|
||||
case 13:
|
||||
return o.importExpr(R3.interpolation6).callFn(args);
|
||||
case 15:
|
||||
return o.importExpr(R3.interpolation7).callFn(args);
|
||||
case 17:
|
||||
return o.importExpr(R3.interpolation8).callFn(args);
|
||||
}
|
||||
(args.length >= 19 && args.length % 2 == 1) ||
|
||||
error(`Invalid interpolation argument length ${args.length}`);
|
||||
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
|
||||
*
|
||||
* @param template text of the template to parse
|
||||
* @param templateUrl URL to use for source mapping of the parsed template
|
||||
*/
|
||||
export function parseTemplate(
|
||||
template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}):
|
||||
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
|
||||
const bindingParser = makeBindingParser();
|
||||
const htmlParser = new HtmlParser();
|
||||
const parseResult = htmlParser.parse(template, templateUrl);
|
||||
|
||||
if (parseResult.errors && parseResult.errors.length > 0) {
|
||||
return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
||||
}
|
||||
|
||||
let rootNodes: html.Node[] = parseResult.rootNodes;
|
||||
if (!options.preserveWhitespace) {
|
||||
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
|
||||
}
|
||||
|
||||
const {nodes, hasNgContent, ngContentSelectors, errors} =
|
||||
htmlAstToRender3Ast(rootNodes, bindingParser);
|
||||
if (errors && errors.length > 0) {
|
||||
return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
|
||||
}
|
||||
|
||||
return {nodes, hasNgContent, ngContentSelectors};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a `BindingParser` with a default configuration.
|
||||
*/
|
||||
export function makeBindingParser(): BindingParser {
|
||||
return new BindingParser(
|
||||
new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), [],
|
||||
[]);
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool} from '../../constant_pool';
|
||||
import * as o from '../../output/output_ast';
|
||||
import * as t from '../r3_ast';
|
||||
|
||||
import {R3QueryMetadata} from './api';
|
||||
|
||||
/** Name of the temporary to use during data binding */
|
||||
export const TEMPORARY_NAME = '_t';
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
export const CONTEXT_NAME = 'ctx';
|
||||
|
||||
/** Name of the RenderFlag passed into a template function */
|
||||
export const RENDER_FLAGS = 'rf';
|
||||
|
||||
/** The prefix reference variables */
|
||||
export const REFERENCE_PREFIX = '_r';
|
||||
|
||||
/** The name of the implicit context reference */
|
||||
export const IMPLICIT_REFERENCE = '$implicit';
|
||||
|
||||
/** Name of the i18n attributes **/
|
||||
export const I18N_ATTR = 'i18n';
|
||||
export const I18N_ATTR_PREFIX = 'i18n-';
|
||||
|
||||
/** I18n separators for metadata **/
|
||||
export const MEANING_SEPARATOR = '|';
|
||||
export const ID_SEPARATOR = '@@';
|
||||
|
||||
/**
|
||||
* Creates an allocator for a temporary variable.
|
||||
*
|
||||
* A variable declaration is added to the statements the first time the allocator is invoked.
|
||||
*/
|
||||
export function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr {
|
||||
let temp: o.ReadVarExpr|null = null;
|
||||
return () => {
|
||||
if (!temp) {
|
||||
statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE));
|
||||
temp = o.variable(name);
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function unsupported(feature: string): never {
|
||||
if (this) {
|
||||
throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`);
|
||||
}
|
||||
throw new Error(`Feature ${feature} is not supported yet`);
|
||||
}
|
||||
|
||||
export function invalid<T>(arg: o.Expression | o.Statement | t.Node): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
}
|
||||
|
||||
export function asLiteral(value: any): o.Expression {
|
||||
if (Array.isArray(value)) {
|
||||
return o.literalArr(value.map(asLiteral));
|
||||
}
|
||||
return o.literal(value, o.INFERRED_TYPE);
|
||||
}
|
||||
|
||||
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|
|
||||
null {
|
||||
if (Object.getOwnPropertyNames(keys).length > 0) {
|
||||
return mapToExpression(keys);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression {
|
||||
return o.literalMap(
|
||||
Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove trailing null nodes as they are implied.
|
||||
*/
|
||||
export function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] {
|
||||
while (o.isNull(parameters[parameters.length - 1])) {
|
||||
parameters.pop();
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
export function getQueryPredicate(
|
||||
query: R3QueryMetadata, constantPool: ConstantPool): o.Expression {
|
||||
if (Array.isArray(query.predicate)) {
|
||||
return constantPool.getConstLiteral(
|
||||
o.literalArr(query.predicate.map(selector => o.literal(selector) as o.Expression)));
|
||||
} else {
|
||||
return query.predicate;
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export class DefinitionMap {
|
||||
values: {key: string, quoted: boolean, value: o.Expression}[] = [];
|
||||
|
||||
set(key: string, value: o.Expression|null): void {
|
||||
if (value) {
|
||||
this.values.push({key, value, quoted: false});
|
||||
}
|
||||
}
|
||||
|
||||
toLiteralMap(): o.LiteralMapExpr { return o.literalMap(this.values); }
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {SecurityContext} from '../core';
|
||||
import {ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, 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 {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
@ -17,6 +17,8 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector} from '../selector';
|
||||
import {splitAtColon, splitAtPeriod} from '../util';
|
||||
|
||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType, VariableAst} from './template_ast';
|
||||
|
||||
const PROPERTY_PARTS_SEPARATOR = '.';
|
||||
const ATTRIBUTE_PREFIX = 'attr';
|
||||
const CLASS_PREFIX = 'class';
|
||||
@ -24,33 +26,47 @@ const STYLE_PREFIX = 'style';
|
||||
|
||||
const ANIMATE_PROP_PREFIX = 'animate-';
|
||||
|
||||
export enum BoundPropertyType {
|
||||
DEFAULT,
|
||||
LITERAL_ATTR,
|
||||
ANIMATION
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a parsed property.
|
||||
*/
|
||||
export class BoundProperty {
|
||||
public readonly isLiteral: boolean;
|
||||
public readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public expression: ASTWithSource, public type: BoundPropertyType,
|
||||
public sourceSpan: ParseSourceSpan) {
|
||||
this.isLiteral = this.type === BoundPropertyType.LITERAL_ATTR;
|
||||
this.isAnimation = this.type === BoundPropertyType.ANIMATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bindings in templates and in the directive host area.
|
||||
*/
|
||||
export class BindingParser {
|
||||
pipesByName: Map<string, CompilePipeSummary>|null = null;
|
||||
|
||||
pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
|
||||
|
||||
constructor(
|
||||
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null,
|
||||
public errors: ParseError[]) {
|
||||
// When the `pipes` parameter is `null`, do not check for used pipes
|
||||
// This is used in IVY when we might not know the available pipes at compile time
|
||||
if (pipes) {
|
||||
const pipesByName: Map<string, CompilePipeSummary> = new Map();
|
||||
pipes.forEach(pipe => pipesByName.set(pipe.name, pipe));
|
||||
this.pipesByName = pipesByName;
|
||||
}
|
||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[],
|
||||
private _targetErrors: ParseError[]) {
|
||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||
}
|
||||
|
||||
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||
|
||||
createBoundHostProperties(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||
ParsedProperty[]|null {
|
||||
BoundProperty[]|null {
|
||||
if (dirMeta.hostProperties) {
|
||||
const boundProps: ParsedProperty[] = [];
|
||||
const boundProps: BoundProperty[] = [];
|
||||
Object.keys(dirMeta.hostProperties).forEach(propName => {
|
||||
const expression = dirMeta.hostProperties[propName];
|
||||
if (typeof expression === 'string') {
|
||||
@ -68,27 +84,27 @@ export class BindingParser {
|
||||
|
||||
createDirectiveHostPropertyAsts(
|
||||
dirMeta: CompileDirectiveSummary, elementSelector: string,
|
||||
sourceSpan: ParseSourceSpan): BoundElementProperty[]|null {
|
||||
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
|
||||
const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan);
|
||||
return boundProps &&
|
||||
boundProps.map((prop) => this.createBoundElementProperty(elementSelector, prop));
|
||||
boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
|
||||
}
|
||||
|
||||
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||
ParsedEvent[]|null {
|
||||
BoundEventAst[]|null {
|
||||
if (dirMeta.hostListeners) {
|
||||
const targetEvents: ParsedEvent[] = [];
|
||||
const targetEventAsts: BoundEventAst[] = [];
|
||||
Object.keys(dirMeta.hostListeners).forEach(propName => {
|
||||
const expression = dirMeta.hostListeners[propName];
|
||||
if (typeof expression === 'string') {
|
||||
this.parseEvent(propName, expression, sourceSpan, [], targetEvents);
|
||||
this.parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
return targetEvents;
|
||||
return targetEventAsts;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -108,17 +124,14 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse an inline template binding. ie `<tag *tplKey="<tplValue>">`
|
||||
parseInlineTemplateBinding(
|
||||
tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[],
|
||||
targetVars: ParsedVariable[]) {
|
||||
const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan);
|
||||
|
||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||
} else if (binding.expression) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
@ -129,12 +142,12 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _parseTemplateBindings(tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan):
|
||||
private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
|
||||
TemplateBinding[] {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo);
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (binding.expression) {
|
||||
@ -152,8 +165,8 @@ export class BindingParser {
|
||||
|
||||
parseLiteralAttr(
|
||||
name: string, value: string|null, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
if (isAnimationLabel(name)) {
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
name = name.substring(1);
|
||||
if (value) {
|
||||
this._reportError(
|
||||
@ -163,20 +176,20 @@ export class BindingParser {
|
||||
}
|
||||
this._parseAnimation(name, value, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetProps.push(new ParsedProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), ParsedPropertyType.LITERAL_ATTR,
|
||||
targetProps.push(new BoundProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), BoundPropertyType.LITERAL_ATTR,
|
||||
sourceSpan));
|
||||
}
|
||||
}
|
||||
|
||||
parsePropertyBinding(
|
||||
name: string, expression: string, isHost: boolean, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
let isAnimationProp = false;
|
||||
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(ANIMATE_PROP_PREFIX.length);
|
||||
} else if (isAnimationLabel(name)) {
|
||||
} else if (_isAnimationLabel(name)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(1);
|
||||
}
|
||||
@ -192,7 +205,7 @@ export class BindingParser {
|
||||
|
||||
parsePropertyInterpolation(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: ParsedProperty[]): boolean {
|
||||
targetProps: BoundProperty[]): boolean {
|
||||
const expr = this.parseInterpolation(value, sourceSpan);
|
||||
if (expr) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
@ -203,20 +216,20 @@ export class BindingParser {
|
||||
|
||||
private _parsePropertyAst(
|
||||
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
targetMatchableAttrs.push([name, ast.source !]);
|
||||
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.DEFAULT, sourceSpan));
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseAnimation(
|
||||
name: string, expression: string|null, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
// This will occur when a @trigger is not paired with an expression.
|
||||
// For animations it is valid to not have an expression since */void
|
||||
// states will be applied by angular when the element is attached/detached
|
||||
const ast = this._parseBinding(expression || 'undefined', false, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source !]);
|
||||
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan));
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseBinding(value: string, isHostBinding: boolean, sourceSpan: ParseSourceSpan):
|
||||
@ -236,16 +249,16 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
createBoundElementProperty(elementSelector: string, boundProp: ParsedProperty):
|
||||
BoundElementProperty {
|
||||
createElementPropertyAst(elementSelector: string, boundProp: BoundProperty):
|
||||
BoundElementPropertyAst {
|
||||
if (boundProp.isAnimation) {
|
||||
return new BoundElementProperty(
|
||||
boundProp.name, BindingType.Animation, SecurityContext.NONE, boundProp.expression, null,
|
||||
boundProp.sourceSpan);
|
||||
return new BoundElementPropertyAst(
|
||||
boundProp.name, PropertyBindingType.Animation, SecurityContext.NONE, boundProp.expression,
|
||||
null, boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
let unit: string|null = null;
|
||||
let bindingType: BindingType = undefined !;
|
||||
let bindingType: PropertyBindingType = undefined !;
|
||||
let boundPropertyName: string|null = null;
|
||||
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
||||
let securityContexts: SecurityContext[] = undefined !;
|
||||
@ -265,15 +278,15 @@ export class BindingParser {
|
||||
boundPropertyName = mergeNsAndName(ns, name);
|
||||
}
|
||||
|
||||
bindingType = BindingType.Attribute;
|
||||
bindingType = PropertyBindingType.Attribute;
|
||||
} else if (parts[0] == CLASS_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = BindingType.Class;
|
||||
bindingType = PropertyBindingType.Class;
|
||||
securityContexts = [SecurityContext.NONE];
|
||||
} else if (parts[0] == STYLE_PREFIX) {
|
||||
unit = parts.length > 2 ? parts[2] : null;
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = BindingType.Style;
|
||||
bindingType = PropertyBindingType.Style;
|
||||
securityContexts = [SecurityContext.STYLE];
|
||||
}
|
||||
}
|
||||
@ -283,28 +296,29 @@ export class BindingParser {
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||
bindingType = BindingType.Property;
|
||||
bindingType = PropertyBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||
}
|
||||
|
||||
return new BoundElementProperty(
|
||||
return new BoundElementPropertyAst(
|
||||
boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit,
|
||||
boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
|
||||
if (isAnimationLabel(name)) {
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
name = name.substr(1);
|
||||
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
|
||||
} else {
|
||||
this._parseRegularEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAnimationEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan, targetEvents: ParsedEvent[]) {
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetEvents: BoundEventAst[]) {
|
||||
const matches = splitAtPeriod(name, [name, '']);
|
||||
const eventName = matches[0];
|
||||
const phase = matches[1].toLowerCase();
|
||||
@ -313,8 +327,7 @@ export class BindingParser {
|
||||
case 'start':
|
||||
case 'done':
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetEvents.push(
|
||||
new ParsedEvent(eventName, phase, ParsedEventType.Animation, ast, sourceSpan));
|
||||
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -330,14 +343,14 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _parseRegularEvent(
|
||||
private _parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
// long format: 'target: eventName'
|
||||
const [target, eventName] = splitAtColon(name, [null !, name]);
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name !, ast.source !]);
|
||||
targetEvents.push(new ParsedEvent(eventName, target, ParsedEventType.Regular, ast, sourceSpan));
|
||||
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
|
||||
// Don't detect directives for event names for now,
|
||||
// so don't add the event name to the matchableAttrs
|
||||
}
|
||||
@ -365,7 +378,7 @@ export class BindingParser {
|
||||
private _reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
|
||||
this.errors.push(new ParseError(sourceSpan, message, level));
|
||||
this._targetErrors.push(new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
|
||||
private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
|
||||
@ -374,13 +387,12 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all the used pipes are known in `this.pipesByName`
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void {
|
||||
if (ast && this.pipesByName) {
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (ast) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((ast, pipeName) => {
|
||||
const pipeMeta = this.pipesByName !.get(pipeName);
|
||||
const pipeMeta = this.pipesByName.get(pipeName);
|
||||
if (!pipeMeta) {
|
||||
this._reportError(
|
||||
`The pipe '${pipeName}' could not be found`,
|
||||
@ -418,7 +430,7 @@ export class PipeCollector extends RecursiveAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
function isAnimationLabel(name: string): boolean {
|
||||
function _isAnimationLabel(name: string): boolean {
|
||||
return name[0] == '@';
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user