Compare commits
295 Commits
6.1.0-beta
...
6.0.6
Author | SHA1 | Date | |
---|---|---|---|
e81982ef90 | |||
776bc38999 | |||
7e7ef8ee25 | |||
dd90d92573 | |||
1b862820e9 | |||
1a642231cd | |||
138e0d79cd | |||
f0a43716e2 | |||
2be27fb888 | |||
74f07f40e0 | |||
aa66e84e44 | |||
e1bc3f5c1a | |||
f5b366147b | |||
3147a92ee9 | |||
ffedbde0b3 | |||
e543c734ab | |||
fbe6871a94 | |||
f43cb9398c | |||
1e04df9ac7 | |||
840ca05fe8 | |||
facc9d498d | |||
9d0999027f | |||
c00dd5a3f3 | |||
3908a63381 | |||
20f9e51b8f | |||
232203242a | |||
0ca634e697 | |||
7fd9918024 | |||
b0aacb81d4 | |||
6c850eb031 | |||
0df61ad107 | |||
fa985ac067 | |||
06f9197361 | |||
c2bad1249e | |||
9d69ff8ddd | |||
3fd33f8eb0 | |||
501a243b3f | |||
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.
|
||||
@ -242,8 +204,6 @@ workflows:
|
||||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_jit
|
||||
- test_ivy_aot
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
@ -256,8 +216,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
|
||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@ -1,14 +1,3 @@
|
||||
<a name="6.1.0-beta.2"></a>
|
||||
# [6.1.0-beta.2](https://github.com/angular/angular/compare/6.1.0-beta.1...6.1.0-beta.2) (2018-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363)
|
||||
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.6"></a>
|
||||
## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20)
|
||||
|
||||
@ -24,40 +13,8 @@
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
|
||||
<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)
|
||||
|
||||
|
||||
### 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)
|
||||
@ -66,51 +23,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/**',
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {TableCell, buildTable, emptyTable} from '../util';
|
||||
|
||||
@ -15,7 +15,7 @@ export class LargeTableComponent {
|
||||
data: TableCell[][] = emptyTable;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<LargeTableComponent> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
|
||||
type: LargeTableComponent,
|
||||
selectors: [['largetable']],
|
||||
template: function(rf: RenderFlags, ctx: LargeTableComponent) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
|
||||
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
|
||||
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
|
||||
@ -35,7 +35,7 @@ export class TreeComponent {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<TreeComponent> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
|
||||
type: TreeComponent,
|
||||
selectors: [['tree']],
|
||||
template: function(rf: RenderFlags, ctx: TreeComponent) {
|
||||
@ -95,7 +95,7 @@ export class TreeFunction {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDefInternal<TreeFunction> = defineComponent({
|
||||
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
|
||||
type: TreeFunction,
|
||||
selectors: [['tree']],
|
||||
template: function(rf: RenderFlags, ctx: TreeFunction) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "6.1.0-beta.2",
|
||||
"version": "6.0.6",
|
||||
"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);
|
||||
|
@ -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 = []
|
||||
|
||||
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)
|
||||
|
||||
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,8 +25,6 @@ ts_library(
|
||||
tsconfig = ":tsconfig",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
||||
|
||||
@ -35,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,17 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "annotations",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
@ -1,13 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {ComponentDecoratorHandler} from './src/component';
|
||||
export {DirectiveDecoratorHandler} from './src/directive';
|
||||
export {InjectableDecoratorHandler} from './src/injectable';
|
||||
export {NgModuleDecoratorHandler} from './src/ng_module';
|
||||
export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope';
|
@ -1,117 +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, Expression, R3ComponentMetadata, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, reflectNonStaticField, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {extractDirectiveMetadata} from './directive';
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
|
||||
const EMPTY_MAP = new Map<string, Expression>();
|
||||
|
||||
/**
|
||||
* `DecoratorHandler` which handles the `@Component` annotation.
|
||||
*/
|
||||
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
|
||||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'Component' && decorator.from === '@angular/core');
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3ComponentMetadata> {
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
|
||||
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
||||
// on it.
|
||||
const directiveMetadata = extractDirectiveMetadata(node, decorator, this.checker);
|
||||
if (directiveMetadata === undefined) {
|
||||
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
||||
// case, compilation of the decorator is skipped. Returning an empty object signifies
|
||||
// that no analysis was produced.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Next, read the `@Component`-specific fields.
|
||||
const component = reflectObjectLiteral(meta);
|
||||
|
||||
// Resolve and parse the template.
|
||||
if (!component.has('template')) {
|
||||
throw new Error(`For now, components must directly have a template.`);
|
||||
}
|
||||
const templateExpr = component.get('template') !;
|
||||
const templateStr = staticallyResolve(templateExpr, this.checker);
|
||||
if (typeof templateStr !== 'string') {
|
||||
throw new Error(`Template must statically resolve to a string: ${node.name!.text}`);
|
||||
}
|
||||
|
||||
let preserveWhitespaces: boolean = false;
|
||||
if (component.has('preserveWhitespaces')) {
|
||||
const value = staticallyResolve(component.get('preserveWhitespaces') !, this.checker);
|
||||
if (typeof value !== 'boolean') {
|
||||
throw new Error(`preserveWhitespaces must resolve to a boolean if present`);
|
||||
}
|
||||
preserveWhitespaces = value;
|
||||
}
|
||||
|
||||
const template = parseTemplate(
|
||||
templateStr, `${node.getSourceFile().fileName}#${node.name!.text}/template.html`,
|
||||
{preserveWhitespaces});
|
||||
if (template.errors !== undefined) {
|
||||
throw new Error(
|
||||
`Errors parsing template: ${template.errors.map(e => e.toString()).join(', ')}`);
|
||||
}
|
||||
|
||||
// If the component has a selector, it should be registered with the `SelectorScopeRegistry` so
|
||||
// when this component appears in an `@NgModule` scope, its selector can be determined.
|
||||
if (directiveMetadata.selector !== null) {
|
||||
this.scopeRegistry.registerSelector(node, directiveMetadata.selector);
|
||||
}
|
||||
|
||||
return {
|
||||
analysis: {
|
||||
...directiveMetadata,
|
||||
template,
|
||||
viewQueries: [],
|
||||
|
||||
// These will be replaced during the compilation step, after all `NgModule`s have been
|
||||
// analyzed and the full compilation scope for the component can be realized.
|
||||
pipes: EMPTY_MAP,
|
||||
directives: EMPTY_MAP,
|
||||
}
|
||||
};
|
||||
}
|
||||
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata): CompileResult {
|
||||
const pool = new ConstantPool();
|
||||
|
||||
// Check whether this component was registered with an NgModule. If so, it should be compiled
|
||||
// under that module's compilation scope.
|
||||
const scope = this.scopeRegistry.lookupCompilationScope(node);
|
||||
if (scope !== null) {
|
||||
// Replace the empty components and directives from the analyze() step with a fully expanded
|
||||
// scope. This is possible now because during compile() the whole compilation unit has been
|
||||
// fully analyzed.
|
||||
analysis = {...analysis, ...scope};
|
||||
}
|
||||
|
||||
const res = compileComponentFromMetadata(analysis, pool, makeBindingParser());
|
||||
return {
|
||||
field: 'ngComponentDef',
|
||||
initializer: res.expression,
|
||||
statements: pool.statements,
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,212 +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, R3DirectiveMetadata, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, staticallyResolve} from '../../metadata';
|
||||
import {DecoratedNode, getDecoratedClassElements, reflectNonStaticField, reflectObjectLiteral} from '../../metadata/src/reflector';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {getConstructorDependencies} from './util';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
|
||||
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata> {
|
||||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'Directive' && decorator.from === '@angular/core');
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3DirectiveMetadata> {
|
||||
const analysis = extractDirectiveMetadata(node, decorator, this.checker);
|
||||
|
||||
// If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so
|
||||
// when this directive appears in an `@NgModule` scope, its selector can be determined.
|
||||
if (analysis && analysis.selector !== null) {
|
||||
this.scopeRegistry.registerSelector(node, analysis.selector);
|
||||
}
|
||||
|
||||
return {analysis};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3DirectiveMetadata): CompileResult {
|
||||
const pool = new ConstantPool();
|
||||
const res = compileDirectiveFromMetadata(analysis, pool, makeBindingParser());
|
||||
return {
|
||||
field: 'ngDirectiveDef',
|
||||
initializer: res.expression,
|
||||
statements: pool.statements,
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract metadata from a `Directive` or `Component`.
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker): R3DirectiveMetadata|
|
||||
undefined {
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
const directive = reflectObjectLiteral(meta);
|
||||
|
||||
if (directive.has('jit')) {
|
||||
// The only allowed value is true, so there's no need to expand further.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Precompute a list of ts.ClassElements that have decorators. This includes things like @Input,
|
||||
// @Output, @HostBinding, etc.
|
||||
const decoratedElements = getDecoratedClassElements(clazz, checker);
|
||||
|
||||
// Construct the map of inputs both from the @Directive/@Component decorator, and the decorated
|
||||
// fields.
|
||||
const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', checker);
|
||||
const inputsFromFields = parseDecoratedFields(
|
||||
findDecoratedFields(decoratedElements, '@angular/core', 'Input'), checker);
|
||||
|
||||
// And outputs.
|
||||
const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', checker);
|
||||
const outputsFromFields = parseDecoratedFields(
|
||||
findDecoratedFields(decoratedElements, '@angular/core', 'Output'), checker);
|
||||
|
||||
// Parse the selector.
|
||||
let selector = '';
|
||||
if (directive.has('selector')) {
|
||||
const resolved = staticallyResolve(directive.get('selector') !, checker);
|
||||
if (typeof resolved !== 'string') {
|
||||
throw new Error(`Selector must be a string`);
|
||||
}
|
||||
selector = resolved;
|
||||
}
|
||||
|
||||
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
|
||||
const usesOnChanges = reflectNonStaticField(clazz, 'ngOnChanges') !== null;
|
||||
|
||||
return {
|
||||
name: clazz.name !.text,
|
||||
deps: getConstructorDependencies(clazz, checker),
|
||||
host: {
|
||||
attributes: {},
|
||||
listeners: {},
|
||||
properties: {},
|
||||
},
|
||||
lifecycle: {
|
||||
usesOnChanges,
|
||||
},
|
||||
inputs: {...inputsFromMeta, ...inputsFromFields},
|
||||
outputs: {...outputsFromMeta, ...outputsFromFields},
|
||||
queries: [], selector,
|
||||
type: new WrappedNodeExpr(clazz.name !),
|
||||
typeSourceSpan: null !,
|
||||
};
|
||||
}
|
||||
|
||||
function assertIsStringArray(value: any[]): value is string[] {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (typeof value[i] !== 'string') {
|
||||
throw new Error(`Failed to resolve @Directive.inputs[${i}] to a string`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
type DecoratedProperty = DecoratedNode<ts.PropertyDeclaration|ts.AccessorDeclaration>;
|
||||
|
||||
/**
|
||||
* Find all fields in the array of `DecoratedNode`s that have a decorator of the given type.
|
||||
*/
|
||||
function findDecoratedFields(
|
||||
elements: DecoratedNode<ts.ClassElement>[], decoratorModule: string,
|
||||
decoratorName: string): DecoratedProperty[] {
|
||||
return elements
|
||||
.map(entry => {
|
||||
const element = entry.element;
|
||||
// Only consider properties and accessors. Filter out everything else.
|
||||
if (!ts.isPropertyDeclaration(element) && !ts.isAccessor(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the array of matching decorators (there could be more than one).
|
||||
const decorators = entry.decorators.filter(
|
||||
decorator => decorator.name === decoratorName && decorator.from === decoratorModule);
|
||||
if (decorators.length === 0) {
|
||||
// No matching decorators, don't include this element.
|
||||
return null;
|
||||
}
|
||||
return {element, decorators};
|
||||
})
|
||||
// Filter out nulls.
|
||||
.filter(entry => entry !== null) as DecoratedProperty[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
|
||||
* correctly shaped metadata object.
|
||||
*/
|
||||
function parseFieldToPropertyMapping(
|
||||
directive: Map<string, ts.Expression>, field: string,
|
||||
checker: ts.TypeChecker): {[field: string]: string} {
|
||||
if (!directive.has(field)) {
|
||||
return EMPTY_OBJECT;
|
||||
}
|
||||
|
||||
// Resolve the field of interest from the directive metadata to a string[].
|
||||
const metaValues = staticallyResolve(directive.get(field) !, checker);
|
||||
if (!Array.isArray(metaValues) || !assertIsStringArray(metaValues)) {
|
||||
throw new Error(`Failed to resolve @Directive.${field}`);
|
||||
}
|
||||
|
||||
return metaValues.reduce(
|
||||
(results, value) => {
|
||||
// Either the value is 'field' or 'field: property'. In the first case, `property` will
|
||||
// be undefined, in which case the field name should also be used as the property name.
|
||||
const [field, property] = value.split(':', 2).map(str => str.trim());
|
||||
results[field] = property || field;
|
||||
return results;
|
||||
},
|
||||
{} as{[field: string]: string});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse property decorators (e.g. `Input` or `Output`) and return the correctly shaped metadata
|
||||
* object.
|
||||
*/
|
||||
function parseDecoratedFields(
|
||||
fields: DecoratedProperty[], checker: ts.TypeChecker): {[field: string]: string} {
|
||||
return fields.reduce(
|
||||
(results, field) => {
|
||||
const fieldName = (field.element.name as ts.Identifier).text;
|
||||
field.decorators.forEach(decorator => {
|
||||
// The decorator either doesn't have an argument (@Input()) in which case the property
|
||||
// name is used, or it has one argument (@Output('named')).
|
||||
if (decorator.args.length === 0) {
|
||||
results[fieldName] = fieldName;
|
||||
} else if (decorator.args.length === 1) {
|
||||
const property = staticallyResolve(decorator.args[0], checker);
|
||||
if (typeof property !== 'string') {
|
||||
throw new Error(`Decorator argument must resolve to a string`);
|
||||
}
|
||||
results[fieldName] = property;
|
||||
} else {
|
||||
// Too many arguments.
|
||||
throw new Error(
|
||||
`Decorator must have 0 or 1 arguments, got ${decorator.args.length} argument(s)`);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
},
|
||||
{} as{[field: string]: string});
|
||||
}
|
@ -1,156 +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 {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform/src/api';
|
||||
|
||||
import {getConstructorDependencies} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
*/
|
||||
export class InjectableDecoratorHandler implements DecoratorHandler<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): CompileResult {
|
||||
const res = compileIvyInjectable(analysis);
|
||||
return {
|
||||
field: 'ngInjectableDef',
|
||||
initializer: res.expression,
|
||||
statements: [],
|
||||
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 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,116 +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, Expression, R3DirectiveMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator, Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {SelectorScopeRegistry} from './selector_scope';
|
||||
import {referenceToExpression} from './util';
|
||||
|
||||
/**
|
||||
* Compiles @NgModule annotations to ngModuleDef fields.
|
||||
*
|
||||
* TODO(alxhub): handle injector side of things as well.
|
||||
*/
|
||||
export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMetadata> {
|
||||
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
|
||||
|
||||
detect(decorators: Decorator[]): Decorator|undefined {
|
||||
return decorators.find(
|
||||
decorator => decorator.name === 'NgModule' && decorator.from === '@angular/core');
|
||||
}
|
||||
|
||||
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3NgModuleMetadata> {
|
||||
const meta = decorator.args[0];
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new Error(`Decorator argument must be literal.`);
|
||||
}
|
||||
const ngModule = reflectObjectLiteral(meta);
|
||||
|
||||
if (ngModule.has('jit')) {
|
||||
// The only allowed value is true, so there's no need to expand further.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Extract the module declarations, imports, and exports.
|
||||
let declarations: Reference[] = [];
|
||||
if (ngModule.has('declarations')) {
|
||||
const declarationMeta = staticallyResolve(ngModule.get('declarations') !, this.checker);
|
||||
declarations = resolveTypeList(declarationMeta, 'declarations');
|
||||
}
|
||||
let imports: Reference[] = [];
|
||||
if (ngModule.has('imports')) {
|
||||
const importsMeta = staticallyResolve(ngModule.get('imports') !, this.checker);
|
||||
imports = resolveTypeList(importsMeta, 'imports');
|
||||
}
|
||||
let exports: Reference[] = [];
|
||||
if (ngModule.has('exports')) {
|
||||
const exportsMeta = staticallyResolve(ngModule.get('exports') !, this.checker);
|
||||
exports = resolveTypeList(exportsMeta, 'exports');
|
||||
}
|
||||
|
||||
// Register this module's information with the SelectorScopeRegistry. This ensures that during
|
||||
// the compile() phase, the module's metadata is available for selector scope computation.
|
||||
this.scopeRegistry.registerModule(node, {declarations, imports, exports});
|
||||
|
||||
const context = node.getSourceFile();
|
||||
|
||||
return {
|
||||
analysis: {
|
||||
type: new WrappedNodeExpr(node.name !),
|
||||
bootstrap: [],
|
||||
declarations: declarations.map(decl => referenceToExpression(decl, context)),
|
||||
exports: exports.map(exp => referenceToExpression(exp, context)),
|
||||
imports: imports.map(imp => referenceToExpression(imp, context)),
|
||||
emitInline: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
compile(node: ts.ClassDeclaration, analysis: R3NgModuleMetadata): CompileResult {
|
||||
const res = compileNgModule(analysis);
|
||||
return {
|
||||
field: 'ngModuleDef',
|
||||
initializer: res.expression,
|
||||
statements: [],
|
||||
type: res.type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a list of `Reference`s from a resolved metadata value.
|
||||
*/
|
||||
function resolveTypeList(resolvedList: ResolvedValue, name: string): Reference[] {
|
||||
const refList: Reference[] = [];
|
||||
if (!Array.isArray(resolvedList)) {
|
||||
throw new Error(`Expected array when reading property ${name}`);
|
||||
}
|
||||
|
||||
resolvedList.forEach((entry, idx) => {
|
||||
if (Array.isArray(entry)) {
|
||||
// Recurse into nested arrays.
|
||||
refList.push(...resolveTypeList(entry, name));
|
||||
} else if (entry instanceof Reference) {
|
||||
if (!entry.expressable) {
|
||||
throw new Error(`Value at position ${idx} in ${name} array is not expressable`);
|
||||
} else if (!ts.isClassDeclaration(entry.node)) {
|
||||
throw new Error(`Value at position ${idx} in ${name} array is not a class declaration`);
|
||||
}
|
||||
refList.push(entry);
|
||||
} else {
|
||||
// TODO(alxhub): expand ModuleWithProviders.
|
||||
throw new Error(`Value at position ${idx} in ${name} array is not a reference`);
|
||||
}
|
||||
});
|
||||
|
||||
return refList;
|
||||
}
|
@ -1,353 +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, ExternalExpr, ExternalReference} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteReference, Reference, reflectStaticField, reflectTypeEntityToDeclaration} from '../../metadata';
|
||||
|
||||
import {referenceToExpression} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Metadata extracted for a given NgModule that can be used to compute selector scopes.
|
||||
*/
|
||||
export interface ModuleData {
|
||||
declarations: Reference[];
|
||||
imports: Reference[];
|
||||
exports: Reference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitively expanded maps of directives and pipes visible to a component being compiled in the
|
||||
* context of some module.
|
||||
*/
|
||||
export interface CompilationScope<T> {
|
||||
directives: Map<string, T>;
|
||||
pipes: Map<string, T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Both transitively expanded scopes for a given NgModule.
|
||||
*/
|
||||
interface SelectorScopes {
|
||||
/**
|
||||
* Set of components, directives, and pipes visible to all components being compiled in the
|
||||
* context of some module.
|
||||
*/
|
||||
compilation: Reference[];
|
||||
|
||||
/**
|
||||
* Set of components, directives, and pipes added to the compilation scope of any module importing
|
||||
* some module.
|
||||
*/
|
||||
exported: Reference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry which records and correlates static analysis information of Angular types.
|
||||
*
|
||||
* Once a compilation unit's information is fed into the SelectorScopeRegistry, it can be asked to
|
||||
* produce transitive `CompilationScope`s for components.
|
||||
*/
|
||||
export class SelectorScopeRegistry {
|
||||
/**
|
||||
* Map of modules declared in the current compilation unit to their (local) metadata.
|
||||
*/
|
||||
private _moduleToData = new Map<ts.ClassDeclaration, ModuleData>();
|
||||
|
||||
/**
|
||||
* Map of modules to their cached `CompilationScope`s.
|
||||
*/
|
||||
private _compilationScopeCache = new Map<ts.ClassDeclaration, CompilationScope<Reference>>();
|
||||
|
||||
/**
|
||||
* Map of components/directives to their selector.
|
||||
*/
|
||||
private _directiveToSelector = new Map<ts.ClassDeclaration, string>();
|
||||
|
||||
/**
|
||||
* Map of pipes to their name.
|
||||
*/
|
||||
private _pipeToName = new Map<ts.ClassDeclaration, string>();
|
||||
|
||||
/**
|
||||
* Map of components/directives/pipes to their module.
|
||||
*/
|
||||
private _declararedTypeToModule = new Map<ts.ClassDeclaration, ts.ClassDeclaration>();
|
||||
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
|
||||
/**
|
||||
* Register a module's metadata with the registry.
|
||||
*/
|
||||
registerModule(node: ts.ClassDeclaration, data: ModuleData): void {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
|
||||
if (this._moduleToData.has(node)) {
|
||||
throw new Error(`Module already registered: ${node.name!.text}`);
|
||||
}
|
||||
this._moduleToData.set(node, data);
|
||||
|
||||
// Register all of the module's declarations in the context map as belonging to this module.
|
||||
data.declarations.forEach(decl => {
|
||||
this._declararedTypeToModule.set(ts.getOriginalNode(decl.node) as ts.ClassDeclaration, node);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the selector of a component or directive with the registry.
|
||||
*/
|
||||
registerSelector(node: ts.ClassDeclaration, selector: string): void {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
|
||||
if (this._directiveToSelector.has(node)) {
|
||||
throw new Error(`Selector already registered: ${node.name!.text} ${selector}`);
|
||||
}
|
||||
this._directiveToSelector.set(node, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the name of a pipe with the registry.
|
||||
*/
|
||||
registerPipe(node: ts.ClassDeclaration, name: string): void { this._pipeToName.set(node, name); }
|
||||
|
||||
/**
|
||||
* Produce the compilation scope of a component, which is determined by the module that declares
|
||||
* it.
|
||||
*/
|
||||
lookupCompilationScope(node: ts.ClassDeclaration): CompilationScope<Expression>|null {
|
||||
node = ts.getOriginalNode(node) as ts.ClassDeclaration;
|
||||
|
||||
// If the component has no associated module, then it has no compilation scope.
|
||||
if (!this._declararedTypeToModule.has(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const module = this._declararedTypeToModule.get(node) !;
|
||||
|
||||
// Compilation scope computation is somewhat expensive, so it's cached. Check the cache for
|
||||
// the module.
|
||||
if (this._compilationScopeCache.has(module)) {
|
||||
// The compilation scope was cached.
|
||||
const scope = this._compilationScopeCache.get(module) !;
|
||||
|
||||
// The scope as cached is in terms of References, not Expressions. Converting between them
|
||||
// requires knowledge of the context file (in this case, the component node's source file).
|
||||
return convertScopeToExpressions(scope, node.getSourceFile());
|
||||
}
|
||||
|
||||
// This is the first time the scope for this module is being computed.
|
||||
const directives = new Map<string, Reference>();
|
||||
const pipes = new Map<string, Reference>();
|
||||
|
||||
// Process the declaration scope of the module, and lookup the selector of every declared type.
|
||||
// The initial value of ngModuleImportedFrom is 'null' which signifies that the NgModule
|
||||
// was not imported from a .d.ts source.
|
||||
this.lookupScopes(module !, /* ngModuleImportedFrom */ null).compilation.forEach(ref => {
|
||||
const selector =
|
||||
this.lookupDirectiveSelector(ts.getOriginalNode(ref.node) as ts.ClassDeclaration);
|
||||
// Only directives/components with selectors get added to the scope.
|
||||
if (selector != null) {
|
||||
directives.set(selector, ref);
|
||||
}
|
||||
});
|
||||
|
||||
const scope: CompilationScope<Reference> = {directives, pipes};
|
||||
|
||||
// Many components may be compiled in the same scope, so cache it.
|
||||
this._compilationScopeCache.set(node, scope);
|
||||
|
||||
// Convert References to Expressions in the context of the component's source file.
|
||||
return convertScopeToExpressions(scope, node.getSourceFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup `SelectorScopes` for a given module.
|
||||
*
|
||||
* This function assumes that if the given module was imported from an absolute path
|
||||
* (`ngModuleImportedFrom`) then all of its declarations are exported at that same path, as well
|
||||
* as imports and exports from other modules that are relatively imported.
|
||||
*/
|
||||
private lookupScopes(node: ts.ClassDeclaration, ngModuleImportedFrom: string|null):
|
||||
SelectorScopes {
|
||||
let data: ModuleData|null = null;
|
||||
|
||||
// Either this module was analyzed directly, or has a precompiled ngModuleDef.
|
||||
if (this._moduleToData.has(node)) {
|
||||
// The module was analyzed before, and thus its data is available.
|
||||
data = this._moduleToData.get(node) !;
|
||||
} else {
|
||||
// The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type
|
||||
// annotation that specifies the needed metadata.
|
||||
if (ngModuleImportedFrom === null) {
|
||||
// TODO(alxhub): handle hand-compiled ngModuleDef in the current Program.
|
||||
throw new Error(`Need to read .d.ts module but ngModuleImportedFrom is unspecified`);
|
||||
}
|
||||
data = this._readMetadataFromCompiledClass(node, ngModuleImportedFrom);
|
||||
// Note that data here could still be null, if the class didn't have a precompiled
|
||||
// ngModuleDef.
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
throw new Error(`Module not registered: ${node.name!.text}`);
|
||||
}
|
||||
|
||||
return {
|
||||
compilation: [
|
||||
...data.declarations,
|
||||
// Expand imports to the exported scope of those imports.
|
||||
...flatten(data.imports.map(
|
||||
ref => this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
.exported)),
|
||||
// And include the compilation scope of exported modules.
|
||||
...flatten(
|
||||
data.exports.filter(ref => this._moduleToData.has(ref.node as ts.ClassDeclaration))
|
||||
.map(
|
||||
ref =>
|
||||
this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
.exported))
|
||||
],
|
||||
exported: flatten(data.exports.map(ref => {
|
||||
if (this._moduleToData.has(ref.node as ts.ClassDeclaration)) {
|
||||
return this.lookupScopes(ref.node as ts.ClassDeclaration, absoluteModuleName(ref))
|
||||
.exported;
|
||||
} else {
|
||||
return [ref];
|
||||
}
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the selector of a component or directive class.
|
||||
*
|
||||
* Potentially this class is declared in a .d.ts file or otherwise has a manually created
|
||||
* ngComponentDef/ngDirectiveDef. In this case, the type metadata of that definition is read
|
||||
* to determine the selector.
|
||||
*/
|
||||
private lookupDirectiveSelector(node: ts.ClassDeclaration): string|null {
|
||||
if (this._directiveToSelector.has(node)) {
|
||||
return this._directiveToSelector.get(node) !;
|
||||
} else {
|
||||
return this._readSelectorFromCompiledClass(node);
|
||||
}
|
||||
}
|
||||
|
||||
private lookupPipeName(node: ts.ClassDeclaration): string|undefined {
|
||||
return this._pipeToName.get(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
|
||||
* file, or in a .ts file with a handwritten definition).
|
||||
*
|
||||
* @param clazz the class of interest
|
||||
* @param ngModuleImportedFrom module specifier of the import path to assume for all declarations
|
||||
* stemming from this module.
|
||||
*/
|
||||
private _readMetadataFromCompiledClass(clazz: ts.ClassDeclaration, ngModuleImportedFrom: string):
|
||||
ModuleData|null {
|
||||
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`.
|
||||
// TODO(alxhub): investigate caching of .d.ts module metadata.
|
||||
const ngModuleDef = reflectStaticField(clazz, 'ngModuleDef');
|
||||
if (ngModuleDef === null) {
|
||||
return null;
|
||||
} else if (
|
||||
// Validate that the shape of the ngModuleDef type is correct.
|
||||
ngModuleDef.type === undefined || !ts.isTypeReferenceNode(ngModuleDef.type) ||
|
||||
ngModuleDef.type.typeArguments === undefined ||
|
||||
ngModuleDef.type.typeArguments.length !== 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the ModuleData out of the type arguments.
|
||||
const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
|
||||
return {
|
||||
declarations: this._extractReferencesFromType(declarationMetadata, ngModuleImportedFrom),
|
||||
exports: this._extractReferencesFromType(exportMetadata, ngModuleImportedFrom),
|
||||
imports: this._extractReferencesFromType(importMetadata, ngModuleImportedFrom),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selector from type metadata for a class with a precompiled ngComponentDef or
|
||||
* ngDirectiveDef.
|
||||
*/
|
||||
private _readSelectorFromCompiledClass(clazz: ts.ClassDeclaration): string|null {
|
||||
const def =
|
||||
reflectStaticField(clazz, 'ngComponentDef') || reflectStaticField(clazz, 'ngDirectiveDef');
|
||||
if (def === null) {
|
||||
// No definition could be found.
|
||||
return null;
|
||||
} else if (
|
||||
def.type === undefined || !ts.isTypeReferenceNode(def.type) ||
|
||||
def.type.typeArguments === undefined || def.type.typeArguments.length !== 2) {
|
||||
// The type metadata was the wrong shape.
|
||||
return null;
|
||||
}
|
||||
const type = def.type.typeArguments[1];
|
||||
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
|
||||
// The type metadata was the wrong type.
|
||||
return null;
|
||||
}
|
||||
return type.literal.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a `TypeNode` which is a tuple of references to other types, and return `Reference`s to
|
||||
* them.
|
||||
*
|
||||
* This operation assumes that these types should be imported from `ngModuleImportedFrom` unless
|
||||
* they themselves were imported from another absolute path.
|
||||
*/
|
||||
private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string): Reference[] {
|
||||
if (!ts.isTupleTypeNode(def)) {
|
||||
return [];
|
||||
}
|
||||
return def.elementTypes.map(element => {
|
||||
if (!ts.isTypeReferenceNode(element)) {
|
||||
throw new Error(`Expected TypeReferenceNode`);
|
||||
}
|
||||
const type = element.typeName;
|
||||
const {node, from} = reflectTypeEntityToDeclaration(type, this.checker);
|
||||
const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
||||
const clazz = node as ts.ClassDeclaration;
|
||||
return new AbsoluteReference(node, clazz.name !, moduleName, clazz.name !.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function flatten<T>(array: T[][]): T[] {
|
||||
return array.reduce((accum, subArray) => {
|
||||
accum.push(...subArray);
|
||||
return accum;
|
||||
}, [] as T[]);
|
||||
}
|
||||
|
||||
function absoluteModuleName(ref: Reference): string|null {
|
||||
const name = (ref.node as ts.ClassDeclaration).name !.text;
|
||||
if (!(ref instanceof AbsoluteReference)) {
|
||||
return null;
|
||||
}
|
||||
return ref.moduleName;
|
||||
}
|
||||
|
||||
function convertReferenceMap(
|
||||
map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> {
|
||||
return new Map<string, Expression>(Array.from(map.entries()).map(([selector, ref]): [
|
||||
string, Expression
|
||||
] => [selector, referenceToExpression(ref, context)]));
|
||||
}
|
||||
|
||||
function convertScopeToExpressions(
|
||||
scope: CompilationScope<Reference>, context: ts.SourceFile): CompilationScope<Expression> {
|
||||
const directives = convertReferenceMap(scope.directives, context);
|
||||
const pipes = convertReferenceMap(scope.pipes, context);
|
||||
return {directives, pipes};
|
||||
}
|
@ -1,84 +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, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference, reflectConstructorParameters} from '../../metadata';
|
||||
import {reflectImportedIdentifier} from '../../metadata/src/reflector';
|
||||
|
||||
export 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, host = false;
|
||||
let resolved = R3ResolvedDependencyType.Token;
|
||||
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 if (dec.name === 'Host') {
|
||||
host = true;
|
||||
} else if (dec.name === 'Attribute') {
|
||||
if (dec.args.length !== 1) {
|
||||
throw new Error(`Unexpected number of arguments to @Attribute().`);
|
||||
}
|
||||
tokenExpr = dec.args[0];
|
||||
resolved = R3ResolvedDependencyType.Attribute;
|
||||
} else {
|
||||
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
|
||||
}
|
||||
});
|
||||
if (tokenExpr === null) {
|
||||
throw new Error(
|
||||
`No suitable token for parameter ${(param.name as ts.Identifier).text} of class ${clazz.name!.text} with decorators ${param.decorators.map(dec => dec.from + '#' + dec.name).join(',')}`);
|
||||
}
|
||||
if (ts.isIdentifier(tokenExpr)) {
|
||||
const importedSymbol = reflectImportedIdentifier(tokenExpr, checker);
|
||||
if (importedSymbol !== null && importedSymbol.from === '@angular/core') {
|
||||
switch (importedSymbol.name) {
|
||||
case 'ElementRef':
|
||||
resolved = R3ResolvedDependencyType.ElementRef;
|
||||
break;
|
||||
case 'Injector':
|
||||
resolved = R3ResolvedDependencyType.Injector;
|
||||
break;
|
||||
case 'TemplateRef':
|
||||
resolved = R3ResolvedDependencyType.TemplateRef;
|
||||
break;
|
||||
case 'ViewContainerRef':
|
||||
resolved = R3ResolvedDependencyType.ViewContainerRef;
|
||||
break;
|
||||
default:
|
||||
// Leave as a Token or Attribute.
|
||||
}
|
||||
}
|
||||
}
|
||||
const token = new WrappedNodeExpr(tokenExpr);
|
||||
useType.push({token, optional, self, skipSelf, host, resolved});
|
||||
});
|
||||
return useType;
|
||||
}
|
||||
|
||||
export function referenceToExpression(ref: Reference, context: ts.SourceFile): Expression {
|
||||
const exp = ref.toExpression(context);
|
||||
if (exp === null) {
|
||||
throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`);
|
||||
}
|
||||
return exp;
|
||||
}
|
@ -1,28 +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",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//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,79 +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 {AbsoluteReference, ResolvedReference} from '../../metadata/src/resolver';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {NgModuleDecoratorHandler} from '../src/ng_module';
|
||||
import {SelectorScopeRegistry} from '../src/selector_scope';
|
||||
|
||||
describe('SelectorScopeRegistry', () => {
|
||||
it('absolute imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{
|
||||
name: 'node_modules/@angular/core/index.d.ts',
|
||||
contents: `
|
||||
export interface NgComponentDef<A, B> {}
|
||||
export interface NgModuleDef<A, B, C, D> {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'node_modules/some_library/index.d.ts',
|
||||
contents: `
|
||||
import {NgComponentDef, NgModuleDef} from '@angular/core';
|
||||
import * as i0 from './component';
|
||||
|
||||
export declare class SomeModule {
|
||||
static ngModuleDef: NgModuleDef<SomeModule, [i0.SomeCmp], any, [i0.SomeCmp]>;
|
||||
}
|
||||
|
||||
export declare class SomeCmp {
|
||||
static ngComponentDef: NgComponentDef<SomeCmp, 'some-cmp'>;
|
||||
}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'node_modules/some_library/component.d.ts',
|
||||
contents: `
|
||||
export declare class SomeCmp {}
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
export class ProgramCmp {}
|
||||
export class ProgramModule {}
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const ProgramModule =
|
||||
getDeclaration(program, 'entry.ts', 'ProgramModule', ts.isClassDeclaration);
|
||||
const ProgramCmp = getDeclaration(program, 'entry.ts', 'ProgramCmp', ts.isClassDeclaration);
|
||||
const SomeModule = getDeclaration(
|
||||
program, 'node_modules/some_library/index.d.ts', 'SomeModule', ts.isClassDeclaration);
|
||||
expect(ProgramModule).toBeDefined();
|
||||
expect(SomeModule).toBeDefined();
|
||||
|
||||
const registry = new SelectorScopeRegistry(checker);
|
||||
|
||||
registry.registerModule(ProgramModule, {
|
||||
declarations: [new ResolvedReference(ProgramCmp, ProgramCmp.name !)],
|
||||
exports: [],
|
||||
imports: [new AbsoluteReference(SomeModule, SomeModule.name !, 'some_library', 'SomeModule')],
|
||||
});
|
||||
|
||||
registry.registerSelector(ProgramCmp, 'program-cmp');
|
||||
|
||||
const scope = registry.lookupCompilationScope(ProgramCmp) !;
|
||||
expect(scope).toBeDefined();
|
||||
expect(scope.directives).toBeDefined();
|
||||
expect(scope.directives.size).toBe(1);
|
||||
});
|
||||
});
|
@ -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,16 +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:types",
|
||||
"//packages/compiler",
|
||||
],
|
||||
)
|
@ -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 {Decorator, Parameter, reflectConstructorParameters, reflectDecorator, reflectNonStaticField, reflectObjectLiteral, reflectStaticField, reflectTypeEntityToDeclaration,} from './src/reflector';
|
||||
|
||||
export {AbsoluteReference, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
|
@ -1,338 +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};
|
||||
}
|
||||
|
||||
export interface DecoratedNode<T extends ts.Node> {
|
||||
element: T;
|
||||
decorators: Decorator[];
|
||||
}
|
||||
|
||||
export function getDecoratedClassElements(
|
||||
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): DecoratedNode<ts.ClassElement>[] {
|
||||
const decoratedElements: DecoratedNode<ts.ClassElement>[] = [];
|
||||
clazz.members.forEach(element => {
|
||||
if (element.decorators !== undefined) {
|
||||
const decorators = element.decorators.map(decorator => reflectDecorator(decorator, checker))
|
||||
.filter(decorator => decorator != null) as Decorator[];
|
||||
if (decorators.length > 0) {
|
||||
decoratedElements.push({element, decorators});
|
||||
}
|
||||
}
|
||||
});
|
||||
return decoratedElements;
|
||||
}
|
||||
|
||||
export function reflectStaticField(
|
||||
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
|
||||
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
|
||||
// Check if the name matches.
|
||||
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
|
||||
return false;
|
||||
}
|
||||
// Check if the property is static.
|
||||
if (member.modifiers === undefined ||
|
||||
!member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
||||
return false;
|
||||
}
|
||||
// Found the field.
|
||||
return true;
|
||||
}) ||
|
||||
null;
|
||||
}
|
||||
|
||||
export function reflectNonStaticField(
|
||||
clazz: ts.ClassDeclaration, field: string): ts.PropertyDeclaration|null {
|
||||
return clazz.members.find((member: ts.ClassElement): member is ts.PropertyDeclaration => {
|
||||
// Check if the name matches.
|
||||
if (member.name === undefined || !ts.isIdentifier(member.name) || member.name.text !== field) {
|
||||
return false;
|
||||
}
|
||||
// Check if the property is static.
|
||||
if (member.modifiers !== undefined &&
|
||||
member.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
||||
return false;
|
||||
}
|
||||
// Found the field.
|
||||
return true;
|
||||
}) ||
|
||||
null;
|
||||
}
|
||||
|
||||
export function reflectTypeEntityToDeclaration(
|
||||
type: ts.EntityName, checker: ts.TypeChecker): {node: ts.Declaration, from: string | null} {
|
||||
let realSymbol = checker.getSymbolAtLocation(type);
|
||||
if (realSymbol === undefined) {
|
||||
throw new Error(`Cannot resolve type entity to symbol`);
|
||||
}
|
||||
while (realSymbol.flags & ts.SymbolFlags.Alias) {
|
||||
realSymbol = checker.getAliasedSymbol(realSymbol);
|
||||
}
|
||||
|
||||
let node: ts.Declaration|null = null;
|
||||
if (realSymbol.valueDeclaration !== undefined) {
|
||||
node = realSymbol.valueDeclaration;
|
||||
} else if (realSymbol.declarations !== undefined && realSymbol.declarations.length === 1) {
|
||||
node = realSymbol.declarations[0];
|
||||
} else {
|
||||
throw new Error(`Cannot resolve type entity symbol to declaration`);
|
||||
}
|
||||
|
||||
if (ts.isQualifiedName(type)) {
|
||||
if (!ts.isIdentifier(type.left)) {
|
||||
throw new Error(`Cannot handle qualified name with non-identifier lhs`);
|
||||
}
|
||||
const symbol = checker.getSymbolAtLocation(type.left);
|
||||
if (symbol === undefined || symbol.declarations === undefined ||
|
||||
symbol.declarations.length !== 1) {
|
||||
throw new Error(`Cannot resolve qualified type entity lhs to symbol`);
|
||||
}
|
||||
const decl = symbol.declarations[0];
|
||||
if (ts.isNamespaceImport(decl)) {
|
||||
const clause = decl.parent !;
|
||||
const importDecl = clause.parent !;
|
||||
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
||||
throw new Error(`Module specifier is not a string`);
|
||||
}
|
||||
return {node, from: importDecl.moduleSpecifier.text};
|
||||
} else {
|
||||
throw new Error(`Unknown import type?`);
|
||||
}
|
||||
} else {
|
||||
return {node, from: null};
|
||||
}
|
||||
}
|
@ -1,634 +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 {Expression, ExternalExpr, ExternalReference, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const TS_DTS_EXTENSION = /(\.d)?\.ts$/;
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
/**
|
||||
* 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 abstract class Reference {
|
||||
constructor(readonly node: ts.Node) {}
|
||||
|
||||
/**
|
||||
* Whether an `Expression` can be generated which references the node.
|
||||
*/
|
||||
readonly expressable: boolean;
|
||||
|
||||
/**
|
||||
* Generate an `Expression` representing this type, in the context of the given SourceFile.
|
||||
*
|
||||
* This could be a local variable reference, if the symbol is imported, or it could be a new
|
||||
* import if needed.
|
||||
*/
|
||||
abstract toExpression(context: ts.SourceFile): Expression|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node only, without any ability to get an `Expression` representing that node.
|
||||
*
|
||||
* This is used for returning references to things like method declarations, which are not directly
|
||||
* referenceable.
|
||||
*/
|
||||
export class NodeReference extends Reference {
|
||||
toExpression(context: ts.SourceFile): null { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node which has a `ts.Identifier` and can be resolved to an `Expression`.
|
||||
*
|
||||
* Imports generated by `ResolvedReference`s are always relative.
|
||||
*/
|
||||
export class ResolvedReference extends Reference {
|
||||
constructor(node: ts.Node, protected identifier: ts.Identifier) { super(node); }
|
||||
|
||||
readonly expressable = true;
|
||||
|
||||
toExpression(context: ts.SourceFile): Expression {
|
||||
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.node).getSourceFile()) {
|
||||
return new WrappedNodeExpr(this.identifier);
|
||||
} else {
|
||||
// Relative import from context -> this.node.getSourceFile().
|
||||
// TODO(alxhub): investigate the impact of multiple source roots here.
|
||||
// TODO(alxhub): investigate the need to map such paths via the Host for proper g3 support.
|
||||
let relative =
|
||||
path.posix.relative(path.dirname(context.fileName), this.node.getSourceFile().fileName)
|
||||
.replace(TS_DTS_EXTENSION, '');
|
||||
|
||||
// path.relative() does not include the leading './'.
|
||||
if (!relative.startsWith('.')) {
|
||||
relative = `./${relative}`;
|
||||
}
|
||||
|
||||
// path.relative() returns the empty string (converted to './' above) if the two paths are the
|
||||
// same.
|
||||
if (relative === './') {
|
||||
// Same file after all.
|
||||
return new WrappedNodeExpr(this.identifier);
|
||||
} else {
|
||||
return new ExternalExpr(new ExternalReference(relative, this.identifier.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node which has a `ts.Identifer` and an expected absolute module name.
|
||||
*
|
||||
* An `AbsoluteReference` can be resolved to an `Expression`, and if that expression is an import
|
||||
* the module specifier will be an absolute module name, not a relative path.
|
||||
*/
|
||||
export class AbsoluteReference extends Reference {
|
||||
constructor(
|
||||
node: ts.Node, private identifier: ts.Identifier, readonly moduleName: string,
|
||||
private symbolName: string) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
readonly expressable = true;
|
||||
|
||||
toExpression(context: ts.SourceFile): Expression {
|
||||
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.node.getSourceFile())) {
|
||||
return new WrappedNodeExpr(this.identifier);
|
||||
} else {
|
||||
return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).visit(
|
||||
node, {absoluteModuleName: null, scope: new Map<ts.ParameterDeclaration, ResolvedValue>()});
|
||||
}
|
||||
|
||||
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]
|
||||
]);
|
||||
|
||||
interface Context {
|
||||
absoluteModuleName: string|null;
|
||||
scope: Scope;
|
||||
}
|
||||
|
||||
class StaticInterpreter {
|
||||
constructor(private checker: ts.TypeChecker) {}
|
||||
|
||||
visit(node: ts.Expression, context: Context): ResolvedValue {
|
||||
return this.visitExpression(node, context);
|
||||
}
|
||||
|
||||
private visitExpression(node: ts.Expression, context: Context): 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.isNoSubstitutionTemplateLiteral(node)) {
|
||||
return node.text;
|
||||
} else if (ts.isNumericLiteral(node)) {
|
||||
return parseFloat(node.text);
|
||||
} else if (ts.isObjectLiteralExpression(node)) {
|
||||
return this.visitObjectLiteralExpression(node, context);
|
||||
} else if (ts.isIdentifier(node)) {
|
||||
return this.visitIdentifier(node, context);
|
||||
} else if (ts.isPropertyAccessExpression(node)) {
|
||||
return this.visitPropertyAccessExpression(node, context);
|
||||
} else if (ts.isCallExpression(node)) {
|
||||
return this.visitCallExpression(node, context);
|
||||
} else if (ts.isConditionalExpression(node)) {
|
||||
return this.visitConditionalExpression(node, context);
|
||||
} else if (ts.isPrefixUnaryExpression(node)) {
|
||||
return this.visitPrefixUnaryExpression(node, context);
|
||||
} else if (ts.isBinaryExpression(node)) {
|
||||
return this.visitBinaryExpression(node, context);
|
||||
} else if (ts.isArrayLiteralExpression(node)) {
|
||||
return this.visitArrayLiteralExpression(node, context);
|
||||
} else if (ts.isParenthesizedExpression(node)) {
|
||||
return this.visitParenthesizedExpression(node, context);
|
||||
} else if (ts.isElementAccessExpression(node)) {
|
||||
return this.visitElementAccessExpression(node, context);
|
||||
} else if (ts.isAsExpression(node)) {
|
||||
return this.visitExpression(node.expression, context);
|
||||
} else if (ts.isNonNullExpression(node)) {
|
||||
return this.visitExpression(node.expression, context);
|
||||
} else if (ts.isClassDeclaration(node)) {
|
||||
return this.visitDeclaration(node, context);
|
||||
} else {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression, context: Context):
|
||||
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, context);
|
||||
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, context);
|
||||
if (isDynamicValue(result)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
array.push(result);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private visitObjectLiteralExpression(node: ts.ObjectLiteralExpression, context: Context):
|
||||
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, context);
|
||||
|
||||
// Check whether the name can be determined statically.
|
||||
if (name === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
map.set(name, this.visitExpression(property.initializer, context));
|
||||
} 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, context));
|
||||
} else if (ts.isSpreadAssignment(property)) {
|
||||
const spread = this.visitExpression(property.expression, context);
|
||||
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, context: Context): ResolvedValue {
|
||||
let symbol: ts.Symbol|undefined = this.checker.getSymbolAtLocation(node);
|
||||
if (symbol === undefined) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
return this.visitSymbol(symbol, context);
|
||||
}
|
||||
|
||||
private visitSymbol(symbol: ts.Symbol, context: Context): ResolvedValue {
|
||||
let absoluteModuleName = context.absoluteModuleName;
|
||||
if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
|
||||
for (let i = 0; i < symbol.declarations.length; i++) {
|
||||
const decl = symbol.declarations[i];
|
||||
if (ts.isImportSpecifier(decl) && decl.parent !== undefined &&
|
||||
decl.parent.parent !== undefined && decl.parent.parent.parent !== undefined) {
|
||||
const importDecl = decl.parent.parent.parent;
|
||||
if (ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
||||
const moduleSpecifier = importDecl.moduleSpecifier.text;
|
||||
if (!moduleSpecifier.startsWith('.')) {
|
||||
absoluteModuleName = moduleSpecifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newContext = {...context, absoluteModuleName};
|
||||
|
||||
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, newContext);
|
||||
}
|
||||
|
||||
return symbol.declarations.reduce<ResolvedValue>((prev, decl) => {
|
||||
if (!(isDynamicValue(prev) || prev instanceof Reference)) {
|
||||
return prev;
|
||||
}
|
||||
return this.visitDeclaration(decl, newContext);
|
||||
}, DYNAMIC_VALUE);
|
||||
}
|
||||
|
||||
private visitDeclaration(node: ts.Declaration, context: Context): ResolvedValue {
|
||||
if (ts.isVariableDeclaration(node)) {
|
||||
if (!node.initializer) {
|
||||
return undefined;
|
||||
}
|
||||
return this.visitExpression(node.initializer, context);
|
||||
} else if (ts.isParameter(node) && context.scope.has(node)) {
|
||||
return context.scope.get(node) !;
|
||||
} else if (ts.isExportAssignment(node)) {
|
||||
return this.visitExpression(node.expression, context);
|
||||
} else if (ts.isSourceFile(node)) {
|
||||
return this.visitSourceFile(node, context);
|
||||
} else {
|
||||
return this.getReference(node, context);
|
||||
}
|
||||
}
|
||||
|
||||
private visitElementAccessExpression(node: ts.ElementAccessExpression, context: Context):
|
||||
ResolvedValue {
|
||||
const lhs = this.visitExpression(node.expression, context);
|
||||
if (node.argumentExpression === undefined) {
|
||||
throw new Error(`Expected argument in ElementAccessExpression`);
|
||||
}
|
||||
if (isDynamicValue(lhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
const rhs = this.visitExpression(node.argumentExpression, context);
|
||||
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, context);
|
||||
}
|
||||
|
||||
private visitPropertyAccessExpression(node: ts.PropertyAccessExpression, context: Context):
|
||||
ResolvedValue {
|
||||
const lhs = this.visitExpression(node.expression, context);
|
||||
const rhs = node.name.text;
|
||||
// TODO: handle reference to class declaration.
|
||||
if (isDynamicValue(lhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
return this.accessHelper(lhs, rhs, context);
|
||||
}
|
||||
|
||||
private visitSourceFile(node: ts.SourceFile, context: Context): 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, context)));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private accessHelper(lhs: ResolvedValue, rhs: string|number, context: Context): 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, context) === strIndex);
|
||||
if (member !== undefined) {
|
||||
if (ts.isPropertyDeclaration(member) && member.initializer !== undefined) {
|
||||
value = this.visitExpression(member.initializer, context);
|
||||
} else if (ts.isMethodDeclaration(member)) {
|
||||
value = new NodeReference(member);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new Error(`Invalid dot property access: ${lhs} dot ${rhs}`);
|
||||
}
|
||||
|
||||
private visitCallExpression(node: ts.CallExpression, context: Context): ResolvedValue {
|
||||
const lhs = this.visitExpression(node.expression, context);
|
||||
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, context);
|
||||
}
|
||||
if (value === undefined && param.initializer !== undefined) {
|
||||
value = this.visitExpression(param.initializer, context);
|
||||
}
|
||||
newScope.set(param, value);
|
||||
});
|
||||
|
||||
return ret.expression !== undefined ?
|
||||
this.visitExpression(ret.expression, {...context, scope: newScope}) :
|
||||
undefined;
|
||||
}
|
||||
|
||||
private visitConditionalExpression(node: ts.ConditionalExpression, context: Context):
|
||||
ResolvedValue {
|
||||
const condition = this.visitExpression(node.condition, context);
|
||||
if (isDynamicValue(condition)) {
|
||||
return condition;
|
||||
}
|
||||
|
||||
if (condition) {
|
||||
return this.visitExpression(node.whenTrue, context);
|
||||
} else {
|
||||
return this.visitExpression(node.whenFalse, context);
|
||||
}
|
||||
}
|
||||
|
||||
private visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression, context: Context):
|
||||
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, context);
|
||||
return isDynamicValue(value) ? DYNAMIC_VALUE : op(value);
|
||||
}
|
||||
|
||||
private visitBinaryExpression(node: ts.BinaryExpression, context: Context): 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) {
|
||||
lhs = literal(this.visitExpression(node.left, context));
|
||||
rhs = literal(this.visitExpression(node.right, context));
|
||||
} else {
|
||||
lhs = this.visitExpression(node.left, context);
|
||||
rhs = this.visitExpression(node.right, context);
|
||||
}
|
||||
|
||||
return isDynamicValue(lhs) || isDynamicValue(rhs) ? DYNAMIC_VALUE : opRecord.op(lhs, rhs);
|
||||
}
|
||||
|
||||
private visitParenthesizedExpression(node: ts.ParenthesizedExpression, context: Context):
|
||||
ResolvedValue {
|
||||
return this.visitExpression(node.expression, context);
|
||||
}
|
||||
|
||||
private stringNameFromPropertyName(node: ts.PropertyName, context: Context): string|undefined {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||
return node.text;
|
||||
} else { // ts.ComputedPropertyName
|
||||
const literal = this.visitExpression(node.expression, context);
|
||||
return typeof literal === 'string' ? literal : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getReference(node: ts.Declaration, context: Context): Reference {
|
||||
const id = identifierOfDeclaration(node);
|
||||
if (id === undefined) {
|
||||
throw new Error(`Don't know how to refer to ${ts.SyntaxKind[node.kind]}`);
|
||||
}
|
||||
if (context.absoluteModuleName !== null) {
|
||||
// TODO(alxhub): investigate whether this can get symbol names wrong in the event of
|
||||
// re-exports under different names.
|
||||
return new AbsoluteReference(node, id, context.absoluteModuleName, id.text);
|
||||
} else {
|
||||
return new ResolvedReference(node, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.`);
|
||||
}
|
||||
|
||||
function identifierOfDeclaration(decl: ts.Declaration): ts.Identifier|undefined {
|
||||
if (ts.isClassDeclaration(decl)) {
|
||||
return decl.name;
|
||||
} else if (ts.isFunctionDeclaration(decl)) {
|
||||
return decl.name;
|
||||
} else if (ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) {
|
||||
return decl.name;
|
||||
} else if (ts.isShorthandPropertyAssignment(decl)) {
|
||||
return decl.name;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
@ -1,27 +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",
|
||||
"//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,249 +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 {ExternalExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {Reference, 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('imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'second.ts', contents: 'export function foo(bar) { return bar; }'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {foo} from './second';
|
||||
const target$ = foo;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const resolved = staticallyResolve(expr, checker);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to a reference');
|
||||
}
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
expect(resolved.expressable).toBe(true);
|
||||
const reference = resolved.toExpression(program.getSourceFile('entry.ts') !);
|
||||
if (!(reference instanceof ExternalExpr)) {
|
||||
return fail('Expected expression reference to be an external (import) expression');
|
||||
}
|
||||
expect(reference.value.moduleName).toBe('./second');
|
||||
expect(reference.value.name).toBe('foo');
|
||||
});
|
||||
|
||||
it('absolute imports work', () => {
|
||||
const {program} = makeProgram([
|
||||
{name: 'node_modules/some_library/index.d.ts', contents: 'export declare function foo(bar);'},
|
||||
{
|
||||
name: 'entry.ts',
|
||||
contents: `
|
||||
import {foo} from 'some_library';
|
||||
const target$ = foo;
|
||||
`
|
||||
},
|
||||
]);
|
||||
const checker = program.getTypeChecker();
|
||||
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
|
||||
const expr = result.initializer !;
|
||||
const resolved = staticallyResolve(expr, checker);
|
||||
if (!(resolved instanceof Reference)) {
|
||||
return fail('Expected expression to resolve to a reference');
|
||||
}
|
||||
expect(ts.isFunctionDeclaration(resolved.node)).toBe(true);
|
||||
expect(resolved.expressable).toBe(true);
|
||||
const reference = resolved.toExpression(program.getSourceFile('entry.ts') !);
|
||||
if (!(reference instanceof ExternalExpr)) {
|
||||
return fail('Expected expression reference to be an external (import) expression');
|
||||
}
|
||||
expect(reference.value.moduleName).toBe('some_library');
|
||||
expect(reference.value.name).toBe('foo');
|
||||
});
|
||||
|
||||
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 !;
|
||||
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,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 {GeneratedFile} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import * as api from '../transformers/api';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, SelectorScopeRegistry} from './annotations';
|
||||
import {CompilerHost} from './compiler_host';
|
||||
import {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();
|
||||
const scopeRegistry = new SelectorScopeRegistry(checker);
|
||||
|
||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||
const handlers = [
|
||||
new ComponentDecoratorHandler(checker, scopeRegistry),
|
||||
new DirectiveDecoratorHandler(checker, scopeRegistry),
|
||||
new InjectableDecoratorHandler(checker),
|
||||
new NgModuleDecoratorHandler(checker, scopeRegistry),
|
||||
];
|
||||
const compilation = new IvyCompilation(handlers, 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,133 +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, moduleResolution: ts.ModuleResolutionKind.NodeJs},
|
||||
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,17 +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",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
)
|
@ -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 * from './src/api';
|
||||
export {IvyCompilation} from './src/compilation';
|
||||
export {ivyTransformFactory} from './src/transform';
|
@ -1,62 +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, Statement, 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 handler 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 DecoratorHandler<A> {
|
||||
/**
|
||||
* Scan a set of reflected decorators and determine if this handler 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): CompileResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 CompileResult {
|
||||
field: string;
|
||||
initializer: Expression;
|
||||
statements: Statement[];
|
||||
type: Type;
|
||||
}
|
@ -1,158 +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 {AnalysisOutput, CompileResult, DecoratorHandler} 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: DecoratorHandler<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 handlers: DecoratorHandler<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 DecoratorHandlers to see if any are relevant.
|
||||
this.handlers.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): CompileResult|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,54 +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 {CompileResult} 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, CompileResult>();
|
||||
private imports = new ImportManager();
|
||||
|
||||
/**
|
||||
* Track that a static field was added to the code for a class.
|
||||
*/
|
||||
recordStaticField(name: string, decl: CompileResult): 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,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 {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor';
|
||||
|
||||
import {IvyCompilation} from './compilation';
|
||||
import {ImportManager, translateExpression, translateStatement} 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);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class IvyVisitor extends Visitor {
|
||||
constructor(private compilation: IvyCompilation, private importManager: ImportManager) {
|
||||
super();
|
||||
}
|
||||
|
||||
visitClassDeclaration(node: ts.ClassDeclaration):
|
||||
VisitListEntryResult<ts.Statement, 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 = this.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, this.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, this.compilation.ivyDecoratorFor(node) !),
|
||||
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
||||
[...node.members, property]);
|
||||
const statements = res.statements.map(stmt => translateStatement(stmt, this.importManager));
|
||||
return {node, before: statements};
|
||||
}
|
||||
|
||||
return {node};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = visit(file, new IvyVisitor(compilation, importManager), context);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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,368 +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, BinaryOperator, 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, Statement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const BINARY_OPERATORS = new Map<BinaryOperator, ts.BinaryOperator>([
|
||||
[BinaryOperator.And, ts.SyntaxKind.AmpersandAmpersandToken],
|
||||
[BinaryOperator.Bigger, ts.SyntaxKind.GreaterThanToken],
|
||||
[BinaryOperator.BiggerEquals, ts.SyntaxKind.GreaterThanEqualsToken],
|
||||
[BinaryOperator.BitwiseAnd, ts.SyntaxKind.AmpersandToken],
|
||||
[BinaryOperator.Divide, ts.SyntaxKind.SlashToken],
|
||||
[BinaryOperator.Equals, ts.SyntaxKind.EqualsEqualsToken],
|
||||
[BinaryOperator.Identical, ts.SyntaxKind.EqualsEqualsEqualsToken],
|
||||
[BinaryOperator.Lower, ts.SyntaxKind.LessThanToken],
|
||||
[BinaryOperator.LowerEquals, ts.SyntaxKind.LessThanEqualsToken],
|
||||
[BinaryOperator.Minus, ts.SyntaxKind.MinusToken],
|
||||
[BinaryOperator.Modulo, ts.SyntaxKind.PercentToken],
|
||||
[BinaryOperator.Multiply, ts.SyntaxKind.AsteriskToken],
|
||||
[BinaryOperator.NotEquals, ts.SyntaxKind.ExclamationEqualsToken],
|
||||
[BinaryOperator.NotIdentical, ts.SyntaxKind.ExclamationEqualsEqualsToken],
|
||||
[BinaryOperator.Or, ts.SyntaxKind.BarBarToken],
|
||||
[BinaryOperator.Plus, ts.SyntaxKind.PlusToken],
|
||||
]);
|
||||
|
||||
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 translateStatement(statement: Statement, imports: ImportManager): ts.Statement {
|
||||
return statement.visitStatement(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): ts.VariableStatement {
|
||||
return ts.createVariableStatement(
|
||||
undefined,
|
||||
ts.createVariableDeclarationList([ts.createVariableDeclaration(
|
||||
stmt.name, undefined, stmt.value && stmt.value.visitExpression(this, context))]));
|
||||
}
|
||||
|
||||
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): ts.FunctionDeclaration {
|
||||
return ts.createFunctionDeclaration(
|
||||
undefined, undefined, undefined, stmt.name, undefined,
|
||||
stmt.params.map(param => ts.createParameter(undefined, undefined, undefined, param.name)),
|
||||
undefined,
|
||||
ts.createBlock(stmt.statements.map(child => child.visitStatement(this, context))));
|
||||
}
|
||||
|
||||
visitExpressionStmt(stmt: ExpressionStatement, context: any): ts.ExpressionStatement {
|
||||
return ts.createStatement(stmt.expr.visitExpression(this, context));
|
||||
}
|
||||
|
||||
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): ts.IfStatement {
|
||||
return ts.createIf(
|
||||
stmt.condition.visitExpression(this, context),
|
||||
ts.createBlock(stmt.trueCase.map(child => child.visitStatement(this, context))),
|
||||
stmt.falseCase.length > 0 ?
|
||||
ts.createBlock(stmt.falseCase.map(child => child.visitStatement(this, context))) :
|
||||
undefined);
|
||||
}
|
||||
|
||||
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): ts.BinaryExpression {
|
||||
return ts.createBinary(
|
||||
ts.createPropertyAccess(expr.receiver.visitExpression(this, context), expr.name),
|
||||
ts.SyntaxKind.EqualsToken, expr.value.visitExpression(this, context));
|
||||
}
|
||||
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): ts.CallExpression {
|
||||
const target = ast.receiver.visitExpression(this, context);
|
||||
return ts.createCall(
|
||||
ast.name !== null ? ts.createPropertyAccess(target, ast.name) : target, undefined,
|
||||
ast.args.map(arg => arg.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
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): ts.Expression {
|
||||
if (!BINARY_OPERATORS.has(ast.operator)) {
|
||||
throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
|
||||
}
|
||||
const binEx = ts.createBinary(
|
||||
ast.lhs.visitExpression(this, context), BINARY_OPERATORS.get(ast.operator) !,
|
||||
ast.rhs.visitExpression(this, context));
|
||||
return ast.parens ? ts.createParen(binEx) : binEx;
|
||||
}
|
||||
|
||||
visitReadPropExpr(ast: ReadPropExpr, context: any): ts.PropertyAccessExpression {
|
||||
return ts.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name);
|
||||
}
|
||||
|
||||
visitReadKeyExpr(ast: ReadKeyExpr, context: any): never {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): ts.ArrayLiteralExpression {
|
||||
return ts.createArrayLiteral(ast.entries.map(expr => expr.visitExpression(this, context)));
|
||||
}
|
||||
|
||||
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): string {
|
||||
const values = ast.entries.map(expr => expr.visitExpression(this, context));
|
||||
return `[${values.join(',')}]`;
|
||||
}
|
||||
|
||||
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,116 +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.Node, 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 _visitListEntryNode<T extends ts.Statement>(
|
||||
node: T, visitor: (node: T) => VisitListEntryResult<ts.Statement, T>): T {
|
||||
const result = visitor(node);
|
||||
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(result.node, result.before);
|
||||
}
|
||||
return result.node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit types of nodes which don't have their own explicit visitor.
|
||||
*/
|
||||
visitOtherNode<T extends ts.Node>(node: T): T { return node; }
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
node = ts.visitEachChild(node, child => this._visit(child, context), context) as T;
|
||||
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
visitedNode = this._visitListEntryNode(
|
||||
node, (node: ts.ClassDeclaration) => this.visitClassDeclaration(node)) as typeof node;
|
||||
} else {
|
||||
visitedNode = this.visitOtherNode(node);
|
||||
}
|
||||
|
||||
// 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({
|
||||
const emitResult = 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;
|
||||
}
|
||||
}
|
||||
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,27 +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 Component = callableClassDecorator();
|
||||
export const Directive = callableClassDecorator();
|
||||
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,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 * 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 Injectables 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>;');
|
||||
});
|
||||
|
||||
it('should compile Components without errors', () => {
|
||||
writeConfig();
|
||||
write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
template: 'this is a test',
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const jsContents = getContents('test.js');
|
||||
expect(jsContents).toContain('TestCmp.ngComponentDef = i0.ɵdefineComponent');
|
||||
expect(jsContents).not.toContain('__decorate');
|
||||
|
||||
const dtsContents = getContents('test.d.ts');
|
||||
expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef<TestCmp, \'test-cmp\'>');
|
||||
});
|
||||
|
||||
it('should compile NgModules without errors', () => {
|
||||
writeConfig();
|
||||
write('test.ts', `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
template: 'this is a test',
|
||||
})
|
||||
export class TestCmp {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [TestCmp],
|
||||
})
|
||||
export class TestModule {}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
const jsContents = getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'i0.ɵdefineNgModule({ type: TestModule, bootstrap: [], ' +
|
||||
'declarations: [TestCmp], imports: [], exports: [] })');
|
||||
|
||||
const dtsContents = getContents('test.d.ts');
|
||||
expect(dtsContents).toContain('static ngComponentDef: i0.ComponentDef<TestCmp, \'test-cmp\'>');
|
||||
expect(dtsContents)
|
||||
.toContain('static ngModuleDef: i0.NgModuleDef<TestModule, [TestCmp], [], []>');
|
||||
expect(dtsContents).not.toContain('__decorate');
|
||||
});
|
||||
});
|
@ -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, parseHostBindings} 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;
|
||||
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}`);
|
||||
}
|
||||
|
||||
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
|
||||
this._emitReferenceToExternal(ast, ast.node, ctx);
|
||||
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,14 +71,15 @@ 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};
|
||||
|
||||
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
|
||||
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
|
||||
|
||||
static inject: o.ExternalReference = {name: 'inject', moduleName: CORE};
|
||||
static refreshComponent: o.ExternalReference = {name: 'ɵr', moduleName: CORE};
|
||||
|
||||
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
|
||||
|
||||
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
|
||||
|
||||
@ -99,33 +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 NgModuleDef: o.ExternalReference = {
|
||||
name: 'NgModuleDef',
|
||||
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};
|
||||
@ -134,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,75 +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),
|
||||
})]);
|
||||
|
||||
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDef, [
|
||||
new o.ExpressionType(moduleType), new o.ExpressionType(o.literalArr(declarations)),
|
||||
new o.ExpressionType(o.literalArr(imports)), new o.ExpressionType(o.literalArr(exports))
|
||||
]));
|
||||
|
||||
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) !;
|
||||
@ -111,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';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user