Compare commits

..

258 Commits

Author SHA1 Message Date
b91b9efc91 release: cut the v6.0.5 release 2018-06-13 15:00:28 -07:00
03718c95ce docs: change capitalization for css hex color values (#23511)
PR Close #23511
2018-06-13 13:31:30 -07:00
0834710c18 docs(aio): remove links to outdated live examples from the API documenation (#23966)
Closes #21525

PR Close #23966
2018-06-13 13:29:13 -07:00
9a9a7de9a4 docs: add message property to compose-message component (#24310)
PR Close #24310
2018-06-13 13:28:47 -07:00
ff51691221 docs: fix typo (#24470)
PR Close #24470
2018-06-13 11:54:26 -07:00
2ad90ab0d3 docs: fix wording in 4-10 (#24385)
PR Close #24385
2018-06-13 11:53:20 -07:00
c5872e6782 docs(aio): Reorganize style guide sections on prefixing components/directives (#22571)
Closes https://github.com/angular/angular/issues/22081

PR Close #22571
2018-06-13 11:20:43 -07:00
d20877bf3f fix(router): fix lazy loading of aux routes (#23459)
Fixes #10981

PR Close #23459
2018-06-13 11:20:20 -07:00
b3089b4963 build: update buildifier to latest (#24296) (#24453)
this matches the version in ngcontainer:0.3.1

PR Close #24296

PR Close #24453
2018-06-13 11:19:59 -07:00
880b03101e build: update to Bazel 0.14.0 (#24296) (#24453)
Also update usage of the ctx.actions.args to a newer preferred API

PR Close #24296

PR Close #24453
2018-06-13 11:19:59 -07:00
451d996414 docs: change aio scope to docs-infra (#24410)
Related to #24295

PR Close #24410
2018-06-12 11:36:14 -07:00
ea3669e334 fix(bazel): Allow ng_module to depend on targets w no deps (#24446)
PR Close #24446
2018-06-12 11:35:53 -07:00
ea2987c7af fix(service-worker): fix SwPush.unsubscribe() (#24162)
Fixes #24095

PR Close #24162
2018-06-11 14:04:31 -04:00
5b823dedcc test(service-worker): allow SwPush tests to run on Node.js (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
80f7c83bdd test(service-worker): add tests for SwPush (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
53072b10da refactor(service-worker): minor mocks refactoring (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
30c2f560b1 build(docs-infra): ensure dist/ directory is cleaned before running tsc --watch (#24372)
PR Close #24372
2018-06-11 09:18:46 -07:00
18c4976f3b build(docs-infra): upgrade preview server to latest @types/shelljs (#24372)
PR Close #24372
2018-06-11 09:18:46 -07:00
0139173c7c fix(animations): always render end-state styles for orphaned DOM nodes (#24236)
This patch ensures that any destination animation styling (state values)
are always applied even if the DOM node is not apart of the DOM.

PR Close #24236
2018-06-08 16:35:27 -07:00
f3c9954a68 docs: adds information about the VSCode clang-format extension (#24351)
PR Close #24351
2018-06-08 16:35:06 -07:00
e8765352e8 fix(docs-infra): use script nomodule to load IE polyfills, skip other polyfills (#24317)
This commit includes two changes:
1. It changes the unreliable dynamic way of loading IE polyfills to use
   `<script nomodule>` instead - for IE it's equivalent to a regular script tag
   while modern browsers will ignore it.
2. It removes other polyfills for browsers not supporting `Object.assign` as
   this API is supported by Chrome 45+, Firefox 34+ and Safari 9+ i.e. it's been
   supported for some time.

Note that as of June 2018 Googlebot uses Chrome 41 to render sites to be
indexed. Chrome 41 doesn't support `Object.assign` but it also doesn't support
ES6 modules so it'll load polyfills meant for IE - which it should do anyway
as it doesn't support most of ES6.

Fixes #23647

PR Close #24317
2018-06-08 13:34:06 -07:00
9e61ad897e build(docs-infra): ensure stability is computed before the API list (#24356)
Previously the API list was being generated before the stability had
been computed. This meant that the API list page showed no API docs
when filtering by `stable` stability status.

Closes #24329

PR Close #24356
2018-06-08 13:33:32 -07:00
0d81151cbc docs(aio): add mix and connect to front page campaigns (#24357)
PR Close #24357
2018-06-08 13:31:28 -07:00
70319722a1 docs(aio): Added resource link to Amexio Canvas Web Based IDE (#24336)
PR Close #24336
2018-06-07 18:46:32 -04:00
cfa5b6567d build(bazel): fix ng_package rollup root dir for fesm2015 output (#24298)
PR Close #24298
2018-06-07 17:56:10 -04:00
41698bf5fd release: cut the v6.0.4 release 2018-06-06 11:49:25 -07:00
23c50e27fc build(docs-infra): log warning rather than error if content errors are not fatal (#24320)
PR Close #24320
2018-06-06 10:25:05 -07:00
0ae8ea254a build(aio): ensure the correct decorator properties are merged (#24289)
Previously only the `description` and `usageNotes` were being copied over
from the call-member of the decorator interface. Important properties such
as `shortDescription` were missed.

These are now added and the code has been refactored to make it simpler and
clearer to update which properties get copied as the requirements change.

PR Close #24289
2018-06-06 10:23:47 -07:00
19deca159b fix(animations): retain trigger-state for nodes that are moved around (#24238)
This patch ensures that if a list of nodes (that contain
animation triggers) are moved around then they will retain their
trigger-value state when animated again at a later point.

PR Close #24238
2018-06-05 18:29:47 -07:00
dc3e8aa0fb fix(forms): properly handle special properties in FormGroup.get (#22249)
closes #17195

PR Close #22249
2018-06-05 18:28:13 -07:00
a9222c0ade docs(aio): Add null type to form validation example (#23949)
Closes #20282

PR Close #23949
2018-06-05 17:32:36 -07:00
ea3127e3f9 build(bazel): ran format (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
8e30f7b1aa build(bazel): ran buildifier (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
46d81b48ac build(bazel): fix //packages/platform-browser/test:test_web (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
3e51a2cb26 build(bazel): enable manual ts_web_test_suite tests that require static_files (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
faf60d9310 docs: rename the "aio" component to "docs-infra" (#24295)
The legacy "aio" is still active for currently pending PRs,
The GH label has been renamed as well

PR Close #24295
2018-06-04 17:25:13 -07:00
0639cb9de1 fix(aio): remove unnecessary scrollbar in code-tabs (#24207)
PR Close #24207
2018-06-04 12:07:25 -07:00
810d025488 fix(aio): add right-margin to .home link (#24207)
PR Close #24207
2018-06-04 12:07:25 -07:00
efe49c141a docs(aio): clean up frequent ng-modules (#24025)
Closes #24017

PR Close #24025
2018-06-04 10:13:19 -07:00
a3d9878c9d docs(aio): remove an extraneous apostrophe (#24293)
PR Close #24293
2018-06-04 10:11:28 -07:00
10213d0ca0 docs(common): improve deprecation notices to be parsed by tslint
Closes: #24237
2018-06-04 09:37:05 -07:00
0e1919c2db build(bazel): update bazel integration test to test secondary angular imports such as @angular/common/http (#24170)
PR Close #24170
2018-06-01 13:40:48 -07:00
d579b8ce05 build(bazel): fix bazel built es5 ngfactory with secondary entry-point angular imports (#24170)
PR Close #24170
2018-06-01 13:40:48 -07:00
d69ba735ee feat(compiler-cli): update tsickle to 0.29.x (#24241)
PR Close #24241
2018-06-01 08:37:46 -07:00
2991b1b217 fix(platform-server): avoid dependency cycle when using http interceptor (#24229)
Fixes #23023.

When a HTTP Interceptor injects HttpClient it causes a DI cycle. This fix is to use Injector to lazily inject HTTP_INTERCEPTORS while setting up the HttpHandler on the server so as to break the cycle.

PR Close #24229
2018-06-01 08:33:46 -07:00
b18cf21e99 build(bazel): re-enable packages/upgrade/test:test_web test with static_files in ts_web_test_suite (#24214)
PR Close #24214
2018-05-31 16:13:06 -07:00
c17098dae6 fix(platform-server): don't reflect innerHTML property to attibute (#24213)
Fixes #19278.

innerHTML is conservatively marked as an attribute for security purpose so that it's sanitized when set. However this same mapping is used by the server renderer to decide whether the `innerHTML` property needs to be reflected to the `innerhtml` attribute. The fix is to just skip the property to attribute reflection for `innerHTML`.

PR Close #24213
2018-05-31 10:08:29 -07:00
43e3073687 build: update to rules_nodejs 0.9.1 and rules_typescript 0.15.0 (#24212)
PR Close #24212
2018-05-31 10:08:07 -07:00
f28878f92f build: sync g3 exclude list from copybara to ngbot (#24224)
PR Close #24224
2018-05-31 10:07:45 -07:00
a10bdefa8b docs(aio): Add GDE Kim Maida to contributors 2018-05-30 17:34:15 -07:00
2f377dbcdd style(compiler-cli): fix typo error (#23897)
PR Close #23897
2018-05-30 17:29:04 -07:00
e2e7b4943e docs: fix typo (#24210)
closes #24191

PR Close #24210
2018-05-30 17:06:13 -07:00
b8a1363bb2 docs(forms): fix API doc (#24210)
closes #24090

PR Close #24210
2018-05-30 17:06:13 -07:00
e9e6a58dd0 docs: fix typo (#24210)
closes #23891

PR Close #24210
2018-05-30 17:06:12 -07:00
5932a79713 refactor(animations): fix typo (#24210)
closes #22459

PR Close #24210
2018-05-30 17:06:12 -07:00
7c6a082afd docs: fix typo if FAQ section (#24210)
closes #22360

PR Close #24210
2018-05-30 17:06:12 -07:00
8a2fe5b7c9 docs: fix WebStorm name (#24210)
closes #21900

PR Close #24210
2018-05-30 17:06:12 -07:00
c9eb4910eb fix(animations): Fix browser detection logic (#24188)
Element type is being polyfilled on the server now and cannot be used to detect browser environment.

PR Close #24188
2018-05-30 16:39:09 -07:00
a634a5abbc Revert "docs: update docs to use HttpClientModule instead of HttpModule (#22727)"
This reverts commit b5533e0ee5.
2018-05-30 16:13:59 -07:00
41225026e4 docs: remove unfinished observables file (#23801)
PR Close #23801
2018-05-30 14:44:29 -07:00
e9f2203347 fix(platform-server): avoid clash between server and client style encapsulation attributes (#24158)
Previously the style encapsulation attributes(_nghost-* and _ngcontent-*) created on the server could overlap with the attributes and styles created by the client side app when it botstraps. In case the client is bootstrapping a lazy route, the client side styles are added before the server-side styles are removed. If the components on the client are bootstrapped in a different order than on the server, the styles generated by the client will cause the elements on the server to have the wrong styles.

The fix puts the styles and attributes generated on the server in a completely differemt space so that they are not affected by the client generated styles. The client generated styles will only affect elements bootstrapped on the client.

PR Close #24158
2018-05-30 14:39:10 -07:00
906b3ec8e7 fix(platform-server): provide Domino DOM types globally (#24116)
Fixes #23280, #23133.

This fix lets code access DOM types like Node, HTMLElement in the code. These are invariant across requests and the corresponding classes from Domino can be safely provided during platform initialization.

This is needed for the current sanitizer to work properly on platform-server. Also allows HTML types in injection - Ex. `@inject(DOCUMENT) doc: Document`.

PR Close #24116
2018-05-30 14:38:57 -07:00
1cb0c4644a feat(platform-server): use EventManagerPlugin on the server (#24132)
Previously event handlers on the server were setup directly. This change makes it so that the event registration on the server go through EventManagerPlugin just like on client. This allows us to add custom event registration handlers on the server which allows us to hook up preboot event handlers cleanly.

PR Close #24132
2018-05-30 14:38:39 -07:00
9d28a27215 test(animations): fix Node.js detection in animation tests (#24139)
PR Close #24139
2018-05-30 14:36:35 -07:00
f04aef48f2 build: replace hard-coded master branch with the variable (#24199)
PR Close #24199
2018-05-30 11:31:39 -07:00
d249852f4a build: update rules_webtesting (#24198)
this includes a fix for spammy browser installs that makes our CI logs hard to read

PR Close #24198
2018-05-30 11:31:03 -07:00
707dd59767 build: update brotli version in WORKSPACE (#24194)
The updated version includes the fix for google/brotli#671.

PR Close #24194
2018-05-30 11:30:40 -07:00
4d2570576a docs(aio): fix typo for @NgModuledecorator to @NgModule decorator (#24201)
closes #23974

PR Close #24201
2018-05-30 11:24:12 -07:00
76e58e6cca docs: fix typo (#24201)
closes #23853

PR Close #24201
2018-05-30 11:24:12 -07:00
a27066b9d2 style(compiler): fix up grammar in error message (#24201)
closes #22746

PR Close #24201
2018-05-30 11:24:12 -07:00
29dfc8f3ac docs(bazel): improve error message for ng_package with no metadata (#22964)
PR Close #22964
2018-05-30 10:04:35 -07:00
b5533e0ee5 docs: update docs to use HttpClientModule instead of HttpModule (#22727)
Updated most examples to use HttpClientModule instead of deprecated HttpModule

fix #19280

PR Close #22727
2018-05-30 10:03:14 -07:00
b275d378df docs(aio): add blox material library to resources (#20539)
PR Close #20539
2018-05-30 10:02:32 -07:00
96655f7aac style(compiler): fix typo and formatting (#23729)
PR Close #23729
2018-05-29 18:48:55 -04:00
253f509493 refactor(core): use Partial<T> for MetadataOverride (#24103)
Allows to write:

const fixture = TestBed
      .overridePipe(DisplayNamePipe, { set: { pure: false } })
      .createComponent(MenuComponent);

when you only want to set the `pure` metadata,
instead of currently:

const fixture = TestBed
      .overridePipe(DisplayNamePipe, { set: { name: 'displayName', pure: false } })
      .createComponent(MenuComponent);

which forces you to redefine the name of the pipe even if it is useless.

Fixes #24102

PR Close #24103
2018-05-29 18:40:05 -04:00
5a02ae2f84 docs: update lts and labs practices (#23922)
PR Close #23922
2018-05-29 18:01:31 -04:00
f860752902 refactor(aio): rename method (loadContainingCustomElements --> loadContainedCustomElements) (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
efa126f157 fix(aio): avoid loading the ToC component until it is necessary (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
715135b117 fix(aio): show embedded ToC (#23944)
On narrow screens (where there is not enough room on the right to show
the floating ToC), an embedded ToC is shown (via an `<aio-toc embedded>`
element in the document). Since ToC was not a custom element, the
component was not instantiated for the embedded element.

This commit fixes it by making `aio-toc` a custom element and loading it
manually for the floating ToC (if necessary).

PR Close #23944
2018-05-29 18:00:34 -04:00
c30fc52d99 feat(aio): add component for lazy-loading custom element (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
434eb971e4 refactor(aio): order custom elements by selector (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
6e31e22d41 fix(aio): do not load custom elements again while already loading (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
8fef926cd2 build(aio): update payload size limits (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
7698afedb1 docs(http): correct spelling error (#23675)
Correct a spelling error. I changed HttpParms to HttpParams
PR Close #23675
2018-05-29 16:55:07 -04:00
a583d12660 feat(aio): display code-tabs in a Material Design "card" (#24027)
This helps to connect the "content" of the tab to its label.

Closes #23985

PR Close #24027
2018-05-29 16:52:06 -04:00
a867d71ece feat(aio): render hover status on code-tabs (#24027)
The Material Design spec states that there should be a change
of background when hovering over a tab label.

See https://material.io/design/components/tabs.html#states

Related to #23985

PR Close #24027
2018-05-29 16:52:06 -04:00
84b43daf65 build: pick up rules_typescript karma fix (#24184)
Error was Cannot find module 'karma/bin/karma'

PR Close #24184
2018-05-29 16:50:11 -04:00
9b3423b50d docs(aio): fix link to correct bio image (#24150)
PR Close #24150
2018-05-29 16:27:49 -04:00
d6041d83ec docs(aio): Added Mashhood as GDE in contributors (#24157)
PR Close #24157
2018-05-29 16:20:05 -04:00
a638f504eb docs(aio): add material community components (#24042)
PR Close #24042
2018-05-25 13:45:40 -04:00
a29c756251 refactor(aio): improve logging output in update-preview-server.sh (#24071)
PR Close #24071
2018-05-25 13:44:44 -04:00
f206eb94bb docs(aio): add Juri Strumpflohner to GDE resources (#24086)
PR Close #24086
2018-05-25 13:43:50 -04:00
3d6e82eccd docs(aio): remove outdated rangle link (#24108)
PR Close #24108
2018-05-25 13:41:59 -04:00
154289305e release: cut the v6.0.3 release 2018-05-22 16:16:26 -07:00
469b1e4a9a docs: add doc to event-management api (#23656)
PR Close #23656
2018-05-22 17:33:49 -04:00
7fee1fd442 docs(aio): fix typo (#23925)
PR Close #23925
2018-05-22 16:35:18 -04:00
0ee5b7efa5 fix(service-worker): check platformBrowser before accessing navigator.serviceWorker (#21231)
PR Close #21231
2018-05-22 15:09:32 -04:00
afff84c03f docs(aio): Remove outdated README.md from cli-quickstart zip (#23947)
Closes #23936

PR Close #23947
2018-05-22 13:37:15 -04:00
23f2a7069f docs: fix typo (#23998)
"Made" doesn't make sense (redoing and closing #23940)
PR Close #23998
2018-05-22 13:35:13 -04:00
42260702f7 docs(aio): Add missing dependencies and files to testing zip file download (#23948)
Closes #23060

PR Close #23948
2018-05-21 16:12:40 -04:00
eec231d21e docs(aio): add ant design of angular in resources (#23953)
PR Close #23953
2018-05-21 16:11:13 -04:00
95344d6039 fix(aio): avoid unnecessary re-calculations in live-examples (#23960)
With `plnkrs`, we used to choose a different plnkr mode (normal vs
embedded) based on the size of the screen. This affected the layout of
the plnkr page ("embedded" plnkr mode was usable on small screens, while
"normal" mode wasn't). This is not to be confused with the live-example
mode we use today to determine whether the live-example should be a link
(that open StackBlitz on a new page) or embedded into the document
(using an iframe).

Since we no longer need to change the live-example URL based on the
screen size, there is no need to listen for rezise events on Window. The
necessary properties can be computed once and certain variables are
obsolete.

PR Close #23960
2018-05-21 16:10:12 -04:00
1c9839e91d fix(aio): allow setting live-example title from content (#23960)
Previously, it was possible to set the live-example title as content in
the `<live-example>` element. This relied on our custom loader
functionality that extracted the content from the DOM element before
passing it to the Angular compiler and stored it on a property for later
retrieval.
Since we switched to custom elements (and got rid of the custom loader),
the property is no longer populated with the contents. As a result, many
live examples show the default title ("live example") instead of the one
specified as content.

This commit fixes it by projecting the content into an invisible node
for later retrieval  (similar to what we do in other components, such as
the `CodeExampleComponent`).

PR Close #23960
2018-05-21 16:10:12 -04:00
bc1a66e907 refactor(aio): clean up attribute-utils (#23960)
PR Close #23960
2018-05-21 16:10:12 -04:00
2a83dbb0d8 style(aio): remove background from lazy-loading concept icon (#23950)
Fixes #23938

PR Close #23950
2018-05-21 16:08:08 -04:00
4007d00403 docs(aio): add mhartington to gde (#23777)
PR Close #23777
2018-05-21 16:05:18 -04:00
61e32ac3c4 docs(aio): fix typo (#23990)
are are -> are
PR Close #23990
2018-05-21 16:04:09 -04:00
43ee10fbbd docs(aio): changed 'onVoted' output property to 'voted' to be in line with the styleguide (#23832)
PR Close #23832
2018-05-16 17:23:05 -04:00
eb90039ea1 docs(aio): Expose server and CLI configuration for universal in guide (#23842)
Closes #23795

PR Close #23842
2018-05-16 17:21:44 -04:00
5d318ff234 ci: ensure github-robot listens on circleci builds (#23863)
PR Close #23863
2018-05-16 17:20:44 -04:00
f3361abdd7 test: don't run unit tests on Firefox (#23942)
PR Close #23942
2018-05-16 17:19:45 -04:00
52cbe894e9 docs(aio): add Angular Conf Australia to events (#22929)
Angular Conf Australia 2018 will be held at June 22 in Melbourne, Australia! 

https://www.angularconf.com.au/
PR Close #22929
2018-05-16 17:18:47 -04:00
41c2030534 docs(aio): fix path to observables guide (#23858)
PR Close #23858
2018-05-16 17:16:48 -04:00
daee41a40a docs(aio): update HTTP error test example (#22844)
Update the example to match the description preceding it, which refers to the
use of the error method and ErrorEvent rather than the flush method with a
non-2xx status as shown previously.

PR Close #22844
2018-05-16 17:15:20 -04:00
68bd45ba87 docs(bazel): add a link to the Bazel doc (#22940)
The developer doc mentions but doesn't link to BAZEL.md. Add link and fix capitalization.
PR Close #22940
2018-05-16 17:14:04 -04:00
0f6407750b docs(aio): Remove Intertech with no courses scheduled (#22867)
PR Close #22867
2018-05-16 17:09:44 -04:00
95fca24fd8 feat(aio): add brand and concept icons, img style class more flexible (#23589)
PR Close #23589
2018-05-15 15:36:07 -07:00
a8f6542115 release: cut the v6.0.2 release 2018-05-15 12:34:04 -07:00
c6b618d020 fix(service-worker): deprecate versionedFiles in asset-group resources (#23584)
Since `versionedFiles` behaves in the exact same way as `files`, there
is no reaason to have both. Users should use `files` instead.

This commit deprecates the property and prints a warning when coming
across an asset-group that uses it. It should be completely removed in
a future version.

Note, it has also been removed from the default `ngsw-config.json`
template in angular/devkit#754.

PR Close #23584
2018-05-15 12:19:08 -07:00
ad6052e397 test: switch to ts_web_test_suite (#23859)
Unit tests now run on Firefox too

PR Close #23859
2018-05-15 11:41:58 -07:00
e9d1709156 build: only match version tags for BUILD_SCM_VERSION (#23903)
PR Close #23903
2018-05-14 12:44:26 -07:00
b5d3de50cc docs(aio): use heroesUrl (#23884)
PR Close #23884
2018-05-14 10:38:15 -07:00
734d37b231 build(aio): improve enum API rendering (#23872)
* The member details section is now called "Members", rather
than "Properties".
* The property table now displays appropriate table headings:
"Member", "Value", "Description".
* The "Value" column is not shown if none of the members have
a value.

Closes #22678

PR Close #23872
2018-05-14 10:37:42 -07:00
bc2063807c build(aio): ensure usageNotes are copied into decorator API docs (#23901)
PR Close #23901
2018-05-14 10:35:33 -07:00
2a528fcb15 build(aio): display types of API const docs correctly (#23850)
Previously these docs always displayed `any` as the type
of the const export. Now the type is computed correctly from
the declared type or initializer of the constant.

PR Close #23850
2018-05-11 16:44:51 -04:00
752b83ac81 fix(animations): do not throw errors when a destroyed component is animated (#23836)
PR Close #23836
2018-05-11 16:08:15 -04:00
56be3375ec fix(aio): make background transparent in 144x144 PWA icon (#23851)
Fixes #23827

PR Close #23851
2018-05-11 12:38:21 -04:00
1b83b3fb15 docs(aio): add Cory Rylan to GDE resources (#23840)
PR Close #23840
2018-05-11 12:32:38 -04:00
14d4625ede docs: update version to 6 in language-service (#20795)
PR Close #20795
2018-05-11 12:22:51 -04:00
fd880a8de4 build: replace the old publish script with a new bazel-based one 2018-05-10 23:01:41 -07:00
bd3dddce4b release: cut the v6.0.1 release 2018-05-10 22:22:49 -07:00
1336a9451f build: update to latest TypeScript rules (#23828)
Fixes #23810

PR Close #23828
2018-05-10 16:45:38 -07:00
d280077412 fix(elements): always check to create strategy (#23825)
PR Close #23825
2018-05-10 16:07:11 -07:00
2b578f5c61 docs(elements): add angular element term to glossary (#23807)
PR Close #23807
2018-05-10 15:50:00 -07:00
12dcb313af style: remove empty comments (#23404)
PR Close #23404
2018-05-10 15:48:13 -07:00
f576851ecc docs(elements): emphasize future direction, update link (#23806)
PR Close #23806
2018-05-10 15:46:53 -07:00
ca6cb66c32 docs: change release_schedule.md to link to new angular release page in docs (#23808)
PR Close #23808
2018-05-10 15:45:28 -07:00
484233f6c1 docs(aio): add Alain Chautard in GDE list (#23783)
PR Close #23783
2018-05-10 12:07:10 -07:00
3d8799b3a2 fix(router): avoid freezing queryParams in-place (#22663)
The recognizer code used to call Object.freeze() on queryParams before
using them to construct ActivatedRoutes, with the intent being to help
avoid common invalid usage. Unfortunately, Object.freeze() works
in-place, so this was also freezing the queryParams on the actual
UrlTree object, making it more difficult to manipulate UrlTrees in
things like UrlHandlingStrategy.

This change simply shallow-copies the queryParams before freezing them.

Fixes #22617

PR Close #22663
2018-05-10 07:54:11 -07:00
8733843c11 fix(router): correct the segment parsing so it won't break on ampersand (#23684)
PR Close #23684
2018-05-10 07:53:54 -07:00
f1097914c5 ci: add config for size plugin of the github rebot (#23665)
PR Close #23665
2018-05-10 07:53:34 -07:00
06776d1d10 fix(aio): fix error in import after RxJS 6 migration (#22886)
PR Close #22886
2018-05-09 11:52:04 -07:00
2b31b6dc3f refactor(service-worker): sort manifest url/hashTable entries (#23586)
This makes it easier to quickly check whether a specific file ended up
in the manifest, for example when debugging.

PR Close #23586
2018-05-09 11:51:23 -07:00
2254ac23e4 fix(service-worker): correctly handle requests with empty clientId (#23625)
Requests from clients that are not assigned a client ID by the browser
will produce `fetch` events with `null` or empty (`''`) `clientId`s.

Previously, the ServiceWorker only handled `null` values correctly. Yet
empty strings are also valid (see for example [here][1] and [there][2]).
With this commit, the SW will interpret _all_ falsy `clientId` values
the same (i.e. "no client ID assigned") and handle them appropriately.

Related Chromium issue/discussion: [#832105][3]

[1]: 4cc72bd0f1/docs/index.bs (L1392)
[2]: https://w3c.github.io/ServiceWorker/#fetchevent-interface
[3]: https://bugs.chromium.org/p/chromium/issues/detail?id=832105

Fixes #23526

PR Close #23625
2018-05-09 11:50:03 -07:00
38c678fdcd test(service-worker): support mock requests with null/empty client ID (#23625)
PR Close #23625
2018-05-09 11:50:03 -07:00
1a655836cb test(service-worker): improve adding clients in SwTestHarness (#23625)
This commits changes how clients are added in `SwTestHarness`, so that
the behavior in tests closer mimics what would happen in an actual
ServiceWorker.
It also removes auto-adding clients when calling `clients.get()`, which
could hide bugs related to non-existing clients.

PR Close #23625
2018-05-09 11:50:03 -07:00
2e466f4bea build(bazel): update to rules_typescript 0.12.3 (#23617)
PR Close #23617
2018-05-09 11:47:11 -07:00
7b06fa88ab build(aio): include navigation.json changes in docs-watch (#23698)
Closes #23582

PR Close #23698
2018-05-09 11:45:19 -07:00
3d712894ae fix(aio): add link to v5 docs (#23794)
Fixes #23781

PR Close #23794
2018-05-09 11:44:45 -07:00
75b8edae03 ci: Remove Chuck from pullapprove (#23798)
Jason takes over his role on core, Keen for everything else

PR Close #23798
2018-05-09 11:43:47 -07:00
fe7f48c1d5 docs(aio): Upgrade example dependencies to Angular V6 (#23660)
PR Close #23660
2018-05-08 13:56:48 -07:00
29600cbb19 docs(aio): Update i18n example to Angular V6 (#23660)
PR Close #23660
2018-05-08 13:56:48 -07:00
5581e97d2a fix(core): call ngOnDestroy on all services that have it (#23755)
Previously, ngOnDestroy was only called on services which were statically
determined to have ngOnDestroy methods. In some cases, such as with services
instantiated via factory functions, it's not statically known that the service
has an ngOnDestroy method.

This commit changes the runtime to look for ngOnDestroy when instantiating
all DI tokens, and to call the method if it's present.

Fixes #22466
Fixes #22240
Fixes #14818

PR Close #23755
2018-05-08 13:55:29 -07:00
19262d9f90 Revert "style(animations): fix short param names (#23668)"
This reverts commit e3518967ad.

This PR accidentaly introduces a breaking change:
https://github.com/angular/angular/pull/23668#discussion_r186265055
2018-05-05 08:40:35 -07:00
1eb1c6315d Revert "docs(animations): fix content errors (#23668)"
This reverts commit 005dc8f68b.

The PR accidently introduced a breaking change
https://github.com/angular/angular/pull/23668#discussion_r186265055
2018-05-05 08:38:13 -07:00
3807599a4d style(animations): fix short param names (#23668)
PR Close #23668
2018-05-05 08:17:02 -07:00
2ed41d9e38 docs(animations): fix content errors (#23668)
PR Close #23668
2018-05-05 08:17:02 -07:00
5eb9c01bb6 ci: hide encryption key from circleci logs (#23585)
PR Close #23585
2018-05-04 16:33:59 -07:00
09d9662386 build: serve ivy todo app with real http-server (#23446)
PR Close #23446
2018-05-04 16:33:53 -07:00
844cbd9774 ci: publish build snapshots from Bazel/CircleCI (#23512)
This uses a new script and CircleCI job called "build-packages-dist"
which shims the new Bazel build to produce outputs matching the legacy
build. We'll use this to get AIO testing onto CircleCI as well.

We move the integration tests to a new circleCI job that depends on this
one, as well as the build publishing job.

Note that every PR will have a trivial green publishing status, because
we always create this job even for PRs. We'd rather not - see
https://discuss.circleci.com/t/workflows-pull-request-filter/14396/4

PR Close #23512
2018-05-04 16:33:39 -07:00
373a47dda9 test: fix firebase deployment script test
When I fixed the project id in 2c4850dc58,
I didn't realize we had a test that verified the wrong behavior.
2018-05-04 15:40:50 -07:00
83f12f3047 build: update to latest nodejs bazel rules (#23683)
PR Close #23683
2018-05-04 15:29:03 -07:00
bbc416cdcd build: update bazel to 0.13 (#23623)
PR Close #23623
2018-05-04 15:23:56 -07:00
07f2098655 feat(aio): add v6 release notification (#23690)
PR Close #23690
2018-05-04 15:23:36 -07:00
9b53a6e779 fix(aio): remove main background color when printing (#23538)
PR Close #23538
2018-05-04 15:21:14 -07:00
65f8505fb6 fix(aio): fix code-example print styles when printing backgrounds (#23538)
Fixes #23431

PR Close #23538
2018-05-04 15:21:14 -07:00
5a5ea45c40 refactor(aio): use the same selectors for screen and print styles (#23538)
PR Close #23538
2018-05-04 15:21:14 -07:00
52a3657b48 refactor(aio): include print styles last to overwrite other styles (#23538)
PR Close #23538
2018-05-04 15:21:14 -07:00
3824e3f858 fix(animations): properly clean up queried element styles in safari/edge (#23686)
Prior to this patch, if an element is queried and animated for 0 seconds
(just a style() call and nothing else) then the styles applied would not
be properly cleaned up due to their camelCased nature.

PR Close #23686
2018-05-04 15:04:20 -07:00
05aa5e0179 fix(animations): retain state styling for nodes that are moved around (#23686)
PR Close #23686
2018-05-04 15:04:20 -07:00
4ddeb030e7 build(aio): use Angular 6.0.0 (#23687)
PR Close #23687
2018-05-03 16:05:34 -07:00
afe6380429 build(aio): update to Angular CLI 6.0.0 (#23687)
PR Close #23687
2018-05-03 16:05:34 -07:00
947ea17a09 build: update the scripts/release/post-check script for 6.0.x 2018-05-03 15:38:52 -07:00
a190c45a64 fix(aio): correct project id for deployment of archive sites 2018-05-03 15:05:38 -07:00
902781803f docs(aio): Upgrade server-side rendering example to Angular V6 (#23649)
PR Close #23649
2018-05-03 14:24:44 -07:00
0d480ac0dc docs: add new info about angular update policies and resources (#23551)
PR Close #23551
2018-05-03 14:24:31 -07:00
6934bf4863 docs: add information on when not to use tree-shakable providers (#23634)
PR Close #23634
2018-05-03 14:24:16 -07:00
7e7ea33fb0 docs: add doc to include updates to the index.html with the new ng add command (#23616)
PR Close #23616
2018-05-03 14:23:30 -07:00
5bd8c6887e docs: improve the GitHub README.md, update links, etc 2018-05-03 13:26:52 -07:00
d28ab372c8 docs: add link to the v6 release announcement to our changelog 2018-05-03 13:26:46 -07:00
d0ccf5f169 release: cut the v6.0.0 release 2018-05-03 12:17:26 -07:00
ecde15298a build: update to rxjs@6.0.0 (#23679)
PR Close #23679
2018-05-03 10:53:39 -07:00
983e5f2d7e fix(aio): correctly route embedded live-example URLs from SW (#23637)
Partially addresses #23626.

PR Close #23637
2018-05-02 15:55:23 -07:00
5fc4299e0a refactor(aio): move right margin from .home image to .home anchor (#23624)
This makes the outline of `.home` symmetric.

PR Close #23624
2018-05-02 15:54:15 -07:00
1823d5dd1c style(aio): add space between .home and .hamburger (#23624)
When the `.hamburger` icon is clicked, it's background is drawn until
the very edge of `.home`'s image, leaving no space.

PR Close #23624
2018-05-02 15:54:15 -07:00
91d4da0d2f docs: add missing link to bootstrapping section (#23214)
PR Close #23214
2018-05-02 15:53:02 -07:00
22eb8e26fc build(aio): align stackblitz files with Angular CLI V6 (#23521)
Also cleans up legacy references to `.angular-cli.json`

PR Close #23521
2018-05-02 15:00:57 -07:00
f6002c1702 docs(forms): Fixed a typo in the reactive form (From 'address' to 'secretLairs') section (#23221)
PR Close #23221
2018-05-02 15:00:28 -07:00
14138f6382 docs(elements): add intro connecting angular elements to custom elements (#23638)
PR Close #23638
2018-05-02 14:57:20 -07:00
f11daa2031 docs(aio): update Egghead.io URL (#23598)
Closes #23597
PR Close #23598
2018-05-01 10:27:16 -07:00
31a435ef5b docs: fix typo in tag name (my-child --> app-child) (#23606)
Fixes #23599

PR Close #23606
2018-05-01 10:26:50 -07:00
3e92b22258 test: add i18n to cli-hello-world integration test (#23527)
PR Close #23527
2018-04-27 11:26:50 -07:00
2e5457c824 docs(aio): update docs error in guide/http (#23567)
Updates documentation to include examples for both req.flush and
req.error in http testing examples.

PR Close #23567
2018-04-27 11:26:27 -07:00
1ab5fba92e build(aio): add support for faster, unoptimized serve (#23569)
When running `yarn start` and `yarn serve-and-sync`, we are usually
more interested in faster re-build times than optimized builds. This was
also the behavior, before upgrading to @angular/cli@6 (fc5af69fb).

This commit introduces a new configuration (`fast`), which is used by
`yarn start` and `yarn serve-and-sync` to restore the faster,
unoptimized builds.
Other commands, such as `ng serve` and `ng e2e`, remain unchanged (using
slower, optimized builds).

PR Close #23569
2018-04-27 11:26:22 -07:00
e1e57ddaa7 docs: correct more typos (#23565)
PR Close #23565
2018-04-27 11:26:17 -07:00
ee7cb48877 docs: correct typos (#23565)
PR Close #23565
2018-04-27 11:26:10 -07:00
a30c57090a docs: correct node.js version and usage (#23565)
PR Close #23565
2018-04-27 11:26:04 -07:00
8a49ec4f27 ci: add Brandon Roberts as an aio approver (#23417)
PR Close #23417
2018-04-27 11:25:59 -07:00
697b6c040c fix(core): avoid eager providers re-initialization (#23559)
Fix a corner case where eager providers were getting constructed twice if the provider was requested before the initialization of the NgModule is complete.

PR Close #23559
2018-04-27 11:25:47 -07:00
4008e36e80 release: cut the v6.0.0-rc.6 release 2018-04-27 10:47:56 -07:00
e47bb52084 Revert "refactor(core): tree-shake application_module providers (#23477)"
This reverts commit ac2b530f4b.

The change is breaking targets in g3 see cl/194336387.
2018-04-27 07:13:56 -07:00
ac2b530f4b refactor(core): tree-shake application_module providers (#23477)
PR Close #23477
2018-04-25 15:54:41 -07:00
64bf6edf00 docs: update glossary architectural terms (#23045)
PR Close #23045
2018-04-25 13:21:52 -07:00
adf6235479 docs: corrected spelling of "ambient". 2018-04-24 15:05:31 -07:00
04c18ac1aa docs: fix typo (#23514)
PR Close #23514
2018-04-24 14:43:34 -07:00
1b26dd8cdb docs(benchpress): fix typo in README (#23471) (#23488)
PR Close #23488
2018-04-24 14:37:03 -07:00
f721b06bde style: format code 2018-04-24 14:36:30 -07:00
0bc8443e12 fix(compiler): avoid a crash in ngc-wrapped. (#23468)
`ng.performCompilation` can return an `undefined` program, which is not handled by ngc-wrapped.

Avoid crashing by checking for the error return and returning the diagnostics.
PR Close #23468
2018-04-24 13:57:04 -07:00
db17231597 ci(aio): fix deploy-to-firebase script (#23470)
Temporary workaround for angular/angular-cli#10398.
The behavior of `yarn build` remains the same, but building for a
specific deployment env (e.g. archive, next) requires
`yarn build-for $deployEnv`.

PR Close #23470
2018-04-24 11:15:35 -07:00
540626a3a6 build(common): mark locales files as side-effect-full (#23509)
Fixes https://github.com/angular/angular-cli/issues/10322
PR Close #23509
2018-04-24 11:14:52 -07:00
391bfcede5 docs(aio): Add UpgradingAngularJS to education resources (#23169)
PR Close #23169
2018-04-23 13:36:48 -07:00
d8de6488dd fix(router): cache route handle if found (#22475)
When asking the route reuse strategy to retrieve a detached route handle, store the
return value in a local variable for further processing instead of asking again later.

resolves #22474

PR Close #22475
2018-04-23 13:35:59 -07:00
151fb66848 ci: add alxhub as owner to a few packages (#23510)
PR Close #23510
2018-04-23 10:08:25 -07:00
02424ff0d0 Revert "style(compiler): fix lint issues (#23480)"
This reverts commit f0925d9705.
2018-04-22 12:21:24 -07:00
f0925d9705 style(compiler): fix lint issues (#23480)
PR Close #23480
2018-04-22 11:55:33 -07:00
212b806eda build: make commit validation accept typical Revert messages (#23480)
fixes #23479

PR Close #23480
2018-04-22 11:49:49 -07:00
b9431e88fb fix(compiler): handle undefined annotation metadata (#23349)
In certain cases seen in production, simplify() can returned
undefined when simplifying decorator metadata. This has proven tricky
to reproduce in an isolated test, but the fix is simple and low-risk:
don't attempt to spread an undefined set of annotations in the first
place.

PR Close #23349
2018-04-19 18:57:22 -07:00
7790cfa0d0 Revert "fix(compiler): Pretty print object instead of [Object object] (#22689)" (#23442)
This reverts commit 8555a3a3cd.

Reverted because of https://github.com/angular/angular/issues/23440

PR Close #23442
2018-04-19 14:52:49 -07:00
41b5149509 docs(aio): add front page campaign for the ng-conf live stream (#23391)
PR Close #23391
2018-04-17 14:13:43 -07:00
06f865640d docs(aio): Cleanup examples with edits from Igor/George (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
824f74f27b build(aio): turn on webpack's stats.json generation for debugging purposes (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
5301c43eed build(aio): add @angular/language-service (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
f280d1aef1 docs(aio): Bump shared yarn.lock file for examples (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
5e741d42a6 docs(aio): Bump shared dependencies to RC5 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
0035d41030 docs(aio): Fix failing upgrade-module tests (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
08f447ceec docs(aio): Fix failing boilerplate tests (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
8953f123e3 docs(aio): Upgrade examples to Angular 6 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
6274007e3b test(aio): fix failing tests (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
ee76be7783 docs(aio): update yarn test command in README.md (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
a8c720bc3a build(aio): update to @angular/material@6.0.0-rc.11 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
ac47a3cd93 test(aio): move reflect-metadata polyfills to test.ts (#23234)
This resolves https://github.com/angular/angular-cli/issues/10333 and nicely cleans up the code.

PR Close #23234
2018-04-17 14:09:04 -07:00
041458a3d2 build(aio): update to angular/core@6.0.0-rc.5 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
2cb74b748f style(aio): lint fixes for examples (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
cb0bfe7a43 test(aio): fix tests and update testing infra (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
8ee11aeaa6 build(aio): update tslint and codelyzer (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
bf89fcb361 build(aio): fix deployment script (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
4f70c5b6f7 build(aio): upgrade @angular/cli to 6.0.0-rc.4 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
ad3ebec2a5 build(aio): upgrade @angular/* to 6.0.0-rc.4 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
3f5d61f2dd build(aio): remove redundant flags from cli commands (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
11ada7f78b build(aio): switch to webpack-cli for IE polyfills (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
0b96bc7456 build(aio): upgrade rxjs to 6.0.0-turbo-rc.4 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
fefadadff3 ci: chown bazel-built packages when running integration tests (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
946057ae29 build: update to rxjs@6.0.0-uncanny-rc.7 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
d4293aaaaa build: remove a postinstall-patch to fix rxjs (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
e3dcc227f6 build(aio): reorder entries in package.json (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
c9230dd90e build: fix angular.json that was missing keys due to cli bugs (#23234)
https://github.com/angular/angular-cli/issues/10225
https://github.com/angular/angular-cli/issues/10226

PR Close #23234
2018-04-17 14:09:04 -07:00
234af9ba59 build(aio): update to @angular/material@6.0.0-rc.1 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
7204028d3e build(aio): update to @angular/material@5.2.4 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
f7c55952bf build: update to rxjs@6.0.0-tactical-rc.1 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
e38e3bd135 test: simplify config for cli-hello-world (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
cd20c01ba1 test: update cli-hello-world to cli@6.0.0-rc.2 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
12a191ef3f build(aio): update to @angular/cli@6.0.0-rc.2 + project layout update (#23234)
project layout was updated using:
yarn ng update @angular/cli --migrate-only --from=1.7.3

PR Close #23234
2018-04-17 14:09:04 -07:00
65e67b3c3a build(aio): upgrade to @angular/*@6.0.0-rc.3 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
32e57f6197 build(aio): upgrade to @angular/*@6.0.0-rc.2 (#23234)
PR Close #23234
2018-04-17 14:09:04 -07:00
7de69ba29b ci(aio): fix aio-monitoring tests (#23390)
Previously, we were running the e2e tests from master against
`https://angular.io` (deployed from the stable branch). Often the e2e
tests from master do not apply to the stable branch, since the app has
deviated slightly.

This commit fixes this by stop running the full e2e tests against the
deployed versions, but a smaller set of "smoke tests", which check basic
functionality that is less likely to change between versions.

PR Close #23390
2018-04-17 13:45:38 -07:00
44193c0b94 refactor(aio): rename directory (tests/deployment-config --> tests/deployment) (#23390)
PR Close #23390
2018-04-17 13:45:38 -07:00
6db8241ffa refactor(aio): rename spec file (#23390)
PR Close #23390
2018-04-17 13:45:38 -07:00
33c594516c refactor(aio): rename yarn script (deployment-config-test --> redirects-test) (#23390)
PR Close #23390
2018-04-17 13:45:38 -07:00
278 changed files with 6069 additions and 21952 deletions

View File

@ -85,7 +85,7 @@ jobs:
# This avoids waiting for the slowest build target to finish before running the first test
# See https://github.com/bazelbuild/bazel/issues/4257
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
- run: bazel query --output=label //... | xargs bazel test
# CircleCI will allow us to go back and view/download these artifacts from past builds.
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
@ -111,42 +111,6 @@ jobs:
paths:
- "node_modules"
- "~/bazel_repository_cache"
# Temporary job to test what will happen when we flip the Ivy flag to true
test_ivy_jit:
<<: *job_defaults
resource_class: xlarge
steps:
- *define_env_vars
- checkout:
<<: *post_checkout
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- restore_cache:
key: *cache_key
- run: bazel run @yarn//:yarn
- run: bazel query --output=label //... | xargs bazel test --define=compile=jit --build_tag_filters=ivy-jit --test_tag_filters=-manual,ivy-jit
test_ivy_aot:
<<: *job_defaults
resource_class: xlarge
steps:
- *define_env_vars
- checkout:
<<: *post_checkout
# See remote cache documentation in /docs/BAZEL.md
- run: .circleci/setup_cache.sh
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- restore_cache:
key: *cache_key
- run: bazel run @yarn//:yarn
- run: bazel query --output=label //... | xargs bazel test --define=compile=local --build_tag_filters=ivy-local --test_tag_filters=-manual,ivy-local
# This job exists only for backwards-compatibility with old scripts and tests
# that rely on the pre-Bazel dist/packages-dist layout.
@ -176,8 +140,6 @@ jobs:
root: dist
paths:
- packages-dist
- packages-dist-ivy-jit
- packages-dist-ivy-local
# We run the integration tests outside of Bazel for now.
# They are a separate workflow job so that they can be easily re-run.
@ -238,8 +200,6 @@ workflows:
jobs:
- lint
- test
- test_ivy_jit
- test_ivy_aot
- build-packages-dist
- integration_test:
requires:
@ -252,8 +212,6 @@ workflows:
requires:
# Only publish if tests and integration tests pass
- test
- test_ivy_jit
- test_ivy_aot
- integration_test
# Get the artifacts to publish from the build-packages-dist job
# since the publishing script expects the legacy outputs layout.

View File

@ -163,7 +163,7 @@ groups:
files:
- "packages/compiler/*"
users:
- alxhub #primary
- chuckjaz #primary
- vicb
- mhevery
- IgorMinar #fallback

View File

@ -1,37 +1,9 @@
<a name="6.1.0-beta.1"></a>
# [6.1.0-beta.1](https://github.com/angular/angular/compare/6.1.0-beta.0...6.1.0-beta.1) (2018-06-13)
<a name="6.0.5"></a>
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
### Bug Fixes
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([dc4a3d0](https://github.com/angular/angular/commit/dc4a3d0))
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([282d351](https://github.com/angular/angular/commit/282d351))
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([8be6892](https://github.com/angular/angular/commit/8be6892)), closes [#23647](https://github.com/angular/angular/issues/23647)
* **ivy:** compute transitive scopes from NgModuleDef only ([#24334](https://github.com/angular/angular/issues/24334)) ([1135563](https://github.com/angular/angular/commit/1135563))
* **ivy:** correctly handle queries with embedded views ([#24418](https://github.com/angular/angular/issues/24418)) ([014949f](https://github.com/angular/angular/commit/014949f))
* **ivy:** remove debugger statement ([#24480](https://github.com/angular/angular/issues/24480)) ([70ef061](https://github.com/angular/angular/commit/70ef061))
* **ivy:** special case [style] and [class] bindings for future use ([#23232](https://github.com/angular/angular/issues/23232)) ([1b253e1](https://github.com/angular/angular/commit/1b253e1))
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
### Features
* **common:** introduce KeyValuePipe ([#24319](https://github.com/angular/angular/issues/24319)) ([2b49bf7](https://github.com/angular/angular/commit/2b49bf7))
* **core:** export defaultKeyValueDiffers to private api ([#24319](https://github.com/angular/angular/issues/24319)) ([92b278c](https://github.com/angular/angular/commit/92b278c))
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
* **ivy:** a generic visitor which allows prefixing nodes for ngtsc ([#24230](https://github.com/angular/angular/issues/24230)) ([ca79e11](https://github.com/angular/angular/commit/ca79e11))
* **ivy:** add support of ApplicationRef.bootstrapModuleFactory ([#23811](https://github.com/angular/angular/issues/23811)) ([e3759f7](https://github.com/angular/angular/commit/e3759f7))
* **ivy:** namespaced attributes added to output instructions ([#24386](https://github.com/angular/angular/issues/24386)) ([82c5313](https://github.com/angular/angular/commit/82c5313))
* **ivy:** now supports SVG and MathML elements ([#24377](https://github.com/angular/angular/issues/24377)) ([8c1ac28](https://github.com/angular/angular/commit/8c1ac28))
* **router:** implement scrolling restoration service ([#20030](https://github.com/angular/angular/issues/20030)) ([49c5234](https://github.com/angular/angular/commit/49c5234)), closes [#13636](https://github.com/angular/angular/issues/13636) [#10929](https://github.com/angular/angular/issues/10929) [#7791](https://github.com/angular/angular/issues/7791) [#6595](https://github.com/angular/angular/issues/6595)
<a name="6.0.5"></a>
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
@ -40,51 +12,6 @@
<a name="6.1.0-beta.0"></a>
## [6.1.0-beta.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0-beta.0) (2018-06-06)
### Bug Fixes
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([d2a8687](https://github.com/angular/angular/commit/d2a8687))
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([b492b9e](https://github.com/angular/angular/commit/b492b9e))
* **animations:** properly clean up queried element styles in safari/edge ([#23633](https://github.com/angular/angular/issues/23633)) ([da9ff25](https://github.com/angular/angular/commit/da9ff25))
* **animations:** retain state styling for nodes that are moved around ([#23534](https://github.com/angular/angular/issues/23534)) ([65211f4](https://github.com/angular/angular/commit/65211f4))
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([8db928d](https://github.com/angular/angular/commit/8db928d))
* **benchpress:** Fix promise chain in chrome_driver_extension. ([#23458](https://github.com/angular/angular/issues/23458)) ([d4b6c41](https://github.com/angular/angular/commit/d4b6c41))
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([b1cda36](https://github.com/angular/angular/commit/b1cda36))
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([b96a3c8](https://github.com/angular/angular/commit/b96a3c8))
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([60aa943](https://github.com/angular/angular/commit/60aa943)), closes [#23023](https://github.com/angular/angular/issues/23023)
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([6a663a4](https://github.com/angular/angular/commit/6a663a4)), closes [#19278](https://github.com/angular/angular/issues/19278)
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([e0ed59e](https://github.com/angular/angular/commit/e0ed59e)), closes [#23526](https://github.com/angular/angular/issues/23526)
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([1d378e2](https://github.com/angular/angular/commit/1d378e2))
### Features
* **compiler:** support `// ...` and `// TODO` in mock compiler expectations ([#23441](https://github.com/angular/angular/issues/23441)) ([c6b206e](https://github.com/angular/angular/commit/c6b206e))
* **compiler-cli:** update `tsickle` to `0.29.x` ([#24233](https://github.com/angular/angular/issues/24233)) ([f69ac67](https://github.com/angular/angular/commit/f69ac67))
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
* **platform-server:** use EventManagerPlugin on the server ([#24132](https://github.com/angular/angular/issues/24132)) ([d6595eb](https://github.com/angular/angular/commit/d6595eb))
* **router:** add navigation execution context info to activation hooks ([#24204](https://github.com/angular/angular/issues/24204)) ([20c463e](https://github.com/angular/angular/commit/20c463e)), closes [#24202](https://github.com/angular/angular/issues/24202)
<a name="6.0.4"></a>
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)

View File

@ -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

View File

@ -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": {

View File

@ -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": {

View File

@ -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,
};

View File

@ -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"
}
}

View File

@ -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"
]
}

View File

@ -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/**',

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "6.1.0-beta.1",
"version": "6.0.5",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -114,7 +114,7 @@
"tslint": "5.7.0",
"tslint-eslint-rules": "4.1.1",
"tsutils": "2.20.0",
"typescript": "2.8.x",
"typescript": "2.7.x",
"uglify-es": "^3.3.9",
"universal-analytics": "0.4.15",
"vlq": "0.2.2",

View File

@ -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",

View File

@ -15,7 +15,12 @@ const DEFAULT_FILL_MODE = 'forwards';
const DEFAULT_EASING = 'linear';
const ANIMATION_END_EVENT = 'animationend';
export const enum AnimatorControlState {INITIALIZED = 1, STARTED = 2, FINISHED = 3, DESTROYED = 4}
export enum AnimatorControlState {
INITIALIZED = 1,
STARTED = 2,
FINISHED = 3,
DESTROYED = 4
}
export class CssKeyframesPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
@ -29,8 +34,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
public readonly totalTime: number;
public readonly easing: string;
public currentSnapshot: {[key: string]: string} = {};
private _state: AnimatorControlState = 0;
public state = 0;
constructor(
public readonly element: any, public readonly keyframes: {[key: string]: string | number}[],
@ -50,8 +54,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
destroy() {
this.init();
if (this._state >= AnimatorControlState.DESTROYED) return;
this._state = AnimatorControlState.DESTROYED;
if (this.state >= AnimatorControlState.DESTROYED) return;
this.state = AnimatorControlState.DESTROYED;
this._styler.destroy();
this._flushStartFns();
this._flushDoneFns();
@ -71,8 +75,8 @@ export class CssKeyframesPlayer implements AnimationPlayer {
finish() {
this.init();
if (this._state >= AnimatorControlState.FINISHED) return;
this._state = AnimatorControlState.FINISHED;
if (this.state >= AnimatorControlState.FINISHED) return;
this.state = AnimatorControlState.FINISHED;
this._styler.finish();
this._flushStartFns();
this._flushDoneFns();
@ -82,10 +86,10 @@ export class CssKeyframesPlayer implements AnimationPlayer {
getPosition(): number { return this._styler.getPosition(); }
hasStarted(): boolean { return this._state >= AnimatorControlState.STARTED; }
hasStarted(): boolean { return this.state >= AnimatorControlState.STARTED; }
init(): void {
if (this._state >= AnimatorControlState.INITIALIZED) return;
this._state = AnimatorControlState.INITIALIZED;
if (this.state >= AnimatorControlState.INITIALIZED) return;
this.state = AnimatorControlState.INITIALIZED;
const elm = this.element;
this._styler.apply();
if (this._delay) {
@ -97,7 +101,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
this.init();
if (!this.hasStarted()) {
this._flushStartFns();
this._state = AnimatorControlState.STARTED;
this.state = AnimatorControlState.STARTED;
}
this._styler.resume();
}
@ -133,7 +137,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
this.init();
const styles: {[key: string]: string} = {};
if (this.hasStarted()) {
const finished = this._state >= AnimatorControlState.FINISHED;
const finished = this.state >= AnimatorControlState.FINISHED;
Object.keys(this._finalStyles).forEach(prop => {
if (prop != 'offset') {
styles[prop] = finished ? this._finalStyles[prop] : computeStyle(this.element, prop);

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -14,90 +14,6 @@ load(":rules_typescript.bzl",
"ts_providers_dict_to_struct",
)
def _compile_strategy(ctx):
"""Detect which strategy should be used to implement ng_module.
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
can be implemented in various ways. This function reads the configuration passed by the user and
determines which mode is active.
Args:
ctx: skylark rule execution context
Returns:
one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx
"""
strategy = 'legacy'
if 'compile' in ctx.var:
strategy = ctx.var['compile']
if strategy not in ['legacy', 'local', 'jit']:
fail("Unknown --define=compile value '%s'" % strategy)
if strategy == 'legacy' and hasattr(ctx.attr, '_global_mode') and ctx.attr._global_mode:
strategy = 'global'
return strategy
def _compiler_name(ctx):
"""Selects a user-visible name depending on the current compilation strategy.
Args:
ctx: skylark rule execution context
Returns:
the name of the current compiler to be displayed in build output
"""
strategy = _compile_strategy(ctx)
if strategy == 'legacy':
return 'ngc'
elif strategy == 'global':
return 'ngc.ivy'
elif strategy == 'local':
return 'ngtsc'
elif strategy == 'jit':
return 'tsc'
else:
fail('unreachable')
def _enable_ivy_value(ctx):
"""Determines the value of the enableIvy option in the generated tsconfig.
Args:
ctx: skylark rule execution context
Returns:
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
"""
strategy = _compile_strategy(ctx)
if strategy == 'legacy':
return False
elif strategy == 'global':
return True
elif strategy == 'local':
return 'ngtsc'
elif strategy == 'jit':
return 'tsc'
else:
fail('unreachable')
def _include_ng_files(ctx):
"""Determines whether Angular outputs will be produced by the current compilation strategy.
Args:
ctx: skylark rule execution context
Returns:
true iff the current compilation strategy will produce View Engine compilation outputs (such as
factory files), false otherwise
"""
strategy = _compile_strategy(ctx)
return strategy == 'legacy' or strategy == 'global'
def _basename_of(ctx, file):
ext_len = len(".ts")
if file.short_path.endswith(".ng.html"):
@ -145,8 +61,6 @@ def _should_produce_flat_module_outs(ctx):
# in the library. Most of these will be produced as empty files but it is
# unknown, without parsing, which will be empty.
def _expected_outs(ctx):
include_ng_files = _include_ng_files(ctx)
devmode_js_files = []
closure_js_files = []
declaration_files = []
@ -164,7 +78,7 @@ def _expected_outs(ctx):
if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
basename = short_path[len(package_prefix):-len(".ts")]
if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set):
if len(factory_basename_set) == 0 or basename in factory_basename_set:
devmode_js = [
".ngfactory.js",
".ngsummary.js",
@ -176,7 +90,7 @@ def _expected_outs(ctx):
devmode_js = [".js"]
summaries = []
metadata = []
elif include_ng_files and short_path.endswith(".css"):
elif short_path.endswith(".css"):
basename = short_path[len(package_prefix):-len(".css")]
devmode_js = [
".css.shim.ngstyle.js",
@ -199,7 +113,7 @@ def _expected_outs(ctx):
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]
# We do this just when producing a flat module index for a publishable ng_module
if include_ng_files and _should_produce_flat_module_outs(ctx):
if _should_produce_flat_module_outs(ctx):
flat_module_out = _flat_module_out_file(ctx)
devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out))
@ -209,12 +123,7 @@ def _expected_outs(ctx):
else:
bundle_index_typings = None
# TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
# when ngtsc can extract messages
if include_ng_files:
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
else:
i18n_messages_files = []
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
return struct(
closure_js = closure_js_files,
@ -226,9 +135,14 @@ def _expected_outs(ctx):
i18n_messages = i18n_messages_files,
)
def _ivy_tsconfig(ctx, files, srcs, **kwargs):
return _ngc_tsconfig_helper(ctx, files, srcs, True, **kwargs)
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
return _ngc_tsconfig_helper(ctx, files, srcs, False, **kwargs)
def _ngc_tsconfig_helper(ctx, files, srcs, enable_ivy, **kwargs):
outs = _expected_outs(ctx)
include_ng_files = _include_ng_files(ctx)
if "devmode_manifest" in kwargs:
expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
else:
@ -238,9 +152,8 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
"enableResourceInlining": ctx.attr.inline_resources,
"generateCodeForLibraries": False,
"allowEmptyCodegenFiles": True,
# Summaries are only enabled if Angular outputs are to be produced.
"enableSummariesForJit": include_ng_files,
"enableIvy": _enable_ivy_value(ctx),
"enableSummariesForJit": True,
"enableIvy": enable_ivy,
"fullTemplateTypeCheck": ctx.attr.type_check,
# FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list()
@ -303,10 +216,8 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
the parameters of the compilation which will be used to replay the ngc action for i18N.
"""
include_ng_files = _include_ng_files(ctx)
mnemonic = "AngularTemplateCompile"
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
progress_message = "Compiling Angular templates (ngc) %s" % label
if locale:
mnemonic = "AngularI18NMerging"
@ -340,7 +251,7 @@ def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
},
)
if include_ng_files and messages_out != None:
if messages_out != None:
ctx.actions.run(
inputs = list(inputs),
outputs = messages_out,
@ -402,7 +313,7 @@ def _ts_expected_outs(ctx, label):
_ignored = [label]
return _expected_outs(ctx)
def ng_module_impl(ctx, ts_compile_actions):
def ng_module_impl(ctx, ts_compile_actions, ivy = False):
"""Implementation function for the ng_module rule.
This is exposed so that google3 can have its own entry point that re-uses this
@ -411,30 +322,29 @@ def ng_module_impl(ctx, ts_compile_actions):
Args:
ctx: the skylark rule context
ts_compile_actions: generates all the actions to run an ngc compilation
ivy: if True, run the compiler in Ivy mode (internal only)
Returns:
the result of the ng_module rule as a dict, suitable for
conversion by ts_providers_dict_to_struct
"""
include_ng_files = _include_ng_files(ctx)
tsconfig = _ngc_tsconfig if not ivy else _ivy_tsconfig
providers = ts_compile_actions(
ctx, is_library=True, compile_action=_prodmode_compile_action,
devmode_compile_action=_devmode_compile_action,
tsc_wrapped_tsconfig=_ngc_tsconfig,
tsc_wrapped_tsconfig=tsconfig,
outputs = _ts_expected_outs)
outs = _expected_outs(ctx)
providers["angular"] = {
"summaries": outs.summaries,
"metadata": outs.metadata
}
providers["ngc_messages"] = outs.i18n_messages
if include_ng_files:
providers["angular"] = {
"summaries": outs.summaries,
"metadata": outs.metadata
}
providers["ngc_messages"] = outs.i18n_messages
if include_ng_files and _should_produce_flat_module_outs(ctx):
if _should_produce_flat_module_outs(ctx):
if len(outs.metadata) > 1:
fail("expecting exactly one metadata output for " + str(ctx.label))
@ -450,6 +360,9 @@ def ng_module_impl(ctx, ts_compile_actions):
def _ng_module_impl(ctx):
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
def _ivy_module_impl(ctx):
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts, True))
NG_MODULE_ATTRIBUTES = {
"srcs": attr.label_list(allow_files = [".ts"]),
@ -516,16 +429,11 @@ ng_module = rule(
outputs = COMMON_OUTPUTS,
)
# TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information.
# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used
# instead, and should be removed once ngtsc is capable of fulfilling the same requirements.
internal_global_ng_module = rule(
implementation = _ng_module_impl,
attrs = dict(NG_MODULE_RULE_ATTRS, **{
"_global_mode": attr.bool(
default = True,
),
}),
# TODO(alxhub): this rule exists to allow early testing of the Ivy compiler within angular/angular,
# and should not be made public. When ng_module() supports Ivy-mode outputs, this rule should be
# removed and its usages refactored to use ng_module() directly.
internal_ivy_ng_module = rule(
implementation = _ivy_module_impl,
attrs = NG_MODULE_RULE_ATTRS,
outputs = COMMON_OUTPUTS,
)

View File

@ -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.

View File

@ -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;

View File

@ -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}');`);
}

View File

@ -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",

View File

@ -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';

View File

@ -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) {

View File

@ -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,
];

View File

@ -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;
}

View File

@ -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 {}
}

View File

@ -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';

View File

@ -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}]);
});
});

View File

@ -25,7 +25,6 @@ ts_library(
tsconfig = ":tsconfig",
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/transform",
],
)
@ -34,9 +33,6 @@ npm_package(
srcs = [
"package.json",
],
tags = [
"ivy-jit",
"release-with-framework",
],
tags = ["release-with-framework"],
deps = [":compiler-cli"],
)

View File

@ -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(

View File

@ -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" : {

View File

@ -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;

View File

@ -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.'); }
}

View File

@ -1,15 +0,0 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "metadata",
srcs = glob([
"index.ts",
"src/*.ts",
]),
module_name = "@angular/compiler-cli/src/ngtsc/metadata",
deps = [
"//packages/compiler",
],
)

View File

@ -1,10 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export {Decorator, Parameter, reflectConstructorParameters, reflectDecorator} from './src/reflector';
export {Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';

View File

@ -1,238 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/**
* reflector.ts implements static reflection of declarations using the TypeScript `ts.TypeChecker`.
*/
/**
* A reflected parameter of a function, method, or constructor, indicating the name, any
* decorators, and an expression representing a reference to the value side of the parameter's
* declared type, if applicable.
*/
export interface Parameter {
/**
* Name of the parameter as a `ts.BindingName`, which allows the parameter name to be identified
* via sourcemaps.
*/
name: ts.BindingName;
/**
* A `ts.Expression` which represents a reference to the value side of the parameter's type.
*/
typeValueExpr: ts.Expression|null;
/**
* Array of decorators present on the parameter.
*/
decorators: Decorator[];
}
/**
* A reflected decorator, indicating the name, where it was imported from, and any arguments if the
* decorator is a call expression.
*/
export interface Decorator {
/**
* Name of the decorator, extracted from the decoration expression.
*/
name: string;
/**
* Import path (relative to the decorator's file) of the decorator itself.
*/
from: string;
/**
* The decorator node itself (useful for printing sourcemap based references to the decorator).
*/
node: ts.Decorator;
/**
* Any arguments of a call expression, if one is present. If the decorator was not a call
* expression, then this will be an empty array.
*/
args: ts.Expression[];
}
/**
* Reflect a `ts.ClassDeclaration` and determine the list of parameters.
*
* Note that this only reflects the referenced class and not any potential parent class - that must
* be handled by the caller.
*
* @param node the `ts.ClassDeclaration` to reflect
* @param checker a `ts.TypeChecker` used for reflection
* @returns a `Parameter` instance for each argument of the constructor, or `null` if no constructor
*/
export function reflectConstructorParameters(
node: ts.ClassDeclaration, checker: ts.TypeChecker): Parameter[]|null {
// Firstly, look for a constructor.
// clang-format off
const maybeCtor: ts.ConstructorDeclaration[] = node
.members
.filter(element => ts.isConstructorDeclaration(element)) as ts.ConstructorDeclaration[];
// clang-format on
if (maybeCtor.length !== 1) {
// No constructor.
return null;
}
// Reflect each parameter.
return maybeCtor[0].parameters.map(param => reflectParameter(param, checker));
}
/**
* Reflect a `ts.ParameterDeclaration` and determine its name, a token which refers to the value
* declaration of its type (if possible to statically determine), and its decorators, if any.
*/
function reflectParameter(node: ts.ParameterDeclaration, checker: ts.TypeChecker): Parameter {
// The name of the parameter is easy.
const name = node.name;
const decorators = node.decorators &&
node.decorators.map(decorator => reflectDecorator(decorator, checker))
.filter(decorator => decorator !== null) as Decorator[] ||
[];
// It may or may not be possible to write an expression that refers to the value side of the
// type named for the parameter.
let typeValueExpr: ts.Expression|null = null;
// It's not possible to get a value expression if the parameter doesn't even have a type.
if (node.type !== undefined) {
// It's only valid to convert a type reference to a value reference if the type actually has a
// value declaration associated with it.
const type = checker.getTypeFromTypeNode(node.type);
if (type.symbol !== undefined && type.symbol.valueDeclaration !== undefined) {
// The type points to a valid value declaration. Rewrite the TypeReference into an Expression
// which references the value pointed to by the TypeReference, if possible.
typeValueExpr = typeNodeToValueExpr(node.type);
}
}
return {
name, typeValueExpr, decorators,
};
}
/**
* Reflect a decorator and return a structure describing where it comes from and any arguments.
*
* Only imported decorators are considered, not locally defined decorators.
*/
export function reflectDecorator(decorator: ts.Decorator, checker: ts.TypeChecker): Decorator|null {
// Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
// expression may contain a call to a function which returns the decorator function, in which
// case we want to return the arguments.
let decoratorOfInterest: ts.Expression = decorator.expression;
let args: ts.Expression[] = [];
// Check for call expressions.
if (ts.isCallExpression(decoratorOfInterest)) {
args = Array.from(decoratorOfInterest.arguments);
decoratorOfInterest = decoratorOfInterest.expression;
}
// The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
// wrong and the decorator can't be resolved statically.
if (!ts.isIdentifier(decoratorOfInterest)) {
return null;
}
const importDecl = reflectImportedIdentifier(decoratorOfInterest, checker);
if (importDecl === null) {
return null;
}
return {
...importDecl,
node: decorator, args,
};
}
function typeNodeToValueExpr(node: ts.TypeNode): ts.Expression|null {
if (ts.isTypeReferenceNode(node)) {
return entityNameToValue(node.typeName);
} else {
return null;
}
}
function entityNameToValue(node: ts.EntityName): ts.Expression|null {
if (ts.isQualifiedName(node)) {
const left = entityNameToValue(node.left);
return left !== null ? ts.createPropertyAccess(left, node.right) : null;
} else if (ts.isIdentifier(node)) {
return ts.getMutableClone(node);
} else {
return null;
}
}
function propertyNameToValue(node: ts.PropertyName): string|null {
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
return node.text;
} else {
return null;
}
}
export function reflectObjectLiteral(node: ts.ObjectLiteralExpression): Map<string, ts.Expression> {
const map = new Map<string, ts.Expression>();
node.properties.forEach(prop => {
if (ts.isPropertyAssignment(prop)) {
const name = propertyNameToValue(prop.name);
if (name === null) {
return;
}
map.set(name, prop.initializer);
} else if (ts.isShorthandPropertyAssignment(prop)) {
map.set(prop.name.text, prop.name);
} else {
return;
}
});
return map;
}
export function reflectImportedIdentifier(
id: ts.Identifier, checker: ts.TypeChecker): {name: string, from: string}|null {
const symbol = checker.getSymbolAtLocation(id);
if (symbol === undefined || symbol.declarations === undefined ||
symbol.declarations.length !== 1) {
return null;
}
// Ignore decorators that are defined locally (not imported).
const decl: ts.Declaration = symbol.declarations[0];
if (!ts.isImportSpecifier(decl)) {
return null;
}
// Walk back from the specifier to find the declaration, which carries the module specifier.
const importDecl = decl.parent !.parent !.parent !;
// The module specifier is guaranteed to be a string literal, so this should always pass.
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
// Not allowed to happen in TypeScript ASTs.
return null;
}
// Read the module specifier.
const from = importDecl.moduleSpecifier.text;
// Compute the name by which the decorator was exported, not imported.
const name = (decl.propertyName !== undefined ? decl.propertyName : decl.name).text;
return {from, name};
}

View File

@ -1,514 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* resolver.ts implements partial computation of expressions, resolving expressions to static
* values where possible and returning a `DynamicValue` signal when not.
*/
import * as ts from 'typescript';
/**
* Represents a value which cannot be determined statically.
*
* Use `isDynamicValue` to determine whether a `ResolvedValue` is a `DynamicValue`.
*/
export class DynamicValue {
/**
* This is needed so the "is DynamicValue" assertion of `isDynamicValue` actually has meaning.
*
* Otherwise, "is DynamicValue" is akin to "is {}" which doesn't trigger narrowing.
*/
private _isDynamic = true;
}
/**
* An internal flyweight for `DynamicValue`. Eventually the dynamic value will carry information
* on the location of the node that could not be statically computed.
*/
const DYNAMIC_VALUE: DynamicValue = new DynamicValue();
/**
* Used to test whether a `ResolvedValue` is a `DynamicValue`.
*/
export function isDynamicValue(value: any): value is DynamicValue {
return value === DYNAMIC_VALUE;
}
/**
* A value resulting from static resolution.
*
* This could be a primitive, collection type, reference to a `ts.Node` that declares a
* non-primitive value, or a special `DynamicValue` type which indicates the value was not
* available statically.
*/
export type ResolvedValue = number | boolean | string | null | undefined | Reference |
ResolvedValueArray | ResolvedValueMap | DynamicValue;
/**
* An array of `ResolvedValue`s.
*
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueArray`
* ->
* `ResolvedValue`.
*/
export interface ResolvedValueArray extends Array<ResolvedValue> {}
/**
* A map of strings to `ResolvedValue`s.
*
* This is a reified type to allow the circular reference of `ResolvedValue` -> `ResolvedValueMap` ->
* `ResolvedValue`.
*/ export interface ResolvedValueMap extends Map<string, ResolvedValue> {}
/**
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
* body.
*/
type Scope = Map<ts.ParameterDeclaration, ResolvedValue>;
/**
* Whether or not to allow references during resolution.
*
* See `StaticInterpreter` for details.
*/
const enum AllowReferences {
No = 0,
Yes = 1,
}
/**
* A reference to a `ts.Node`.
*
* For example, if an expression evaluates to a function or class definition, it will be returned
* as a `Reference` (assuming references are allowed in evaluation).
*/
export class Reference {
constructor(readonly node: ts.Node) {}
}
/**
* Statically resolve the given `ts.Expression` into a `ResolvedValue`.
*
* @param node the expression to statically resolve if possible
* @param checker a `ts.TypeChecker` used to understand the expression
* @returns a `ResolvedValue` representing the resolved value
*/
export function staticallyResolve(node: ts.Expression, checker: ts.TypeChecker): ResolvedValue {
return new StaticInterpreter(
checker, new Map<ts.ParameterDeclaration, ResolvedValue>(), AllowReferences.No)
.visit(node);
}
interface BinaryOperatorDef {
literal: boolean;
op: (a: any, b: any) => ResolvedValue;
}
function literalBinaryOp(op: (a: any, b: any) => any): BinaryOperatorDef {
return {op, literal: true};
}
function referenceBinaryOp(op: (a: any, b: any) => any): BinaryOperatorDef {
return {op, literal: false};
}
const BINARY_OPERATORS = new Map<ts.SyntaxKind, BinaryOperatorDef>([
[ts.SyntaxKind.PlusToken, literalBinaryOp((a, b) => a + b)],
[ts.SyntaxKind.MinusToken, literalBinaryOp((a, b) => a - b)],
[ts.SyntaxKind.AsteriskToken, literalBinaryOp((a, b) => a * b)],
[ts.SyntaxKind.SlashToken, literalBinaryOp((a, b) => a / b)],
[ts.SyntaxKind.PercentToken, literalBinaryOp((a, b) => a % b)],
[ts.SyntaxKind.AmpersandToken, literalBinaryOp((a, b) => a & b)],
[ts.SyntaxKind.BarToken, literalBinaryOp((a, b) => a | b)],
[ts.SyntaxKind.CaretToken, literalBinaryOp((a, b) => a ^ b)],
[ts.SyntaxKind.LessThanToken, literalBinaryOp((a, b) => a < b)],
[ts.SyntaxKind.LessThanEqualsToken, literalBinaryOp((a, b) => a <= b)],
[ts.SyntaxKind.GreaterThanToken, literalBinaryOp((a, b) => a > b)],
[ts.SyntaxKind.GreaterThanEqualsToken, literalBinaryOp((a, b) => a >= b)],
[ts.SyntaxKind.LessThanLessThanToken, literalBinaryOp((a, b) => a << b)],
[ts.SyntaxKind.GreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >> b)],
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >>> b)],
[ts.SyntaxKind.AsteriskAsteriskToken, literalBinaryOp((a, b) => Math.pow(a, b))],
[ts.SyntaxKind.AmpersandAmpersandToken, referenceBinaryOp((a, b) => a && b)],
[ts.SyntaxKind.BarBarToken, referenceBinaryOp((a, b) => a || b)]
]);
const UNARY_OPERATORS = new Map<ts.SyntaxKind, (a: any) => any>([
[ts.SyntaxKind.TildeToken, a => ~a], [ts.SyntaxKind.MinusToken, a => -a],
[ts.SyntaxKind.PlusToken, a => +a], [ts.SyntaxKind.ExclamationToken, a => !a]
]);
class StaticInterpreter {
constructor(
private checker: ts.TypeChecker, private scope: Scope,
private allowReferences: AllowReferences) {}
visit(node: ts.Expression): ResolvedValue { return this.visitExpression(node); }
private visitExpression(node: ts.Expression): ResolvedValue {
if (node.kind === ts.SyntaxKind.TrueKeyword) {
return true;
} else if (node.kind === ts.SyntaxKind.FalseKeyword) {
return false;
} else if (ts.isStringLiteral(node)) {
return node.text;
} else if (ts.isNumericLiteral(node)) {
return parseFloat(node.text);
} else if (ts.isObjectLiteralExpression(node)) {
return this.visitObjectLiteralExpression(node);
} else if (ts.isIdentifier(node)) {
return this.visitIdentifier(node);
} else if (ts.isPropertyAccessExpression(node)) {
return this.visitPropertyAccessExpression(node);
} else if (ts.isCallExpression(node)) {
return this.visitCallExpression(node);
} else if (ts.isConditionalExpression(node)) {
return this.visitConditionalExpression(node);
} else if (ts.isPrefixUnaryExpression(node)) {
return this.visitPrefixUnaryExpression(node);
} else if (ts.isBinaryExpression(node)) {
return this.visitBinaryExpression(node);
} else if (ts.isArrayLiteralExpression(node)) {
return this.visitArrayLiteralExpression(node);
} else if (ts.isParenthesizedExpression(node)) {
return this.visitParenthesizedExpression(node);
} else if (ts.isElementAccessExpression(node)) {
return this.visitElementAccessExpression(node);
} else if (ts.isAsExpression(node)) {
return this.visitExpression(node.expression);
} else if (ts.isNonNullExpression(node)) {
return this.visitExpression(node.expression);
} else if (ts.isClassDeclaration(node)) {
return this.visitDeclaration(node);
} else {
return DYNAMIC_VALUE;
}
}
private visitArrayLiteralExpression(node: ts.ArrayLiteralExpression): ResolvedValue {
const array: ResolvedValueArray = [];
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (ts.isSpreadElement(element)) {
const spread = this.visitExpression(element.expression);
if (isDynamicValue(spread)) {
return DYNAMIC_VALUE;
}
if (!Array.isArray(spread)) {
throw new Error(`Unexpected value in spread expression: ${spread}`);
}
array.push(...spread);
} else {
const result = this.visitExpression(element);
if (isDynamicValue(result)) {
return DYNAMIC_VALUE;
}
array.push(result);
}
}
return array;
}
private visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): ResolvedValue {
const map: ResolvedValueMap = new Map<string, ResolvedValue>();
for (let i = 0; i < node.properties.length; i++) {
const property = node.properties[i];
if (ts.isPropertyAssignment(property)) {
const name = this.stringNameFromPropertyName(property.name);
// Check whether the name can be determined statically.
if (name === undefined) {
return DYNAMIC_VALUE;
}
map.set(name, this.visitExpression(property.initializer));
} else if (ts.isShorthandPropertyAssignment(property)) {
const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
if (symbol === undefined || symbol.valueDeclaration === undefined) {
return DYNAMIC_VALUE;
}
map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration));
} else if (ts.isSpreadAssignment(property)) {
const spread = this.visitExpression(property.expression);
if (isDynamicValue(spread)) {
return DYNAMIC_VALUE;
}
if (!(spread instanceof Map)) {
throw new Error(`Unexpected value in spread assignment: ${spread}`);
}
spread.forEach((value, key) => map.set(key, value));
} else {
return DYNAMIC_VALUE;
}
}
return map;
}
private visitIdentifier(node: ts.Identifier): ResolvedValue {
let symbol: ts.Symbol|undefined = this.checker.getSymbolAtLocation(node);
if (symbol === undefined) {
return DYNAMIC_VALUE;
}
const result = this.visitSymbol(symbol);
if (this.allowReferences === AllowReferences.Yes && isDynamicValue(result)) {
return new Reference(node);
}
return result;
}
private visitSymbol(symbol: ts.Symbol): ResolvedValue {
while (symbol.flags & ts.SymbolFlags.Alias) {
symbol = this.checker.getAliasedSymbol(symbol);
}
if (symbol.declarations === undefined) {
return DYNAMIC_VALUE;
}
if (symbol.valueDeclaration !== undefined) {
return this.visitDeclaration(symbol.valueDeclaration);
}
return symbol.declarations.reduce<ResolvedValue>((prev, decl) => {
if (!(isDynamicValue(prev) || prev instanceof Reference)) {
return prev;
}
return this.visitDeclaration(decl);
}, DYNAMIC_VALUE);
}
private visitDeclaration(node: ts.Declaration): ResolvedValue {
if (ts.isVariableDeclaration(node)) {
if (!node.initializer) {
return undefined;
}
return this.visitExpression(node.initializer);
} else if (ts.isParameter(node) && this.scope.has(node)) {
return this.scope.get(node) !;
} else if (ts.isExportAssignment(node)) {
return this.visitExpression(node.expression);
} else if (ts.isSourceFile(node)) {
return this.visitSourceFile(node);
}
return this.allowReferences === AllowReferences.Yes ? new Reference(node) : DYNAMIC_VALUE;
}
private visitElementAccessExpression(node: ts.ElementAccessExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
if (node.argumentExpression === undefined) {
throw new Error(`Expected argument in ElementAccessExpression`);
}
if (isDynamicValue(lhs)) {
return DYNAMIC_VALUE;
}
const rhs = this.withNoReferences.visitExpression(node.argumentExpression);
if (isDynamicValue(rhs)) {
return DYNAMIC_VALUE;
}
if (typeof rhs !== 'string' && typeof rhs !== 'number') {
throw new Error(
`ElementAccessExpression index should be string or number, got ${typeof rhs}: ${rhs}`);
}
return this.accessHelper(lhs, rhs);
}
private visitPropertyAccessExpression(node: ts.PropertyAccessExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
const rhs = node.name.text;
// TODO: handle reference to class declaration.
if (isDynamicValue(lhs)) {
return DYNAMIC_VALUE;
}
return this.accessHelper(lhs, rhs);
}
private visitSourceFile(node: ts.SourceFile): ResolvedValue {
const map = new Map<string, ResolvedValue>();
const symbol = this.checker.getSymbolAtLocation(node);
if (symbol === undefined) {
return DYNAMIC_VALUE;
}
const exports = this.checker.getExportsOfModule(symbol);
exports.forEach(symbol => map.set(symbol.name, this.visitSymbol(symbol)));
return map;
}
private accessHelper(lhs: ResolvedValue, rhs: string|number): ResolvedValue {
const strIndex = `${rhs}`;
if (lhs instanceof Map) {
if (lhs.has(strIndex)) {
return lhs.get(strIndex) !;
} else {
throw new Error(`Invalid map access: [${Array.from(lhs.keys())}] dot ${rhs}`);
}
} else if (Array.isArray(lhs)) {
if (rhs === 'length') {
return rhs.length;
}
if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
return DYNAMIC_VALUE;
}
if (rhs < 0 || rhs >= lhs.length) {
throw new Error(`Index out of bounds: ${rhs} vs ${lhs.length}`);
}
return lhs[rhs];
} else if (lhs instanceof Reference) {
const ref = lhs.node;
if (ts.isClassDeclaration(ref)) {
let value: ResolvedValue = undefined;
const member = ref.members.filter(member => isStatic(member))
.find(
member => member.name !== undefined &&
this.stringNameFromPropertyName(member.name) === strIndex);
if (member !== undefined) {
if (ts.isPropertyDeclaration(member) && member.initializer !== undefined) {
value = this.visitExpression(member.initializer);
} else if (ts.isMethodDeclaration(member)) {
value = this.allowReferences === AllowReferences.Yes ? new Reference(member) :
DYNAMIC_VALUE;
}
}
return value;
}
}
throw new Error(`Invalid dot property access: ${lhs} dot ${rhs}`);
}
private visitCallExpression(node: ts.CallExpression): ResolvedValue {
const lhs = this.withReferences.visitExpression(node.expression);
if (!(lhs instanceof Reference)) {
throw new Error(`attempting to call something that is not a function: ${lhs}`);
} else if (!isFunctionOrMethodDeclaration(lhs.node) || !lhs.node.body) {
throw new Error(
`calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]}`);
}
const fn = lhs.node;
const body = fn.body as ts.Block;
if (body.statements.length !== 1 || !ts.isReturnStatement(body.statements[0])) {
throw new Error('Function body must have a single return statement only.');
}
const ret = body.statements[0] as ts.ReturnStatement;
const newScope: Scope = new Map<ts.ParameterDeclaration, ResolvedValue>();
fn.parameters.forEach((param, index) => {
let value: ResolvedValue = undefined;
if (index < node.arguments.length) {
const arg = node.arguments[index];
value = this.visitExpression(arg);
}
if (value === undefined && param.initializer !== undefined) {
value = this.visitExpression(param.initializer);
}
newScope.set(param, value);
});
return ret.expression !== undefined ? this.withScope(newScope).visitExpression(ret.expression) :
undefined;
}
private visitConditionalExpression(node: ts.ConditionalExpression): ResolvedValue {
const condition = this.withNoReferences.visitExpression(node.condition);
if (isDynamicValue(condition)) {
return condition;
}
if (condition) {
return this.visitExpression(node.whenTrue);
} else {
return this.visitExpression(node.whenFalse);
}
}
private visitPrefixUnaryExpression(node: ts.PrefixUnaryExpression): ResolvedValue {
const operatorKind = node.operator;
if (!UNARY_OPERATORS.has(operatorKind)) {
throw new Error(`Unsupported prefix unary operator: ${ts.SyntaxKind[operatorKind]}`);
}
const op = UNARY_OPERATORS.get(operatorKind) !;
const value = this.visitExpression(node.operand);
return isDynamicValue(value) ? DYNAMIC_VALUE : op(value);
}
private visitBinaryExpression(node: ts.BinaryExpression): ResolvedValue {
const tokenKind = node.operatorToken.kind;
if (!BINARY_OPERATORS.has(tokenKind)) {
throw new Error(`Unsupported binary operator: ${ts.SyntaxKind[tokenKind]}`);
}
const opRecord = BINARY_OPERATORS.get(tokenKind) !;
let lhs: ResolvedValue, rhs: ResolvedValue;
if (opRecord.literal) {
const withNoReferences = this.withNoReferences;
lhs = literal(withNoReferences.visitExpression(node.left));
rhs = literal(withNoReferences.visitExpression(node.right));
} else {
lhs = this.visitExpression(node.left);
rhs = this.visitExpression(node.right);
}
return isDynamicValue(lhs) || isDynamicValue(rhs) ? DYNAMIC_VALUE : opRecord.op(lhs, rhs);
}
private visitParenthesizedExpression(node: ts.ParenthesizedExpression): ResolvedValue {
return this.visitExpression(node.expression);
}
private stringNameFromPropertyName(node: ts.PropertyName): string|undefined {
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
return node.text;
} else { // ts.ComputedPropertyName
const literal = this.withNoReferences.visitExpression(node.expression);
return typeof literal === 'string' ? literal : undefined;
}
}
private get withReferences(): StaticInterpreter {
return this.allowReferences === AllowReferences.Yes ?
this :
new StaticInterpreter(this.checker, this.scope, AllowReferences.Yes);
}
private get withNoReferences(): StaticInterpreter {
return this.allowReferences === AllowReferences.No ?
this :
new StaticInterpreter(this.checker, this.scope, AllowReferences.No);
}
private withScope(scope: Scope): StaticInterpreter {
return new StaticInterpreter(this.checker, scope, this.allowReferences);
}
}
function isStatic(element: ts.ClassElement): boolean {
return element.modifiers !== undefined &&
element.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
}
function isFunctionOrMethodDeclaration(node: ts.Node): node is ts.FunctionDeclaration|
ts.MethodDeclaration {
return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node);
}
function literal(value: ResolvedValue): any {
if (value === null || value === undefined || typeof value === 'string' ||
typeof value === 'number' || typeof value === 'boolean') {
return value;
}
if (isDynamicValue(value)) {
return DYNAMIC_VALUE;
}
throw new Error(`Value ${value} is not literal and cannot be used in this context.`);
}

View File

@ -1,26 +0,0 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob([
"**/*.ts",
]),
deps = [
"//packages:types",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/testing",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
deps = [
":test_lib",
"//tools/testing:node_no_angular",
],
)

View File

@ -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]}.`);
}
}

View File

@ -1,193 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
import {ResolvedValue, staticallyResolve} from '../src/resolver';
function makeSimpleProgram(contents: string): ts.Program {
return makeProgram([{name: 'entry.ts', contents}]).program;
}
function makeExpression(
code: string, expr: string): {expression: ts.Expression, checker: ts.TypeChecker} {
const {program} =
makeProgram([{name: 'entry.ts', contents: `${code}; const target$ = ${expr};`}]);
const checker = program.getTypeChecker();
const decl = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
return {
expression: decl.initializer !,
checker,
};
}
function evaluate<T extends ResolvedValue>(code: string, expr: string): T {
const {expression, checker} = makeExpression(code, expr);
return staticallyResolve(expression, checker) as T;
}
describe('ngtsc metadata', () => {
it('reads a file correctly', () => {
const {program} = makeProgram([
{
name: 'entry.ts',
contents: `
import {Y} from './other';
const A = Y;
export const X = A;
`
},
{
name: 'other.ts',
contents: `
export const Y = 'test';
`
}
]);
const decl = getDeclaration(program, 'entry.ts', 'X', ts.isVariableDeclaration);
const value = staticallyResolve(decl.initializer !, program.getTypeChecker());
expect(value).toEqual('test');
});
it('map access works',
() => { expect(evaluate('const obj = {a: "test"};', 'obj.a')).toEqual('test'); });
it('function calls work', () => {
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
});
it('conditionals work', () => {
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
});
it('addition works', () => { expect(evaluate(`const x = 1 + 2;`, 'x')).toEqual(3); });
it('static property on class works',
() => { expect(evaluate(`class Foo { static bar = 'test'; }`, 'Foo.bar')).toEqual('test'); });
it('static property call works', () => {
expect(evaluate(`class Foo { static bar(test) { return test; } }`, 'Foo.bar("test")'))
.toEqual('test');
});
it('indirected static property call works', () => {
expect(
evaluate(
`class Foo { static bar(test) { return test; } }; const fn = Foo.bar;`, 'fn("test")'))
.toEqual('test');
});
it('array works', () => {
expect(evaluate(`const x = 'test'; const y = [1, x, 2];`, 'y')).toEqual([1, 'test', 2]);
});
it('array spread works', () => {
expect(evaluate(`const a = [1, 2]; const b = [4, 5]; const c = [...a, 3, ...b];`, 'c'))
.toEqual([1, 2, 3, 4, 5]);
});
it('&& operations work', () => {
expect(evaluate(`const a = 'hello', b = 'world';`, 'a && b')).toEqual('world');
expect(evaluate(`const a = false, b = 'world';`, 'a && b')).toEqual(false);
expect(evaluate(`const a = 'hello', b = 0;`, 'a && b')).toEqual(0);
});
it('|| operations work', () => {
expect(evaluate(`const a = 'hello', b = 'world';`, 'a || b')).toEqual('hello');
expect(evaluate(`const a = false, b = 'world';`, 'a || b')).toEqual('world');
expect(evaluate(`const a = 'hello', b = 0;`, 'a || b')).toEqual('hello');
});
it('parentheticals work',
() => { expect(evaluate(`const a = 3, b = 4;`, 'a * (a + b)')).toEqual(21); });
it('array access works',
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[1] + a[0]')).toEqual(3); });
it('negation works', () => {
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
});
it('reads values from default exports', () => {
const {program} = makeProgram([
{name: 'second.ts', contents: 'export default {property: "test"}'},
{
name: 'entry.ts',
contents: `
import mod from './second';
const target$ = mod.property;
`
},
]);
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
debugger;
expect(staticallyResolve(expr, checker)).toEqual('test');
});
it('reads values from named exports', () => {
const {program} = makeProgram([
{name: 'second.ts', contents: 'export const a = {property: "test"};'},
{
name: 'entry.ts',
contents: `
import * as mod from './second';
const target$ = mod.a.property;
`
},
]);
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
expect(staticallyResolve(expr, checker)).toEqual('test');
});
it('chain of re-exports works', () => {
const {program} = makeProgram([
{name: 'const.ts', contents: 'export const value = {property: "test"};'},
{name: 'def.ts', contents: `import {value} from './const'; export default value;`},
{name: 'indirect-reexport.ts', contents: `import value from './def'; export {value};`},
{name: 'direct-reexport.ts', contents: `export {value} from './indirect-reexport';`},
{
name: 'entry.ts',
contents: `import * as mod from './direct-reexport'; const target$ = mod.value.property;`
},
]);
const checker = program.getTypeChecker();
const result = getDeclaration(program, 'entry.ts', 'target$', ts.isVariableDeclaration);
const expr = result.initializer !;
expect(staticallyResolve(expr, checker)).toEqual('test');
});
it('map spread works', () => {
const map: Map<string, number> = evaluate<Map<string, number>>(
`const a = {a: 1}; const b = {b: 2, c: 1}; const c = {...a, ...b, c: 3};`, 'c');
const obj: {[key: string]: number} = {};
map.forEach((value, key) => obj[key] = value);
expect(obj).toEqual({
a: 1,
b: 2,
c: 3,
});
});
it('indirected-via-object function call works', () => {
expect(evaluate(
`
function fn(res) { return res; }
const obj = {fn};
`,
'obj.fn("test")'))
.toEqual('test');
});
});

View File

@ -1,145 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {GeneratedFile} from '@angular/compiler';
import * as path from 'path';
import * as ts from 'typescript';
import * as api from '../transformers/api';
import {CompilerHost} from './compiler_host';
import {InjectableCompilerAdapter, IvyCompilation, ivyTransformFactory} from './transform';
export class NgtscProgram implements api.Program {
private tsProgram: ts.Program;
constructor(
rootNames: ReadonlyArray<string>, private options: api.CompilerOptions,
private host: api.CompilerHost, oldProgram?: api.Program) {
this.tsProgram =
ts.createProgram(rootNames, options, host, oldProgram && oldProgram.getTsProgram());
}
getTsProgram(): ts.Program { return this.tsProgram; }
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
undefined): ReadonlyArray<ts.Diagnostic> {
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
}
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
undefined): ReadonlyArray<api.Diagnostic> {
return [];
}
getTsSyntacticDiagnostics(
sourceFile?: ts.SourceFile|undefined,
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
}
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken|
undefined): ReadonlyArray<api.Diagnostic> {
return [];
}
getTsSemanticDiagnostics(
sourceFile?: ts.SourceFile|undefined,
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
}
getNgSemanticDiagnostics(
fileName?: string|undefined,
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<api.Diagnostic> {
return [];
}
loadNgStructureAsync(): Promise<void> { return Promise.resolve(); }
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
throw new Error('Method not implemented.');
}
getLibrarySummaries(): Map<string, api.LibrarySummary> {
throw new Error('Method not implemented.');
}
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
throw new Error('Method not implemented.');
}
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
throw new Error('Method not implemented.');
}
emit(opts?: {
emitFlags?: api.EmitFlags,
cancellationToken?: ts.CancellationToken,
customTransformers?: api.CustomTransformers,
emitCallback?: api.TsEmitCallback,
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback
}): ts.EmitResult {
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
const mergeEmitResultsCallback = opts && opts.mergeEmitResultsCallback || mergeEmitResults;
const checker = this.tsProgram.getTypeChecker();
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const adapters = [new InjectableCompilerAdapter(checker)];
const compilation = new IvyCompilation(adapters, checker);
// Analyze every source file in the program.
this.tsProgram.getSourceFiles()
.filter(file => !file.fileName.endsWith('.d.ts'))
.forEach(file => compilation.analyze(file));
// Since there is no .d.ts transformation API, .d.ts files are transformed during write.
const writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError: ((message: string) => void) | undefined,
sourceFiles: ReadonlyArray<ts.SourceFile>) => {
if (fileName.endsWith('.d.ts')) {
data = sourceFiles.reduce(
(data, sf) => compilation.transformedDtsFor(sf.fileName, data), data);
}
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
};
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
const emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
emitOnlyDtsFiles: false, writeFile,
customTransformers: {
before: [ivyTransformFactory(compilation)],
},
});
return emitResult;
}
}
const defaultEmitCallback: api.TsEmitCallback =
({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
customTransformers}) =>
program.emit(
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
const diagnostics: ts.Diagnostic[] = [];
let emitSkipped = false;
const emittedFiles: string[] = [];
for (const er of emitResults) {
diagnostics.push(...er.diagnostics);
emitSkipped = emitSkipped || er.emitSkipped;
emittedFiles.push(...(er.emittedFiles || []));
}
return {diagnostics, emitSkipped, emittedFiles};
}

View File

@ -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",
],
)

View File

@ -1,130 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
export function makeProgram(files: {name: string, contents: string}[]):
{program: ts.Program, host: ts.CompilerHost} {
const host = new InMemoryHost();
files.forEach(file => host.writeFile(file.name, file.contents));
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
const program = ts.createProgram(rootNames, {noLib: true, experimentalDecorators: true}, host);
const diags = [...program.getSyntacticDiagnostics(), ...program.getSemanticDiagnostics()];
if (diags.length > 0) {
throw new Error(
`Typescript diagnostics failed! ${diags.map(diag => diag.messageText).join(', ')}`);
}
return {program, host};
}
export class InMemoryHost implements ts.CompilerHost {
private fileSystem = new Map<string, string>();
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined,
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
const contents = this.fileSystem.get(this.getCanonicalFileName(fileName));
if (contents === undefined) {
onError && onError(`File does not exist: ${this.getCanonicalFileName(fileName)})`);
return undefined;
}
return ts.createSourceFile(fileName, contents, languageVersion, undefined, ts.ScriptKind.TS);
}
getDefaultLibFileName(options: ts.CompilerOptions): string { return '/lib.d.ts'; }
writeFile(
fileName: string, data: string, writeByteOrderMark?: boolean,
onError?: ((message: string) => void)|undefined,
sourceFiles?: ReadonlyArray<ts.SourceFile>): void {
this.fileSystem.set(this.getCanonicalFileName(fileName), data);
}
getCurrentDirectory(): string { return '/'; }
getDirectories(dir: string): string[] {
const fullDir = this.getCanonicalFileName(dir) + '/';
const dirSet = new Set(Array
// Look at all paths known to the host.
.from(this.fileSystem.keys())
// Filter out those that aren't under the requested directory.
.filter(candidate => candidate.startsWith(fullDir))
// Relativize the rest by the requested directory.
.map(candidate => candidate.substr(fullDir.length))
// What's left are dir/.../file.txt entries, and file.txt entries.
// Get the dirname, which
// yields '.' for the latter and dir/... for the former.
.map(candidate => path.dirname(candidate))
// Filter out the '.' entries, which were files.
.filter(candidate => candidate !== '.')
// Finally, split on / and grab the first entry.
.map(candidate => candidate.split('/', 1)[0]));
// Get the resulting values out of the Set.
return Array.from(dirSet);
}
getCanonicalFileName(fileName: string): string {
return path.posix.normalize(`${this.getCurrentDirectory()}/${fileName}`);
}
useCaseSensitiveFileNames(): boolean { return true; }
getNewLine(): string { return '\n'; }
fileExists(fileName: string): boolean { return this.fileSystem.has(fileName); }
readFile(fileName: string): string|undefined { return this.fileSystem.get(fileName); }
}
function bindingNameEquals(node: ts.BindingName, name: string): boolean {
if (ts.isIdentifier(node)) {
return node.text === name;
}
return false;
}
export function getDeclaration<T extends ts.Declaration>(
program: ts.Program, fileName: string, name: string, assert: (value: any) => value is T): T {
const sf = program.getSourceFile(fileName);
if (!sf) {
throw new Error(`No such file: ${fileName}`);
}
let chosenDecl: ts.Declaration|null = null;
sf.statements.forEach(stmt => {
if (chosenDecl !== null) {
return;
} else if (ts.isVariableStatement(stmt)) {
stmt.declarationList.declarations.forEach(decl => {
if (bindingNameEquals(decl.name, name)) {
chosenDecl = decl;
}
});
} else if (ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) {
if (stmt.name !== undefined && stmt.name.text === name) {
chosenDecl = stmt;
}
}
});
chosenDecl = chosenDecl as ts.Declaration | null;
if (chosenDecl === null) {
throw new Error(`No such symbol: ${name} in ${fileName}`);
}
if (!assert(chosenDecl)) {
throw new Error(`Symbol ${name} from ${fileName} is a ${ts.SyntaxKind[chosenDecl.kind]}`);
}
return chosenDecl;
}

View File

@ -1,16 +0,0 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "transform",
srcs = glob([
"index.ts",
"src/**/*.ts",
]),
module_name = "@angular/compiler-cli/src/ngtsc/transform",
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/metadata",
],
)

View File

@ -1,11 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export {IvyCompilation} from './src/compilation';
export {InjectableCompilerAdapter} from './src/injectable';
export {ivyTransformFactory} from './src/transform';

View File

@ -1,61 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, Type} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator} from '../../metadata';
/**
* Provides the interface between a decorator compiler from @angular/compiler and the Typescript
* compiler/transform.
*
* The decorator compilers in @angular/compiler do not depend on Typescript. The adapter is
* responsible for extracting the information required to perform compilation from the decorators
* and Typescript source, invoking the decorator compiler, and returning the result.
*/
export interface CompilerAdapter<A> {
/**
* Scan a set of reflected decorators and determine if this adapter is responsible for compilation
* of one of them.
*/
detect(decorator: Decorator[]): Decorator|undefined;
/**
* Perform analysis on the decorator/class combination, producing instructions for compilation
* if successful, or an array of diagnostic messages if the analysis fails or the decorator
* isn't valid.
*/
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<A>;
/**
* Generate a description of the field which should be added to the class, including any
* initialization code to be generated.
*/
compile(node: ts.ClassDeclaration, analysis: A): AddStaticFieldInstruction;
}
/**
* The output of an analysis operation, consisting of possibly an arbitrary analysis object (used as
* the input to code generation) and potentially diagnostics if there were errors uncovered during
* analysis.
*/
export interface AnalysisOutput<A> {
analysis?: A;
diagnostics?: ts.Diagnostic[];
}
/**
* A description of the static field to add to a class, including an initialization expression
* and a type for the .d.ts file.
*/
export interface AddStaticFieldInstruction {
field: string;
initializer: Expression;
type: Type;
}

View File

@ -1,157 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, Type} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator, reflectDecorator} from '../../metadata';
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
import {DtsFileTransformer} from './declaration';
import {ImportManager, translateType} from './translator';
/**
* Record of an adapter which decided to emit a static field, and the analysis it performed to
* prepare for that operation.
*/
interface EmitFieldOperation<T> {
adapter: CompilerAdapter<T>;
analysis: AnalysisOutput<T>;
decorator: ts.Decorator;
}
/**
* Manages a compilation of Ivy decorators into static fields across an entire ts.Program.
*
* The compilation is stateful - source files are analyzed and records of the operations that need
* to be performed during the transform/emit process are maintained internally.
*/
export class IvyCompilation {
/**
* Tracks classes which have been analyzed and found to have an Ivy decorator, and the
* information recorded about them for later compilation.
*/
private analysis = new Map<ts.ClassDeclaration, EmitFieldOperation<any>>();
/**
* Tracks the `DtsFileTransformer`s for each TS file that needs .d.ts transformations.
*/
private dtsMap = new Map<string, DtsFileTransformer>();
constructor(private adapters: CompilerAdapter<any>[], private checker: ts.TypeChecker) {}
/**
* Analyze a source file and produce diagnostics for it (if any).
*/
analyze(sf: ts.SourceFile): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = [];
const visit = (node: ts.Node) => {
// Process nodes recursively, and look for class declarations with decorators.
if (ts.isClassDeclaration(node) && node.decorators !== undefined) {
// The first step is to reflect the decorators, which will identify decorators
// that are imported from another module.
const decorators =
node.decorators.map(decorator => reflectDecorator(decorator, this.checker))
.filter(decorator => decorator !== null) as Decorator[];
// Look through the CompilerAdapters to see if any are relevant.
this.adapters.forEach(adapter => {
// An adapter is relevant if it matches one of the decorators on the class.
const decorator = adapter.detect(decorators);
if (decorator === undefined) {
return;
}
// Check for multiple decorators on the same node. Technically speaking this
// could be supported, but right now it's an error.
if (this.analysis.has(node)) {
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
}
// Run analysis on the decorator. This will produce either diagnostics, an
// analysis result, or both.
const analysis = adapter.analyze(node, decorator);
if (analysis.diagnostics !== undefined) {
diagnostics.push(...analysis.diagnostics);
}
if (analysis.analysis !== undefined) {
this.analysis.set(node, {
adapter,
analysis: analysis.analysis,
decorator: decorator.node,
});
}
});
}
ts.forEachChild(node, visit);
};
visit(sf);
return diagnostics;
}
/**
* Perform a compilation operation on the given class declaration and return instructions to an
* AST transformer if any are available.
*/
compileIvyFieldFor(node: ts.ClassDeclaration): AddStaticFieldInstruction|undefined {
// Look to see whether the original node was analyzed. If not, there's nothing to do.
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
if (!this.analysis.has(original)) {
return undefined;
}
const op = this.analysis.get(original) !;
// Run the actual compilation, which generates an Expression for the Ivy field.
const res = op.adapter.compile(node, op.analysis);
// Look up the .d.ts transformer for the input file and record that a field was generated,
// which will allow the .d.ts to be transformed later.
const fileName = node.getSourceFile().fileName;
const dtsTransformer = this.getDtsTransformer(fileName);
dtsTransformer.recordStaticField(node.name !.text, res);
// Return the instruction to the transformer so the field will be added.
return res;
}
/**
* Lookup the `ts.Decorator` which triggered transformation of a particular class declaration.
*/
ivyDecoratorFor(node: ts.ClassDeclaration): ts.Decorator|undefined {
const original = ts.getOriginalNode(node) as ts.ClassDeclaration;
if (!this.analysis.has(original)) {
return undefined;
}
return this.analysis.get(original) !.decorator;
}
/**
* Process a .d.ts source string and return a transformed version that incorporates the changes
* made to the source file.
*/
transformedDtsFor(tsFileName: string, dtsOriginalSource: string): string {
// No need to transform if no changes have been requested to the input file.
if (!this.dtsMap.has(tsFileName)) {
return dtsOriginalSource;
}
// Return the transformed .d.ts source.
return this.dtsMap.get(tsFileName) !.transform(dtsOriginalSource);
}
private getDtsTransformer(tsFileName: string): DtsFileTransformer {
if (!this.dtsMap.has(tsFileName)) {
this.dtsMap.set(tsFileName, new DtsFileTransformer());
}
return this.dtsMap.get(tsFileName) !;
}
}

View File

@ -1,56 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {AddStaticFieldInstruction} from './api';
import {ImportManager, translateType} from './translator';
/**
* Processes .d.ts file text and adds static field declarations, with types.
*/
export class DtsFileTransformer {
private ivyFields = new Map<string, AddStaticFieldInstruction>();
private imports = new ImportManager();
/**
* Track that a static field was added to the code for a class.
*/
recordStaticField(name: string, decl: AddStaticFieldInstruction): void {
this.ivyFields.set(name, decl);
}
/**
* Process the .d.ts text for a file and add any declarations which were recorded.
*/
transform(dts: string): string {
const dtsFile =
ts.createSourceFile('out.d.ts', dts, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
for (let i = dtsFile.statements.length - 1; i >= 0; i--) {
const stmt = dtsFile.statements[i];
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined &&
this.ivyFields.has(stmt.name.text)) {
const desc = this.ivyFields.get(stmt.name.text) !;
const before = dts.substring(0, stmt.end - 1);
const after = dts.substring(stmt.end - 1);
const type = translateType(desc.type, this.imports);
dts = before + ` static ${desc.field}: ${type};\n` + after;
}
}
const imports = this.imports.getAllImports();
if (imports.length !== 0) {
dts = imports.map(i => `import * as ${i.as} from '${i.name}';\n`).join() + dts;
}
return dts;
}
}

View File

@ -1,185 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, LiteralExpr, R3DependencyMetadata, R3InjectableMetadata, R3ResolvedDependencyType, WrappedNodeExpr, compileInjectable as compileIvyInjectable} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator} from '../../metadata';
import {reflectConstructorParameters, reflectImportedIdentifier, reflectObjectLiteral} from '../../metadata/src/reflector';
import {AddStaticFieldInstruction, AnalysisOutput, CompilerAdapter} from './api';
/**
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
*/
export class InjectableCompilerAdapter implements CompilerAdapter<R3InjectableMetadata> {
constructor(private checker: ts.TypeChecker) {}
detect(decorator: Decorator[]): Decorator|undefined {
return decorator.find(dec => dec.name === 'Injectable' && dec.from === '@angular/core');
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
return {
analysis: extractInjectableMetadata(node, decorator, this.checker),
};
}
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): AddStaticFieldInstruction {
const res = compileIvyInjectable(analysis);
return {
field: 'ngInjectableDef',
initializer: res.expression,
type: res.type,
};
}
}
/**
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the input
* metadata needed to run `compileIvyInjectable`.
*/
function extractInjectableMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator,
checker: ts.TypeChecker): R3InjectableMetadata {
if (clazz.name === undefined) {
throw new Error(`@Injectables must have names`);
}
const name = clazz.name.text;
const type = new WrappedNodeExpr(clazz.name);
if (decorator.args.length === 0) {
return {
name,
type,
providedIn: new LiteralExpr(null),
deps: getConstructorDependencies(clazz, checker),
};
} else if (decorator.args.length === 1) {
const metaNode = decorator.args[0];
// Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
// transport references from one location to another. This is the problem that lowering
// used to solve - if this restriction proves too undesirable we can re-implement lowering.
if (!ts.isObjectLiteralExpression(metaNode)) {
throw new Error(`In Ivy, decorator metadata must be inline.`);
}
// Resolve the fields of the literal into a map of field name to expression.
const meta = reflectObjectLiteral(metaNode);
let providedIn: Expression = new LiteralExpr(null);
if (meta.has('providedIn')) {
providedIn = new WrappedNodeExpr(meta.get('providedIn') !);
}
if (meta.has('useValue')) {
return {name, type, providedIn, useValue: new WrappedNodeExpr(meta.get('useValue') !)};
} else if (meta.has('useExisting')) {
return {name, type, providedIn, useExisting: new WrappedNodeExpr(meta.get('useExisting') !)};
} else if (meta.has('useClass')) {
return {name, type, providedIn, useClass: new WrappedNodeExpr(meta.get('useClass') !)};
} else if (meta.has('useFactory')) {
// useFactory is special - the 'deps' property must be analyzed.
const factory = new WrappedNodeExpr(meta.get('useFactory') !);
const deps: R3DependencyMetadata[] = [];
if (meta.has('deps')) {
const depsExpr = meta.get('deps') !;
if (!ts.isArrayLiteralExpression(depsExpr)) {
throw new Error(`In Ivy, deps metadata must be inline.`);
}
if (depsExpr.elements.length > 0) {
throw new Error(`deps not yet supported`);
}
deps.push(...depsExpr.elements.map(dep => getDep(dep, checker)));
}
return {name, type, providedIn, useFactory: factory, deps};
} else {
const deps = getConstructorDependencies(clazz, checker);
return {name, type, providedIn, deps};
}
} else {
throw new Error(`Too many arguments to @Injectable`);
}
}
function getConstructorDependencies(
clazz: ts.ClassDeclaration, checker: ts.TypeChecker): R3DependencyMetadata[] {
const useType: R3DependencyMetadata[] = [];
const ctorParams = (reflectConstructorParameters(clazz, checker) || []);
ctorParams.forEach(param => {
let tokenExpr = param.typeValueExpr;
let optional = false, self = false, skipSelf = false;
param.decorators.filter(dec => dec.from === '@angular/core').forEach(dec => {
if (dec.name === 'Inject') {
if (dec.args.length !== 1) {
throw new Error(`Unexpected number of arguments to @Inject().`);
}
tokenExpr = dec.args[0];
} else if (dec.name === 'Optional') {
optional = true;
} else if (dec.name === 'SkipSelf') {
skipSelf = true;
} else if (dec.name === 'Self') {
self = true;
} else {
throw new Error(`Unexpected decorator ${dec.name} on parameter.`);
}
if (tokenExpr === null) {
throw new Error(`No suitable token for parameter!`);
}
});
const token = new WrappedNodeExpr(tokenExpr);
useType.push(
{token, optional, self, skipSelf, host: false, resolved: R3ResolvedDependencyType.Token});
});
return useType;
}
function getDep(dep: ts.Expression, checker: ts.TypeChecker): R3DependencyMetadata {
const meta: R3DependencyMetadata = {
token: new WrappedNodeExpr(dep),
host: false,
resolved: R3ResolvedDependencyType.Token,
optional: false,
self: false,
skipSelf: false,
};
function maybeUpdateDecorator(dec: ts.Identifier, token?: ts.Expression): void {
const source = reflectImportedIdentifier(dec, checker);
if (source === null || source.from !== '@angular/core') {
return;
}
switch (source.name) {
case 'Inject':
if (token !== undefined) {
meta.token = new WrappedNodeExpr(token);
}
break;
case 'Optional':
meta.optional = true;
break;
case 'SkipSelf':
meta.skipSelf = true;
break;
case 'Self':
meta.self = true;
break;
}
}
if (ts.isArrayLiteralExpression(dep)) {
dep.elements.forEach(el => {
if (ts.isIdentifier(el)) {
maybeUpdateDecorator(el);
} else if (ts.isNewExpression(el) && ts.isIdentifier(el.expression)) {
const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined;
maybeUpdateDecorator(el.expression, token);
}
});
}
return meta;
}

View File

@ -1,96 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {IvyCompilation} from './compilation';
import {ImportManager, translateExpression} from './translator';
export function ivyTransformFactory(compilation: IvyCompilation):
ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
return (file: ts.SourceFile): ts.SourceFile => {
return transformIvySourceFile(compilation, context, file);
};
};
}
/**
* A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
*/
function transformIvySourceFile(
compilation: IvyCompilation, context: ts.TransformationContext,
file: ts.SourceFile): ts.SourceFile {
const importManager = new ImportManager();
// Recursively scan through the AST and perform any updates requested by the IvyCompilation.
const sf = visitNode(file);
// Generate the import statements to prepend.
const imports = importManager.getAllImports().map(
i => ts.createImportDeclaration(
undefined, undefined,
ts.createImportClause(undefined, ts.createNamespaceImport(ts.createIdentifier(i.as))),
ts.createLiteral(i.name)));
// Prepend imports if needed.
if (imports.length > 0) {
sf.statements = ts.createNodeArray([...imports, ...sf.statements]);
}
return sf;
// Helper function to process a class declaration.
function visitClassDeclaration(node: ts.ClassDeclaration): ts.ClassDeclaration {
// Determine if this class has an Ivy field that needs to be added, and compile the field
// to an expression if so.
const res = compilation.compileIvyFieldFor(node);
if (res !== undefined) {
// There is a field to add. Translate the initializer for the field into TS nodes.
const exprNode = translateExpression(res.initializer, importManager);
// Create a static property declaration for the new field.
const property = ts.createProperty(
undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined,
exprNode);
// Replace the class declaration with an updated version.
node = ts.updateClassDeclaration(
node,
// Remove the decorator which triggered this compilation, leaving the others alone.
maybeFilterDecorator(node.decorators, compilation.ivyDecoratorFor(node) !),
node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
[...node.members, property]);
}
// Recurse into the class declaration in case there are nested class declarations.
return ts.visitEachChild(node, child => visitNode(child), context);
}
// Helper function that recurses through the nodes and processes each one.
function visitNode<T extends ts.Node>(node: T): T;
function visitNode(node: ts.Node): ts.Node {
if (ts.isClassDeclaration(node)) {
return visitClassDeclaration(node);
} else {
return ts.visitEachChild(node, child => visitNode(child), context);
}
}
}
function maybeFilterDecorator(
decorators: ts.NodeArray<ts.Decorator>| undefined,
toRemove: ts.Decorator): ts.NodeArray<ts.Decorator>|undefined {
if (decorators === undefined) {
return undefined;
}
const filtered = decorators.filter(dec => ts.getOriginalNode(dec) !== toRemove);
if (filtered.length === 0) {
return undefined;
}
return ts.createNodeArray(filtered);
}

View File

@ -1,319 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ArrayType, AssertNotNull, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import * as ts from 'typescript';
export class ImportManager {
private moduleToIndex = new Map<string, string>();
private nextIndex = 0;
generateNamedImport(moduleName: string): string {
if (!this.moduleToIndex.has(moduleName)) {
this.moduleToIndex.set(moduleName, `i${this.nextIndex++}`);
}
return this.moduleToIndex.get(moduleName) !;
}
getAllImports(): {name: string, as: string}[] {
return Array.from(this.moduleToIndex.keys()).map(name => {
const as = this.moduleToIndex.get(name) !;
return {name, as};
});
}
}
export function translateExpression(expression: Expression, imports: ImportManager): ts.Expression {
return expression.visitExpression(new ExpressionTranslatorVisitor(imports), null);
}
export function translateType(type: Type, imports: ImportManager): string {
return type.visitType(new TypeTranslatorVisitor(imports), null);
}
class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor {
constructor(private imports: ImportManager) {}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any) {
throw new Error('Method not implemented.');
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any) {
throw new Error('Method not implemented.');
}
visitExpressionStmt(stmt: ExpressionStatement, context: any) {
throw new Error('Method not implemented.');
}
visitReturnStmt(stmt: ReturnStatement, context: any): ts.ReturnStatement {
return ts.createReturn(stmt.value.visitExpression(this, context));
}
visitDeclareClassStmt(stmt: ClassStmt, context: any) {
throw new Error('Method not implemented.');
}
visitIfStmt(stmt: IfStmt, context: any) { throw new Error('Method not implemented.'); }
visitTryCatchStmt(stmt: TryCatchStmt, context: any) {
throw new Error('Method not implemented.');
}
visitThrowStmt(stmt: ThrowStmt, context: any) { throw new Error('Method not implemented.'); }
visitCommentStmt(stmt: CommentStmt, context: any): never {
throw new Error('Method not implemented.');
}
visitJSDocCommentStmt(stmt: JSDocCommentStmt, context: any): never {
throw new Error('Method not implemented.');
}
visitReadVarExpr(ast: ReadVarExpr, context: any): ts.Identifier {
return ts.createIdentifier(ast.name !);
}
visitWriteVarExpr(expr: WriteVarExpr, context: any): ts.BinaryExpression {
return ts.createBinary(
ts.createIdentifier(expr.name), ts.SyntaxKind.EqualsToken,
expr.value.visitExpression(this, context));
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitWritePropExpr(expr: WritePropExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): ts.CallExpression {
return ts.createCall(
ast.fn.visitExpression(this, context), undefined,
ast.args.map(arg => arg.visitExpression(this, context)));
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): ts.NewExpression {
return ts.createNew(
ast.classExpr.visitExpression(this, context), undefined,
ast.args.map(arg => arg.visitExpression(this, context)));
}
visitLiteralExpr(ast: LiteralExpr, context: any): ts.Expression {
if (ast.value === undefined) {
return ts.createIdentifier('undefined');
} else if (ast.value === null) {
return ts.createNull();
} else {
return ts.createLiteral(ast.value);
}
}
visitExternalExpr(ast: ExternalExpr, context: any): ts.PropertyAccessExpression {
if (ast.value.moduleName === null || ast.value.name === null) {
throw new Error(`Import unknown module or symbol ${ast.value}`);
}
return ts.createPropertyAccess(
ts.createIdentifier(this.imports.generateNamedImport(ast.value.moduleName)),
ts.createIdentifier(ast.value.name));
}
visitConditionalExpr(ast: ConditionalExpr, context: any): ts.ParenthesizedExpression {
return ts.createParen(ts.createConditional(
ast.condition.visitExpression(this, context), ast.trueCase.visitExpression(this, context),
ast.falseCase !.visitExpression(this, context)));
}
visitNotExpr(ast: NotExpr, context: any): ts.PrefixUnaryExpression {
return ts.createPrefix(
ts.SyntaxKind.ExclamationToken, ast.condition.visitExpression(this, context));
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): ts.NonNullExpression {
return ts.createNonNullExpression(ast.condition.visitExpression(this, context));
}
visitCastExpr(ast: CastExpr, context: any): ts.Expression {
return ast.value.visitExpression(this, context);
}
visitFunctionExpr(ast: FunctionExpr, context: any): ts.FunctionExpression {
return ts.createFunctionExpression(
undefined, undefined, ast.name || undefined, undefined,
ast.params.map(
param => ts.createParameter(
undefined, undefined, undefined, param.name, undefined, undefined, undefined)),
undefined, ts.createBlock(ast.statements.map(stmt => stmt.visitStatement(this, context))));
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitReadPropExpr(ast: ReadPropExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): ts.ObjectLiteralExpression {
const entries = ast.entries.map(
entry => ts.createPropertyAssignment(
entry.quoted ? ts.createLiteral(entry.key) : ts.createIdentifier(entry.key),
entry.value.visitExpression(this, context)));
return ts.createObjectLiteral(entries);
}
visitCommaExpr(ast: CommaExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any): any { return ast.node; }
}
export class TypeTranslatorVisitor implements ExpressionVisitor, TypeVisitor {
constructor(private imports: ImportManager) {}
visitBuiltinType(type: BuiltinType, context: any): string {
switch (type.name) {
case BuiltinTypeName.Bool:
return 'boolean';
case BuiltinTypeName.Dynamic:
return 'any';
case BuiltinTypeName.Int:
case BuiltinTypeName.Number:
return 'number';
case BuiltinTypeName.String:
return 'string';
default:
throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
}
}
visitExpressionType(type: ExpressionType, context: any): any {
return type.value.visitExpression(this, context);
}
visitArrayType(type: ArrayType, context: any): string {
return `Array<${type.visitType(this, context)}>`;
}
visitMapType(type: MapType, context: any): string {
if (type.valueType !== null) {
return `{[key: string]: ${type.valueType.visitType(this, context)}}`;
} else {
return '{[key: string]: any}';
}
}
visitReadVarExpr(ast: ReadVarExpr, context: any): string {
if (ast.name === null) {
throw new Error(`ReadVarExpr with no variable name in type`);
}
return ast.name;
}
visitWriteVarExpr(expr: WriteVarExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitWritePropExpr(expr: WritePropExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): never {
throw new Error('Method not implemented.');
}
visitLiteralExpr(ast: LiteralExpr, context: any): string {
if (typeof ast.value === 'string') {
const escaped = ast.value.replace(/\'/g, '\\\'');
return `'${escaped}'`;
} else {
return `${ast.value}`;
}
}
visitExternalExpr(ast: ExternalExpr, context: any): string {
if (ast.value.moduleName === null || ast.value.name === null) {
throw new Error(`Import unknown module or symbol`);
}
const base = `${this.imports.generateNamedImport(ast.value.moduleName)}.${ast.value.name}`;
if (ast.typeParams !== null) {
const generics = ast.typeParams.map(type => type.visitType(this, context)).join(', ');
return `${base}<${generics}>`;
} else {
return base;
}
}
visitConditionalExpr(ast: ConditionalExpr, context: any) {
throw new Error('Method not implemented.');
}
visitNotExpr(ast: NotExpr, context: any) { throw new Error('Method not implemented.'); }
visitAssertNotNullExpr(ast: AssertNotNull, context: any) {
throw new Error('Method not implemented.');
}
visitCastExpr(ast: CastExpr, context: any) { throw new Error('Method not implemented.'); }
visitFunctionExpr(ast: FunctionExpr, context: any) { throw new Error('Method not implemented.'); }
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any) {
throw new Error('Method not implemented.');
}
visitReadPropExpr(ast: ReadPropExpr, context: any) { throw new Error('Method not implemented.'); }
visitReadKeyExpr(ast: ReadKeyExpr, context: any) { throw new Error('Method not implemented.'); }
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any) {
throw new Error('Method not implemented.');
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any) {
throw new Error('Method not implemented.');
}
visitCommaExpr(ast: CommaExpr, context: any) { throw new Error('Method not implemented.'); }
visitWrappedNodeExpr(ast: WrappedNodeExpr<any>, context: any) {
const node: ts.Node = ast.node;
if (ts.isIdentifier(node)) {
return node.text;
} else {
throw new Error(
`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts.SyntaxKind[node.kind]}`);
}
}
}

View File

@ -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",
)

View File

@ -1,118 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/**
* Result type of visiting a node that's typically an entry in a list, which allows specifying that
* nodes should be added before the visited node in the output.
*/
export type VisitListEntryResult<B extends ts.Node, T extends B> = {
node: T,
before?: B[]
};
/**
* Visit a node with the given visitor and return a transformed copy.
*/
export function visit<T extends ts.Node>(
node: T, visitor: Visitor, context: ts.TransformationContext): T {
return visitor._visit(node, context);
}
/**
* Abstract base class for visitors, which processes certain nodes specially to allow insertion
* of other nodes before them.
*/
export abstract class Visitor {
/**
* Maps statements to an array of statements that should be inserted before them.
*/
private _before = new Map<ts.Statement, ts.Statement[]>();
/**
* Visit a class declaration, returning at least the transformed declaration and optionally other
* nodes to insert before the declaration.
*/
visitClassDeclaration(node: ts.ClassDeclaration):
VisitListEntryResult<ts.Statement, ts.ClassDeclaration> {
return {node};
}
private _visitClassDeclaration(node: ts.ClassDeclaration, context: ts.TransformationContext):
ts.ClassDeclaration {
const result = this.visitClassDeclaration(node);
const visited = ts.visitEachChild(result.node, child => this._visit(child, context), context);
if (result.before !== undefined) {
// Record that some nodes should be inserted before the given declaration. The declaration's
// parent's _visit call is responsible for performing this insertion.
this._before.set(visited, result.before);
}
return visited;
}
/**
* Visit types of nodes which don't have their own explicit visitor.
*/
visitOtherNode<T extends ts.Node>(node: T): T { return node; }
private _visitOtherNode<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
return ts.visitEachChild(
this.visitOtherNode(node), child => this._visit(child, context), context);
}
/**
* @internal
*/
_visit<T extends ts.Node>(node: T, context: ts.TransformationContext): T {
// First, visit the node. visitedNode starts off as `null` but should be set after visiting
// is completed.
let visitedNode: T|null = null;
if (ts.isClassDeclaration(node)) {
visitedNode = this._visitClassDeclaration(node, context) as typeof node;
} else {
visitedNode = this._visitOtherNode(node, context);
}
// If the visited node has a `statements` array then process them, maybe replacing the visited
// node and adding additional statements.
if (hasStatements(visitedNode)) {
visitedNode = this._maybeProcessStatements(visitedNode);
}
return visitedNode;
}
private _maybeProcessStatements<T extends ts.Node&{statements: ts.NodeArray<ts.Statement>}>(
node: T): T {
// Shortcut - if every statement doesn't require nodes to be prepended, this is a no-op.
if (node.statements.every(stmt => !this._before.has(stmt))) {
return node;
}
// There are statements to prepend, so clone the original node.
const clone = ts.getMutableClone(node);
// Build a new list of statements and patch it onto the clone.
const newStatements: ts.Statement[] = [];
clone.statements.forEach(stmt => {
if (this._before.has(stmt)) {
newStatements.push(...(this._before.get(stmt) !as ts.Statement[]));
this._before.delete(stmt);
}
newStatements.push(stmt);
});
clone.statements = ts.createNodeArray(newStatements, node.statements.hasTrailingComma);
return clone;
}
}
function hasStatements(node: ts.Node): node is ts.Node&{statements: ts.NodeArray<ts.Statement>} {
const block = node as{statements?: any};
return block.statements !== undefined && Array.isArray(block.statements);
}

View File

@ -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",
],
)

View File

@ -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 = /);
});
});

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -15,7 +15,6 @@ import * as ts from 'typescript';
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics';
import {compareVersions} from '../diagnostics/typescript_version';
import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {NgtscProgram} from '../ngtsc/program';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback, TsMergeEmitResultsCallback} from './api';
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
@ -26,7 +25,6 @@ import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
import {StripDecoratorsMetadataTransformer, getDecoratorStripTransformerFactory} from './r3_strip_decorators';
import {getAngularClassTransformerFactory} from './r3_transform';
import {TscPassThroughProgram} from './tsc_pass_through';
import {DTS, GENERATED_FILES, StructureIsReused, TS, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
@ -109,7 +107,7 @@ const MIN_TS_VERSION = '2.7.2';
* ∀ supported typescript version v, v < MAX_TS_VERSION
* MAX_TS_VERSION is not considered as a supported TypeScript version
*/
const MAX_TS_VERSION = '2.9.0';
const MAX_TS_VERSION = '2.8.0';
class AngularCompilerProgram implements Program {
private rootNames: string[];
@ -288,9 +286,6 @@ class AngularCompilerProgram implements Program {
emitCallback?: TsEmitCallback,
mergeEmitResultsCallback?: TsMergeEmitResultsCallback,
} = {}): ts.EmitResult {
if (this.options.enableIvy === 'ngtsc' || this.options.enableIvy === 'tsc') {
throw new Error('Cannot run legacy compiler in ngtsc mode');
}
return this.options.enableIvy === true ? this._emitRender3(parameters) :
this._emitRender2(parameters);
}
@ -338,34 +333,14 @@ class AngularCompilerProgram implements Program {
/* genFiles */ undefined, /* partialModules */ modules,
/* stripDecorators */ this.reifiedDecorators, customTransformers);
// Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file.
const augmentedReferences = new Map<ts.SourceFile, ReadonlyArray<ts.FileReference>>();
for (const sourceFile of this.tsProgram.getSourceFiles()) {
const originalReferences = getOriginalReferences(sourceFile);
if (originalReferences) {
augmentedReferences.set(sourceFile, sourceFile.referencedFiles);
sourceFile.referencedFiles = originalReferences;
}
}
try {
return emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTransformers
});
} finally {
// Restore the references back to the augmented value to ensure that the
// checks that TypeScript makes for project structure reuse will succeed.
for (const [sourceFile, references] of Array.from(augmentedReferences)) {
// TODO(chuckj): Remove any cast after updating build to 2.6
(sourceFile as any).referencedFiles = references;
}
}
const emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTransformers
});
return emitResult;
}
private _emitRender2(
@ -928,11 +903,6 @@ export function createProgram({rootNames, options, host, oldProgram}: {
options: CompilerOptions,
host: CompilerHost, oldProgram?: Program
}): Program {
if (options.enableIvy === 'ngtsc') {
return new NgtscProgram(rootNames, options, host, oldProgram);
} else if (options.enableIvy === 'tsc') {
return new TscPassThroughProgram(rootNames, options, host, oldProgram);
}
return new AngularCompilerProgram(rootNames, options, host, oldProgram);
}

View File

@ -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);

View File

@ -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', () => {

View File

@ -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",
],
)

View File

@ -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",
],
)

View File

@ -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.

View File

@ -1,25 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
type FnWithArg<T> = (arg?: any) => T;
function callableClassDecorator(): FnWithArg<(clazz: any) => any> {
return null !;
}
function callableParamDecorator(): FnWithArg<(a: any, b: any, c: any) => void> {
return null !;
}
export const Injectable = callableClassDecorator();
export const NgModule = callableClassDecorator();
export const Inject = callableParamDecorator();
export const Self = callableParamDecorator();
export const SkipSelf = callableParamDecorator();
export const Optional = callableParamDecorator();

View File

@ -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"
}

View File

@ -1,125 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {main, readCommandLineAndConfiguration, watchMode} from '../../src/main';
import {TestSupport, isInBazel, makeTempDir, setup} from '../test_support';
function setupFakeCore(support: TestSupport): void {
const fakeCore = path.join(
process.env.TEST_SRCDIR, 'angular/packages/compiler-cli/test/ngtsc/fake_core/npm_package');
const nodeModulesPath = path.join(support.basePath, 'node_modules');
const angularCoreDirectory = path.join(nodeModulesPath, '@angular/core');
fs.symlinkSync(fakeCore, angularCoreDirectory);
}
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
return moduleFilename.substr(0, distIndex);
}
describe('ngtsc behavioral tests', () => {
if (!isInBazel()) {
// These tests should be excluded from the non-Bazel build.
return;
}
let basePath: string;
let outDir: string;
let write: (fileName: string, content: string) => void;
let errorSpy: jasmine.Spy&((s: string) => void);
function shouldExist(fileName: string) {
if (!fs.existsSync(path.resolve(outDir, fileName))) {
throw new Error(`Expected ${fileName} to be emitted (outDir: ${outDir})`);
}
}
function shouldNotExist(fileName: string) {
if (fs.existsSync(path.resolve(outDir, fileName))) {
throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`);
}
}
function getContents(fileName: string): string {
shouldExist(fileName);
const modulePath = path.resolve(outDir, fileName);
return fs.readFileSync(modulePath, 'utf8');
}
function writeConfig(
tsconfig: string =
'{"extends": "./tsconfig-base.json", "angularCompilerOptions": {"enableIvy": "ngtsc"}}') {
write('tsconfig.json', tsconfig);
}
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
const support = setup();
basePath = support.basePath;
outDir = path.join(basePath, 'built');
process.chdir(basePath);
write = (fileName: string, content: string) => { support.write(fileName, content); };
setupFakeCore(support);
write('tsconfig-base.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"skipLibCheck": true,
"noImplicitAny": true,
"types": [],
"outDir": "built",
"rootDir": ".",
"baseUrl": ".",
"declaration": true,
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"lib": ["es6", "dom"],
"typeRoots": ["node_modules/@types"]
},
"angularCompilerOptions": {
"enableIvy": "ngtsc"
}
}`);
});
it('should compile without errors', () => {
writeConfig();
write('test.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export class Dep {}
@Injectable()
export class Service {
constructor(dep: Dep) {}
}
`);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
const jsContents = getContents('test.js');
expect(jsContents).toContain('Dep.ngInjectableDef =');
expect(jsContents).toContain('Service.ngInjectableDef =');
expect(jsContents).not.toContain('__decorate');
const dtsContents = getContents('test.d.ts');
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Dep>;');
expect(dtsContents).toContain('static ngInjectableDef: i0.InjectableDef<Service>;');
});
});

View File

@ -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",

View File

@ -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);

View File

@ -18,5 +18,5 @@ export interface AotCompilerOptions {
fullTemplateTypeCheck?: boolean;
allowEmptyCodegenFiles?: boolean;
strictInjectionParameters?: boolean;
enableIvy?: boolean|'ngtsc'|'tsc';
enableIvy?: boolean;
}

View File

@ -50,7 +50,6 @@ export {JitCompiler} from './jit/compiler';
export * from './compile_reflector';
export * from './url_resolver';
export * from './resource_loader';
export {ConstantPool} from './constant_pool';
export {DirectiveResolver} from './directive_resolver';
export {PipeResolver} from './pipe_resolver';
export {NgModuleResolver} from './ng_module_resolver';
@ -68,7 +67,7 @@ export * from './ml_parser/html_tags';
export * from './ml_parser/interpolation_config';
export * from './ml_parser/tags';
export {NgModuleCompiler} from './ng_module_compiler';
export {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinType, BuiltinTypeName, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
export {EmitterVisitorContext} from './output/abstract_emitter';
export * from './output/ts_emitter';
export * from './parse_util';
@ -79,11 +78,4 @@ export * from './template_parser/template_parser';
export {ViewCompiler} from './view_compiler/view_compiler';
export {getParseErrors, isSyntaxError, syntaxError, Version} from './util';
export {SourceMap} from './output/source_map';
export * from './injectable_compiler_2';
export * from './render3/view/api';
export {jitExpression} from './render3/r3_jit';
export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory';
export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler';
export {makeBindingParser, parseTemplate} from './render3/view/template';
export {compileComponentFromMetadata, compileDirectiveFromMetadata} from './render3/view/compiler';
// This file only reexports content of the `src` folder. Keep it that way.
// This file only reexports content of the `src` folder. Keep it that way.

View File

@ -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;

View File

@ -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
}

View File

@ -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) {}
}

View File

@ -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);
}

View File

@ -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,

View File

@ -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,
};
}

View File

@ -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

View File

@ -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) {

View File

@ -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');

View File

@ -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);
}

View File

@ -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) {

View File

@ -68,12 +68,15 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
}
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx);
return null;
}
visitWrappedNodeExpr(ast: o.WrappedNodeExpr<any>, ctx: EmitterVisitorContext): any {
this._emitReferenceToExternal(ast, ast.node, ctx);
const value = this.reflector.resolveExternalReference(ast.value);
let id = this._evalArgValues.indexOf(value);
if (id === -1) {
id = this._evalArgValues.length;
this._evalArgValues.push(value);
const name = identifierName({reference: value}) || 'val';
this._evalArgNames.push(`jit_${name}_${id}`);
}
ctx.print(ast, this._evalArgNames[id]);
return null;
}
@ -97,16 +100,4 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
}
return super.visitDeclareClassStmt(stmt, ctx);
}
private _emitReferenceToExternal(ast: o.Expression, value: any, ctx: EmitterVisitorContext):
void {
let id = this._evalArgValues.indexOf(value);
if (id === -1) {
id = this._evalArgValues.length;
this._evalArgValues.push(value);
const name = identifierName({reference: value}) || 'val';
this._evalArgNames.push(`jit_${name}_${id}`);
}
ctx.print(ast, this._evalArgNames[id]);
}
}

View File

@ -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);

View File

@ -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;
}

View 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]));
}

View File

@ -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;
}

View File

@ -17,15 +17,7 @@ export class Identifiers {
static PATCH_DEPS = 'patchedDeps';
/* Instructions */
static namespaceHTML: o.ExternalReference = {name: NH', moduleName: CORE};
static namespaceMathML: o.ExternalReference = {name: 'ɵNM', moduleName: CORE};
static namespaceSVG: o.ExternalReference = {name: 'ɵNS', moduleName: CORE};
static element: o.ExternalReference = {name: 'ɵEe', moduleName: CORE};
static elementStart: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
static createElement: o.ExternalReference = {name: E', moduleName: CORE};
static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE};
@ -33,19 +25,21 @@ export class Identifiers {
static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE};
static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE};
static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE};
static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE};
static elementStyleNamed: o.ExternalReference = {name: 'ɵsn', moduleName: CORE};
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
static textBinding: o.ExternalReference = {name: t', moduleName: CORE};
static directiveInput: o.ExternalReference = {name: i', moduleName: CORE};
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
@ -77,7 +71,6 @@ export class Identifiers {
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
static loadDirective: o.ExternalReference = {name: 'ɵd', moduleName: CORE};
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
@ -88,8 +81,6 @@ export class Identifiers {
static directiveLifeCycle: o.ExternalReference = {name: 'ɵl', moduleName: CORE};
static inject: o.ExternalReference = {name: 'inject', moduleName: CORE};
static injectAttribute: o.ExternalReference = {name: 'ɵinjectAttribute', moduleName: CORE};
static injectElementRef: o.ExternalReference = {name: 'ɵinjectElementRef', moduleName: CORE};
@ -103,28 +94,16 @@ export class Identifiers {
static defineComponent: o.ExternalReference = {name: 'ɵdefineComponent', moduleName: CORE};
static ComponentDef: o.ExternalReference = {
name: 'ComponentDef',
moduleName: CORE,
};
static defineDirective: o.ExternalReference = {
name: 'ɵdefineDirective',
moduleName: CORE,
};
static DirectiveDef: o.ExternalReference = {
name: 'DirectiveDef',
moduleName: CORE,
};
static defineInjector: o.ExternalReference = {
name: 'defineInjector',
moduleName: CORE,
};
static defineNgModule: o.ExternalReference = {name: 'ɵdefineNgModule', moduleName: CORE};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE};
@ -133,7 +112,4 @@ export class Identifiers {
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
// Reserve slots for pure functions
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
}

View File

@ -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'];
}

View File

@ -14,72 +14,22 @@ import * as o from '../output/output_ast';
import {OutputContext} from '../util';
import {Identifiers as R3} from './r3_identifiers';
import {convertMetaToOutput, mapToMapExpression} from './util';
export interface R3NgModuleDef {
expression: o.Expression;
type: o.Type;
additionalStatements: o.Statement[];
const EMPTY_ARRAY = o.literalArr([]);
function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
if (Array.isArray(meta)) {
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
} else if (meta instanceof StaticSymbol) {
return ctx.importExpr(meta);
} else if (meta == null) {
return o.literal(meta);
} else {
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
}
}
/**
* Metadata required by the module compiler to generate a `ngModuleDef` for a type.
*/
export interface R3NgModuleMetadata {
/**
* An expression representing the module type being compiled.
*/
type: o.Expression;
/**
* An array of expressions representing the bootstrap components specified by the module.
*/
bootstrap: o.Expression[];
/**
* An array of expressions representing the directives and pipes declared by the module.
*/
declarations: o.Expression[];
/**
* An array of expressions representing the imports of the module.
*/
imports: o.Expression[];
/**
* An array of expressions representing the exports of the module.
*/
exports: o.Expression[];
/**
* Whether to emit the selector scope values (declarations, imports, exports) inline into the
* module definition, or to generate additional statements which patch them on. Inline emission
* does not allow components to be tree-shaken, but is useful for JIT mode.
*/
emitInline: boolean;
}
/**
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
*/
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
const {type: moduleType, bootstrap, declarations, imports, exports} = meta;
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression({
type: moduleType,
bootstrap: o.literalArr(bootstrap),
declarations: o.literalArr(declarations),
imports: o.literalArr(imports),
exports: o.literalArr(exports),
})]);
// TODO(alxhub): write a proper type reference when AOT compilation of @NgModule is implemented.
const type = new o.ExpressionType(o.NULL_EXPR);
const additionalStatements: o.Statement[] = [];
return {expression, type, additionalStatements};
}
// TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations.
export function compileNgModuleFromRender2(
export function compileNgModule(
ctx: OutputContext, ngModule: CompileShallowModuleMetadata,
injectableCompiler: InjectableCompiler): void {
const className = identifierName(ngModule.type) !;
@ -107,9 +57,4 @@ export function compileNgModuleFromRender2(
/* getters */[],
/* 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');
}
}

View File

@ -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';

View File

@ -6,21 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompilePipeMetadata, identifierName} from '../compile_metadata';
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {DefinitionKind} from '../constant_pool';
import * as o from '../output/output_ast';
import {OutputContext, error} from '../util';
import {compileFactoryFunction, dependenciesFromGlobalMetadata} from './r3_factory';
import {Identifiers as R3} from './r3_identifiers';
import {BUILD_OPTIMIZER_COLOCATE, OutputMode} from './r3_types';
import {createFactory} from './r3_view_compiler';
/**
* Write a pipe definition to the output context.
*/
export function compilePipe(
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector,
mode: OutputMode) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. `name: 'myPipe'`
@ -30,18 +31,11 @@ export function compilePipe(
definitionMapValues.push(
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
// e.g. `factory: function MyPipe_Factory() { return new MyPipe(); }`
const deps = dependenciesFromGlobalMetadata(pipe.type, outputCtx, reflector);
const templateFactory = compileFactoryFunction({
name: identifierName(pipe.type) !,
fnOrClass: outputCtx.importExpr(pipe.type.reference), deps,
useNew: true,
injectFn: R3.directiveInject,
useOptionalParam: false,
});
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
const templateFactory = createFactory(pipe.type, outputCtx, reflector, []);
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
// e.g. `pure: true`
// e.g. pure: true
if (pipe.pure) {
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
}
@ -53,15 +47,25 @@ export function compilePipe(
const definitionFunction =
o.importExpr(R3.definePipe).callFn([o.literalMap(definitionMapValues)]);
outputCtx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ definitionField,
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ definitionFunction)],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
if (mode === OutputMode.PartialClass) {
outputCtx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ definitionField,
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ definitionFunction)],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
} else {
// Create back-patch definition.
const classReference = outputCtx.importExpr(pipe.type.reference);
// Create the back-patch statement
outputCtx.statements.push(
new o.CommentStmt(BUILD_OPTIMIZER_COLOCATE),
classReference.prop(definitionField).set(definitionFunction).toStmt());
}
}

View File

@ -1,386 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ParsedEvent, ParsedProperty, ParsedVariable, ParserError} from '../expression_parser/ast';
import * as html from '../ml_parser/ast';
import {replaceNgsp} from '../ml_parser/html_whitespaces';
import {isNgTemplate} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
import {isStyleUrlResolvable} from '../style_url_resolver';
import {BindingParser} from '../template_parser/binding_parser';
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
import {syntaxError} from '../util';
import * as t from './r3_ast';
const BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/;
// Group 1 = "bind-"
const KW_BIND_IDX = 1;
// Group 2 = "let-"
const KW_LET_IDX = 2;
// Group 3 = "ref-/#"
const KW_REF_IDX = 3;
// Group 4 = "on-"
const KW_ON_IDX = 4;
// Group 5 = "bindon-"
const KW_BINDON_IDX = 5;
// Group 6 = "@"
const KW_AT_IDX = 6;
// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
const IDENT_KW_IDX = 7;
// Group 8 = identifier inside [()]
const IDENT_BANANA_BOX_IDX = 8;
// Group 9 = identifier inside []
const IDENT_PROPERTY_IDX = 9;
// Group 10 = identifier inside ()
const IDENT_EVENT_IDX = 10;
const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class';
// Default selector used by `<ng-content>` if none specified
const DEFAULT_CONTENT_SELECTOR = '*';
// Result of the html AST to Ivy AST transformation
export type Render3ParseResult = {
nodes: t.Node[]; errors: ParseError[];
// Any non default (empty or '*') selector found in the template
ngContentSelectors: string[];
// Wether the template contains any `<ng-content>`
hasNgContent: boolean;
};
export function htmlAstToRender3Ast(
htmlNodes: html.Node[], bindingParser: BindingParser): Render3ParseResult {
const transformer = new HtmlAstToIvyAst(bindingParser);
const ivyNodes = html.visitAll(transformer, htmlNodes);
// Errors might originate in either the binding parser or the html to ivy transformer
const allErrors = bindingParser.errors.concat(transformer.errors);
const errors: ParseError[] = allErrors.filter(e => e.level === ParseErrorLevel.ERROR);
if (errors.length > 0) {
const errorString = errors.join('\n');
throw syntaxError(`Template parse errors:\n${errorString}`, errors);
}
return {
nodes: ivyNodes,
errors: allErrors,
ngContentSelectors: transformer.ngContentSelectors,
hasNgContent: transformer.hasNgContent,
};
}
class HtmlAstToIvyAst implements html.Visitor {
errors: ParseError[] = [];
// Selectors for the `ng-content` tags. Only non `*` selectors are recorded here
ngContentSelectors: string[] = [];
// Any `<ng-content>` in the template ?
hasNgContent = false;
constructor(private bindingParser: BindingParser) {}
// HTML visitor
visitElement(element: html.Element): t.Node|null {
const preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE) {
// Skipping <script> for security reasons
// Skipping <style> as we already processed them
// in the StyleCompiler
return null;
}
if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
isStyleUrlResolvable(preparsedElement.hrefAttr)) {
// Skipping stylesheets with either relative urls or package scheme as we already processed
// them in the StyleCompiler
return null;
}
// Whether the element is a `<ng-template>`
const isTemplateElement = isNgTemplate(element.name);
const matchableAttributes: [string, string][] = [];
const parsedProperties: ParsedProperty[] = [];
const boundEvents: t.BoundEvent[] = [];
const variables: t.Variable[] = [];
const references: t.Reference[] = [];
const attributes: t.TextAttribute[] = [];
const templateMatchableAttributes: [string, string][] = [];
let inlineTemplateSourceSpan: ParseSourceSpan;
const templateParsedProperties: ParsedProperty[] = [];
const templateVariables: t.Variable[] = [];
// Whether the element has any *-attribute
let elementHasInlineTemplate = false;
for (const attribute of element.attrs) {
let hasBinding = false;
const normalizedName = normalizeAttributeName(attribute.name);
// `*attr` defines template bindings
let isTemplateBinding = false;
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
if (elementHasInlineTemplate) {
this.reportError(
`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`,
attribute.sourceSpan);
}
isTemplateBinding = true;
elementHasInlineTemplate = true;
const templateValue = attribute.value;
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
const parsedVariables: ParsedVariable[] = [];
this.bindingParser.parseInlineTemplateBinding(
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
templateParsedProperties, parsedVariables);
templateVariables.push(
...parsedVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
} else {
// Check for variables, events, property bindings, interpolation
hasBinding = this.parseAttribute(
isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents,
variables, references);
}
if (!hasBinding && !isTemplateBinding) {
// don't include the bindings as attributes as well in the AST
attributes.push(this.visitAttribute(attribute) as t.TextAttribute);
matchableAttributes.push([attribute.name, attribute.value]);
}
}
const children: t.Node[] =
html.visitAll(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
let parsedElement: t.Node|undefined;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
// `<ng-content>`
this.hasNgContent = true;
if (element.children && !element.children.every(isEmptyTextNode)) {
this.reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
}
const selector = preparsedElement.selectAttr;
let attributes: t.TextAttribute[] = element.attrs.map(attribute => {
return new t.TextAttribute(
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
});
const selectorIndex =
selector === DEFAULT_CONTENT_SELECTOR ? 0 : this.ngContentSelectors.push(selector);
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
} else if (isTemplateElement) {
// `<ng-template>`
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
parsedElement = new t.Template(
attributes, boundAttributes, children, references, variables, element.sourceSpan,
element.startSourceSpan, element.endSourceSpan);
} else {
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
parsedElement = new t.Element(
element.name, attributes, boundAttributes, boundEvents, children, references,
element.sourceSpan, element.startSourceSpan, element.endSourceSpan);
}
if (elementHasInlineTemplate) {
const attributes: t.TextAttribute[] = [];
templateMatchableAttributes.forEach(
([name, value]) =>
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
const boundAttributes = this.createBoundAttributes('ng-template', templateParsedProperties);
parsedElement = new t.Template(
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
element.startSourceSpan, element.endSourceSpan);
}
return parsedElement;
}
visitAttribute(attribute: html.Attribute): t.Node {
return new t.TextAttribute(
attribute.name, attribute.value, attribute.sourceSpan, attribute.valueSpan);
}
visitText(text: html.Text): t.Node {
const valueNoNgsp = replaceNgsp(text.value);
const expr = this.bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan);
return expr ? new t.BoundText(expr, text.sourceSpan) : new t.Text(valueNoNgsp, text.sourceSpan);
}
visitComment(comment: html.Comment): null { return null; }
visitExpansion(expansion: html.Expansion): null { return null; }
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
private createBoundAttributes(elementName: string, properties: ParsedProperty[]):
t.BoundAttribute[] {
return properties.filter(prop => !prop.isLiteral)
.map(prop => this.bindingParser.createBoundElementProperty(elementName, prop))
.map(prop => t.BoundAttribute.fromBoundElementProperty(prop));
}
private parseAttribute(
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
parsedProperties: ParsedProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
references: t.Reference[]) {
const name = normalizeAttributeName(attribute.name);
const value = attribute.value;
const srcSpan = attribute.sourceSpan;
const bindParts = name.match(BIND_NAME_REGEXP);
let hasBinding = false;
if (bindParts) {
hasBinding = true;
if (bindParts[KW_BIND_IDX] != null) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
} else if (bindParts[KW_LET_IDX]) {
if (isTemplateElement) {
const identifier = bindParts[IDENT_KW_IDX];
this.parseVariable(identifier, value, srcSpan, variables);
} else {
this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
}
} else if (bindParts[KW_REF_IDX]) {
const identifier = bindParts[IDENT_KW_IDX];
this.parseReference(identifier, value, srcSpan, references);
} else if (bindParts[KW_ON_IDX]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
addEvents(events, boundEvents);
} else if (bindParts[KW_BINDON_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
} else if (bindParts[KW_AT_IDX]) {
this.bindingParser.parseLiteralAttr(
name, value, srcSpan, matchableAttributes, parsedProperties);
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
parsedProperties);
this.parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) {
this.bindingParser.parsePropertyBinding(
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
parsedProperties);
} else if (bindParts[IDENT_EVENT_IDX]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
addEvents(events, boundEvents);
}
} else {
hasBinding = this.bindingParser.parsePropertyInterpolation(
name, value, srcSpan, matchableAttributes, parsedProperties);
}
return hasBinding;
}
private parseVariable(
identifier: string, value: string, sourceSpan: ParseSourceSpan, variables: t.Variable[]) {
if (identifier.indexOf('-') > -1) {
this.reportError(`"-" is not allowed in variable names`, sourceSpan);
}
variables.push(new t.Variable(identifier, value, sourceSpan));
}
private parseReference(
identifier: string, value: string, sourceSpan: ParseSourceSpan, references: t.Reference[]) {
if (identifier.indexOf('-') > -1) {
this.reportError(`"-" is not allowed in reference names`, sourceSpan);
}
references.push(new t.Reference(identifier, value, sourceSpan));
}
private parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
const events: ParsedEvent[] = [];
this.bindingParser.parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
addEvents(events, boundEvents);
}
private reportError(
message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
this.errors.push(new ParseError(sourceSpan, message, level));
}
}
class NonBindableVisitor implements html.Visitor {
visitElement(ast: html.Element): t.Element|null {
const preparsedElement = preparseElement(ast);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
preparsedElement.type === PreparsedElementType.STYLESHEET) {
// Skipping <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null;
}
const children: t.Node[] = html.visitAll(this, ast.children, null);
return new t.Element(
ast.name, html.visitAll(this, ast.attrs) as t.TextAttribute[],
/* inputs */[], /* outputs */[], children,  /* references */[], ast.sourceSpan,
ast.startSourceSpan, ast.endSourceSpan);
}
visitComment(comment: html.Comment): any { return null; }
visitAttribute(attribute: html.Attribute): t.TextAttribute {
return new t.TextAttribute(attribute.name, attribute.value, attribute.sourceSpan);
}
visitText(text: html.Text): t.Text { return new t.Text(text.value, text.sourceSpan); }
visitExpansion(expansion: html.Expansion): any { return null; }
visitExpansionCase(expansionCase: html.ExpansionCase): any { return null; }
}
const NON_BINDABLE_VISITOR = new NonBindableVisitor();
function normalizeAttributeName(attrName: string): string {
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
}
function addEvents(events: ParsedEvent[], boundEvents: t.BoundEvent[]) {
boundEvents.push(...events.map(e => t.BoundEvent.fromParsedEvent(e)));
}
function isEmptyTextNode(node: html.Node): boolean {
return node instanceof html.Text && node.value.trim().length == 0;
}

View File

@ -6,6 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
/**
* The statement mode for the render, either as a class back-patch or as a partial class
*/
export const enum OutputMode {
PartialClass,
BackPatch,
}
/**
* Comment to insert above back-patch
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '../aot/static_symbol';
import * as o from '../output/output_ast';
import {OutputContext} from '../util';
/**
* Convert an object map with `Expression` values into a `LiteralMapExpr`.
*/
export function mapToMapExpression(map: {[key: string]: o.Expression}): o.LiteralMapExpr {
const result = Object.keys(map).map(key => ({key, value: map[key], quoted: false}));
return o.literalMap(result);
}
/**
* Convert metadata into an `Expression` in the given `OutputContext`.
*
* This operation will handle arrays, references to symbols, or literal `null` or `undefined`.
*/
export function convertMetaToOutput(meta: any, ctx: OutputContext): o.Expression {
if (Array.isArray(meta)) {
return o.literalArr(meta.map(entry => convertMetaToOutput(entry, ctx)));
}
if (meta instanceof StaticSymbol) {
return ctx.importExpr(meta);
}
if (meta == null) {
return o.literal(meta);
}
throw new Error(`Internal error: Unsupported or unknown metadata: ${meta}`);
}

View File

@ -1,178 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../output/output_ast';
import {ParseSourceSpan} from '../../parse_util';
import * as t from '../r3_ast';
import {R3DependencyMetadata} from '../r3_factory';
/**
* Information needed to compile a directive for the render3 runtime.
*/
export interface R3DirectiveMetadata {
/**
* Name of the directive type.
*/
name: string;
/**
* An expression representing a reference to the directive itself.
*/
type: o.Expression;
/**
* A source span for the directive type.
*/
typeSourceSpan: ParseSourceSpan;
/**
* Dependencies of the directive's constructor.
*/
deps: R3DependencyMetadata[];
/**
* Unparsed selector of the directive, or `null` if there was no selector.
*/
selector: string|null;
/**
* Information about the content queries made by the directive.
*/
queries: R3QueryMetadata[];
/**
* Mappings indicating how the directive interacts with its host element (host bindings,
* listeners, etc).
*/
host: {
/**
* A mapping of attribute binding keys to unparsed expressions.
*/
attributes: {[key: string]: string};
/**
* A mapping of event binding keys to unparsed expressions.
*/
listeners: {[key: string]: string};
/**
* A mapping of property binding keys to unparsed expressions.
*/
properties: {[key: string]: string};
};
/**
* Information about usage of specific lifecycle events which require special treatment in the
* code generator.
*/
lifecycle: {
/**
* Whether the directive uses NgOnChanges.
*/
usesOnChanges: boolean;
};
/**
* A mapping of input field names to the property names.
*/
inputs: {[field: string]: string};
/**
* A mapping of output field names to the property names.
*/
outputs: {[field: string]: string};
}
/**
* Information needed to compile a component for the render3 runtime.
*/
export interface R3ComponentMetadata extends R3DirectiveMetadata {
/**
* Information about the component's template.
*/
template: {
/**
* Parsed nodes of the template.
*/
nodes: t.Node[];
/**
* Whether the template includes <ng-content> tags.
*/
hasNgContent: boolean;
/**
* Selectors found in the <ng-content> tags in the template.
*/
ngContentSelectors: string[];
};
/**
* Information about the view queries made by the component.
*/
viewQueries: R3QueryMetadata[];
/**
* A map of pipe names to an expression referencing the pipe type which are in the scope of the
* compilation.
*/
pipes: Map<string, o.Expression>;
/**
* A map of directive selectors to an expression referencing the directive type which are in the
* scope of the compilation.
*/
directives: Map<string, o.Expression>;
}
/**
* Information needed to compile a query (view or content).
*/
export interface R3QueryMetadata {
/**
* Name of the property on the class to update with query results.
*/
propertyName: string;
/**
* Whether to read only the first matching result, or an array of results.
*/
first: boolean;
/**
* Either an expression representing a type for the query predicate, or a set of string selectors.
*/
predicate: o.Expression|string[];
/**
* Whether to include only direct children or all descendants.
*/
descendants: boolean;
/**
* An expression representing a type to read from each matched node, or null if the node itself
* is to be returned.
*/
read: o.Expression|null;
}
/**
* Output of render3 directive compilation.
*/
export interface R3DirectiveDef {
expression: o.Expression;
type: o.Type;
}
/**
* Output of render3 component compilation.
*/
export interface R3ComponentDef {
expression: o.Expression;
type: o.Type;
}

View File

@ -1,447 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StaticSymbol} from '../../aot/static_symbol';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, sanitizeIdentifier, tokenReference} from '../../compile_metadata';
import {CompileReflector} from '../../compile_reflector';
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../../constant_pool';
import * as core from '../../core';
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
import {Identifiers} from '../../identifiers';
import {LifecycleHooks} from '../../lifecycle_reflector';
import * as o from '../../output/output_ast';
import {ParseSourceSpan, typeSourceSpan} from '../../parse_util';
import {CssSelector, SelectorMatcher} from '../../selector';
import {BindingParser} from '../../template_parser/binding_parser';
import {OutputContext, error} from '../../util';
import * as t from '../r3_ast';
import {R3DependencyMetadata, R3ResolvedDependencyType, compileFactoryFunction, dependenciesFromGlobalMetadata} from '../r3_factory';
import {Identifiers as R3} from '../r3_identifiers';
import {Render3ParseResult} from '../r3_template_transform';
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
import {BindingScope, TemplateDefinitionBuilder} from './template';
import {CONTEXT_NAME, DefinitionMap, ID_SEPARATOR, MEANING_SEPARATOR, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator, unsupported} from './util';
function baseDirectiveFields(
meta: R3DirectiveMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): DefinitionMap {
const definitionMap = new DefinitionMap();
// e.g. `type: MyDirective`
definitionMap.set('type', meta.type);
// e.g. `selectors: [['', 'someDir', '']]`
definitionMap.set('selectors', createDirectiveSelector(meta.selector !));
const queryDefinitions = createQueryDefinitions(meta.queries, constantPool);
// e.g. `factory: () => new MyApp(injectElementRef())`
definitionMap.set('factory', compileFactoryFunction({
name: meta.name,
fnOrClass: meta.type,
deps: meta.deps,
useNew: true,
injectFn: R3.directiveInject,
useOptionalParam: false,
extraResults: queryDefinitions,
}));
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
definitionMap.set('hostBindings', createHostBindingsFunction(meta, bindingParser));
// e.g. `attributes: ['role', 'listbox']`
definitionMap.set('attributes', createHostAttributesArray(meta));
// e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs));
// e.g 'outputs: {a: 'a'}`
definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
// e.g. `features: [NgOnChangesFeature(MyComponent)]`
const features: o.Expression[] = [];
if (meta.lifecycle.usesOnChanges) {
features.push(o.importExpr(R3.NgOnChangesFeature, null, null).callFn([meta.type]));
}
if (features.length) {
definitionMap.set('features', o.literalArr(features));
}
return definitionMap;
}
/**
* Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
*/
export function compileDirectiveFromMetadata(
meta: R3DirectiveMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3DirectiveDef {
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);
const type =
new o.ExpressionType(o.importExpr(R3.DirectiveDef, [new o.ExpressionType(meta.type)]));
return {expression, type};
}
/**
* Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
*/
export function compileComponentFromMetadata(
meta: R3ComponentMetadata, constantPool: ConstantPool,
bindingParser: BindingParser): R3ComponentDef {
const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
const selector = meta.selector && CssSelector.parse(meta.selector);
const firstSelector = selector && selector[0];
// e.g. `attr: ["class", ".my.app"]`
// This is optional an only included if the first selector of a component specifies attributes.
if (firstSelector) {
const selectorAttributes = firstSelector.getAttrs();
if (selectorAttributes.length) {
definitionMap.set(
'attrs', constantPool.getConstLiteral(
o.literalArr(selectorAttributes.map(
value => value != null ? o.literal(value) : o.literal(undefined))),
/* forceShared */ true));
}
}
// Generate the CSS matcher that recognize directive
let directiveMatcher: SelectorMatcher|null = null;
if (meta.directives.size) {
const matcher = new SelectorMatcher();
meta.directives.forEach((expression, selector: string) => {
matcher.addSelectables(CssSelector.parse(selector), expression);
});
directiveMatcher = matcher;
}
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
const templateTypeName = meta.name;
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
const directivesUsed = new Set<o.Expression>();
const pipesUsed = new Set<o.Expression>();
const template = meta.template;
const templateFunctionExpression =
new TemplateDefinitionBuilder(
constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName,
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed,
R3.namespaceHTML)
.buildTemplateFunction(
template.nodes, [], template.hasNgContent, template.ngContentSelectors);
definitionMap.set('template', templateFunctionExpression);
// e.g. `directives: [MyDirective]`
if (directivesUsed.size) {
definitionMap.set('directives', o.literalArr(Array.from(directivesUsed)));
}
// e.g. `pipes: [MyPipe]`
if (pipesUsed.size) {
definitionMap.set('pipes', o.literalArr(Array.from(pipesUsed)));
}
const expression = o.importExpr(R3.defineComponent).callFn([definitionMap.toLiteralMap()]);
const type =
new o.ExpressionType(o.importExpr(R3.ComponentDef, [new o.ExpressionType(meta.type)]));
return {expression, type};
}
/**
* A wrapper around `compileDirective` which depends on render2 global analysis data as its input
* instead of the `R3DirectiveMetadata`.
*
* `R3DirectiveMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
* information.
*/
export function compileDirectiveFromRender2(
outputCtx: OutputContext, directive: CompileDirectiveMetadata, reflector: CompileReflector,
bindingParser: BindingParser) {
const name = identifierName(directive.type) !;
name || error(`Cannot resolver the name of ${directive.type}`);
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive);
const meta = directiveMetadataFromGlobalMetadata(directive, outputCtx, reflector);
const res = compileDirectiveFromMetadata(meta, outputCtx.constantPool, bindingParser);
// Create the partial class to be merged with the actual class.
outputCtx.statements.push(new o.ClassStmt(
name, null,
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
[], new o.ClassMethod(null, [], []), []));
}
/**
* A wrapper around `compileComponent` which depends on render2 global analysis data as its input
* instead of the `R3DirectiveMetadata`.
*
* `R3ComponentMetadata` is computed from `CompileDirectiveMetadata` and other statically reflected
* information.
*/
export function compileComponentFromRender2(
outputCtx: OutputContext, component: CompileDirectiveMetadata, render3Ast: Render3ParseResult,
reflector: CompileReflector, bindingParser: BindingParser, directiveTypeBySel: Map<string, any>,
pipeTypeByName: Map<string, any>) {
const name = identifierName(component.type) !;
name || error(`Cannot resolver the name of ${component.type}`);
const definitionField = outputCtx.constantPool.propertyNameOf(DefinitionKind.Component);
const summary = component.toSummary();
// Compute the R3ComponentMetadata from the CompileDirectiveMetadata
const meta: R3ComponentMetadata = {
...directiveMetadataFromGlobalMetadata(component, outputCtx, reflector),
selector: component.selector,
template: {
nodes: render3Ast.nodes,
hasNgContent: render3Ast.hasNgContent,
ngContentSelectors: render3Ast.ngContentSelectors,
},
directives: typeMapToExpressionMap(directiveTypeBySel, outputCtx),
pipes: typeMapToExpressionMap(pipeTypeByName, outputCtx),
viewQueries: queriesFromGlobalMetadata(component.viewQueries, outputCtx),
};
const res = compileComponentFromMetadata(meta, outputCtx.constantPool, bindingParser);
// Create the partial class to be merged with the actual class.
outputCtx.statements.push(new o.ClassStmt(
name, null,
[new o.ClassField(definitionField, o.INFERRED_TYPE, [o.StmtModifier.Static], res.expression)],
[], new o.ClassMethod(null, [], []), []));
}
/**
* Compute `R3DirectiveMetadata` given `CompileDirectiveMetadata` and a `CompileReflector`.
*/
function directiveMetadataFromGlobalMetadata(
directive: CompileDirectiveMetadata, outputCtx: OutputContext,
reflector: CompileReflector): R3DirectiveMetadata {
const summary = directive.toSummary();
const name = identifierName(directive.type) !;
name || error(`Cannot resolver the name of ${directive.type}`);
return {
name,
type: outputCtx.importExpr(directive.type.reference),
typeSourceSpan:
typeSourceSpan(directive.isComponent ? 'Component' : 'Directive', directive.type),
selector: directive.selector,
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
host: {
attributes: directive.hostAttributes,
listeners: summary.hostListeners,
properties: summary.hostProperties,
},
lifecycle: {
usesOnChanges:
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
},
inputs: directive.inputs,
outputs: directive.outputs,
};
}
/**
* Convert `CompileQueryMetadata` into `R3QueryMetadata`.
*/
function queriesFromGlobalMetadata(
queries: CompileQueryMetadata[], outputCtx: OutputContext): R3QueryMetadata[] {
return queries.map(query => {
let read: o.Expression|null = null;
if (query.read && query.read.identifier) {
read = outputCtx.importExpr(query.read.identifier.reference);
}
return {
propertyName: query.propertyName,
first: query.first,
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
descendants: query.descendants, read,
};
});
}
/**
* Convert `CompileTokenMetadata` for query selectors into either an expression for a predicate
* type, or a list of string predicates.
*/
function selectorsFromGlobalMetadata(
selectors: CompileTokenMetadata[], outputCtx: OutputContext): o.Expression|string[] {
if (selectors.length > 1 || (selectors.length == 1 && selectors[0].value)) {
const selectorStrings = selectors.map(value => value.value as string);
selectorStrings.some(value => !value) &&
error('Found a type among the string selectors expected');
return outputCtx.constantPool.getConstLiteral(
o.literalArr(selectorStrings.map(value => o.literal(value))));
}
if (selectors.length == 1) {
const first = selectors[0];
if (first.identifier) {
return outputCtx.importExpr(first.identifier.reference);
}
}
error('Unexpected query form');
return o.NULL_EXPR;
}
/**
*
* @param meta
* @param constantPool
*/
function createQueryDefinitions(
queries: R3QueryMetadata[], constantPool: ConstantPool): o.Expression[]|undefined {
const queryDefinitions: o.Expression[] = [];
for (let i = 0; i < queries.length; i++) {
const query = queries[i];
const predicate = getQueryPredicate(query, constantPool);
// e.g. r3.Q(null, somePredicate, false) or r3.Q(null, ['div'], false)
const parameters = [
o.literal(null, o.INFERRED_TYPE),
predicate,
o.literal(query.descendants),
];
if (query.read) {
parameters.push(query.read);
}
queryDefinitions.push(o.importExpr(R3.query).callFn(parameters));
}
return queryDefinitions.length > 0 ? queryDefinitions : undefined;
}
// Turn a directive selector into an R3-compatible selector for directive def
function createDirectiveSelector(selector: string): o.Expression {
return asLiteral(core.parseSelectorToR3Selector(selector));
}
function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null {
const values: o.Expression[] = [];
const attributes = meta.host.attributes;
for (let key of Object.getOwnPropertyNames(attributes)) {
const value = attributes[key];
values.push(o.literal(key), o.literal(value));
}
if (values.length > 0) {
return o.literalArr(values);
}
return null;
}
// Return a host binding function or null if one is not necessary.
function createHostBindingsFunction(
meta: R3DirectiveMetadata, bindingParser: BindingParser): o.Expression|null {
const statements: o.Statement[] = [];
const temporary = temporaryAllocator(statements, TEMPORARY_NAME);
const hostBindingSourceSpan = meta.typeSourceSpan;
// Calculate the queries
for (let index = 0; index < meta.queries.length; index++) {
const query = meta.queries[index];
// e.g. r3.qR(tmp = r3.d(dirIndex)[1]) && (r3.d(dirIndex)[0].someDir = tmp);
const getDirectiveMemory = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
// The query list is at the query index + 1 because the directive itself is in slot 0.
const getQueryList = getDirectiveMemory.key(o.literal(index + 1));
const assignToTemporary = temporary().set(getQueryList);
const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]);
const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE))
.prop(query.propertyName)
.set(query.first ? temporary().prop('first') : temporary());
const andExpression = callQueryRefresh.and(updateDirective);
statements.push(andExpression.toStmt());
}
const directiveSummary = metadataAsSummary(meta);
// Calculate the host property bindings
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
const bindingContext = o.importExpr(R3.loadDirective).callFn([o.variable('dirIndex')]);
if (bindings) {
for (const binding of bindings) {
const bindingExpr = convertPropertyBinding(
null, bindingContext, binding.expression, 'b', BindingForm.TrySimple,
() => error('Unexpected interpolation'));
statements.push(...bindingExpr.stmts);
statements.push(o.importExpr(R3.elementProperty)
.callFn([
o.variable('elIndex'),
o.literal(binding.name),
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
])
.toStmt());
}
}
// Calculate host event bindings
const eventBindings =
bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
if (eventBindings) {
for (const binding of eventBindings) {
const bindingExpr = convertActionBinding(
null, bindingContext, binding.handler, 'b', () => error('Unexpected interpolation'));
const bindingName = binding.name && sanitizeIdentifier(binding.name);
const typeName = meta.name;
const functionName =
typeName && bindingName ? `${typeName}_${bindingName}_HostBindingHandler` : null;
const handler = o.fn(
[new o.FnParam('$event', o.DYNAMIC_TYPE)],
[...bindingExpr.stmts, new o.ReturnStatement(bindingExpr.allowDefault)], o.INFERRED_TYPE,
null, functionName);
statements.push(
o.importExpr(R3.listener).callFn([o.literal(binding.name), handler]).toStmt());
}
}
if (statements.length > 0) {
const typeName = meta.name;
return o.fn(
[
new o.FnParam('dirIndex', o.NUMBER_TYPE),
new o.FnParam('elIndex', o.NUMBER_TYPE),
],
statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null);
}
return null;
}
function metadataAsSummary(meta: R3DirectiveMetadata): CompileDirectiveSummary {
// clang-format off
return {
hostAttributes: meta.host.attributes,
hostListeners: meta.host.listeners,
hostProperties: meta.host.properties,
} as CompileDirectiveSummary;
// clang-format on
}
function typeMapToExpressionMap(
map: Map<string, StaticSymbol>, outputCtx: OutputContext): Map<string, o.Expression> {
// Convert each map entry into another entry where the value is an expression importing the type.
const entries = Array.from(map).map(
([key, type]): [string, o.Expression] => [key, outputCtx.importExpr(type)]);
return new Map(entries);
}

View File

@ -1,883 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {flatten, sanitizeIdentifier} from '../../compile_metadata';
import {CompileReflector} from '../../compile_reflector';
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
import {ConstantPool} from '../../constant_pool';
import * as core from '../../core';
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
import {Lexer} from '../../expression_parser/lexer';
import {Parser} from '../../expression_parser/parser';
import * as html from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {WhitespaceVisitor} from '../../ml_parser/html_whitespaces';
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
import {splitNsName} from '../../ml_parser/tags';
import * as o from '../../output/output_ast';
import {ParseError, ParseSourceSpan} from '../../parse_util';
import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry';
import {CssSelector, SelectorMatcher} from '../../selector';
import {BindingParser} from '../../template_parser/binding_parser';
import {OutputContext, error} from '../../util';
import * as t from '../r3_ast';
import {Identifiers as R3} from '../r3_identifiers';
import {htmlAstToRender3Ast} from '../r3_template_transform';
import {R3QueryMetadata} from './api';
import {CONTEXT_NAME, I18N_ATTR, I18N_ATTR_PREFIX, ID_SEPARATOR, IMPLICIT_REFERENCE, MEANING_SEPARATOR, REFERENCE_PREFIX, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, getQueryPredicate, invalid, mapToExpression, noop, temporaryAllocator, trimTrailingNulls, unsupported} from './util';
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
[BindingType.Property]: R3.elementProperty,
[BindingType.Attribute]: R3.elementAttribute,
[BindingType.Class]: R3.elementClassNamed,
[BindingType.Style]: R3.elementStyleNamed,
};
// `className` is used below instead of `class` because the interception
// code (where this map is used) deals with DOM element property values
// (like elm.propName) and not component bindining properties (like [propName]).
const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP: {[index: string]: o.ExternalReference} = {
'className': R3.elementClass,
'style': R3.elementStyle
};
export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
private _dataIndex = 0;
private _bindingContext = 0;
private _prefixCode: o.Statement[] = [];
private _creationCode: o.Statement[] = [];
private _variableCode: o.Statement[] = [];
private _bindingCode: o.Statement[] = [];
private _postfixCode: o.Statement[] = [];
private _temporary = temporaryAllocator(this._prefixCode, TEMPORARY_NAME);
private _projectionDefinitionIndex = -1;
private _valueConverter: ValueConverter;
private _unsupported = unsupported;
private _bindingScope: BindingScope;
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
private _inI18nSection: boolean = false;
private _i18nSectionIndex = -1;
// Maps of placeholder to node indexes for each of the i18n section
private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}];
// Number of slots to reserve for pureFunctions
private _pureFunctionSlots = 0;
constructor(
private constantPool: ConstantPool, private contextParameter: string,
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
private templateName: string|null, private viewQueries: R3QueryMetadata[],
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
private _namespace: o.ExternalReference) {
this._bindingScope =
parentBindingScope.nestedScope((lhsVar: o.ReadVarExpr, expression: o.Expression) => {
this._bindingCode.push(
lhsVar.set(expression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
});
this._valueConverter = new ValueConverter(
constantPool, () => this.allocateDataSlot(),
(numSlots: number): number => this._pureFunctionSlots += numSlots,
(name, localName, slot, value: o.ReadVarExpr) => {
const pipeType = pipeTypeByName.get(name);
if (pipeType) {
this.pipes.add(pipeType);
}
this._bindingScope.set(localName, value);
this._creationCode.push(
o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt());
});
}
buildTemplateFunction(
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
ngContentSelectors: string[] = []): o.FunctionExpr {
if (this._namespace !== R3.namespaceHTML) {
this.instruction(this._creationCode, null, this._namespace);
}
// Create variable bindings
for (const variable of variables) {
const variableName = variable.name;
const expression =
o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
const scopedName = this._bindingScope.freshReferenceName();
// Add the reference to the local scope.
this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression);
}
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
if (hasNgContent) {
this._projectionDefinitionIndex = this.allocateDataSlot();
const parameters: o.Expression[] = [o.literal(this._projectionDefinitionIndex)];
// Only selectors with a non-default value are generated
if (ngContentSelectors.length > 1) {
const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
// `projectionDef` needs both the parsed and raw value of the selectors
const parsed = this.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
const unParsed = this.constantPool.getConstLiteral(asLiteral(ngContentSelectors), true);
parameters.push(parsed, unParsed);
}
this.instruction(this._creationCode, null, R3.projectionDef, ...parameters);
}
// Define and update any view queries
for (let query of this.viewQueries) {
// e.g. r3.Q(0, somePredicate, true);
const querySlot = this.allocateDataSlot();
const predicate = getQueryPredicate(query, this.constantPool);
const args: o.Expression[] = [
o.literal(querySlot, o.INFERRED_TYPE),
predicate,
o.literal(query.descendants, o.INFERRED_TYPE),
];
if (query.read) {
args.push(query.read);
}
this.instruction(this._creationCode, null, R3.query, ...args);
// (r3.qR(tmp = r3.ɵld(0)) && (ctx.someDir = tmp));
const temporary = this._temporary();
const getQueryList = o.importExpr(R3.load).callFn([o.literal(querySlot)]);
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
const updateDirective = o.variable(CONTEXT_NAME)
.prop(query.propertyName)
.set(query.first ? temporary.prop('first') : temporary);
this._bindingCode.push(refresh.and(updateDirective).toStmt());
}
t.visitAll(this, nodes);
if (this._pureFunctionSlots > 0) {
this.instruction(
this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
}
const creationCode = this._creationCode.length > 0 ?
[o.ifStmt(
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false),
this._creationCode)] :
[];
const updateCode = this._bindingCode.length > 0 ?
[o.ifStmt(
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Update), null, false),
this._bindingCode)] :
[];
// Generate maps of placeholder name to node indexes
// TODO(vicb): This is a WIP, not fully supported yet
for (const phToNodeIdx of this._phToNodeIdxes) {
if (Object.keys(phToNodeIdx).length > 0) {
const scopedName = this._bindingScope.freshReferenceName();
const phMap = o.variable(scopedName)
.set(mapToExpression(phToNodeIdx, true))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
this._prefixCode.push(phMap);
}
}
return o.fn(
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(this.contextParameter, null)],
[
// Temporary variable declarations for query refresh (i.e. let _t: any;)
...this._prefixCode,
// Creating mode (i.e. if (rf & RenderFlags.Create) { ... })
...creationCode,
// Temporary variable declarations for local refs (i.e. const tmp = ld(1) as any)
...this._variableCode,
// Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...})
...updateCode,
// Nested templates (i.e. function CompTemplate() {})
...this._postfixCode
],
o.INFERRED_TYPE, null, this.templateName);
}
// LocalResolver
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
visitContent(ngContent: t.Content) {
const slot = this.allocateDataSlot();
const selectorIndex = ngContent.selectorIndex;
const parameters: o.Expression[] = [
o.literal(slot),
o.literal(this._projectionDefinitionIndex),
];
const attributeAsList: string[] = [];
ngContent.attributes.forEach((attribute) => {
const name = attribute.name;
if (name !== 'select') {
attributeAsList.push(name, attribute.value);
}
});
if (attributeAsList.length > 0) {
parameters.push(o.literal(selectorIndex), asLiteral(attributeAsList));
} else if (selectorIndex !== 0) {
parameters.push(o.literal(selectorIndex));
}
this.instruction(this._creationCode, ngContent.sourceSpan, R3.projection, ...parameters);
}
getNamespaceInstruction(namespaceKey: string|null) {
switch (namespaceKey) {
case 'math':
return R3.namespaceMathML;
case 'svg':
return R3.namespaceSVG;
default:
return R3.namespaceHTML;
}
}
addNamespaceInstruction(nsInstruction: o.ExternalReference, element: t.Element) {
this._namespace = nsInstruction;
this.instruction(this._creationCode, element.sourceSpan, nsInstruction);
}
visitElement(element: t.Element) {
const elementIndex = this.allocateDataSlot();
const referenceDataSlots = new Map<string, number>();
const wasInI18nSection = this._inI18nSection;
const outputAttrs: {[name: string]: string} = {};
const attrI18nMetas: {[name: string]: string} = {};
let i18nMeta: string = '';
const [namespaceKey, elementName] = splitNsName(element.name);
// Elements inside i18n sections are replaced with placeholders
// TODO(vicb): nested elements are a WIP in this phase
if (this._inI18nSection) {
const phName = element.name.toLowerCase();
if (!this._phToNodeIdxes[this._i18nSectionIndex][phName]) {
this._phToNodeIdxes[this._i18nSectionIndex][phName] = [];
}
this._phToNodeIdxes[this._i18nSectionIndex][phName].push(elementIndex);
}
// Handle i18n attributes
for (const attr of element.attributes) {
const name = attr.name;
const value = attr.value;
if (name === I18N_ATTR) {
if (this._inI18nSection) {
throw new Error(
`Could not mark an element as translatable inside of a translatable section`);
}
this._inI18nSection = true;
this._i18nSectionIndex++;
this._phToNodeIdxes[this._i18nSectionIndex] = {};
i18nMeta = value;
} else if (name.startsWith(I18N_ATTR_PREFIX)) {
attrI18nMetas[name.slice(I18N_ATTR_PREFIX.length)] = value;
} else {
outputAttrs[name] = value;
}
}
// Match directives on non i18n attributes
if (this.directiveMatcher) {
const selector = createCssSelector(element.name, outputAttrs);
this.directiveMatcher.match(
selector, (sel: CssSelector, staticType: any) => { this.directives.add(staticType); });
}
// Element creation mode
const parameters: o.Expression[] = [
o.literal(elementIndex),
o.literal(elementName),
];
// Add the attributes
const i18nMessages: o.Statement[] = [];
const attributes: o.Expression[] = [];
Object.getOwnPropertyNames(outputAttrs).forEach(name => {
const value = outputAttrs[name];
attributes.push(o.literal(name));
if (attrI18nMetas.hasOwnProperty(name)) {
const meta = parseI18nMeta(attrI18nMetas[name]);
const variable = this.constantPool.getTranslation(value, meta);
attributes.push(variable);
} else {
attributes.push(o.literal(value));
}
});
const attrArg: o.Expression = attributes.length > 0 ?
this.constantPool.getConstLiteral(o.literalArr(attributes), true) :
o.TYPED_NULL_EXPR;
parameters.push(attrArg);
if (element.references && element.references.length > 0) {
const references = flatten(element.references.map(reference => {
const slot = this.allocateDataSlot();
referenceDataSlots.set(reference.name, slot);
// Generate the update temporary.
const variableName = this._bindingScope.freshReferenceName();
this._variableCode.push(o.variable(variableName, o.INFERRED_TYPE)
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
this._bindingScope.set(reference.name, o.variable(variableName));
return [reference.name, reference.value];
}));
parameters.push(this.constantPool.getConstLiteral(asLiteral(references), true));
} else {
parameters.push(o.TYPED_NULL_EXPR);
}
// Generate the instruction create element instruction
if (i18nMessages.length > 0) {
this._creationCode.push(...i18nMessages);
}
const wasInNamespace = this._namespace;
const currentNamespace = this.getNamespaceInstruction(namespaceKey);
// If the namespace is changing now, include an instruction to change it
// during element creation.
if (currentNamespace !== wasInNamespace) {
this.addNamespaceInstruction(currentNamespace, element);
}
const isEmptyElement = element.children.length === 0 && element.outputs.length === 0;
const implicit = o.variable(CONTEXT_NAME);
if (isEmptyElement) {
this.instruction(
this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters));
} else {
// Generate the instruction create element instruction
if (i18nMessages.length > 0) {
this._creationCode.push(...i18nMessages);
}
this.instruction(
this._creationCode, element.sourceSpan, R3.elementStart,
...trimTrailingNulls(parameters));
// Generate Listeners (outputs)
element.outputs.forEach((outputAst: t.BoundEvent) => {
const elName = sanitizeIdentifier(element.name);
const evName = sanitizeIdentifier(outputAst.name);
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
const localVars: o.Statement[] = [];
const bindingScope =
this._bindingScope.nestedScope((lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => {
localVars.push(
lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
});
const bindingExpr = convertActionBinding(
bindingScope, implicit, outputAst.handler, 'b',
() => error('Unexpected interpolation'));
const handler = o.fn(
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts],
o.INFERRED_TYPE, null, functionName);
this.instruction(
this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name),
handler);
});
}
// Generate element input bindings
element.inputs.forEach((input: t.BoundAttribute) => {
if (input.type === BindingType.Animation) {
this._unsupported('animations');
}
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name];
if (specialInstruction) {
// special case for [style] and [class] bindings since they are not handled as
// standard properties within this implementation. Instead they are
// handed off to special cased instruction handlers which will then
// delegate them as animation sequences (or input bindings for dirs/cmps)
this.instruction(
this._bindingCode, input.sourceSpan, specialInstruction, o.literal(elementIndex),
convertedBinding);
return;
}
const instruction = BINDING_INSTRUCTION_MAP[input.type];
if (instruction) {
// TODO(chuckj): runtime: security context?
this.instruction(
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), convertedBinding);
} else {
this._unsupported(`binding type ${input.type}`);
}
});
// Traverse element child nodes
if (this._inI18nSection && element.children.length == 1 &&
element.children[0] instanceof t.Text) {
const text = element.children[0] as t.Text;
this.visitSingleI18nTextChild(text, i18nMeta);
} else {
t.visitAll(this, element.children);
}
if (!isEmptyElement) {
// Finish element construction mode.
this.instruction(
this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd);
}
// Restore the state before exiting this node
this._inI18nSection = wasInI18nSection;
}
visitTemplate(template: t.Template) {
const templateIndex = this.allocateDataSlot();
let elName = '';
if (template.children.length === 1 && template.children[0] instanceof t.Element) {
// When the template as a single child, derive the context name from the tag
elName = sanitizeIdentifier((template.children[0] as t.Element).name);
}
const contextName = elName ? `${this.contextName}_${elName}` : '';
const templateName =
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
const templateContext = `ctx${this.level}`;
const parameters: o.Expression[] = [
o.literal(templateIndex),
o.variable(templateName),
o.TYPED_NULL_EXPR,
];
const attributeNames: o.Expression[] = [];
const attributeMap: {[name: string]: string} = {};
template.attributes.forEach(a => {
attributeNames.push(asLiteral(a.name), asLiteral(''));
attributeMap[a.name] = a.value;
});
// Match directives on template attributes
if (this.directiveMatcher) {
const selector = createCssSelector('ng-template', attributeMap);
this.directiveMatcher.match(
selector, (cssSelector, staticType) => { this.directives.add(staticType); });
}
if (attributeNames.length) {
parameters.push(this.constantPool.getConstLiteral(o.literalArr(attributeNames), true));
}
// e.g. C(1, C1Template)
this.instruction(
this._creationCode, template.sourceSpan, R3.containerCreate,
...trimTrailingNulls(parameters));
// e.g. p(1, 'forOf', ɵb(ctx.items));
const context = o.variable(CONTEXT_NAME);
template.inputs.forEach(input => {
const convertedBinding = this.convertPropertyBinding(context, input.value);
this.instruction(
this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex),
o.literal(input.name), convertedBinding);
});
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName,
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes,
this._namespace);
const templateFunctionExpr =
templateVisitor.buildTemplateFunction(template.children, template.variables);
this._postfixCode.push(templateFunctionExpr.toDeclStmt(templateName, null));
}
// These should be handled in the template or element directly.
readonly visitReference = invalid;
readonly visitVariable = invalid;
readonly visitTextAttribute = invalid;
readonly visitBoundAttribute = invalid;
readonly visitBoundEvent = invalid;
visitBoundText(text: t.BoundText) {
const nodeIndex = this.allocateDataSlot();
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex));
this.instruction(
this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex),
this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value));
}
visitText(text: t.Text) {
this.instruction(
this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()),
o.literal(text.value));
}
// When the content of the element is a single text node the translation can be inlined:
//
// `<p i18n="desc|mean">some content</p>`
// compiles to
// ```
// /**
// * @desc desc
// * @meaning mean
// */
// const MSG_XYZ = goog.getMsg('some content');
// i0.ɵT(1, MSG_XYZ);
// ```
visitSingleI18nTextChild(text: t.Text, i18nMeta: string) {
const meta = parseI18nMeta(i18nMeta);
const variable = this.constantPool.getTranslation(text.value, meta);
this.instruction(
this._creationCode, text.sourceSpan, R3.text, o.literal(this.allocateDataSlot()), variable);
}
private allocateDataSlot() { return this._dataIndex++; }
private bindingContext() { return `${this._bindingContext++}`; }
private instruction(
statements: o.Statement[], span: ParseSourceSpan|null, reference: o.ExternalReference,
...params: o.Expression[]) {
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
}
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const pipesConvertedValue = value.visit(this._valueConverter);
if (pipesConvertedValue instanceof Interpolation) {
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
this._bindingCode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
} else {
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
() => error('Unexpected interpolation'));
this._bindingCode.push(...convertedPropertyBinding.stmts);
return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]);
}
}
}
class ValueConverter extends AstMemoryEfficientTransformer {
constructor(
private constantPool: ConstantPool, private allocateSlot: () => number,
private allocatePureFunctionSlots: (numSlots: number) => number,
private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) {
super();
}
// AstMemoryEfficientTransformer
visitPipe(pipe: BindingPipe, context: any): AST {
// Allocate a slot to create the pipe
const slot = this.allocateSlot();
const slotPseudoLocal = `PIPE:${slot}`;
// Allocate one slot for the result plus one slot per pipe argument
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
const {identifier, isVarLength} = pipeBindingCallInfo(pipe.args);
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(identifier));
const args: AST[] = [pipe.exp, ...pipe.args];
const convertedArgs: AST[] =
isVarLength ? this.visitAll([new LiteralArray(pipe.span, args)]) : this.visitAll(args);
return new FunctionCall(pipe.span, target, [
new LiteralPrimitive(pipe.span, slot),
new LiteralPrimitive(pipe.span, pureFunctionSlot),
...convertedArgs,
]);
}
visitLiteralArray(array: LiteralArray, context: any): AST {
return new BuiltinFunctionCall(array.span, this.visitAll(array.expressions), values => {
// If the literal has calculated (non-literal) elements transform it into
// calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalArr(values);
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
visitLiteralMap(map: LiteralMap, context: any): AST {
return new BuiltinFunctionCall(map.span, this.visitAll(map.values), values => {
// If the literal has calculated (non-literal) elements transform it into
// calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalMap(values.map(
(value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted})));
return values.every(a => a.isConstant()) ?
this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
});
}
}
// Pipes always have at least one parameter, the value they operate on
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
function pipeBindingCallInfo(args: o.Expression[]) {
const identifier = pipeBindingIdentifiers[args.length];
return {
identifier: identifier || R3.pipeBindV,
isVarLength: !identifier,
};
}
const pureFunctionIdentifiers = [
R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4,
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
];
function pureFunctionCallInfo(args: o.Expression[]) {
const identifier = pureFunctionIdentifiers[args.length];
return {
identifier: identifier || R3.pureFunctionV,
isVarLength: !identifier,
};
}
function getLiteralFactory(
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
allocateSlots: (numSlots: number) => number): o.Expression {
const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal);
// Allocate 1 slot for the result plus 1 per argument
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
const {identifier, isVarLength} = pureFunctionCallInfo(literalFactoryArguments);
// Literal factories are pure functions that only need to be re-invoked when the parameters
// change.
const args = [
o.literal(startSlot),
literalFactory,
];
if (isVarLength) {
args.push(o.literalArr(literalFactoryArguments));
} else {
args.push(...literalFactoryArguments);
}
return o.importExpr(identifier).callFn(args);
}
/**
* Function which is executed whenever a variable is referenced for the first time in a given
* scope.
*
* It is expected that the function creates the `const localName = expression`; statement.
*/
export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void;
export class BindingScope implements LocalResolver {
/**
* Keeps a map from local variables to their expressions.
*
* This is used when one refers to variable such as: 'let abc = a.b.c`.
* - key to the map is the string literal `"abc"`.
* - value `lhs` is the left hand side which is an AST representing `abc`.
* - value `rhs` is the right hand side which is an AST representing `a.b.c`.
* - value `declared` is true if the `declareLocalVarCallback` has been called for this scope
* already.
*/
private map = new Map < string, {
lhs: o.ReadVarExpr;
rhs: o.Expression|undefined;
declared: boolean;
}
> ();
private referenceNameIndex = 0;
static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event'));
private constructor(
private parent: BindingScope|null = null,
private declareLocalVarCallback: DeclareLocalVarCallback = noop) {}
get(name: string): o.Expression|null {
let current: BindingScope|null = this;
while (current) {
let value = current.map.get(name);
if (value != null) {
if (current !== this) {
// make a local copy and reset the `declared` state.
value = {lhs: value.lhs, rhs: value.rhs, declared: false};
// Cache the value locally.
this.map.set(name, value);
}
if (value.rhs && !value.declared) {
// if it is first time we are referencing the variable in the scope
// than invoke the callback to insert variable declaration.
this.declareLocalVarCallback(value.lhs, value.rhs);
value.declared = true;
}
return value.lhs;
}
current = current.parent;
}
return null;
}
/**
* Create a local variable for later reference.
*
* @param name Name of the variable.
* @param lhs AST representing the left hand side of the `let lhs = rhs;`.
* @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be
* `undefined` for variable that are ambient such as `$event` and which don't have `rhs`
* declaration.
*/
set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression): BindingScope {
!this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
this.map.set(name, {lhs: lhs, rhs: rhs, declared: false});
return this;
}
getLocal(name: string): (o.Expression|null) { return this.get(name); }
nestedScope(declareCallback: DeclareLocalVarCallback): BindingScope {
return new BindingScope(this, declareCallback);
}
freshReferenceName(): string {
let current: BindingScope = this;
// Find the top scope as it maintains the global reference count
while (current.parent) current = current.parent;
const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`;
return ref;
}
}
/**
* Creates a `CssSelector` given a tag name and a map of attributes
*/
function createCssSelector(tag: string, attributes: {[name: string]: string}): CssSelector {
const cssSelector = new CssSelector();
cssSelector.setElement(tag);
Object.getOwnPropertyNames(attributes).forEach((name) => {
const value = attributes[name];
cssSelector.addAttribute(name, value);
if (name.toLowerCase() === 'class') {
const classes = value.trim().split(/\s+/g);
classes.forEach(className => cssSelector.addClassName(className));
}
});
return cssSelector;
}
// Parse i18n metas like:
// - "@@id",
// - "description[@@id]",
// - "meaning|description[@@id]"
function parseI18nMeta(i18n?: string): {description?: string, id?: string, meaning?: string} {
let meaning: string|undefined;
let description: string|undefined;
let id: string|undefined;
if (i18n) {
// TODO(vicb): figure out how to force a message ID with closure ?
const idIndex = i18n.indexOf(ID_SEPARATOR);
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
let meaningAndDesc: string;
[meaningAndDesc, id] =
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
[meaning, description] = (descIndex > -1) ?
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
['', meaningAndDesc];
}
return {description, id, meaning};
}
function interpolate(args: o.Expression[]): o.Expression {
args = args.slice(1); // Ignore the length prefix added for render2
switch (args.length) {
case 3:
return o.importExpr(R3.interpolation1).callFn(args);
case 5:
return o.importExpr(R3.interpolation2).callFn(args);
case 7:
return o.importExpr(R3.interpolation3).callFn(args);
case 9:
return o.importExpr(R3.interpolation4).callFn(args);
case 11:
return o.importExpr(R3.interpolation5).callFn(args);
case 13:
return o.importExpr(R3.interpolation6).callFn(args);
case 15:
return o.importExpr(R3.interpolation7).callFn(args);
case 17:
return o.importExpr(R3.interpolation8).callFn(args);
}
(args.length >= 19 && args.length % 2 == 1) ||
error(`Invalid interpolation argument length ${args.length}`);
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
}
/**
* Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
*
* @param template text of the template to parse
* @param templateUrl URL to use for source mapping of the parsed template
*/
export function parseTemplate(
template: string, templateUrl: string, options: {preserveWhitespace?: boolean} = {}):
{errors?: ParseError[], nodes: t.Node[], hasNgContent: boolean, ngContentSelectors: string[]} {
const bindingParser = makeBindingParser();
const htmlParser = new HtmlParser();
const parseResult = htmlParser.parse(template, templateUrl);
if (parseResult.errors && parseResult.errors.length > 0) {
return {errors: parseResult.errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
}
let rootNodes: html.Node[] = parseResult.rootNodes;
if (!options.preserveWhitespace) {
rootNodes = html.visitAll(new WhitespaceVisitor(), rootNodes);
}
const {nodes, hasNgContent, ngContentSelectors, errors} =
htmlAstToRender3Ast(rootNodes, bindingParser);
if (errors && errors.length > 0) {
return {errors, nodes: [], hasNgContent: false, ngContentSelectors: []};
}
return {nodes, hasNgContent, ngContentSelectors};
}
/**
* Construct a `BindingParser` with a default configuration.
*/
export function makeBindingParser(): BindingParser {
return new BindingParser(
new Parser(new Lexer()), DEFAULT_INTERPOLATION_CONFIG, new DomElementSchemaRegistry(), [],
[]);
}

View File

@ -1,119 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ConstantPool} from '../../constant_pool';
import * as o from '../../output/output_ast';
import * as t from '../r3_ast';
import {R3QueryMetadata} from './api';
/** Name of the temporary to use during data binding */
export const TEMPORARY_NAME = '_t';
/** Name of the context parameter passed into a template function */
export const CONTEXT_NAME = 'ctx';
/** Name of the RenderFlag passed into a template function */
export const RENDER_FLAGS = 'rf';
/** The prefix reference variables */
export const REFERENCE_PREFIX = '_r';
/** The name of the implicit context reference */
export const IMPLICIT_REFERENCE = '$implicit';
/** Name of the i18n attributes **/
export const I18N_ATTR = 'i18n';
export const I18N_ATTR_PREFIX = 'i18n-';
/** I18n separators for metadata **/
export const MEANING_SEPARATOR = '|';
export const ID_SEPARATOR = '@@';
/**
* Creates an allocator for a temporary variable.
*
* A variable declaration is added to the statements the first time the allocator is invoked.
*/
export function temporaryAllocator(statements: o.Statement[], name: string): () => o.ReadVarExpr {
let temp: o.ReadVarExpr|null = null;
return () => {
if (!temp) {
statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE));
temp = o.variable(name);
}
return temp;
};
}
export function unsupported(feature: string): never {
if (this) {
throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`);
}
throw new Error(`Feature ${feature} is not supported yet`);
}
export function invalid<T>(arg: o.Expression | o.Statement | t.Node): never {
throw new Error(
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
}
export function asLiteral(value: any): o.Expression {
if (Array.isArray(value)) {
return o.literalArr(value.map(asLiteral));
}
return o.literal(value, o.INFERRED_TYPE);
}
export function conditionallyCreateMapObjectLiteral(keys: {[key: string]: string}): o.Expression|
null {
if (Object.getOwnPropertyNames(keys).length > 0) {
return mapToExpression(keys);
}
return null;
}
export function mapToExpression(map: {[key: string]: any}, quoted = false): o.Expression {
return o.literalMap(
Object.getOwnPropertyNames(map).map(key => ({key, quoted, value: asLiteral(map[key])})));
}
/**
* Remove trailing null nodes as they are implied.
*/
export function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] {
while (o.isNull(parameters[parameters.length - 1])) {
parameters.pop();
}
return parameters;
}
export function getQueryPredicate(
query: R3QueryMetadata, constantPool: ConstantPool): o.Expression {
if (Array.isArray(query.predicate)) {
return constantPool.getConstLiteral(
o.literalArr(query.predicate.map(selector => o.literal(selector) as o.Expression)));
} else {
return query.predicate;
}
}
export function noop() {}
export class DefinitionMap {
values: {key: string, quoted: boolean, value: o.Expression}[] = [];
set(key: string, value: o.Expression|null): void {
if (value) {
this.values.push({key, value, quoted: false});
}
}
toLiteralMap(): o.LiteralMapExpr { return o.literalMap(this.values); }
}

View File

@ -8,7 +8,7 @@
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {SecurityContext} from '../core';
import {ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser';
import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {mergeNsAndName} from '../ml_parser/tags';
@ -17,6 +17,8 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector} from '../selector';
import {splitAtColon, splitAtPeriod} from '../util';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType, VariableAst} from './template_ast';
const PROPERTY_PARTS_SEPARATOR = '.';
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
@ -24,33 +26,47 @@ const STYLE_PREFIX = 'style';
const ANIMATE_PROP_PREFIX = 'animate-';
export enum BoundPropertyType {
DEFAULT,
LITERAL_ATTR,
ANIMATION
}
/**
* Represents a parsed property.
*/
export class BoundProperty {
public readonly isLiteral: boolean;
public readonly isAnimation: boolean;
constructor(
public name: string, public expression: ASTWithSource, public type: BoundPropertyType,
public sourceSpan: ParseSourceSpan) {
this.isLiteral = this.type === BoundPropertyType.LITERAL_ATTR;
this.isAnimation = this.type === BoundPropertyType.ANIMATION;
}
}
/**
* Parses bindings in templates and in the directive host area.
*/
export class BindingParser {
pipesByName: Map<string, CompilePipeSummary>|null = null;
pipesByName: Map<string, CompilePipeSummary> = new Map();
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null,
public errors: ParseError[]) {
// When the `pipes` parameter is `null`, do not check for used pipes
// This is used in IVY when we might not know the available pipes at compile time
if (pipes) {
const pipesByName: Map<string, CompilePipeSummary> = new Map();
pipes.forEach(pipe => pipesByName.set(pipe.name, pipe));
this.pipesByName = pipesByName;
}
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[],
private _targetErrors: ParseError[]) {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
createBoundHostProperties(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
ParsedProperty[]|null {
BoundProperty[]|null {
if (dirMeta.hostProperties) {
const boundProps: ParsedProperty[] = [];
const boundProps: BoundProperty[] = [];
Object.keys(dirMeta.hostProperties).forEach(propName => {
const expression = dirMeta.hostProperties[propName];
if (typeof expression === 'string') {
@ -68,27 +84,27 @@ export class BindingParser {
createDirectiveHostPropertyAsts(
dirMeta: CompileDirectiveSummary, elementSelector: string,
sourceSpan: ParseSourceSpan): BoundElementProperty[]|null {
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan);
return boundProps &&
boundProps.map((prop) => this.createBoundElementProperty(elementSelector, prop));
boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
}
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
ParsedEvent[]|null {
BoundEventAst[]|null {
if (dirMeta.hostListeners) {
const targetEvents: ParsedEvent[] = [];
const targetEventAsts: BoundEventAst[] = [];
Object.keys(dirMeta.hostListeners).forEach(propName => {
const expression = dirMeta.hostListeners[propName];
if (typeof expression === 'string') {
this.parseEvent(propName, expression, sourceSpan, [], targetEvents);
this.parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
} else {
this._reportError(
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
sourceSpan);
}
});
return targetEvents;
return targetEventAsts;
}
return null;
}
@ -108,17 +124,14 @@ export class BindingParser {
}
}
// Parse an inline template binding. ie `<tag *tplKey="<tplValue>">`
parseInlineTemplateBinding(
tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: ParsedProperty[],
targetVars: ParsedVariable[]) {
const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan);
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
} else if (binding.expression) {
this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
@ -129,12 +142,12 @@ export class BindingParser {
}
}
private _parseTemplateBindings(tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan):
private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
TemplateBinding[] {
const sourceInfo = sourceSpan.start.toString();
try {
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo);
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (binding.expression) {
@ -152,8 +165,8 @@ export class BindingParser {
parseLiteralAttr(
name: string, value: string|null, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
if (isAnimationLabel(name)) {
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
if (_isAnimationLabel(name)) {
name = name.substring(1);
if (value) {
this._reportError(
@ -163,20 +176,20 @@ export class BindingParser {
}
this._parseAnimation(name, value, sourceSpan, targetMatchableAttrs, targetProps);
} else {
targetProps.push(new ParsedProperty(
name, this._exprParser.wrapLiteralPrimitive(value, ''), ParsedPropertyType.LITERAL_ATTR,
targetProps.push(new BoundProperty(
name, this._exprParser.wrapLiteralPrimitive(value, ''), BoundPropertyType.LITERAL_ATTR,
sourceSpan));
}
}
parsePropertyBinding(
name: string, expression: string, isHost: boolean, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
let isAnimationProp = false;
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
isAnimationProp = true;
name = name.substring(ANIMATE_PROP_PREFIX.length);
} else if (isAnimationLabel(name)) {
} else if (_isAnimationLabel(name)) {
isAnimationProp = true;
name = name.substring(1);
}
@ -192,7 +205,7 @@ export class BindingParser {
parsePropertyInterpolation(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: ParsedProperty[]): boolean {
targetProps: BoundProperty[]): boolean {
const expr = this.parseInterpolation(value, sourceSpan);
if (expr) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
@ -203,20 +216,20 @@ export class BindingParser {
private _parsePropertyAst(
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
targetMatchableAttrs.push([name, ast.source !]);
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.DEFAULT, sourceSpan));
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
}
private _parseAnimation(
name: string, expression: string|null, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
// This will occur when a @trigger is not paired with an expression.
// For animations it is valid to not have an expression since */void
// states will be applied by angular when the element is attached/detached
const ast = this._parseBinding(expression || 'undefined', false, sourceSpan);
targetMatchableAttrs.push([name, ast.source !]);
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan));
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
}
private _parseBinding(value: string, isHostBinding: boolean, sourceSpan: ParseSourceSpan):
@ -236,16 +249,16 @@ export class BindingParser {
}
}
createBoundElementProperty(elementSelector: string, boundProp: ParsedProperty):
BoundElementProperty {
createElementPropertyAst(elementSelector: string, boundProp: BoundProperty):
BoundElementPropertyAst {
if (boundProp.isAnimation) {
return new BoundElementProperty(
boundProp.name, BindingType.Animation, SecurityContext.NONE, boundProp.expression, null,
boundProp.sourceSpan);
return new BoundElementPropertyAst(
boundProp.name, PropertyBindingType.Animation, SecurityContext.NONE, boundProp.expression,
null, boundProp.sourceSpan);
}
let unit: string|null = null;
let bindingType: BindingType = undefined !;
let bindingType: PropertyBindingType = undefined !;
let boundPropertyName: string|null = null;
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
let securityContexts: SecurityContext[] = undefined !;
@ -265,15 +278,15 @@ export class BindingParser {
boundPropertyName = mergeNsAndName(ns, name);
}
bindingType = BindingType.Attribute;
bindingType = PropertyBindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
bindingType = BindingType.Class;
bindingType = PropertyBindingType.Class;
securityContexts = [SecurityContext.NONE];
} else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = parts[1];
bindingType = BindingType.Style;
bindingType = PropertyBindingType.Style;
securityContexts = [SecurityContext.STYLE];
}
}
@ -283,28 +296,29 @@ export class BindingParser {
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
securityContexts = calcPossibleSecurityContexts(
this._schemaRegistry, elementSelector, boundPropertyName, false);
bindingType = BindingType.Property;
bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
}
return new BoundElementProperty(
return new BoundElementPropertyAst(
boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit,
boundProp.sourceSpan);
}
parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
if (isAnimationLabel(name)) {
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
if (_isAnimationLabel(name)) {
name = name.substr(1);
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
} else {
this._parseRegularEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
}
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, targetEvents: ParsedEvent[]) {
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetEvents: BoundEventAst[]) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
@ -313,8 +327,7 @@ export class BindingParser {
case 'start':
case 'done':
const ast = this._parseAction(expression, sourceSpan);
targetEvents.push(
new ParsedEvent(eventName, phase, ParsedEventType.Animation, ast, sourceSpan));
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
break;
default:
@ -330,14 +343,14 @@ export class BindingParser {
}
}
private _parseRegularEvent(
private _parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null !, name]);
const ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name !, ast.source !]);
targetEvents.push(new ParsedEvent(eventName, target, ParsedEventType.Regular, ast, sourceSpan));
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
@ -365,7 +378,7 @@ export class BindingParser {
private _reportError(
message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.ERROR) {
this.errors.push(new ParseError(sourceSpan, message, level));
this._targetErrors.push(new ParseError(sourceSpan, message, level));
}
private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
@ -374,13 +387,12 @@ export class BindingParser {
}
}
// Make sure all the used pipes are known in `this.pipesByName`
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void {
if (ast && this.pipesByName) {
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (ast) {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => {
const pipeMeta = this.pipesByName !.get(pipeName);
const pipeMeta = this.pipesByName.get(pipeName);
if (!pipeMeta) {
this._reportError(
`The pipe '${pipeName}' could not be found`,
@ -418,7 +430,7 @@ export class PipeCollector extends RecursiveAstVisitor {
}
}
function isAnimationLabel(name: string): boolean {
function _isAnimationLabel(name: string): boolean {
return name[0] == '@';
}

Some files were not shown because too many files have changed in this diff Show More