Compare commits
130 Commits
Author | SHA1 | Date | |
---|---|---|---|
07bd4b0630 | |||
df1718d624 | |||
17e3410d98 | |||
5effc330ed | |||
3df00828d7 | |||
8c477b2f45 | |||
7787771aba | |||
7275e1beb3 | |||
12ba62e5e2 | |||
e6e007e2f1 | |||
91dd138fa5 | |||
d972d82354 | |||
bdcf46f82e | |||
79e1c7b807 | |||
d22eeb70b8 | |||
aa92512ac6 | |||
f782b08f58 | |||
4202936bbf | |||
e1faca6386 | |||
f5b0e22d35 | |||
00693d70a2 | |||
bcef5efffe | |||
13ecc140e8 | |||
709a6dea06 | |||
16cfb88c00 | |||
efee6f5199 | |||
2aa8aae76d | |||
afb4bd9ef6 | |||
d641c36a45 | |||
f4566f8128 | |||
a67c06708d | |||
d9d57d71dd | |||
e06303a987 | |||
40b92ddf21 | |||
1681e4f57f | |||
71b7654660 | |||
eaaec6979c | |||
c587c63591 | |||
f50c1da4e2 | |||
0254ce1f6c | |||
c9b765f5c0 | |||
8c975ed156 | |||
bb35fcb562 | |||
57230b70a9 | |||
43dc60ce4f | |||
230b3b73d8 | |||
0b7dc2f9ff | |||
de1f44f51f | |||
f1cfddf6d6 | |||
ef621a2f00 | |||
df9761951b | |||
f786c560f1 | |||
c5557de3e7 | |||
ec3a5b54de | |||
cf269d9ff4 | |||
5fa5ffb82a | |||
4a57dcfd8d | |||
43923ffcf5 | |||
50c37d45dc | |||
a63359689f | |||
43d3a84df3 | |||
8310c91823 | |||
b64b5ece65 | |||
ed9c2b6281 | |||
1cf5f5fa38 | |||
a32078f85e | |||
decd129a4d | |||
c3c9ecb302 | |||
af520947aa | |||
040bf57966 | |||
65a60b7456 | |||
756ef09d12 | |||
9316f95467 | |||
83d94b7504 | |||
a121136fae | |||
a6bb84e02b | |||
3898dc488e | |||
ca3f9926f9 | |||
1c012a035f | |||
38c5304b7f | |||
9a049be67f | |||
2045c9e8ee | |||
6c4ec05a4a | |||
f7bfda31ff | |||
a92b573309 | |||
4fd13d71c8 | |||
bf7b82b658 | |||
c143fee849 | |||
0286956107 | |||
e884f4854d | |||
df1822fc2a | |||
42b4b6d21b | |||
36bc2ff269 | |||
1564042fe8 | |||
41c8c30973 | |||
61129fa12d | |||
3a5b4882bc | |||
425c1e6042 | |||
58605cf350 | |||
34b31dea7c | |||
a241ab7c07 | |||
745e10e6d2 | |||
33340dbbd1 | |||
52812c08e2 | |||
52f5ae1961 | |||
9be895b6da | |||
9f1c82537e | |||
5ab5cc77bb | |||
f1b6c6efa1 | |||
45ad13560b | |||
2045268cec | |||
fb1076b44a | |||
6fc46526ae | |||
3ef5ede6d6 | |||
136621ebc9 | |||
f23b22a0f4 | |||
0ca971c5bd | |||
3a6fcee0e6 | |||
8972137c29 | |||
cc6481077f | |||
c041b93418 | |||
bc33765913 | |||
31dce72b7b | |||
212f8dbde7 | |||
44da4984f9 | |||
d95344430c | |||
131626fc61 | |||
676bb0fa7d | |||
5a849829c4 | |||
671f73448c |
66
CHANGELOG.md
66
CHANGELOG.md
@ -1,3 +1,39 @@
|
||||
<a name="2.1.0"></a>
|
||||
# [2.1.0](https://github.com/angular/angular/compare/2.1.0-rc.0...2.1.0) (2016-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **benchmarks:** allow ng2_switch benchmark to be used with AoT. ([#12124](https://github.com/angular/angular/issues/12124)) ([de1f44f](https://github.com/angular/angular/commit/de1f44f))
|
||||
* **compiler:** allow whitespace as `<ng-content>` content ([#12225](https://github.com/angular/angular/issues/12225)) ([df1718d](https://github.com/angular/angular/commit/df1718d))
|
||||
* **compiler:** interpolation expressions report the correct offset ([#12125](https://github.com/angular/angular/issues/12125)) ([d641c36](https://github.com/angular/angular/commit/d641c36))
|
||||
* **compiler:** properly shim `:host:before` and `:host(:before)` ([#12171](https://github.com/angular/angular/issues/12171)) ([aa92512](https://github.com/angular/angular/commit/aa92512)), closes [#12165](https://github.com/angular/angular/issues/12165)
|
||||
* **compiler:** validate `[@HostBinding](https://github.com/HostBinding)` name ([#12139](https://github.com/angular/angular/issues/12139)) ([13ecc14](https://github.com/angular/angular/commit/13ecc14))
|
||||
* **compiler-cli:** don't clone static symbols when simplifying annotation metadata ([#12158](https://github.com/angular/angular/issues/12158)) ([8c477b2](https://github.com/angular/angular/commit/8c477b2))
|
||||
* **compiler-cli:** remove peerDependency on [@angular](https://github.com/angular)/platform-server ([#12122](https://github.com/angular/angular/issues/12122)) ([71b7654](https://github.com/angular/angular/commit/71b7654))
|
||||
* **compiler-cli:** remove unused parse5 dependency from package.json ([eaaec69](https://github.com/angular/angular/commit/eaaec69))
|
||||
* **forms:** allow optional fields with pattern and minlength validators ([#12147](https://github.com/angular/angular/issues/12147)) ([d22eeb7](https://github.com/angular/angular/commit/d22eeb7))
|
||||
* **forms:** properly validate blank strings with minlength ([#12091](https://github.com/angular/angular/issues/12091)) ([f50c1da](https://github.com/angular/angular/commit/f50c1da))
|
||||
* **http:** fix Headers initialization from Headers and Object ([#12106](https://github.com/angular/angular/issues/12106)) ([f4566f8](https://github.com/angular/angular/commit/f4566f8))
|
||||
* **http:** Headers.append should append to the list ([a67c067](https://github.com/angular/angular/commit/a67c067))
|
||||
* **platform-browser-dynamic:** mark platformBrowserDynamic as stable API ([#12154](https://github.com/angular/angular/issues/12154)) ([bcef5ef](https://github.com/angular/angular/commit/bcef5ef))
|
||||
* **router:** improve error message ([#12102](https://github.com/angular/angular/issues/12102)) ([e06303a](https://github.com/angular/angular/commit/e06303a))
|
||||
* **router:** parent resolve should complete before merging resolved data ([1681e4f](https://github.com/angular/angular/commit/1681e4f)), closes [#12032](https://github.com/angular/angular/issues/12032)
|
||||
* **router:** wildcards routes should support lazy loading ([40b92dd](https://github.com/angular/angular/commit/40b92dd)), closes [#12024](https://github.com/angular/angular/issues/12024)
|
||||
* **upgrade:** allow compilerOptions in bootstrap ([#10575](https://github.com/angular/angular/issues/10575)) ([5effc33](https://github.com/angular/angular/commit/5effc33))
|
||||
|
||||
|
||||
<a name="2.1.0-rc.0"></a>
|
||||
# [2.1.0-rc.0](https://github.com/angular/angular/compare/2.1.0-beta.0...2.1.0-rc.0) (2016-10-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** provide aliases for `:enter` and `:leave` transitions ([#11991](https://github.com/angular/angular/issues/11991)) ([e884f48](https://github.com/angular/angular/commit/e884f48))
|
||||
|
||||
Note: 2.1.0-rc.0 release also contains all the changes present in the 2.0.2 release.
|
||||
|
||||
|
||||
|
||||
<a name="2.0.2"></a>
|
||||
## [2.0.2](https://github.com/angular/angular/compare/2.0.1...2.0.2) (2016-10-05)
|
||||
|
||||
@ -7,18 +43,34 @@
|
||||
* **common:** correctly removes styles on IE ([#11953](https://github.com/angular/angular/pull/11953)), closes [#7916](https://github.com/angular/angular/issues/7916)
|
||||
* **compiler:** do not embed templateUrl in view factories in non-debug mode. ([#11818](https://github.com/angular/angular/issues/11818)) ([51e1994](https://github.com/angular/angular/commit/51e1994)), closes [#11117](https://github.com/angular/angular/issues/11117)
|
||||
* **compiler:** move detection of unsafe properties for binding to ElementSchemaRegistry ([#11378](https://github.com/angular/angular/issues/11378)) ([5911c3b](https://github.com/angular/angular/commit/5911c3b))
|
||||
* **forms:** properly validate empty strings with patterns ([#11450](https://github.com/angular/angular/issues/11450)) ([e00de0c](https://github.com/angular/angular/commit/e00de0c))
|
||||
* **http:** preserve case of the first init, `set()` or `append()` ([#12023](https://github.com/angular/angular/issues/12023)) ([adb17fe](https://github.com/angular/angular/commit/adb17fe)), closes [#11624](https://github.com/angular/angular/issues/11624)
|
||||
* **compiler-cli:** allow ReflectorHost passed as argument to CodeGenerator#create ([#11951](https://github.com/angular/angular/issues/11951)) ([826c98e](https://github.com/angular/angular/commit/826c98e))
|
||||
* **router:** do not reset the router state when updating the component ([#11867](https://github.com/angular/angular/issues/11867)) ([cf750e1](https://github.com/angular/angular/commit/cf750e1))
|
||||
* **compiler:** fix `:host(tag)` and `:host-context(tag)` ([a6bb84e0](https://github.com/angular/angular/commit/a6bb84e02b7579f8d957ef6ba5b10d83482ed756)), closes [#11972](https://github.com/angular/angular/issues/11972)
|
||||
* **compiler:** fix attribute selectors in :host and :host-context ([#12056](https://github.com/angular/angular/issues/12056)) ([6f7ed32](https://github.com/angular/angular/commit/6f7ed32)), closes [#11917](https://github.com/angular/angular/issues/11917)
|
||||
* **compiler:** support `[@page](https://github.com/page)` and `[@document](https://github.com/document)` CSS rules ([#11878](https://github.com/angular/angular/issues/11878)) ([c99ef49](https://github.com/angular/angular/commit/c99ef49)), closes [#11860](https://github.com/angular/angular/issues/11860)
|
||||
* **compiler:** support `@page` and `@document` CSS rules ([#11878](https://github.com/angular/angular/issues/11878)) ([c99ef49](https://github.com/angular/angular/commit/c99ef49)), closes [#11860](https://github.com/angular/angular/issues/11860)
|
||||
* **compiler:** support `[attr="value with space"]` ([bd012ef](https://github.com/angular/angular/commit/bd012ef)), closes [#6249](https://github.com/angular/angular/issues/6249)
|
||||
* **compiler:** support quoted attribute values ([7395400](https://github.com/angular/angular/commit/7395400)), closes [#6085](https://github.com/angular/angular/issues/6085)
|
||||
* **upgrade:** bind optional properties when upgrading from ng1 ([#11411](https://github.com/angular/angular/issues/11411)) ([0851238](https://github.com/angular/angular/commit/0851238)), closes [#10181](https://github.com/angular/angular/issues/10181)
|
||||
* **http:** change a behavior when a param value is null or undefined ([#11990](https://github.com/angular/angular/issues/11990)) ([9cc0a4e](https://github.com/angular/angular/commit/9cc0a4e))
|
||||
* **compiler:** fix `<x>` ctype names ([7578d85](https://github.com/angular/angular/commit/7578d85)), closes [#12000](https://github.com/angular/angular/issues/12000)
|
||||
* **compiler-cli:** allow ReflectorHost passed as argument to CodeGenerator#create ([#11951](https://github.com/angular/angular/issues/11951)) ([826c98e](https://github.com/angular/angular/commit/826c98e))
|
||||
* **forms:** properly validate empty strings with patterns ([#11450](https://github.com/angular/angular/issues/11450)) ([e00de0c](https://github.com/angular/angular/commit/e00de0c))
|
||||
* **http:** preserve case of the first init, `set()` or `append()` ([#12023](https://github.com/angular/angular/issues/12023)) ([adb17fe](https://github.com/angular/angular/commit/adb17fe)), closes [#11624](https://github.com/angular/angular/issues/11624)
|
||||
* **http:** remove url params if provided value is null or undefined ([#11990](https://github.com/angular/angular/issues/11990)) ([9cc0a4e](https://github.com/angular/angular/commit/9cc0a4e))
|
||||
* **router:** do not reset the router state when updating the component ([#11867](https://github.com/angular/angular/issues/11867)) ([cf750e1](https://github.com/angular/angular/commit/cf750e1))
|
||||
* **upgrade:** bind optional properties when upgrading from ng1 ([#11411](https://github.com/angular/angular/issues/11411)) ([0851238](https://github.com/angular/angular/commit/0851238)), closes [#10181](https://github.com/angular/angular/issues/10181)
|
||||
|
||||
|
||||
|
||||
<a name="2.1.0-beta.0"></a>
|
||||
# [2.1.0-beta.0](https://github.com/angular/angular/compare/2.0.0...2.1.0-beta.0) (2016-09-23)
|
||||
|
||||
### Features
|
||||
|
||||
* **router:** add router preloader to optimistically preload routes ([5a84982](https://github.com/angular/angular/commit/5a84982))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
* **router:** update the router not to reset router state when updating root component ([#11799](https://github.com/angular/angular/issues/11799)) ([31dce72](https://github.com/angular/angular/commit/31dce72))
|
||||
|
||||
Note: 2.1.0-beta.0 release also contains all the changes present in the 2.0.1 release.
|
||||
|
||||
|
||||
|
||||
<a name="2.0.1"></a>
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Contributing to Angular 2
|
||||
# Contributing to Angular
|
||||
|
||||
We would love for you to contribute to Angular 2 and help make it even better than it is
|
||||
We would love for you to contribute to Angular and help make it even better than it is
|
||||
today! As a contributor, here are the guidelines we would like you to follow:
|
||||
|
||||
- [Code of Conduct](#coc)
|
||||
@ -17,17 +17,26 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
|
||||
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
If you have questions about how to *use* Angular, please direct them to the [Google Group][angular-group]
|
||||
discussion list or [StackOverflow][stackoverflow]. Please note that the Angular team's capacity to answer usage questions is limited. We are also available on [Gitter][gitter].
|
||||
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
|
||||
|
||||
## <a name="issue"></a> Found an Issue?
|
||||
StackOverflow is a much better place to ask questions since:
|
||||
|
||||
- there are thousands of people willing to help on StackOverflow
|
||||
- questions and answers stay available for public viewing so your question / answer might help someone else
|
||||
- StackOverflow's voting system assures that the best answers are prominently visible.
|
||||
|
||||
To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow.
|
||||
|
||||
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
|
||||
|
||||
## <a name="issue"></a> Found a Bug?
|
||||
If you find a bug in the source code, you can help us by
|
||||
[submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can
|
||||
[submit a Pull Request](#submit-pr) with a fix.
|
||||
|
||||
## <a name="feature"></a> Want a Feature?
|
||||
You can *request* a new feature by [submitting an issue](#submit-issue) to our [GitHub
|
||||
Repository][github]. If you would like to *implement* a new feature, please submit an issue with
|
||||
## <a name="feature"></a> Missing a Feature?
|
||||
You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub
|
||||
Repository. If you would like to *implement* a new feature, please submit an issue with
|
||||
a proposal for your work first, to be sure that we can use it.
|
||||
Please consider what kind of change it is:
|
||||
|
||||
@ -39,24 +48,22 @@ and help you to craft the change so that it is successfully accepted into the pr
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
### <a name="submit-issue"></a> Submitting an Issue
|
||||
Before you submit an issue, search the archive, maybe your question was already answered.
|
||||
|
||||
If your issue appears to be a bug, and hasn't been reported, open a new issue.
|
||||
Help us to maximize the effort we can spend fixing issues and adding new
|
||||
features, by not reporting duplicate issues. Providing the following information will increase the
|
||||
chances of your issue being dealt with quickly:
|
||||
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
|
||||
|
||||
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
|
||||
* **Angular Version** - what version of Angular is affected (e.g. 2.0.0-alpha.53)
|
||||
* **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you
|
||||
* **Browsers and Operating System** - is this a problem with all browsers?
|
||||
* **Reproduce the Error** - provide a live example (using [Plunker][plunker],
|
||||
[JSFiddle][jsfiddle] or [Runnable][runnable]) or a unambiguous set of steps
|
||||
* **Related Issues** - has a similar issue been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
|
||||
causing the problem (line of code or commit)
|
||||
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like:
|
||||
|
||||
You can file new issues by providing the above information [here](https://github.com/angular/angular/issues/new).
|
||||
- version of Angular used
|
||||
- 3rd-party libraries and their versions
|
||||
- and most importantly - a use-case that fails
|
||||
|
||||
A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. If plunker is not a suitable way to demostrate the problem (for example for issues related to our npm packaging), please create a standalone git repository demostrating the problem.
|
||||
|
||||
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
|
||||
|
||||
Unfortunately we are not able to investigate / fix bugs without a minimal reproductions, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
|
||||
|
||||
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
|
||||
|
||||
|
||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||
@ -94,7 +101,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
* In GitHub, send a pull request to `angular:master`.
|
||||
* If we suggest changes then:
|
||||
* Make the required updates.
|
||||
* Re-run the Angular 2 test suites to ensure tests are still passing.
|
||||
* Re-run the Angular test suites to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
|
62
SAVED_REPLIES.md
Normal file
62
SAVED_REPLIES.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Saved Responses for Angular's Issue Tracker
|
||||
|
||||
The following are canned responses that the Angular team should use to close issues on our issue tracker that fall into the listed resolution categories.
|
||||
|
||||
Since GitHub currently doesn't allow us to have a repository-wide or organization-wide list of [saved replies](https://help.github.com/articles/working-with-saved-replies/), these replies need to be maintained by individual team members. Since the responses can be modified in the future, all responses are versioned to simplify the process of keeping the responses up to date.
|
||||
|
||||
|
||||
## Angular: Already Fixed (v1)
|
||||
```
|
||||
Thanks for reporting this issue. Luckily it has already been fixed in one of the recent releases. Please update to the most recent version to resolve the problem.
|
||||
|
||||
If after upgrade the problem still exists in your application please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
|
||||
```
|
||||
|
||||
## Angular: Don't Understand (v1)
|
||||
```
|
||||
I'm sorry but we don't understand the problem you are reporting.
|
||||
|
||||
If the problem still exists please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
|
||||
```
|
||||
|
||||
|
||||
## Angular: Duplicate (v1)
|
||||
```
|
||||
Thanks for reporting this issue. However this issue is a duplicate of an existing issue #<ISSUE_NUMBER>. Please subscribe to that issue for future updates.
|
||||
```
|
||||
|
||||
|
||||
## Angular: Insufficient Information Provided (v1)
|
||||
```
|
||||
Thanks for reporting this issue. However, you didn't provide sufficient information for us to understand and reproduce the problem. Please check out [our submission guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue) to understand why we can't act on issues that are lacking important information.
|
||||
|
||||
If the problem still persists, please file a new issue and ensure you provide all of the required information when filling out the issue template.
|
||||
```
|
||||
|
||||
## Angular: Issue Outside of Angular (v1)
|
||||
```
|
||||
I'm sorry but this issue is not caused by Angular. Please contact the author(s) of project <PROJECT NAME> or file issue on their issue tracker.
|
||||
```
|
||||
|
||||
|
||||
## Angular: Non-reproducible (v1)
|
||||
```
|
||||
I'm sorry but we can't reproduce the problem following the instructions you provided.
|
||||
|
||||
If the problem still exists please open a new issue following [our submission guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-submitting-an-issue).
|
||||
```
|
||||
|
||||
## Angular: Obsolete (v1)
|
||||
```
|
||||
Thanks for reporting this issue. This issue is now obsolete due to changes in the recent releases. Please update to the most recent Angular version.
|
||||
|
||||
If the problem still persists, please file a new issue and ensure you provide the version of Angular affected and include the steps to reproduce the problem when filling out the issue template.
|
||||
```
|
||||
|
||||
|
||||
## Angular: Support Request (v1)
|
||||
```
|
||||
Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on [StackOverflow](http://stackoverflow.com/) using tag `angular`.
|
||||
|
||||
If you are wondering why we don't resolve support issues via the issue tracker, please [check out this explanation](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-got-a-question-or-problem).
|
||||
```
|
@ -1,7 +1,7 @@
|
||||
# Triage Process and Github Labels for Angular 2
|
||||
|
||||
This document describes how the Angular team uses labels and milestones
|
||||
to triage issues on github. The basic idea of the new process is that
|
||||
to triage issues on github. The basic idea of the process is that
|
||||
caretaker only assigns a component and type (bug, feature) label. The
|
||||
owner of the component than is in full control of how the issues should
|
||||
be triaged further.
|
||||
@ -35,7 +35,7 @@ There are few components which are cross-cutting. They don't have
|
||||
a clear location in the source tree. We will treat them as a component
|
||||
even thought no specific source tree is associated with them.
|
||||
|
||||
* `comp: documentation`: `@naomiblack`
|
||||
* `comp: docs`: `@naomiblack`
|
||||
* `comp: packaging`: `@IgorMinar`
|
||||
* `comp: performance`: `@tbosch`
|
||||
* `comp: security`: `@IgorMinar`
|
||||
@ -53,11 +53,11 @@ What kind of problem is this?
|
||||
|
||||
## Caretaker Triage Process
|
||||
|
||||
It is the caretaker's responsibility to assign `comp: *` and `type: *`
|
||||
to each new issue as they come in. The reason why we limit the
|
||||
responsibility of the caretaker to these two labels is that it is
|
||||
unlikely that without domain knowledge the caretaker could add any
|
||||
additional labels of value.
|
||||
It is the caretaker's responsibility to assign `comp: *` to each new
|
||||
issue as they come in. The reason why we limit the responsibility of the
|
||||
caretaker to this one label is that it is likely that without domain
|
||||
knowledge the caretaker could mislabel issues or lack knowledge of
|
||||
duplicate issues.
|
||||
|
||||
|
||||
## Component's owner Triage Process
|
||||
@ -68,11 +68,37 @@ process for their component.
|
||||
It will be up to the component owner to determine the order in which the
|
||||
issues within the component will be resolved.
|
||||
|
||||
Several owners have adopted the issue categorization based on
|
||||
[user pain](http://www.lostgarden.com/2008/05/improving-bug-triage-with-user-pain.html)
|
||||
used by Angular 1. In this system every issue is assigned frequency and
|
||||
severity based on which the total user pain score is calculated.
|
||||
|
||||
Following is the definition of various frequency and severity levels:
|
||||
|
||||
1. `freq(score): *` – How often does this issue come up? How many developers does this affect?
|
||||
* low (1) - obscure issue affecting a handful of developers
|
||||
* moderate (2) - impacts auxiliary usage patterns, only small number of applications are affected
|
||||
* high (3) - impacts primary usage patterns, affecting most Angular apps
|
||||
* critical (4) - impacts all Angular apps
|
||||
1. `severity(score): *` - How bad is the issue?
|
||||
* inconvenience (1) - causes ugly/boilerplate code in apps
|
||||
* confusing (2) - unexpected or inconsistent behavior; hard-to-debug
|
||||
* broken expected use (3) - it's hard or impossible for a developer using Angular to accomplish something that Angular should be able to do
|
||||
* memory leak (4)
|
||||
* regression (5) - functionality that used to work no longer works in a new release due to an unintentional change
|
||||
* security issue (6)
|
||||
|
||||
|
||||
These criteria are then used to calculate a "user pain" score as follows:
|
||||
|
||||
`pain = severity × frequency`
|
||||
|
||||
|
||||
### Assigning Issues to Milestones
|
||||
|
||||
Any issue that is being worked on must have:
|
||||
|
||||
* An `assignee`: The person doing the work.
|
||||
* An `Assignee`: The person doing the work.
|
||||
* A `Milestone`: When we expect to complete this work.
|
||||
|
||||
We aim to only have at most three milestones open at a time:
|
||||
|
40
docs/PUBLIC_API.md
Normal file
40
docs/PUBLIC_API.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Supported Public API Surface of Angular
|
||||
|
||||
Our SemVer, timed-release cycle and deprecation policy currently applies to these npm packages:
|
||||
|
||||
- `@angular/core`
|
||||
- `@angular/common`
|
||||
- `@angular/platform-browser`
|
||||
- `@angular/platform-browser-dynamic`
|
||||
- `@angular/platform-server`
|
||||
- `@angular/platform-webworker`
|
||||
- `@angular/platform-webworker-dynamic`
|
||||
- `@angular/upgrade`
|
||||
- `@angular/router`
|
||||
- `@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.
|
||||
|
||||
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
|
||||
|
||||
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature.
|
||||
|
||||
Within the supported packages, we provide guarantees for:
|
||||
|
||||
- symbols exported via the main entry point (e.g. `@angular/core`) and testing entry point (e.g. `@angular/core/testing`). This applies to both runtime/JavaScript values and TypeScript types.
|
||||
- symbols exported via global namespace `ng` (e.g. `ng.core`)
|
||||
- bundles located in the `bundles/` directory of our npm packages (e.g. `@angular/core/bundles/core.umd.js`)
|
||||
|
||||
|
||||
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/*`
|
||||
- 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
|
||||
- extending any of our classes unless the support for this is specifically documented in the API docs
|
||||
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
|
||||
|
||||
|
||||
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.
|
@ -5,7 +5,7 @@ See [here for an example project](https://github.com/angular/benchpress-tree).
|
||||
|
||||
The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.
|
||||
|
||||
License: Apache MIT 2.0
|
||||
License: MIT
|
||||
|
||||
# Why?
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
@ -48,6 +48,6 @@ export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); }
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'firefox');
|
||||
return capabilities['browserName'].toLowerCase() === 'firefox';
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {WebDriverAdapter} from '../web_driver_adapter';
|
||||
import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_extension';
|
||||
|
||||
@ -42,7 +42,7 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
var records: any[] = [];
|
||||
entries.forEach(entry => {
|
||||
var message = JSON.parse(entry['message'])['message'];
|
||||
if (StringWrapper.equals(message['method'], 'Timeline.eventRecorded')) {
|
||||
if (message['method'] === 'Timeline.eventRecorded') {
|
||||
records.push(message['params']['record']);
|
||||
}
|
||||
});
|
||||
@ -62,19 +62,16 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
var startTime = record['startTime'];
|
||||
var endTime = record['endTime'];
|
||||
|
||||
if (StringWrapper.equals(type, 'FunctionCall') &&
|
||||
(isBlank(data) || !StringWrapper.equals(data['scriptName'], 'InjectedScript'))) {
|
||||
if (type === 'FunctionCall' && (isBlank(data) || data['scriptName'] !== 'InjectedScript')) {
|
||||
events.push(createStartEvent('script', startTime));
|
||||
endEvent = createEndEvent('script', endTime);
|
||||
} else if (StringWrapper.equals(type, 'Time')) {
|
||||
} else if (type === 'Time') {
|
||||
events.push(createMarkStartEvent(data['message'], startTime));
|
||||
} else if (StringWrapper.equals(type, 'TimeEnd')) {
|
||||
} else if (type === 'TimeEnd') {
|
||||
events.push(createMarkEndEvent(data['message'], startTime));
|
||||
} else if (
|
||||
StringWrapper.equals(type, 'RecalculateStyles') || StringWrapper.equals(type, 'Layout') ||
|
||||
StringWrapper.equals(type, 'UpdateLayerTree') || StringWrapper.equals(type, 'Paint') ||
|
||||
StringWrapper.equals(type, 'Rasterize') ||
|
||||
StringWrapper.equals(type, 'CompositeLayers')) {
|
||||
type === 'RecalculateStyles' || type === 'Layout' || type === 'UpdateLayerTree' ||
|
||||
type === 'Paint' || type === 'Rasterize' || type === 'CompositeLayers') {
|
||||
events.push(createStartEvent('render', startTime));
|
||||
endEvent = createEndEvent('render', endTime);
|
||||
}
|
||||
@ -92,7 +89,7 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true}); }
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return StringWrapper.equals(capabilities['browserName'].toLowerCase(), 'safari');
|
||||
return capabilities['browserName'].toLowerCase() === 'safari';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ function createCountingValidator(
|
||||
return new MockValidator(log, (completeSample: MeasureValues[]) => {
|
||||
count--;
|
||||
if (count === 0) {
|
||||
return isPresent(validSample) ? validSample : completeSample;
|
||||
return validSample || completeSample;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {Options, ReflectiveInjector, WebDriverExtension} from '../index';
|
||||
import {StringWrapper, isPresent} from '../src/facade/lang';
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
function createExtension(ids: any[], caps: any) {
|
||||
@ -52,6 +52,6 @@ class MockExtension extends WebDriverExtension {
|
||||
constructor(public id: string) { super(); }
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return StringWrapper.equals(capabilities['browser'], this.id);
|
||||
return capabilities['browser'] === this.id;
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {Component} from '@angular/core';
|
||||
import {TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
import {Json, StringWrapper} from '../../src/facade/lang';
|
||||
import {Json} from '../../src/facade/lang';
|
||||
|
||||
export function main() {
|
||||
describe('JsonPipe', () => {
|
||||
@ -20,7 +20,7 @@ export function main() {
|
||||
var inceptionObjString: string;
|
||||
var pipe: JsonPipe;
|
||||
|
||||
function normalize(obj: string): string { return StringWrapper.replace(obj, regNewLine, ''); }
|
||||
function normalize(obj: string): string { return obj.replace(regNewLine, ''); }
|
||||
|
||||
beforeEach(() => {
|
||||
inceptionObj = {dream: {dream: {dream: 'Limbo'}}};
|
||||
|
@ -0,0 +1,6 @@
|
||||
# Overview
|
||||
|
||||
This folder will be filled with the benchmark sources
|
||||
so that we can do offline compilation for them.
|
||||
|
||||
|
@ -15,5 +15,15 @@
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": "."
|
||||
}
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/module",
|
||||
"src/bootstrap",
|
||||
"test/all_spec",
|
||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
|
||||
]
|
||||
}
|
||||
|
@ -11,13 +11,11 @@
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "^0.3.0",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"parse5": "^2.2.1",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^2.0.2",
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER",
|
||||
"@angular/platform-server": "0.0.0-PLACEHOLDER",
|
||||
"@angular/core": "0.0.0-PLACEHOLDER"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -21,7 +21,7 @@ import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry,
|
||||
import {Console} from './private_import_core';
|
||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
|
||||
|
||||
const nodeFs = require('fs');
|
||||
|
||||
@ -36,11 +36,26 @@ const PREAMBLE = `/**
|
||||
|
||||
`;
|
||||
|
||||
export class CodeGenerator {
|
||||
export class CodeGeneratorModuleCollector {
|
||||
constructor(
|
||||
private options: AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private compiler: compiler.OfflineCompiler, private reflectorHost: ReflectorHost) {}
|
||||
private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost,
|
||||
private program: ts.Program, private options: AngularCompilerOptions) {}
|
||||
|
||||
getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} {
|
||||
// Compare with false since the default should be true
|
||||
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
|
||||
GENERATED_OR_DTS_FILES :
|
||||
GENERATED_FILES;
|
||||
let filePaths = this.program.getSourceFiles()
|
||||
.filter(sf => !skipFileNames.test(sf.fileName))
|
||||
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
return {fileMetas, ngModules};
|
||||
}
|
||||
|
||||
private readFileMetadata(absSourcePath: string): FileMetadata {
|
||||
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
||||
@ -71,6 +86,18 @@ export class CodeGenerator {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeGenerator {
|
||||
private moduleCollector: CodeGeneratorModuleCollector;
|
||||
|
||||
constructor(
|
||||
private options: AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {
|
||||
this.moduleCollector =
|
||||
new CodeGeneratorModuleCollector(staticReflector, reflectorHost, program, options);
|
||||
}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
@ -95,18 +122,7 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
// Compare with false since the default should be true
|
||||
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
|
||||
GENERATED_OR_DTS_FILES :
|
||||
GENERATED_FILES;
|
||||
let filePaths = this.program.getSourceFiles()
|
||||
.filter(sf => !skipFileNames.test(sf.fileName))
|
||||
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program);
|
||||
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
|
||||
return Promise.all(fileMetas.map(
|
||||
(fileMeta) =>
|
||||
@ -185,7 +201,7 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
interface FileMetadata {
|
||||
export interface FileMetadata {
|
||||
fileUrl: string;
|
||||
components: StaticSymbol[];
|
||||
ngModules: StaticSymbol[];
|
||||
|
@ -44,6 +44,8 @@ export interface StaticReflectorHost {
|
||||
animationMetadata: string,
|
||||
provider: string
|
||||
};
|
||||
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -366,6 +368,9 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (expression instanceof StaticSymbol) {
|
||||
return expression;
|
||||
}
|
||||
if (expression) {
|
||||
if (expression['__symbolic']) {
|
||||
let staticSymbol: StaticSymbol;
|
||||
|
@ -119,6 +119,11 @@ describe('StaticReflector', () => {
|
||||
expect(simplify(noContext, 'some value')).toBe('some value');
|
||||
});
|
||||
|
||||
it('should simplify a static symbol into itself', () => {
|
||||
const staticSymbol = new StaticSymbol('', '');
|
||||
expect(simplify(noContext, staticSymbol)).toBe(staticSymbol);
|
||||
});
|
||||
|
||||
it('should simplify an array into a copy of the array', () => {
|
||||
expect(simplify(noContext, [1, 2, 3])).toEqual([1, 2, 3]);
|
||||
});
|
||||
@ -450,6 +455,9 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
provider: 'angular2/src/core/di/provider'
|
||||
};
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
||||
var cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`;
|
||||
var result = this.staticTypeCache.get(cacheKey);
|
||||
|
@ -61,7 +61,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
}
|
||||
|
||||
ast.styles.forEach(entry => {
|
||||
stylesArr.push(o.literalMap(Object.keys(entry).map(key => [key, o.literal(entry[key])])));
|
||||
const entries =
|
||||
Object.keys(entry).map((key): [string, o.Expression] => [key, o.literal(entry[key])]);
|
||||
stylesArr.push(o.literalMap(entries));
|
||||
});
|
||||
|
||||
return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([
|
||||
|
@ -115,13 +115,10 @@ function _parseAnimationStateTransition(
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): AnimationStateTransitionAst {
|
||||
var styles = new StylesCollection();
|
||||
var transitionExprs: any[] /** TODO #9100 */ = [];
|
||||
var transitionExprs: AnimationStateTransitionExpression[] = [];
|
||||
var transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
|
||||
transitionStates.forEach(expr => {
|
||||
_parseAnimationTransitionExpr(expr, errors).forEach(transExpr => {
|
||||
transitionExprs.push(transExpr);
|
||||
});
|
||||
});
|
||||
transitionStates.forEach(
|
||||
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
|
||||
var entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
|
||||
var animation = _normalizeStyleSteps(entry, stateStyles, errors);
|
||||
var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
|
||||
@ -136,9 +133,25 @@ function _parseAnimationStateTransition(
|
||||
return new AnimationStateTransitionAst(transitionExprs, stepsAst);
|
||||
}
|
||||
|
||||
function _parseAnimationAlias(alias: string, errors: AnimationParseError[]): string {
|
||||
switch (alias) {
|
||||
case ':enter':
|
||||
return 'void => *';
|
||||
case ':leave':
|
||||
return '* => void';
|
||||
default:
|
||||
errors.push(
|
||||
new AnimationParseError(`the transition alias value "${alias}" is not supported`));
|
||||
return '* => *';
|
||||
}
|
||||
}
|
||||
|
||||
function _parseAnimationTransitionExpr(
|
||||
eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
|
||||
var expressions: any[] /** TODO #9100 */ = [];
|
||||
var expressions: AnimationStateTransitionExpression[] = [];
|
||||
if (eventStr[0] == ':') {
|
||||
eventStr = _parseAnimationAlias(eventStr, errors);
|
||||
}
|
||||
var match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
|
||||
if (!isPresent(match) || match.length < 4) {
|
||||
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
|
||||
@ -165,8 +178,8 @@ function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnima
|
||||
|
||||
function _normalizeStyleMetadata(
|
||||
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): Array<{[key: string]: string | number}> {
|
||||
var normalizedStyles: any[] /** TODO #9100 */ = [];
|
||||
errors: AnimationParseError[]): {[key: string]: string | number}[] {
|
||||
var normalizedStyles: {[key: string]: string | number}[] = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (isString(styleEntry)) {
|
||||
ListWrapper.addAll(
|
||||
@ -338,7 +351,6 @@ function _parseAnimationKeyframes(
|
||||
ListWrapper.sort(rawKeyframes, (a, b) => a[0] <= b[0] ? -1 : 1);
|
||||
}
|
||||
|
||||
var i: any /** TODO #9100 */;
|
||||
var firstKeyframe = rawKeyframes[0];
|
||||
if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
|
||||
ListWrapper.insert(rawKeyframes, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
|
||||
@ -353,7 +365,7 @@ function _parseAnimationKeyframes(
|
||||
}
|
||||
|
||||
var lastKeyframeStyles = lastKeyframe[1];
|
||||
for (i = 1; i <= limit; i++) {
|
||||
for (let i = 1; i <= limit; i++) {
|
||||
let entry = rawKeyframes[i];
|
||||
let styles = entry[1];
|
||||
|
||||
@ -364,7 +376,7 @@ function _parseAnimationKeyframes(
|
||||
});
|
||||
}
|
||||
|
||||
for (i = limit - 1; i >= 0; i--) {
|
||||
for (let i = limit - 1; i >= 0; i--) {
|
||||
let entry = rawKeyframes[i];
|
||||
let styles = entry[1];
|
||||
|
||||
|
@ -304,7 +304,7 @@ export class CompileTemplateMetadata {
|
||||
this.styleUrls = _normalizeArray(styleUrls);
|
||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
|
||||
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
|
||||
this.ngContentSelectors = ngContentSelectors || [];
|
||||
if (isPresent(interpolation) && interpolation.length != 2) {
|
||||
throw new Error(`'interpolation' should have a start and an end symbol.`);
|
||||
}
|
||||
@ -590,7 +590,7 @@ export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifi
|
||||
}
|
||||
|
||||
function _normalizeArray(obj: any[]): any[] {
|
||||
return isPresent(obj) ? obj : [];
|
||||
return obj || [];
|
||||
}
|
||||
|
||||
export function isStaticSymbol(value: any): value is StaticSymbol {
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
import * as chars from '../chars';
|
||||
import {BaseError} from '../facade/errors';
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
export enum CssTokenType {
|
||||
EOF,
|
||||
@ -155,7 +155,7 @@ export class CssScanner {
|
||||
}
|
||||
|
||||
peekAt(index: number): number {
|
||||
return index >= this.length ? chars.$EOF : StringWrapper.charCodeAt(this.input, index);
|
||||
return index >= this.length ? chars.$EOF : this.input.charCodeAt(index);
|
||||
}
|
||||
|
||||
consumeEmptyStatements(): void {
|
||||
@ -310,7 +310,7 @@ export class CssScanner {
|
||||
return this.scanCharacter();
|
||||
}
|
||||
|
||||
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`);
|
||||
return this.error(`Unexpected character [${String.fromCharCode(peek)}]`);
|
||||
}
|
||||
|
||||
scanComment(): CssToken {
|
||||
@ -476,8 +476,7 @@ export class CssScanner {
|
||||
var index: number = this.index;
|
||||
var column: number = this.column;
|
||||
var line: number = this.line;
|
||||
errorTokenValue =
|
||||
isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek);
|
||||
errorTokenValue = errorTokenValue || String.fromCharCode(this.peek);
|
||||
var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue);
|
||||
var errorMessage =
|
||||
generateErrorMessage(this.input, message, errorTokenValue, index, line, column);
|
||||
@ -696,11 +695,11 @@ function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
|
||||
}
|
||||
|
||||
function charCode(input: string, index: number): number {
|
||||
return index >= input.length ? chars.$EOF : StringWrapper.charCodeAt(input, index);
|
||||
return index >= input.length ? chars.$EOF : input.charCodeAt(index);
|
||||
}
|
||||
|
||||
function charStr(code: number): string {
|
||||
return StringWrapper.fromCharCode(code);
|
||||
return String.fromCharCode(code);
|
||||
}
|
||||
|
||||
export function isNewline(code: number): boolean {
|
||||
|
@ -71,6 +71,13 @@ export class DirectiveResolver {
|
||||
} else if (a instanceof HostBinding) {
|
||||
const hostBinding: HostBinding = a;
|
||||
if (hostBinding.hostPropertyName) {
|
||||
const startWith = hostBinding.hostPropertyName[0];
|
||||
if (startWith === '(') {
|
||||
throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`);
|
||||
} else if (startWith === '[') {
|
||||
throw new Error(
|
||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||
}
|
||||
host[`[${hostBinding.hostPropertyName}]`] = propName;
|
||||
} else {
|
||||
host[`[${propName}]`] = propName;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
import * as chars from '../chars';
|
||||
import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang';
|
||||
import {NumberWrapper, StringJoiner, isPresent} from '../facade/lang';
|
||||
|
||||
export enum TokenType {
|
||||
Character,
|
||||
@ -93,7 +93,7 @@ export class Token {
|
||||
}
|
||||
|
||||
function newCharacterToken(index: number, code: number): Token {
|
||||
return new Token(index, TokenType.Character, code, StringWrapper.fromCharCode(code));
|
||||
return new Token(index, TokenType.Character, code, String.fromCharCode(code));
|
||||
}
|
||||
|
||||
function newIdentifierToken(index: number, text: string): Token {
|
||||
@ -133,8 +133,7 @@ class _Scanner {
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.peek =
|
||||
++this.index >= this.length ? chars.$EOF : StringWrapper.charCodeAt(this.input, this.index);
|
||||
this.peek = ++this.index >= this.length ? chars.$EOF : this.input.charCodeAt(this.index);
|
||||
}
|
||||
|
||||
scanToken(): Token {
|
||||
@ -146,7 +145,7 @@ class _Scanner {
|
||||
peek = chars.$EOF;
|
||||
break;
|
||||
} else {
|
||||
peek = StringWrapper.charCodeAt(input, index);
|
||||
peek = input.charCodeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,16 +186,16 @@ class _Scanner {
|
||||
case chars.$SLASH:
|
||||
case chars.$PERCENT:
|
||||
case chars.$CARET:
|
||||
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
|
||||
return this.scanOperator(start, String.fromCharCode(peek));
|
||||
case chars.$QUESTION:
|
||||
return this.scanComplexOperator(start, '?', chars.$PERIOD, '.');
|
||||
case chars.$LT:
|
||||
case chars.$GT:
|
||||
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), chars.$EQ, '=');
|
||||
return this.scanComplexOperator(start, String.fromCharCode(peek), chars.$EQ, '=');
|
||||
case chars.$BANG:
|
||||
case chars.$EQ:
|
||||
return this.scanComplexOperator(
|
||||
start, StringWrapper.fromCharCode(peek), chars.$EQ, '=', chars.$EQ, '=');
|
||||
start, String.fromCharCode(peek), chars.$EQ, '=', chars.$EQ, '=');
|
||||
case chars.$AMPERSAND:
|
||||
return this.scanComplexOperator(start, '&', chars.$AMPERSAND, '&');
|
||||
case chars.$BAR:
|
||||
@ -207,7 +206,7 @@ class _Scanner {
|
||||
}
|
||||
|
||||
this.advance();
|
||||
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
|
||||
return this.error(`Unexpected character [${String.fromCharCode(peek)}]`, 0);
|
||||
}
|
||||
|
||||
scanCharacter(start: number, code: number): Token {
|
||||
@ -310,7 +309,7 @@ class _Scanner {
|
||||
unescapedCode = unescape(this.peek);
|
||||
this.advance();
|
||||
}
|
||||
buffer.add(StringWrapper.fromCharCode(unescapedCode));
|
||||
buffer.add(String.fromCharCode(unescapedCode));
|
||||
marker = this.index;
|
||||
} else if (this.peek == chars.$EOF) {
|
||||
return this.error('Unterminated quote', 0);
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import * as chars from '../chars';
|
||||
import {StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang';
|
||||
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
|
||||
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
|
||||
@ -17,7 +17,7 @@ import {EOF, Lexer, Token, TokenType, isIdentifier, isQuote} from './lexer';
|
||||
|
||||
|
||||
export class SplitInterpolation {
|
||||
constructor(public strings: string[], public expressions: string[]) {}
|
||||
constructor(public strings: string[], public expressions: string[], public offsets: number[]) {}
|
||||
}
|
||||
|
||||
export class TemplateBindingParseResult {
|
||||
@ -41,8 +41,12 @@ export class Parser {
|
||||
input: string, location: any,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
this._checkNoInterpolation(input, location, interpolationConfig);
|
||||
const sourceToLex = this._stripComments(input);
|
||||
const tokens = this._lexer.tokenize(this._stripComments(input));
|
||||
const ast = new _ParseAST(input, location, tokens, true, this.errors).parseChain();
|
||||
const ast = new _ParseAST(
|
||||
input, location, tokens, sourceToLex.length, true, this.errors,
|
||||
input.length - sourceToLex.length)
|
||||
.parseChain();
|
||||
return new ASTWithSource(ast, input, location, this.errors);
|
||||
}
|
||||
|
||||
@ -79,8 +83,12 @@ export class Parser {
|
||||
}
|
||||
|
||||
this._checkNoInterpolation(input, location, interpolationConfig);
|
||||
var tokens = this._lexer.tokenize(this._stripComments(input));
|
||||
return new _ParseAST(input, location, tokens, false, this.errors).parseChain();
|
||||
const sourceToLex = this._stripComments(input);
|
||||
const tokens = this._lexer.tokenize(sourceToLex);
|
||||
return new _ParseAST(
|
||||
input, location, tokens, sourceToLex.length, false, this.errors,
|
||||
input.length - sourceToLex.length)
|
||||
.parseChain();
|
||||
}
|
||||
|
||||
private _parseQuote(input: string, location: any): AST {
|
||||
@ -95,7 +103,8 @@ export class Parser {
|
||||
|
||||
parseTemplateBindings(input: string, location: any): TemplateBindingParseResult {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
return new _ParseAST(input, location, tokens, false, this.errors).parseTemplateBindings();
|
||||
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
||||
.parseTemplateBindings();
|
||||
}
|
||||
|
||||
parseInterpolation(
|
||||
@ -107,8 +116,13 @@ export class Parser {
|
||||
let expressions: AST[] = [];
|
||||
|
||||
for (let i = 0; i < split.expressions.length; ++i) {
|
||||
var tokens = this._lexer.tokenize(this._stripComments(split.expressions[i]));
|
||||
var ast = new _ParseAST(input, location, tokens, false, this.errors).parseChain();
|
||||
const expressionText = split.expressions[i];
|
||||
const sourceToLex = this._stripComments(expressionText);
|
||||
const tokens = this._lexer.tokenize(this._stripComments(split.expressions[i]));
|
||||
const ast = new _ParseAST(
|
||||
input, location, tokens, sourceToLex.length, false, this.errors,
|
||||
split.offsets[i] + (expressionText.length - sourceToLex.length))
|
||||
.parseChain();
|
||||
expressions.push(ast);
|
||||
}
|
||||
|
||||
@ -122,20 +136,25 @@ export class Parser {
|
||||
input: string, location: string,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): SplitInterpolation {
|
||||
const regexp = _createInterpolateRegExp(interpolationConfig);
|
||||
const parts = StringWrapper.split(input, regexp);
|
||||
const parts = input.split(regexp);
|
||||
if (parts.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
const strings: string[] = [];
|
||||
const expressions: string[] = [];
|
||||
|
||||
const offsets: number[] = [];
|
||||
let offset = 0;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
var part: string = parts[i];
|
||||
if (i % 2 === 0) {
|
||||
// fixed string
|
||||
strings.push(part);
|
||||
offset += part.length;
|
||||
} else if (part.trim().length > 0) {
|
||||
offset += interpolationConfig.start.length;
|
||||
expressions.push(part);
|
||||
offsets.push(offset);
|
||||
offset += part.length + interpolationConfig.end.length;
|
||||
} else {
|
||||
this._reportError(
|
||||
'Blank expressions are not allowed in interpolated strings', input,
|
||||
@ -143,7 +162,7 @@ export class Parser {
|
||||
location);
|
||||
}
|
||||
}
|
||||
return new SplitInterpolation(strings, expressions);
|
||||
return new SplitInterpolation(strings, expressions, offsets);
|
||||
}
|
||||
|
||||
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
|
||||
@ -160,8 +179,8 @@ export class Parser {
|
||||
private _commentStart(input: string): number {
|
||||
var outerQuote: number = null;
|
||||
for (let i = 0; i < input.length - 1; i++) {
|
||||
const char = StringWrapper.charCodeAt(input, i);
|
||||
const nextChar = StringWrapper.charCodeAt(input, i + 1);
|
||||
const char = input.charCodeAt(i);
|
||||
const nextChar = input.charCodeAt(i + 1);
|
||||
|
||||
if (char === chars.$SLASH && nextChar == chars.$SLASH && isBlank(outerQuote)) return i;
|
||||
|
||||
@ -177,7 +196,7 @@ export class Parser {
|
||||
private _checkNoInterpolation(
|
||||
input: string, location: any, interpolationConfig: InterpolationConfig): void {
|
||||
var regexp = _createInterpolateRegExp(interpolationConfig);
|
||||
var parts = StringWrapper.split(input, regexp);
|
||||
var parts = input.split(regexp);
|
||||
if (parts.length > 1) {
|
||||
this._reportError(
|
||||
`Got interpolation (${interpolationConfig.start}${interpolationConfig.end}) where expression was expected`,
|
||||
@ -208,8 +227,9 @@ export class _ParseAST {
|
||||
index: number = 0;
|
||||
|
||||
constructor(
|
||||
public input: string, public location: any, public tokens: any[], public parseAction: boolean,
|
||||
private errors: ParserError[]) {}
|
||||
public input: string, public location: any, public tokens: Token[],
|
||||
public inputLength: number, public parseAction: boolean, private errors: ParserError[],
|
||||
private offset: number) {}
|
||||
|
||||
peek(offset: number): Token {
|
||||
var i = this.index + offset;
|
||||
@ -219,7 +239,8 @@ export class _ParseAST {
|
||||
get next(): Token { return this.peek(0); }
|
||||
|
||||
get inputIndex(): number {
|
||||
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
|
||||
return (this.index < this.tokens.length) ? this.next.index + this.offset :
|
||||
this.inputLength + this.offset;
|
||||
}
|
||||
|
||||
span(start: number) { return new ParseSpan(start, this.inputIndex); }
|
||||
@ -239,7 +260,7 @@ export class _ParseAST {
|
||||
|
||||
expectCharacter(code: number) {
|
||||
if (this.optionalCharacter(code)) return;
|
||||
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
|
||||
this.error(`Missing expected ${String.fromCharCode(code)}`);
|
||||
}
|
||||
|
||||
optionalOperator(op: string): boolean {
|
||||
@ -311,7 +332,7 @@ export class _ParseAST {
|
||||
while (this.optionalCharacter(chars.$COLON)) {
|
||||
args.push(this.parseExpression());
|
||||
}
|
||||
result = new BindingPipe(this.span(result.span.start), result, name, args);
|
||||
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args);
|
||||
} while (this.optionalOperator('|'));
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,9 @@ export class ExpansionCase implements Node {
|
||||
}
|
||||
|
||||
export class Attribute implements Node {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
constructor(
|
||||
public name: string, public value: string, public sourceSpan: ParseSourceSpan,
|
||||
public valueSpan?: ParseSourceSpan) {}
|
||||
visit(visitor: Visitor, context: any): any { return visitor.visitAttribute(this, context); }
|
||||
}
|
||||
|
||||
@ -52,6 +54,10 @@ export class Comment implements Node {
|
||||
}
|
||||
|
||||
export interface Visitor {
|
||||
// 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, context: any): any;
|
||||
|
||||
visitElement(element: Element, context: any): any;
|
||||
visitAttribute(attribute: Attribute, context: any): any;
|
||||
visitText(text: Text, context: any): any;
|
||||
@ -62,8 +68,12 @@ export interface Visitor {
|
||||
|
||||
export function visitAll(visitor: Visitor, nodes: Node[], context: any = null): any[] {
|
||||
let result: any[] = [];
|
||||
|
||||
let visit = visitor.visit ?
|
||||
(ast: Node) => visitor.visit(ast, context) || ast.visit(visitor, context) :
|
||||
(ast: Node) => ast.visit(visitor, context);
|
||||
nodes.forEach(ast => {
|
||||
const astResult = ast.visit(visitor, context);
|
||||
const astResult = visit(ast);
|
||||
if (astResult) {
|
||||
result.push(astResult);
|
||||
}
|
||||
|
@ -331,12 +331,15 @@ class _TreeBuilder {
|
||||
const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
|
||||
let end = attrName.sourceSpan.end;
|
||||
let value = '';
|
||||
let valueSpan: ParseSourceSpan;
|
||||
if (this._peek.type === lex.TokenType.ATTR_VALUE) {
|
||||
const valueToken = this._advance();
|
||||
value = valueToken.parts[0];
|
||||
end = valueToken.sourceSpan.end;
|
||||
valueSpan = valueToken.sourceSpan;
|
||||
}
|
||||
return new html.Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end));
|
||||
return new html.Attribute(
|
||||
fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end), valueSpan);
|
||||
}
|
||||
|
||||
private _getParentElement(): html.Element {
|
||||
|
@ -163,11 +163,11 @@ class _InjectorBuilder {
|
||||
if (isPresent(provider.useExisting)) {
|
||||
result = this._getDependency(new CompileDiDependencyMetadata({token: provider.useExisting}));
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
|
||||
var deps = provider.deps || provider.useFactory.diDeps;
|
||||
var depsExpr = deps.map((dep) => this._getDependency(dep));
|
||||
result = o.importExpr(provider.useFactory).callFn(depsExpr);
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
|
||||
var deps = provider.deps || provider.useClass.diDeps;
|
||||
var depsExpr = deps.map((dep) => this._getDependency(dep));
|
||||
result =
|
||||
o.importExpr(provider.useClass).instantiate(depsExpr, o.importType(provider.useClass));
|
||||
|
@ -26,7 +26,26 @@ export class SourceModule {
|
||||
}
|
||||
|
||||
export class NgModulesSummary {
|
||||
constructor(public ngModuleByComponent: Map<StaticSymbol, CompileNgModuleMetadata>) {}
|
||||
constructor(
|
||||
public ngModuleByComponent: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
public ngModules: CompileNgModuleMetadata[]) {}
|
||||
}
|
||||
|
||||
export function analyzeModules(
|
||||
ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
|
||||
const ngModuleByComponent = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
const modules: CompileNgModuleMetadata[] = [];
|
||||
|
||||
ngModules.forEach((ngModule) => {
|
||||
const ngModuleMeta = metadataResolver.getNgModuleMetadata(<any>ngModule);
|
||||
modules.push(ngModuleMeta);
|
||||
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
|
||||
if (dirMeta.isComponent) {
|
||||
ngModuleByComponent.set(dirMeta.type.reference, ngModuleMeta);
|
||||
}
|
||||
});
|
||||
});
|
||||
return new NgModulesSummary(ngModuleByComponent, modules);
|
||||
}
|
||||
|
||||
export class OfflineCompiler {
|
||||
@ -41,17 +60,7 @@ export class OfflineCompiler {
|
||||
private _localeId: string, private _translationFormat: string) {}
|
||||
|
||||
analyzeModules(ngModules: StaticSymbol[]): NgModulesSummary {
|
||||
const ngModuleByComponent = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
|
||||
ngModules.forEach((ngModule) => {
|
||||
const ngModuleMeta = this._metadataResolver.getNgModuleMetadata(<any>ngModule);
|
||||
ngModuleMeta.declaredDirectives.forEach((dirMeta) => {
|
||||
if (dirMeta.isComponent) {
|
||||
ngModuleByComponent.set(dirMeta.type.reference, ngModuleMeta);
|
||||
}
|
||||
});
|
||||
});
|
||||
return new NgModulesSummary(ngModuleByComponent);
|
||||
return analyzeModules(ngModules, this._metadataResolver);
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StringWrapper, isBlank, isPresent, isString} from '../facade/lang';
|
||||
import {isBlank, isPresent, isString} from '../facade/lang';
|
||||
|
||||
import * as o from './output_ast';
|
||||
|
||||
@ -368,7 +368,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
var useNewLine = ast.entries.length > 1;
|
||||
ctx.print(`{`, useNewLine);
|
||||
ctx.incIndent();
|
||||
this.visitAllObjects((entry: any /** TODO #9100 */) => {
|
||||
this.visitAllObjects(entry => {
|
||||
ctx.print(`${escapeIdentifier(entry[0], this._escapeDollarInStrings, false)}: `);
|
||||
entry[1].visitExpression(this, ctx);
|
||||
}, ast.entries, ctx, ',', useNewLine);
|
||||
@ -381,12 +381,11 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
expressions: o.Expression[], ctx: EmitterVisitorContext, separator: string,
|
||||
newLine: boolean = false): void {
|
||||
this.visitAllObjects(
|
||||
(expr: any /** TODO #9100 */) => expr.visitExpression(this, ctx), expressions, ctx,
|
||||
separator, newLine);
|
||||
expr => expr.visitExpression(this, ctx), expressions, ctx, separator, newLine);
|
||||
}
|
||||
|
||||
visitAllObjects(
|
||||
handler: Function, expressions: any, ctx: EmitterVisitorContext, separator: string,
|
||||
visitAllObjects<T>(
|
||||
handler: (t: T) => void, expressions: T[], ctx: EmitterVisitorContext, separator: string,
|
||||
newLine: boolean = false): void {
|
||||
for (var i = 0; i < expressions.length; i++) {
|
||||
if (i > 0) {
|
||||
@ -409,8 +408,7 @@ export function escapeIdentifier(
|
||||
if (isBlank(input)) {
|
||||
return null;
|
||||
}
|
||||
var body = StringWrapper.replaceAllMapped(
|
||||
input, _SINGLE_QUOTE_ESCAPE_STRING_RE, (match: any /** TODO #9100 */) => {
|
||||
var body = input.replace(_SINGLE_QUOTE_ESCAPE_STRING_RE, (...match: string[]) => {
|
||||
if (match[0] == '$') {
|
||||
return escapeDollar ? '\\$' : '$';
|
||||
} else if (match[0] == '\n') {
|
||||
|
@ -144,11 +144,11 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
|
||||
}
|
||||
|
||||
private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void {
|
||||
this.visitAllObjects((param: any /** TODO #9100 */) => ctx.print(param.name), params, ctx, ',');
|
||||
this.visitAllObjects(param => ctx.print(param.name), params, ctx, ',');
|
||||
}
|
||||
|
||||
getBuiltinMethodName(method: o.BuiltinMethod): string {
|
||||
var name: any /** TODO #9100 */;
|
||||
var name: string;
|
||||
switch (method) {
|
||||
case o.BuiltinMethod.ConcatArray:
|
||||
name = 'concat';
|
||||
|
@ -20,7 +20,7 @@ export class JavaScriptEmitter implements OutputEmitter {
|
||||
var converter = new JsEmitterVisitor(moduleUrl);
|
||||
var ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
var srcParts: any[] /** TODO #9100 */ = [];
|
||||
var srcParts: string[] = [];
|
||||
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
|
@ -215,7 +215,7 @@ export class ReadVarExpr extends Expression {
|
||||
export class WriteVarExpr extends Expression {
|
||||
public value: Expression;
|
||||
constructor(public name: string, value: Expression, type: Type = null) {
|
||||
super(isPresent(type) ? type : value.type);
|
||||
super(type || value.type);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ export class WriteKeyExpr extends Expression {
|
||||
public value: Expression;
|
||||
constructor(
|
||||
public receiver: Expression, public index: Expression, value: Expression, type: Type = null) {
|
||||
super(isPresent(type) ? type : value.type);
|
||||
super(type || value.type);
|
||||
this.value = value;
|
||||
}
|
||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||
@ -246,7 +246,7 @@ export class WritePropExpr extends Expression {
|
||||
public value: Expression;
|
||||
constructor(
|
||||
public receiver: Expression, public name: string, value: Expression, type: Type = null) {
|
||||
super(isPresent(type) ? type : value.type);
|
||||
super(type || value.type);
|
||||
this.value = value;
|
||||
}
|
||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||
@ -322,7 +322,7 @@ export class ConditionalExpr extends Expression {
|
||||
constructor(
|
||||
public condition: Expression, trueCase: Expression, public falseCase: Expression = null,
|
||||
type: Type = null) {
|
||||
super(isPresent(type) ? type : trueCase.type);
|
||||
super(type || trueCase.type);
|
||||
this.trueCase = trueCase;
|
||||
}
|
||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||
@ -369,7 +369,7 @@ export class BinaryOperatorExpr extends Expression {
|
||||
public lhs: Expression;
|
||||
constructor(
|
||||
public operator: BinaryOperator, lhs: Expression, public rhs: Expression, type: Type = null) {
|
||||
super(isPresent(type) ? type : lhs.type);
|
||||
super(type || lhs.type);
|
||||
this.lhs = lhs;
|
||||
}
|
||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||
@ -416,7 +416,7 @@ export class LiteralArrayExpr extends Expression {
|
||||
|
||||
export class LiteralMapExpr extends Expression {
|
||||
public valueType: Type = null;
|
||||
constructor(public entries: Array<Array<string|Expression>>, type: MapType = null) {
|
||||
constructor(public entries: [string, Expression][], type: MapType = null) {
|
||||
super(type);
|
||||
if (isPresent(type)) {
|
||||
this.valueType = type.valueType;
|
||||
@ -479,7 +479,7 @@ export class DeclareVarStmt extends Statement {
|
||||
public name: string, public value: Expression, type: Type = null,
|
||||
modifiers: StmtModifier[] = null) {
|
||||
super(modifiers);
|
||||
this.type = isPresent(type) ? type : value.type;
|
||||
this.type = type || value.type;
|
||||
}
|
||||
|
||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||
@ -625,7 +625,7 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
|
||||
expr.value.visitExpression(this, context));
|
||||
}
|
||||
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
|
||||
var method = isPresent(ast.builtin) ? ast.builtin : ast.name;
|
||||
var method = ast.builtin || ast.name;
|
||||
return new InvokeMethodExpr(
|
||||
ast.receiver.visitExpression(this, context), method,
|
||||
this.visitAllExpressions(ast.args, context), ast.type);
|
||||
@ -673,9 +673,11 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
|
||||
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
|
||||
return new LiteralArrayExpr(this.visitAllExpressions(ast.entries, context));
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||
return new LiteralMapExpr(ast.entries.map(
|
||||
(entry) => [entry[0], (<Expression>entry[1]).visitExpression(this, context)]));
|
||||
const entries = ast.entries.map(
|
||||
(entry): [string, Expression] => [entry[0], entry[1].visitExpression(this, context), ]);
|
||||
return new LiteralMapExpr(entries);
|
||||
}
|
||||
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
|
||||
return exprs.map(expr => expr.visitExpression(this, context));
|
||||
@ -881,8 +883,7 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
||||
return new LiteralArrayExpr(values, type);
|
||||
}
|
||||
|
||||
export function literalMap(
|
||||
values: Array<Array<string|Expression>>, type: MapType = null): LiteralMapExpr {
|
||||
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
||||
return new LiteralMapExpr(values, type);
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,9 @@ class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
private _evalArgValues: any[] = [];
|
||||
|
||||
getArgs(): {[key: string]: any} {
|
||||
var result = {};
|
||||
var result: {[key: string]: any} = {};
|
||||
for (var i = 0; i < this._evalArgNames.length; i++) {
|
||||
(result as any /** TODO #9100 */)[this._evalArgNames[i]] = this._evalArgValues[i];
|
||||
result[this._evalArgNames[i]] = this._evalArgValues[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export class TypeScriptEmitter implements OutputEmitter {
|
||||
var converter = new _TsEmitterVisitor(moduleUrl);
|
||||
var ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
var srcParts: any[] /** TODO #9100 */ = [];
|
||||
var srcParts: string[] = [];
|
||||
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
@ -75,6 +75,22 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
super.visitLiteralExpr(ast, ctx, '(null as any)');
|
||||
}
|
||||
|
||||
|
||||
// Temporary workaround to support strictNullCheck enabled consumers of ngc emit.
|
||||
// In SNC mode, [] have the type never[], so we cast here to any[].
|
||||
// TODO: narrow the cast to a more explicit type, or use a pattern that does not
|
||||
// start with [].concat. see https://github.com/angular/angular/pull/11846
|
||||
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any {
|
||||
if (ast.entries.length === 0) {
|
||||
ctx.print('(');
|
||||
}
|
||||
const result = super.visitLiteralArrayExpr(ast, ctx);
|
||||
if (ast.entries.length === 0) {
|
||||
ctx.print(' as any[])');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
||||
this._visitIdentifier(ast.value, ast.typeParams, ctx);
|
||||
return null;
|
||||
@ -227,7 +243,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
}
|
||||
|
||||
visitBuiltintType(type: o.BuiltinType, ctx: EmitterVisitorContext): any {
|
||||
var typeStr: any /** TODO #9100 */;
|
||||
var typeStr: string;
|
||||
switch (type.name) {
|
||||
case o.BuiltinTypeName.Bool:
|
||||
typeStr = 'boolean';
|
||||
@ -291,7 +307,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
}
|
||||
|
||||
private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void {
|
||||
this.visitAllObjects((param: any /** TODO #9100 */) => {
|
||||
this.visitAllObjects(param => {
|
||||
ctx.print(param.name);
|
||||
ctx.print(':');
|
||||
this.visitType(param.type, ctx);
|
||||
@ -320,8 +336,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
}
|
||||
if (isPresent(typeParams) && typeParams.length > 0) {
|
||||
ctx.print(`<`);
|
||||
this.visitAllObjects(
|
||||
(type: any /** TODO #9100 */) => type.visitType(this, ctx), typeParams, ctx, ',');
|
||||
this.visitAllObjects(type => type.visitType(this, ctx), typeParams, ctx, ',');
|
||||
ctx.print(`>`);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class _ValueOutputAstTransformer implements ValueTransformer {
|
||||
}
|
||||
|
||||
visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression {
|
||||
var entries: Array<string|o.Expression>[] = [];
|
||||
const entries: [string, o.Expression][] = [];
|
||||
Object.keys(map).forEach(key => { entries.push([key, visitValue(map[key], this, null)]); });
|
||||
return o.literalMap(entries, type);
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ export class ProviderElementContext {
|
||||
|
||||
private _addQueryReadsTo(token: CompileTokenMetadata, queryReadTokens: Map<any, boolean>) {
|
||||
this._getQueriesFor(token).forEach((query) => {
|
||||
const queryReadToken = isPresent(query.read) ? query.read : token;
|
||||
const queryReadToken = query.read || token;
|
||||
if (isBlank(queryReadTokens.get(queryReadToken.reference))) {
|
||||
queryReadTokens.set(queryReadToken.reference, true);
|
||||
}
|
||||
@ -169,11 +169,11 @@ export class ProviderElementContext {
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
|
||||
var deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
|
||||
var deps = provider.deps || provider.useClass.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||
}
|
||||
@ -338,11 +338,11 @@ export class NgModuleProviderAnalyzer {
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
|
||||
var deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
|
||||
var deps = provider.deps || provider.useClass.diDeps;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||
}
|
||||
|
@ -381,7 +381,10 @@ export class ShadowCss {
|
||||
|
||||
if (_polyfillHostRe.test(selector)) {
|
||||
const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
|
||||
return selector.replace(_polyfillHostNoCombinatorRe, (hnc, selector) => selector + replaceBy)
|
||||
return selector
|
||||
.replace(
|
||||
_polyfillHostNoCombinatorRe,
|
||||
(hnc, selector) => selector[0] === ':' ? replaceBy + selector : selector + replaceBy)
|
||||
.replace(_polyfillHostRe, replaceBy + ' ');
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
// Some of the code comes from WebComponents.JS
|
||||
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
|
||||
|
||||
import {StringWrapper, isBlank, isPresent} from './facade/lang';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
|
||||
import {UrlResolver} from './url_resolver';
|
||||
|
||||
@ -30,8 +30,8 @@ export function isStyleUrlResolvable(url: string): boolean {
|
||||
export function extractStyleUrls(
|
||||
resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports {
|
||||
var foundUrls: string[] = [];
|
||||
var modifiedCssText = StringWrapper.replaceAllMapped(cssText, _cssImportRe, (m: string[]) => {
|
||||
var url = isPresent(m[1]) ? m[1] : m[2];
|
||||
var modifiedCssText = cssText.replace(_cssImportRe, function(...m: string[]) {
|
||||
const url = m[1] || m[2];
|
||||
if (!isStyleUrlResolvable(url)) {
|
||||
// Do not attempt to resolve non-package absolute URLs with URI scheme
|
||||
return m[0];
|
||||
|
@ -10,7 +10,6 @@ import {SecurityContext} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {LifecycleHooks} from '../private_import_core';
|
||||
|
||||
@ -84,7 +83,7 @@ export class BoundEventAst implements TemplateAst {
|
||||
return visitor.visitEvent(this, context);
|
||||
}
|
||||
get fullName() {
|
||||
if (isPresent(this.target)) {
|
||||
if (this.target) {
|
||||
return `${this.target}:${this.name}`;
|
||||
} else {
|
||||
return this.name;
|
||||
@ -124,7 +123,8 @@ export class ElementAst implements TemplateAst {
|
||||
public outputs: BoundEventAst[], public references: ReferenceAst[],
|
||||
public directives: DirectiveAst[], public providers: ProviderAst[],
|
||||
public hasViewContainer: boolean, public children: TemplateAst[],
|
||||
public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {}
|
||||
public ngContentIndex: number, public sourceSpan: ParseSourceSpan,
|
||||
public endSourceSpan: ParseSourceSpan) {}
|
||||
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElement(this, context);
|
||||
@ -241,6 +241,11 @@ export enum PropertyBindingType {
|
||||
* A visitor for {@link TemplateAst} trees that will process each node.
|
||||
*/
|
||||
export interface TemplateAstVisitor {
|
||||
// Returning a truthy value from `visit()` will prevent `templateVisitAll()` from the call to
|
||||
// the typed method and result returned will become the result included in `visitAll()`s
|
||||
// result array.
|
||||
visit?(ast: TemplateAst, context: any): any;
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any;
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any;
|
||||
visitElement(ast: ElementAst, context: any): any;
|
||||
@ -260,10 +265,13 @@ export interface TemplateAstVisitor {
|
||||
*/
|
||||
export function templateVisitAll(
|
||||
visitor: TemplateAstVisitor, asts: TemplateAst[], context: any = null): any[] {
|
||||
var result: any[] = [];
|
||||
const result: any[] = [];
|
||||
const visit = visitor.visit ?
|
||||
(ast: TemplateAst) => visitor.visit(ast, context) || ast.visit(visitor, context) :
|
||||
(ast: TemplateAst) => ast.visit(visitor, context);
|
||||
asts.forEach(ast => {
|
||||
var astResult = ast.visit(visitor, context);
|
||||
if (isPresent(astResult)) {
|
||||
const astResult = visit(ast);
|
||||
if (astResult) {
|
||||
result.push(astResult);
|
||||
}
|
||||
});
|
||||
|
@ -118,22 +118,18 @@ export class TemplateParser {
|
||||
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[],
|
||||
templateUrl: string): TemplateParseResult {
|
||||
let interpolationConfig: any;
|
||||
if (component.template) {
|
||||
interpolationConfig = InterpolationConfig.fromArray(component.template.interpolation);
|
||||
}
|
||||
let htmlAstWithErrors =
|
||||
this._htmlParser.parse(template, templateUrl, true, interpolationConfig);
|
||||
const errors: ParseError[] = htmlAstWithErrors.errors;
|
||||
let result: TemplateAst[];
|
||||
|
||||
if (errors.length == 0) {
|
||||
// Transform ICU messages to angular directives
|
||||
const expandedHtmlAst = expandNodes(htmlAstWithErrors.rootNodes);
|
||||
errors.push(...expandedHtmlAst.errors);
|
||||
htmlAstWithErrors = new ParseTreeResult(expandedHtmlAst.nodes, errors);
|
||||
return this.tryParseHtml(
|
||||
this.expandHtml(this._htmlParser.parse(
|
||||
template, templateUrl, true, this.getInterpolationConfig(component))),
|
||||
component, template, directives, pipes, schemas, templateUrl);
|
||||
}
|
||||
|
||||
tryParseHtml(
|
||||
htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, template: string,
|
||||
directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[],
|
||||
schemas: SchemaMetadata[], templateUrl: string): TemplateParseResult {
|
||||
var result: TemplateAst[];
|
||||
var errors = htmlAstWithErrors.errors;
|
||||
if (htmlAstWithErrors.rootNodes.length > 0) {
|
||||
const uniqDirectives = removeIdentifierDuplicates(directives);
|
||||
const uniqPipes = removeIdentifierDuplicates(pipes);
|
||||
@ -147,7 +143,6 @@ export class TemplateParser {
|
||||
} else {
|
||||
result = [];
|
||||
}
|
||||
|
||||
this._assertNoReferenceDuplicationOnTemplate(result, errors);
|
||||
|
||||
if (errors.length > 0) {
|
||||
@ -162,6 +157,24 @@ export class TemplateParser {
|
||||
return new TemplateParseResult(result, errors);
|
||||
}
|
||||
|
||||
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
|
||||
const errors: ParseError[] = htmlAstWithErrors.errors;
|
||||
|
||||
if (errors.length == 0 || forced) {
|
||||
// Transform ICU messages to angular directives
|
||||
const expandedHtmlAst = expandNodes(htmlAstWithErrors.rootNodes);
|
||||
errors.push(...expandedHtmlAst.errors);
|
||||
htmlAstWithErrors = new ParseTreeResult(expandedHtmlAst.nodes, errors);
|
||||
}
|
||||
return htmlAstWithErrors;
|
||||
}
|
||||
|
||||
getInterpolationConfig(component: CompileDirectiveMetadata): InterpolationConfig {
|
||||
if (component.template) {
|
||||
return InterpolationConfig.fromArray(component.template.interpolation);
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_assertNoReferenceDuplicationOnTemplate(result: TemplateAst[], errors: TemplateParseError[]):
|
||||
void {
|
||||
@ -415,10 +428,8 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
let parsedElement: TemplateAst;
|
||||
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
if (isPresent(element.children) && element.children.length > 0) {
|
||||
this._reportError(
|
||||
`<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content>`,
|
||||
element.sourceSpan);
|
||||
if (element.children && !element.children.every(_isEmptyTextNode)) {
|
||||
this._reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
|
||||
}
|
||||
|
||||
parsedElement = new NgContentAst(
|
||||
@ -442,7 +453,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
nodeName, attrs, elementProps, events, references,
|
||||
providerContext.transformedDirectiveAsts, providerContext.transformProviders,
|
||||
providerContext.transformedHasViewContainer, children,
|
||||
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
|
||||
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan, element.endSourceSpan);
|
||||
|
||||
this._findComponentDirectives(directiveAsts)
|
||||
.forEach(
|
||||
@ -1083,7 +1094,7 @@ class NonBindableVisitor implements html.Visitor {
|
||||
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
return new ElementAst(
|
||||
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, children,
|
||||
ngContentIndex, ast.sourceSpan);
|
||||
ngContentIndex, ast.sourceSpan, ast.endSourceSpan);
|
||||
}
|
||||
visitComment(comment: html.Comment, context: any): any { return null; }
|
||||
|
||||
@ -1188,3 +1199,7 @@ export class PipeCollector extends RecursiveAstVisitor {
|
||||
function _isAnimationLabel(name: string): boolean {
|
||||
return name[0] == '@';
|
||||
}
|
||||
|
||||
function _isEmptyTextNode(node: html.Node): boolean {
|
||||
return node instanceof html.Text && node.value.trim().length == 0;
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core';
|
||||
|
||||
import {StringWrapper, isBlank, isPresent} from './facade/lang';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
|
||||
|
||||
const _ASSET_SCHEME = 'asset:';
|
||||
@ -74,8 +74,8 @@ export class UrlResolver {
|
||||
var pathSegements = path.split(/\//);
|
||||
resolvedUrl = `asset:${pathSegements[0]}/lib/${pathSegements.slice(1).join('/')}`;
|
||||
} else {
|
||||
prefix = StringWrapper.stripRight(prefix, '/');
|
||||
path = StringWrapper.stripLeft(path, '/');
|
||||
prefix = prefix.replace(/\/+$/, '');
|
||||
path = path.replace(/^\/+/, '');
|
||||
return `${prefix}/${path}`;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CompileTokenMetadata} from './compile_metadata';
|
||||
import {StringWrapper, isArray, isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
import {isArray, isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
import * as o from './output/output_ast';
|
||||
|
||||
export const MODULE_SUFFIX = '';
|
||||
@ -15,8 +15,7 @@ export const MODULE_SUFFIX = '';
|
||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(
|
||||
input, CAMEL_CASE_REGEXP, (m: string[]) => '-' + m[1].toLowerCase());
|
||||
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
|
||||
}
|
||||
|
||||
export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
||||
@ -34,7 +33,7 @@ function _splitAt(input: string, character: string, defaultValues: string[]): st
|
||||
}
|
||||
|
||||
export function sanitizeIdentifier(name: string): string {
|
||||
return StringWrapper.replaceAll(name, /\W/g, '_');
|
||||
return name.replace(/\W/g, '_');
|
||||
}
|
||||
|
||||
export function visitValue(value: any, visitor: ValueVisitor, context: any): any {
|
||||
@ -61,9 +60,8 @@ export class ValueTransformer implements ValueVisitor {
|
||||
return arr.map(value => visitValue(value, this, context));
|
||||
}
|
||||
visitStringMap(map: {[key: string]: any}, context: any): any {
|
||||
var result = {};
|
||||
Object.keys(map).forEach(
|
||||
key => { (result as any /** TODO #9100 */)[key] = visitValue(map[key], this, context); });
|
||||
var result: {[key: string]: any} = {};
|
||||
Object.keys(map).forEach(key => { result[key] = visitValue(map[key], this, context); });
|
||||
return result;
|
||||
}
|
||||
visitPrimitive(value: any, context: any): any { return value; }
|
||||
|
@ -161,11 +161,11 @@ export class CompileElement extends CompileNode {
|
||||
resolvedProvider.providerType,
|
||||
new CompileDiDependencyMetadata({token: provider.useExisting}));
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps;
|
||||
var deps = provider.deps || provider.useFactory.diDeps;
|
||||
var depsExpr = deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep));
|
||||
return o.importExpr(provider.useFactory).callFn(depsExpr);
|
||||
} else if (isPresent(provider.useClass)) {
|
||||
var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps;
|
||||
var deps = provider.deps || provider.useClass.diDeps;
|
||||
var depsExpr = deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep));
|
||||
return o.importExpr(provider.useClass)
|
||||
.instantiate(depsExpr, o.importType(provider.useClass));
|
||||
@ -435,6 +435,6 @@ function createProviderProperty(
|
||||
class _QueryWithRead {
|
||||
public read: CompileTokenMetadata;
|
||||
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
|
||||
this.read = isPresent(query.meta.read) ? query.meta.read : match;
|
||||
this.read = query.meta.read || match;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ export class CompileMethod {
|
||||
|
||||
resetDebugInfoExpr(nodeIndex: number, templateAst: TemplateAst): o.Expression {
|
||||
var res = this._updateDebugContext(new _DebugState(nodeIndex, templateAst));
|
||||
return isPresent(res) ? res : o.NULL_EXPR;
|
||||
return res || o.NULL_EXPR;
|
||||
}
|
||||
|
||||
resetDebugInfo(nodeIndex: number, templateAst: TemplateAst) {
|
||||
|
@ -64,7 +64,7 @@ export class CompileQuery {
|
||||
return !this._values.values.some(value => value instanceof ViewQueryValues);
|
||||
}
|
||||
|
||||
afterChildren(targetStaticMethod: any /** TODO #9100 */, targetDynamicMethod: CompileMethod) {
|
||||
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
var values = createQueryValues(this._values);
|
||||
var updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
|
||||
if (isPresent(this.ownerDirectiveExpression)) {
|
||||
|
@ -169,16 +169,16 @@ export class CompileView implements NameResolver {
|
||||
return proxyExpr.callFn(values);
|
||||
}
|
||||
|
||||
createLiteralMap(entries: Array<Array<string|o.Expression>>): o.Expression {
|
||||
createLiteralMap(entries: [string, o.Expression][]): o.Expression {
|
||||
if (entries.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
|
||||
}
|
||||
var proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
|
||||
var proxyParams: o.FnParam[] = [];
|
||||
var proxyReturnEntries: Array<Array<string|o.Expression>> = [];
|
||||
var values: o.Expression[] = [];
|
||||
const proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
|
||||
const proxyParams: o.FnParam[] = [];
|
||||
const proxyReturnEntries: [string, o.Expression][] = [];
|
||||
const values: o.Expression[] = [];
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var paramName = `p${i}`;
|
||||
const paramName = `p${i}`;
|
||||
proxyParams.push(new o.FnParam(paramName));
|
||||
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
|
||||
values.push(<o.Expression>entries[i][1]);
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {identifierToken} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
@ -46,7 +46,7 @@ export class CompileEventListener {
|
||||
public eventPhase: string, listenerIndex: number) {
|
||||
this._method = new CompileMethod(compileElement.view);
|
||||
this._methodName =
|
||||
`_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
|
||||
`_handle_${sanitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
|
||||
this._eventParam = new o.FnParam(
|
||||
EventHandlerVars.event.name,
|
||||
o.importType(this.compileElement.view.genConfig.renderTypes.renderEvent));
|
||||
@ -59,8 +59,7 @@ export class CompileEventListener {
|
||||
this._hasComponentHostListener = true;
|
||||
}
|
||||
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent);
|
||||
var context = isPresent(directiveInstance) ? directiveInstance :
|
||||
this.compileElement.view.componentContext;
|
||||
var context = directiveInstance || this.compileElement.view.componentContext;
|
||||
var actionStmts = convertCdStatementToIr(
|
||||
this.compileElement.view, context, hostEvent.handler, this.compileElement.nodeIndex);
|
||||
var lastIndex = actionStmts.length - 1;
|
||||
@ -96,7 +95,7 @@ export class CompileEventListener {
|
||||
}
|
||||
|
||||
listenToRenderer() {
|
||||
var listenExpr: any /** TODO #9100 */;
|
||||
var listenExpr: o.Expression;
|
||||
var eventListener = o.THIS_EXPR.callMethod(
|
||||
'eventHandler',
|
||||
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
|
||||
@ -148,13 +147,15 @@ export class CompileEventListener {
|
||||
export function collectEventListeners(
|
||||
hostEvents: BoundEventAst[], dirs: DirectiveAst[],
|
||||
compileElement: CompileElement): CompileEventListener[] {
|
||||
var eventListeners: CompileEventListener[] = [];
|
||||
const eventListeners: CompileEventListener[] = [];
|
||||
|
||||
hostEvents.forEach((hostEvent) => {
|
||||
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
||||
var listener = CompileEventListener.getOrCreate(
|
||||
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||
listener.addAction(hostEvent, null, null);
|
||||
});
|
||||
|
||||
dirs.forEach((directiveAst) => {
|
||||
var directiveInstance =
|
||||
compileElement.instances.get(identifierToken(directiveAst.directive.type).reference);
|
||||
@ -165,6 +166,7 @@ export function collectEventListeners(
|
||||
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
|
||||
});
|
||||
});
|
||||
|
||||
eventListeners.forEach((listener) => listener.finishMethod());
|
||||
return eventListeners;
|
||||
}
|
||||
@ -174,6 +176,7 @@ export function bindDirectiveOutputs(
|
||||
eventListeners: CompileEventListener[]) {
|
||||
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => {
|
||||
const eventName = directiveAst.directive.outputs[observablePropName];
|
||||
|
||||
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
|
||||
listener.listenToDirective(directiveInstance, observablePropName);
|
||||
});
|
||||
@ -199,6 +202,6 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
||||
return null;
|
||||
}
|
||||
|
||||
function santitizeEventName(name: string): string {
|
||||
return StringWrapper.replaceAll(name, /[^a-zA-Z_]/g, '_');
|
||||
function sanitizeEventName(name: string): string {
|
||||
return name.replace(/[^a-zA-Z_]/g, '_');
|
||||
}
|
||||
|
@ -99,10 +99,9 @@ function bindAndWriteToRenderer(
|
||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
|
||||
var fieldExpr = createBindFieldExpr(bindingIndex);
|
||||
var currValExpr = createCurrValueExpr(bindingIndex);
|
||||
var renderMethod: string;
|
||||
var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr);
|
||||
var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr);
|
||||
var updateStmts: any[] /** TODO #9100 */ = [];
|
||||
var updateStmts: o.Statement[] = [];
|
||||
var compileMethod = view.detectChangesRenderPropertiesMethod;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
|
@ -57,7 +57,7 @@ export function getViewFactoryName(
|
||||
}
|
||||
|
||||
export function createFlatArray(expressions: o.Expression[]): o.Expression {
|
||||
var lastNonArrayExpressions: any[] /** TODO #9100 */ = [];
|
||||
var lastNonArrayExpressions: o.Expression[] = [];
|
||||
var result: o.Expression = o.literalArr([]);
|
||||
for (var i = 0; i < expressions.length; i++) {
|
||||
var expr = expressions[i];
|
||||
|
@ -10,7 +10,7 @@ import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
@ -79,10 +79,10 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
if (this._isRootNode(parent)) {
|
||||
// store appElement as root node only for ViewContainers
|
||||
if (this.view.viewType !== ViewType.COMPONENT) {
|
||||
this.view.rootNodesOrAppElements.push(isPresent(vcAppEl) ? vcAppEl : node.renderNode);
|
||||
this.view.rootNodesOrAppElements.push(vcAppEl || node.renderNode);
|
||||
}
|
||||
} else if (isPresent(parent.component) && isPresent(ngContentIndex)) {
|
||||
parent.addContentNode(ngContentIndex, isPresent(vcAppEl) ? vcAppEl : node.renderNode);
|
||||
parent.addContentNode(ngContentIndex, vcAppEl || node.renderNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,7 +376,7 @@ function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
|
||||
Object.keys(data).forEach(name => { entryArray.push([name, data[name]]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
|
||||
ListWrapper.sort(entryArray);
|
||||
return entryArray;
|
||||
}
|
||||
|
||||
@ -511,10 +511,13 @@ function createViewFactory(
|
||||
templateUrlInfo = view.component.template.templateUrl;
|
||||
}
|
||||
if (view.viewIndex === 0) {
|
||||
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnExp]));
|
||||
initRenderCompTypeStmts = [new o.IfStmt(
|
||||
var animationsExpr = o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]));
|
||||
initRenderCompTypeStmts = [
|
||||
new o.IfStmt(
|
||||
renderCompTypeVar.identical(o.NULL_EXPR),
|
||||
[renderCompTypeVar
|
||||
[
|
||||
renderCompTypeVar
|
||||
.set(ViewConstructorVars.viewUtils.callMethod(
|
||||
'createRenderComponentType',
|
||||
[
|
||||
@ -524,13 +527,16 @@ function createViewFactory(
|
||||
view.styles,
|
||||
animationsExpr,
|
||||
]))
|
||||
.toStmt()])];
|
||||
.toStmt(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
return o
|
||||
.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([new o.ReturnStatement(
|
||||
o.variable(viewClass.name)
|
||||
.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([
|
||||
new o.ReturnStatement(o.variable(viewClass.name)
|
||||
.instantiate(viewClass.constructorMethod.params.map(
|
||||
(param) => o.variable(param.name))))]),
|
||||
(param) => o.variable(param.name)))),
|
||||
]),
|
||||
o.importType(resolveIdentifier(Identifiers.AppView), [getContextType(view)]))
|
||||
.toDeclStmt(view.viewFactory.name, [o.StmtModifier.Final]);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||
|
||||
export function main() {
|
||||
describe('RuntimeAnimationCompiler', () => {
|
||||
var resolver: any /** TODO #9100 */;
|
||||
var resolver: CompileMetadataResolver;
|
||||
beforeEach(
|
||||
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
||||
|
||||
|
@ -28,9 +28,9 @@ export function main() {
|
||||
(keyframe: AnimationKeyframeAst): {[key: string]: string | number} =>
|
||||
combineStyles(keyframe.styles);
|
||||
|
||||
var collectStepStyles = (step: AnimationStepAst): Array<{[key: string]: string | number}> => {
|
||||
var collectStepStyles = (step: AnimationStepAst): {[key: string]: string | number}[] => {
|
||||
var keyframes = step.keyframes;
|
||||
var styles: any[] /** TODO #9100 */ = [];
|
||||
var styles: {[key: string]: string | number}[] = [];
|
||||
if (step.startingStyles.styles.length > 0) {
|
||||
styles.push(combineStyles(step.startingStyles));
|
||||
}
|
||||
@ -38,7 +38,7 @@ export function main() {
|
||||
return styles;
|
||||
};
|
||||
|
||||
var resolver: any /** TODO #9100 */;
|
||||
var resolver: CompileMetadataResolver;
|
||||
beforeEach(
|
||||
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
|
||||
|
||||
|
@ -275,7 +275,7 @@ export function main() {
|
||||
it('should throw an error if a selector is being parsed while in the wrong mode', () => {
|
||||
var cssCode = '.class > tag';
|
||||
|
||||
var capturedMessage: any /** TODO #9100 */;
|
||||
var capturedMessage: string;
|
||||
try {
|
||||
tokenize(cssCode, false, CssLexerMode.STYLE_BLOCK);
|
||||
} catch (e) {
|
||||
@ -298,7 +298,7 @@ export function main() {
|
||||
describe('Attribute Mode', () => {
|
||||
it('should consider attribute selectors as valid input and throw when an invalid modifier is used',
|
||||
() => {
|
||||
function tokenizeAttr(modifier: any /** TODO #9100 */) {
|
||||
function tokenizeAttr(modifier: string) {
|
||||
var cssCode = 'value' + modifier + '=\'something\'';
|
||||
return tokenize(cssCode, false, CssLexerMode.ATTRIBUTE_SELECTOR);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class MyVisitor implements CssAstVisitor {
|
||||
* @internal
|
||||
*/
|
||||
_capture(method: string, ast: CssAst, context: any) {
|
||||
this.captures[method] = isPresent(this.captures[method]) ? this.captures[method] : [];
|
||||
this.captures[method] = this.captures[method] || [];
|
||||
this.captures[method].push([ast, context]);
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import {hasLifecycleHook} from '@angular/compiler/src/lifecycle_reflector';
|
||||
import {LifecycleHooks} from '@angular/core/src/metadata/lifecycle_hooks';
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {SimpleChanges} from '@angular/core';
|
||||
import {LifecycleHooks as Hooks} from '@angular/core/src/metadata/lifecycle_hooks';
|
||||
|
||||
export function main() {
|
||||
describe('Create Directive', () => {
|
||||
@ -16,92 +16,81 @@ export function main() {
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should be true when the directive has the ngOnChanges method', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnChanges, DirectiveWithOnChangesMethod))
|
||||
.toBe(true);
|
||||
expect(hasLifecycleHook(Hooks.OnChanges, DirectiveWithOnChangesMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnChanges, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnChanges, DirectiveNoHooks)).toBe(false); });
|
||||
});
|
||||
|
||||
describe('ngOnDestroy', () => {
|
||||
it('should be true when the directive has the ngOnDestroy method', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnDestroy, DirectiveWithOnDestroyMethod))
|
||||
.toBe(true);
|
||||
expect(hasLifecycleHook(Hooks.OnDestroy, DirectiveWithOnDestroyMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnDestroy, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnDestroy, DirectiveNoHooks)).toBe(false); });
|
||||
});
|
||||
|
||||
describe('ngOnInit', () => {
|
||||
it('should be true when the directive has the ngOnInit method', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnInit, DirectiveWithOnInitMethod)).toBe(true);
|
||||
});
|
||||
it('should be true when the directive has the ngOnInit method',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnInit, DirectiveWithOnInitMethod)).toBe(true); });
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.OnInit, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.OnInit, DirectiveNoHooks)).toBe(false); });
|
||||
});
|
||||
|
||||
describe('ngDoCheck', () => {
|
||||
it('should be true when the directive has the ngDoCheck method', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.DoCheck, DirectiveWithOnCheckMethod)).toBe(true);
|
||||
expect(hasLifecycleHook(Hooks.DoCheck, DirectiveWithOnCheckMethod)).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.DoCheck, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.DoCheck, DirectiveNoHooks)).toBe(false); });
|
||||
});
|
||||
|
||||
describe('ngAfterContentInit', () => {
|
||||
it('should be true when the directive has the ngAfterContentInit method', () => {
|
||||
expect(hasLifecycleHook(
|
||||
LifecycleHooks.AfterContentInit, DirectiveWithAfterContentInitMethod))
|
||||
expect(hasLifecycleHook(Hooks.AfterContentInit, DirectiveWithAfterContentInitMethod))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.AfterContentInit, DirectiveNoHooks)).toBe(false);
|
||||
expect(hasLifecycleHook(Hooks.AfterContentInit, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngAfterContentChecked', () => {
|
||||
it('should be true when the directive has the ngAfterContentChecked method', () => {
|
||||
expect(hasLifecycleHook(
|
||||
LifecycleHooks.AfterContentChecked, DirectiveWithAfterContentCheckedMethod))
|
||||
expect(
|
||||
hasLifecycleHook(Hooks.AfterContentChecked, DirectiveWithAfterContentCheckedMethod))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.AfterContentChecked, DirectiveNoHooks))
|
||||
.toBe(false);
|
||||
expect(hasLifecycleHook(Hooks.AfterContentChecked, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ngAfterViewInit', () => {
|
||||
it('should be true when the directive has the ngAfterViewInit method', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.AfterViewInit, DirectiveWithAfterViewInitMethod))
|
||||
expect(hasLifecycleHook(Hooks.AfterViewInit, DirectiveWithAfterViewInitMethod))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.AfterViewInit, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
it('should be false otherwise',
|
||||
() => { expect(hasLifecycleHook(Hooks.AfterViewInit, DirectiveNoHooks)).toBe(false); });
|
||||
});
|
||||
|
||||
describe('ngAfterViewChecked', () => {
|
||||
it('should be true when the directive has the ngAfterViewChecked method', () => {
|
||||
expect(hasLifecycleHook(
|
||||
LifecycleHooks.AfterViewChecked, DirectiveWithAfterViewCheckedMethod))
|
||||
expect(hasLifecycleHook(Hooks.AfterViewChecked, DirectiveWithAfterViewCheckedMethod))
|
||||
.toBe(true);
|
||||
});
|
||||
|
||||
it('should be false otherwise', () => {
|
||||
expect(hasLifecycleHook(LifecycleHooks.AfterViewChecked, DirectiveNoHooks)).toBe(false);
|
||||
expect(hasLifecycleHook(Hooks.AfterViewChecked, DirectiveNoHooks)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -111,7 +100,7 @@ export function main() {
|
||||
class DirectiveNoHooks {}
|
||||
|
||||
class DirectiveWithOnChangesMethod {
|
||||
ngOnChanges(_: any /** TODO #9100 */) {}
|
||||
ngOnChanges(_: SimpleChanges) {}
|
||||
}
|
||||
|
||||
class DirectiveWithOnInitMethod {
|
||||
|
@ -435,7 +435,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function programResourceLoaderSpy(spy: SpyResourceLoader, results: {[key: string]: string}) {
|
||||
spy.spy('get').andCallFake((url: string): Promise<any> => {
|
||||
spy.spy('get').and.callFake((url: string): Promise<any> => {
|
||||
var result = results[url];
|
||||
if (result) {
|
||||
return Promise.resolve(result);
|
||||
|
@ -117,6 +117,18 @@ class SomeDirectiveWithSameHostBindingAndInput {
|
||||
@Input() @HostBinding() prop: any;
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithMalformedHostBinding1 {
|
||||
@HostBinding('(a)')
|
||||
onA() {}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithMalformedHostBinding2 {
|
||||
@HostBinding('[a]')
|
||||
onA() {}
|
||||
}
|
||||
|
||||
class SomeDirectiveWithoutMetadata {}
|
||||
|
||||
export function main() {
|
||||
@ -210,6 +222,17 @@ export function main() {
|
||||
expect(directiveMetadata.host)
|
||||
.toEqual({'(c)': 'onC()', '(a)': 'onA()', '(b)': 'onB($event.value)'});
|
||||
});
|
||||
|
||||
it('should throw when @HostBinding name starts with "("', () => {
|
||||
expect(() => resolver.resolve(SomeDirectiveWithMalformedHostBinding1))
|
||||
.toThrowError('@HostBinding can not bind to events. Use @HostListener instead.');
|
||||
});
|
||||
|
||||
it('should throw when @HostBinding name starts with "["', () => {
|
||||
expect(() => resolver.resolve(SomeDirectiveWithMalformedHostBinding2))
|
||||
.toThrowError(
|
||||
`@HostBinding parameter should be a property name, 'class.<name>', or 'attr.<name>'.`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queries', () => {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {ASTWithSource, BindingPipe, Interpolation, ParserError, TemplateBinding} from '@angular/compiler/src/expression_parser/ast';
|
||||
import {Lexer} from '@angular/compiler/src/expression_parser/lexer';
|
||||
import {Parser, TemplateBindingParseResult} from '@angular/compiler/src/expression_parser/parser';
|
||||
import {Parser, SplitInterpolation, TemplateBindingParseResult} from '@angular/compiler/src/expression_parser/parser';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
import {isBlank, isPresent} from '../../src/facade/lang';
|
||||
@ -39,6 +39,10 @@ export function main() {
|
||||
return createParser().parseInterpolation(text, location);
|
||||
}
|
||||
|
||||
function splitInterpolation(text: string, location: any = null): SplitInterpolation {
|
||||
return createParser().splitInterpolation(text, location);
|
||||
}
|
||||
|
||||
function parseSimpleBinding(text: string, location: any = null): ASTWithSource {
|
||||
return createParser().parseSimpleBinding(text, location);
|
||||
}
|
||||
@ -539,5 +543,18 @@ export function main() {
|
||||
it('should be able to recover from a missing selector in a array literal',
|
||||
() => recover('[[a.], b, c]'));
|
||||
});
|
||||
|
||||
describe('offsets', () => {
|
||||
it('should retain the offsets of an interpolation', () => {
|
||||
const interpolations = splitInterpolation('{{a}} {{b}} {{c}}');
|
||||
expect(interpolations.offsets).toEqual([2, 9, 16]);
|
||||
});
|
||||
|
||||
it('should retain the offsets into the expression AST of interpolations', () => {
|
||||
const source = parseInterpolation('{{a}} {{b}} {{c}}');
|
||||
const interpolation = source.ast as Interpolation;
|
||||
expect(interpolation.expressions.map(e => e.span.start)).toEqual([2, 9, 16]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {AST, AstVisitor, Binary, BindingPipe, Chain, Conditional, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead} from '../../src/expression_parser/ast';
|
||||
import {StringWrapper, isString} from '../../src/facade/lang';
|
||||
import {isString} from '../../src/facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../src/ml_parser/interpolation_config';
|
||||
|
||||
class Unparser implements AstVisitor {
|
||||
@ -134,7 +134,7 @@ class Unparser implements AstVisitor {
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {
|
||||
if (isString(ast.value)) {
|
||||
this._expression += `"${StringWrapper.replaceAll(ast.value, Unparser._quoteRegExp, '\"')}"`;
|
||||
this._expression += `"${ast.value.replace( Unparser._quoteRegExp, '\"')}"`;
|
||||
} else {
|
||||
this._expression += `${ast.value}`;
|
||||
}
|
||||
|
@ -376,6 +376,82 @@ export function main() {
|
||||
[html.ExpansionCase, '=0', 2, '=0 {msg}'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not report a value span for an attribute without a value', () => {
|
||||
const ast = parser.parse('<div bar></div>', 'TestComp');
|
||||
expect((ast.rootNodes[0] as html.Element).attrs[0].valueSpan).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should report a value span for an attibute with a value', () => {
|
||||
const ast = parser.parse('<div bar="12"></div>', 'TestComp');
|
||||
const attr = (ast.rootNodes[0] as html.Element).attrs[0];
|
||||
expect(attr.valueSpan.start.offset).toEqual(9);
|
||||
expect(attr.valueSpan.end.offset).toEqual(13);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visitor', () => {
|
||||
it('should visit text nodes', () => {
|
||||
const result = humanizeDom(parser.parse('text', 'TestComp'));
|
||||
expect(result).toEqual([[html.Text, 'text', 0]]);
|
||||
});
|
||||
|
||||
it('should visit element nodes', () => {
|
||||
const result = humanizeDom(parser.parse('<div></div>', 'TestComp'));
|
||||
expect(result).toEqual([[html.Element, 'div', 0]]);
|
||||
});
|
||||
|
||||
it('should visit attribute nodes', () => {
|
||||
const result = humanizeDom(parser.parse('<div id="foo"></div>', 'TestComp'));
|
||||
expect(result).toContain([html.Attribute, 'id', 'foo']);
|
||||
});
|
||||
|
||||
it('should visit all nodes', () => {
|
||||
const result =
|
||||
parser.parse('<div id="foo"><span id="bar">a</span><span>b</span></div>', 'TestComp');
|
||||
const accumulator: html.Node[] = [];
|
||||
const visitor = new class {
|
||||
visit(node: html.Node, context: any) { accumulator.push(node); }
|
||||
visitElement(element: html.Element, context: any): any {
|
||||
html.visitAll(this, element.attrs);
|
||||
html.visitAll(this, element.children);
|
||||
}
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {}
|
||||
visitText(text: html.Text, context: any): any {}
|
||||
visitComment(comment: html.Comment, context: any): any {}
|
||||
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||
html.visitAll(this, expansion.cases);
|
||||
}
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {}
|
||||
};
|
||||
|
||||
html.visitAll(visitor, result.rootNodes);
|
||||
expect(accumulator.map(n => n.constructor)).toEqual([
|
||||
html.Element, html.Attribute, html.Element, html.Attribute, html.Text, html.Element,
|
||||
html.Text
|
||||
]);
|
||||
});
|
||||
|
||||
it('should skip typed visit if visit() returns a truthy value', () => {
|
||||
const visitor = new class {
|
||||
visit(node: html.Node, context: any) { return true; }
|
||||
visitElement(element: html.Element, context: any): any { throw Error('Unexpected'); }
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitText(text: html.Text, context: any): any { throw Error('Unexpected'); }
|
||||
visitComment(comment: html.Comment, context: any): any { throw Error('Unexpected'); }
|
||||
visitExpansion(expansion: html.Expansion, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any {
|
||||
throw Error('Unexpected');
|
||||
}
|
||||
};
|
||||
const result = parser.parse('<div id="foo"></div><div id="bar"></div>', 'TestComp');
|
||||
const traversal = html.visitAll(visitor, result.rootNodes);
|
||||
expect(traversal).toEqual([true, true]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
@ -43,7 +43,7 @@ export function main() {
|
||||
describe('output emitter', () => {
|
||||
outputDefs.forEach((outputDef) => {
|
||||
describe(`${outputDef['name']}`, () => {
|
||||
var expressions: any /** TODO #9100 */;
|
||||
var expressions: {[k: string]: any};
|
||||
beforeEach(() => { expressions = outputDef['getExpressions']()(); });
|
||||
|
||||
it('should support literals', () => {
|
||||
@ -109,13 +109,16 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('operators', () => {
|
||||
var ops: any /** TODO #9100 */;
|
||||
var aObj: any /** TODO #9100 */, bObj: any /** TODO #9100 */;
|
||||
var ops: {[k: string]: Function};
|
||||
var aObj: any;
|
||||
var bObj: any;
|
||||
|
||||
beforeEach(() => {
|
||||
ops = expressions['operators'];
|
||||
aObj = new Object();
|
||||
bObj = new Object();
|
||||
aObj = {};
|
||||
bObj = {};
|
||||
});
|
||||
|
||||
it('should support ==', () => {
|
||||
expect(ops['=='](aObj, aObj)).toBe(true);
|
||||
expect(ops['=='](aObj, bObj)).toBe(false);
|
||||
|
@ -53,7 +53,7 @@ export function main() {
|
||||
it('should return an error from the definitions',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var url = '/foo';
|
||||
var response: any /** TODO #9100 */ = null;
|
||||
var response: string = null;
|
||||
resourceLoader.when(url, response);
|
||||
expectResponse(resourceLoader.get(url), url, response, () => async.done());
|
||||
resourceLoader.flush();
|
||||
@ -71,7 +71,7 @@ export function main() {
|
||||
it('should return an error from the expectations',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
var url = '/foo';
|
||||
var response: any /** TODO #9100 */ = null;
|
||||
var response: string = null;
|
||||
resourceLoader.expect(url, response);
|
||||
expectResponse(resourceLoader.get(url), url, response, () => async.done());
|
||||
resourceLoader.flush();
|
||||
|
@ -117,7 +117,7 @@ export function main() {
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.spy('get').andCallFake(() => Promise.resolve('hello'));
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve('hello'));
|
||||
let ngModuleFactory: NgModuleFactory<any>;
|
||||
compiler.compileModuleAsync(SomeModule).then((f) => ngModuleFactory = f);
|
||||
tick();
|
||||
@ -132,7 +132,7 @@ export function main() {
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.spy('get').andCallFake(() => Promise.resolve(''));
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve(''));
|
||||
expect(() => compiler.compileModuleSync(SomeModule))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(SomeCompWithUrlTemplate)} is still being loaded!`);
|
||||
@ -144,7 +144,7 @@ export function main() {
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.spy('get').andCallFake(() => Promise.resolve(''));
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve(''));
|
||||
dirResolver.setView(SomeComp, new ViewMetadata({template: ''}));
|
||||
dirResolver.setView(ChildComp, new ViewMetadata({templateUrl: '/someTpl.html'}));
|
||||
expect(() => compiler.compileModuleSync(SomeModule))
|
||||
@ -161,7 +161,7 @@ export function main() {
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.spy('get').andCallFake(() => Promise.resolve('hello'));
|
||||
resourceLoader.spy('get').and.callFake(() => Promise.resolve('hello'));
|
||||
compiler.compileModuleAsync(SomeModule);
|
||||
tick();
|
||||
|
||||
|
@ -112,10 +112,8 @@ export function main() {
|
||||
it('should handle no context',
|
||||
() => { expect(s(':host {}', 'a', 'a-host')).toEqual('[a-host] {}'); });
|
||||
|
||||
it('should handle tag selector', () => {
|
||||
expect(s(':host(ul) {}', 'a', 'a-host')).toEqual('ul[a-host] {}');
|
||||
|
||||
});
|
||||
it('should handle tag selector',
|
||||
() => { expect(s(':host(ul) {}', 'a', 'a-host')).toEqual('ul[a-host] {}'); });
|
||||
|
||||
it('should handle class selector',
|
||||
() => { expect(s(':host(.x) {}', 'a', 'a-host')).toEqual('.x[a-host] {}'); });
|
||||
@ -141,6 +139,11 @@ export function main() {
|
||||
expect(s(':host([a="b"],[c=d]) {}', 'a', 'a-host'))
|
||||
.toEqual('[a="b"][a-host], [c="d"][a-host] {}');
|
||||
});
|
||||
|
||||
it('should handle pseudo selector', () => {
|
||||
expect(s(':host(:before) {}', 'a', 'a-host')).toEqual('[a-host]:before {}');
|
||||
expect(s(':host:before {}', 'a', 'a-host')).toEqual('[a-host]:before {}');
|
||||
});
|
||||
});
|
||||
|
||||
describe((':host-context'), () => {
|
||||
|
@ -11,7 +11,7 @@ import {UrlResolver} from '@angular/compiler/src/url_resolver';
|
||||
|
||||
export function main() {
|
||||
describe('extractStyleUrls', () => {
|
||||
var urlResolver: any /** TODO #9100 */;
|
||||
var urlResolver: UrlResolver;
|
||||
|
||||
beforeEach(() => { urlResolver = new UrlResolver(); });
|
||||
|
||||
|
@ -71,6 +71,116 @@ export function main() {
|
||||
}));
|
||||
}
|
||||
|
||||
describe('TemplateAstVisitor', () => {
|
||||
function expectVisitedNode(visitor: TemplateAstVisitor, node: TemplateAst) {
|
||||
expect(node.visit(visitor, null)).toEqual(node);
|
||||
}
|
||||
|
||||
it('should visit NgContentAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitNgContent(ast: NgContentAst, context: any): any{return ast;}},
|
||||
new NgContentAst(0, 0, null));
|
||||
});
|
||||
|
||||
it('should visit EmbeddedTemplateAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends NullVisitor{
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any) { return ast; }
|
||||
},
|
||||
new EmbeddedTemplateAst([], [], [], [], [], [], false, [], 0, null));
|
||||
});
|
||||
|
||||
it('should visit ElementAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitElement(ast: ElementAst, context: any) { return ast; }},
|
||||
new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null));
|
||||
});
|
||||
|
||||
it('should visit RefererenceAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitReference(ast: ReferenceAst, context: any): any{return ast;}},
|
||||
new ReferenceAst('foo', null, null));
|
||||
});
|
||||
|
||||
it('should visit VariableAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitVariable(ast: VariableAst, context: any): any{return ast;}},
|
||||
new VariableAst('foo', 'bar', null));
|
||||
});
|
||||
|
||||
it('should visit BoundEventAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitEvent(ast: BoundEventAst, context: any): any{return ast;}},
|
||||
new BoundEventAst('foo', 'bar', 'goo', null, null));
|
||||
});
|
||||
|
||||
it('should visit BoundElementPropertyAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends NullVisitor{
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;}
|
||||
},
|
||||
new BoundElementPropertyAst('foo', null, null, null, 'bar', null));
|
||||
});
|
||||
|
||||
it('should visit AttrAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends NullVisitor{visitAttr(ast: AttrAst, context: any): any{return ast;}},
|
||||
new AttrAst('foo', 'bar', null));
|
||||
});
|
||||
|
||||
it('should visit BoundTextAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitBoundText(ast: BoundTextAst, context: any): any{return ast;}},
|
||||
new BoundTextAst(null, 0, null));
|
||||
});
|
||||
|
||||
it('should visit TextAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends NullVisitor{visitText(ast: TextAst, context: any): any{return ast;}},
|
||||
new TextAst('foo', 0, null));
|
||||
});
|
||||
|
||||
it('should visit DirectiveAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends
|
||||
NullVisitor{visitDirective(ast: DirectiveAst, context: any): any{return ast;}},
|
||||
new DirectiveAst(null, [], [], [], null));
|
||||
});
|
||||
|
||||
it('should visit DirectiveAst', () => {
|
||||
expectVisitedNode(
|
||||
new class extends NullVisitor{
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any{return ast;}
|
||||
},
|
||||
new BoundDirectivePropertyAst('foo', 'bar', null, null));
|
||||
});
|
||||
|
||||
it('should skip the typed call of a visitor if visit() returns a truthy value', () => {
|
||||
const visitor = new class extends ThrowingVisitor {
|
||||
visit(ast: TemplateAst, context: any): any { return true; }
|
||||
};
|
||||
const nodes: TemplateAst[] = [
|
||||
new NgContentAst(0, 0, null),
|
||||
new EmbeddedTemplateAst([], [], [], [], [], [], false, [], 0, null),
|
||||
new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null),
|
||||
new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null),
|
||||
new BoundEventAst('foo', 'bar', 'goo', null, null),
|
||||
new BoundElementPropertyAst('foo', null, null, null, 'bar', null),
|
||||
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
|
||||
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
|
||||
new BoundDirectivePropertyAst('foo', 'bar', null, null)
|
||||
];
|
||||
const result = templateVisitAll(visitor, nodes, null);
|
||||
expect(result).toEqual(new Array(nodes.length).fill(true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('TemplateParser template transform', () => {
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
@ -164,12 +274,17 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should parse ngContent', () => {
|
||||
var parsed = parse('<ng-content select="a">', []);
|
||||
const parsed = parse('<ng-content select="a"></ng-content>', []);
|
||||
expect(humanizeTplAst(parsed)).toEqual([[NgContentAst]]);
|
||||
});
|
||||
|
||||
it('should parse ngContent when it contains WS only', () => {
|
||||
const parsed = parse('<ng-content select="a"> \n </ng-content>', []);
|
||||
expect(humanizeTplAst(parsed)).toEqual([[NgContentAst]]);
|
||||
});
|
||||
|
||||
it('should parse ngContent regardless the namespace', () => {
|
||||
var parsed = parse('<svg><ng-content></ng-content></svg>', []);
|
||||
const parsed = parse('<svg><ng-content></ng-content></svg>', []);
|
||||
expect(humanizeTplAst(parsed)).toEqual([
|
||||
[ElementAst, ':svg:svg'],
|
||||
[NgContentAst],
|
||||
@ -1146,7 +1261,7 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
||||
});
|
||||
|
||||
describe('content projection', () => {
|
||||
var compCounter: any /** TODO #9100 */;
|
||||
var compCounter: number;
|
||||
beforeEach(() => { compCounter = 0; });
|
||||
|
||||
function createComp(
|
||||
@ -1308,10 +1423,11 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
|
||||
});
|
||||
|
||||
describe('error cases', () => {
|
||||
it('should report when ng-content has content', () => {
|
||||
it('should report when ng-content has non WS content', () => {
|
||||
expect(() => parse('<ng-content>content</ng-content>', []))
|
||||
.toThrowError(`Template parse errors:
|
||||
<ng-content> element cannot have content. <ng-content> must be immediately followed by </ng-content> ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
|
||||
.toThrowError(
|
||||
`Template parse errors:\n` +
|
||||
`<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
|
||||
});
|
||||
|
||||
it('should treat *attr on a template element as valid',
|
||||
@ -1614,6 +1730,35 @@ Property binding a not used by any directive on an embedded template. Make sure
|
||||
]);
|
||||
});
|
||||
|
||||
it('should support endSourceSpan for elements', () => {
|
||||
const tagSel = CompileDirectiveMetadata.create({
|
||||
selector: 'circle',
|
||||
type: new CompileTypeMetadata(
|
||||
{moduleUrl: someModuleUrl, name: 'elDir', reference: {} as Type<any>})
|
||||
});
|
||||
const result = parse('<circle></circle>', [tagSel]);
|
||||
const circle = result[0] as ElementAst;
|
||||
expect(circle.endSourceSpan).toBeDefined();
|
||||
expect(circle.endSourceSpan.start.offset).toBe(8);
|
||||
expect(circle.endSourceSpan.end.offset).toBe(17);
|
||||
});
|
||||
|
||||
it('should report undefined for endSourceSpan for elements without an end-tag', () => {
|
||||
const ulSel = CompileDirectiveMetadata.create({
|
||||
selector: 'ul',
|
||||
type: new CompileTypeMetadata(
|
||||
{moduleUrl: someModuleUrl, name: 'ulDir', reference: {} as Type<any>})
|
||||
});
|
||||
const liSel = CompileDirectiveMetadata.create({
|
||||
selector: 'li',
|
||||
type: new CompileTypeMetadata(
|
||||
{moduleUrl: someModuleUrl, name: 'liDir', reference: {} as Type<any>})
|
||||
});
|
||||
const result = parse('<ul><li><li></ul>', [ulSel, liSel]);
|
||||
const ul = result[0] as ElementAst;
|
||||
const li = ul.children[0] as ElementAst;
|
||||
expect(li.endSourceSpan).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pipes', () => {
|
||||
@ -1819,14 +1964,10 @@ class TemplateContentProjectionHumanizer implements TemplateAstVisitor {
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
}
|
||||
|
||||
class FooAstTransformer implements TemplateAstVisitor {
|
||||
class ThrowingVisitor implements TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst, context: any): any { throw 'not implemented'; }
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { throw 'not implemented'; }
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'div') return ast;
|
||||
return new ElementAst(
|
||||
'foo', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan);
|
||||
}
|
||||
visitElement(ast: ElementAst, context: any): any { throw 'not implemented'; }
|
||||
visitReference(ast: ReferenceAst, context: any): any { throw 'not implemented'; }
|
||||
visitVariable(ast: VariableAst, context: any): any { throw 'not implemented'; }
|
||||
visitEvent(ast: BoundEventAst, context: any): any { throw 'not implemented'; }
|
||||
@ -1840,14 +1981,39 @@ class FooAstTransformer implements TemplateAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
class FooAstTransformer extends ThrowingVisitor {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'div') return ast;
|
||||
return new ElementAst(
|
||||
'foo', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan,
|
||||
ast.endSourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
class BarAstTransformer extends FooAstTransformer {
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.name != 'foo') return ast;
|
||||
return new ElementAst(
|
||||
'bar', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan);
|
||||
'bar', [], [], [], [], [], [], false, [], ast.ngContentIndex, ast.sourceSpan,
|
||||
ast.endSourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
class NullVisitor implements TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst, context: any): any {}
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {}
|
||||
visitElement(ast: ElementAst, context: any): any {}
|
||||
visitReference(ast: ReferenceAst, context: any): any {}
|
||||
visitVariable(ast: VariableAst, context: any): any {}
|
||||
visitEvent(ast: BoundEventAst, context: any): any {}
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any {}
|
||||
visitAttr(ast: AttrAst, context: any): any {}
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {}
|
||||
visitText(ast: TextAst, context: any): any {}
|
||||
visitDirective(ast: DirectiveAst, context: any): any {}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
|
||||
}
|
||||
|
||||
class ArrayConsole implements Console {
|
||||
logs: string[] = [];
|
||||
warnings: string[] = [];
|
||||
|
@ -46,16 +46,14 @@ export class MockDirectiveResolver extends DirectiveResolver {
|
||||
|
||||
let providers = metadata.providers;
|
||||
if (isPresent(providerOverrides)) {
|
||||
const originalViewProviders: Provider[] =
|
||||
isPresent(metadata.providers) ? metadata.providers : [];
|
||||
const originalViewProviders: Provider[] = metadata.providers || [];
|
||||
providers = originalViewProviders.concat(providerOverrides);
|
||||
}
|
||||
|
||||
if (metadata instanceof Component) {
|
||||
let viewProviders = metadata.viewProviders;
|
||||
if (isPresent(viewProviderOverrides)) {
|
||||
const originalViewProviders: Provider[] =
|
||||
isPresent(metadata.viewProviders) ? metadata.viewProviders : [];
|
||||
const originalViewProviders: Provider[] = metadata.viewProviders || [];
|
||||
viewProviders = originalViewProviders.concat(viewProviderOverrides);
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class MockResourceLoader extends ResourceLoader {
|
||||
verifyNoOutstandingExpectations() {
|
||||
if (this._expectations.length === 0) return;
|
||||
|
||||
var urls: any[] /** TODO #9100 */ = [];
|
||||
var urls: string[] = [];
|
||||
for (var i = 0; i < this._expectations.length; i++) {
|
||||
var expectation = this._expectations[i];
|
||||
urls.push(expectation.url);
|
||||
|
@ -540,6 +540,22 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* ### Transition Aliases (`:enter` and `:leave`)
|
||||
*
|
||||
* Given that enter (insertion) and leave (removal) animations are so common,
|
||||
* the `transition` function accepts both `:enter` and `:leave` values which
|
||||
* are aliases for the `void => *` and `* => void` state changes.
|
||||
*
|
||||
* ```
|
||||
* transition(":enter", [
|
||||
* style({ opacity: 0 }),
|
||||
* animate(500, style({ opacity: 1 }))
|
||||
* ])
|
||||
* transition(":leave", [
|
||||
* animate(500, style({ opacity: 0 }))
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/Kez8XGWBxWue7qP7nNvF?p=preview))
|
||||
*
|
||||
* {@example core/animation/ts/dsl/animation_example.ts region='Component'}
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StringWrapper} from '../src/facade/lang';
|
||||
|
||||
import {OpaqueToken} from './di';
|
||||
|
||||
|
||||
@ -38,7 +36,7 @@ export const APP_ID_RANDOM_PROVIDER = {
|
||||
};
|
||||
|
||||
function _randomChar(): string {
|
||||
return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25));
|
||||
return String.fromCharCode(97 + Math.floor(Math.random() * 25));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,7 +47,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
private _identityChangesTail: CollectionChangeRecord = null;
|
||||
|
||||
constructor(private _trackByFn?: TrackByFn) {
|
||||
this._trackByFn = isPresent(this._trackByFn) ? this._trackByFn : trackByIdentity;
|
||||
this._trackByFn = this._trackByFn || trackByIdentity;
|
||||
}
|
||||
|
||||
get collection() { return this._collection; }
|
||||
|
@ -159,7 +159,7 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
componentFactory: ComponentFactory<C>, index: number = -1, injector: Injector = null,
|
||||
projectableNodes: any[][] = null): ComponentRef<C> {
|
||||
var s = this._createComponentInContainerScope();
|
||||
var contextInjector = isPresent(injector) ? injector : this._element.parentInjector;
|
||||
var contextInjector = injector || this._element.parentInjector;
|
||||
var componentRef = componentFactory.create(contextInjector, projectableNodes);
|
||||
this.insert(componentRef.hostView, index);
|
||||
return wtfLeave(s, componentRef);
|
||||
|
@ -16,4 +16,4 @@ export {ReflectionInfo, Reflector} from './reflector';
|
||||
* The {@link Reflector} used internally in Angular to access metadata
|
||||
* about symbols.
|
||||
*/
|
||||
export var reflector = new Reflector(new ReflectionCapabilities());
|
||||
export const reflector = new Reflector(new ReflectionCapabilities());
|
||||
|
@ -19,19 +19,11 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
|
||||
isReflectionEnabled(): boolean { return true; }
|
||||
|
||||
factory(t: Type<any>): Function {
|
||||
var prototype = t.prototype;
|
||||
return function(...args: any[]) {
|
||||
var instance = Object.create(prototype);
|
||||
t.apply(instance, args);
|
||||
return instance;
|
||||
};
|
||||
}
|
||||
factory<T>(t: Type<T>): (args: any[]) => T { return (...args: any[]) => new t(...args); }
|
||||
|
||||
/** @internal */
|
||||
_zipTypesAndAnnotations(
|
||||
paramTypes: any /** TODO #9100 */, paramAnnotations: any /** TODO #9100 */): any[][] {
|
||||
var result: any /** TODO #9100 */;
|
||||
_zipTypesAndAnnotations(paramTypes: any[], paramAnnotations: any[]): any[][] {
|
||||
var result: any[][];
|
||||
|
||||
if (typeof paramTypes === 'undefined') {
|
||||
result = new Array(paramAnnotations.length);
|
||||
@ -50,48 +42,45 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
} else {
|
||||
result[i] = [];
|
||||
}
|
||||
if (isPresent(paramAnnotations) && isPresent(paramAnnotations[i])) {
|
||||
if (paramAnnotations && isPresent(paramAnnotations[i])) {
|
||||
result[i] = result[i].concat(paramAnnotations[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parameters(typeOrFunc: Type<any>): any[][] {
|
||||
parameters(type: Type<any>): any[][] {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOrFunc).parameters)) {
|
||||
return (<any>typeOrFunc).parameters;
|
||||
if ((<any>type).parameters) {
|
||||
return (<any>type).parameters;
|
||||
}
|
||||
|
||||
// API of tsickle for lowering decorators to properties on the class.
|
||||
if (isPresent((<any>typeOrFunc).ctorParameters)) {
|
||||
let ctorParameters = (<any>typeOrFunc).ctorParameters;
|
||||
let paramTypes =
|
||||
ctorParameters.map((ctorParam: any /** TODO #9100 */) => ctorParam && ctorParam.type);
|
||||
let paramAnnotations = ctorParameters.map(
|
||||
(ctorParam: any /** TODO #9100 */) =>
|
||||
if ((<any>type).ctorParameters) {
|
||||
const ctorParameters = (<any>type).ctorParameters;
|
||||
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
|
||||
const paramAnnotations = ctorParameters.map(
|
||||
(ctorParam: any) =>
|
||||
ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators));
|
||||
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
|
||||
}
|
||||
|
||||
// API for metadata created by invoking the decorators.
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var paramAnnotations = this._reflect.getMetadata('parameters', typeOrFunc);
|
||||
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOrFunc);
|
||||
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||
const paramAnnotations = this._reflect.getMetadata('parameters', type);
|
||||
const paramTypes = this._reflect.getMetadata('design:paramtypes', type);
|
||||
if (paramTypes || paramAnnotations) {
|
||||
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
|
||||
}
|
||||
}
|
||||
// The array has to be filled with `undefined` because holes would be skipped by `some`
|
||||
let parameters = new Array((<any>typeOrFunc.length));
|
||||
parameters.fill(undefined);
|
||||
return parameters;
|
||||
return new Array((<any>type.length)).fill(undefined);
|
||||
}
|
||||
|
||||
annotations(typeOrFunc: Type<any>): any[] {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOrFunc).annotations)) {
|
||||
var annotations = (<any>typeOrFunc).annotations;
|
||||
if ((<any>typeOrFunc).annotations) {
|
||||
let annotations = (<any>typeOrFunc).annotations;
|
||||
if (isFunction(annotations) && annotations.annotations) {
|
||||
annotations = annotations.annotations;
|
||||
}
|
||||
@ -99,22 +88,22 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
// API of tsickle for lowering decorators to properties on the class.
|
||||
if (isPresent((<any>typeOrFunc).decorators)) {
|
||||
if ((<any>typeOrFunc).decorators) {
|
||||
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
|
||||
}
|
||||
|
||||
// API for metadata created by invoking the decorators.
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var annotations = this._reflect.getMetadata('annotations', typeOrFunc);
|
||||
if (isPresent(annotations)) return annotations;
|
||||
if (this._reflect && this._reflect.getMetadata) {
|
||||
const annotations = this._reflect.getMetadata('annotations', typeOrFunc);
|
||||
if (annotations) return annotations;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
propMetadata(typeOrFunc: any): {[key: string]: any[]} {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOrFunc).propMetadata)) {
|
||||
var propMetadata = (<any>typeOrFunc).propMetadata;
|
||||
if ((<any>typeOrFunc).propMetadata) {
|
||||
let propMetadata = (<any>typeOrFunc).propMetadata;
|
||||
if (isFunction(propMetadata) && propMetadata.propMetadata) {
|
||||
propMetadata = propMetadata.propMetadata;
|
||||
}
|
||||
@ -122,9 +111,9 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
// API of tsickle for lowering decorators to properties on the class.
|
||||
if (isPresent((<any>typeOrFunc).propDecorators)) {
|
||||
let propDecorators = (<any>typeOrFunc).propDecorators;
|
||||
let propMetadata = <{[key: string]: any[]}>{};
|
||||
if ((<any>typeOrFunc).propDecorators) {
|
||||
const propDecorators = (<any>typeOrFunc).propDecorators;
|
||||
const propMetadata = <{[key: string]: any[]}>{};
|
||||
Object.keys(propDecorators).forEach(prop => {
|
||||
propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]);
|
||||
});
|
||||
@ -132,9 +121,9 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
// API for metadata created by invoking the decorators.
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
|
||||
if (isPresent(propMetadata)) return propMetadata;
|
||||
if (this._reflect && this._reflect.getMetadata) {
|
||||
const propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
|
||||
if (propMetadata) return propMetadata;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@ -147,7 +136,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
hasLifecycleHook(type: any, lcInterface: Type<any>, lcProperty: string): boolean {
|
||||
if (!(type instanceof Type)) return false;
|
||||
|
||||
var proto = (<any>type).prototype;
|
||||
const proto = (<any>type).prototype;
|
||||
return !!proto[lcProperty];
|
||||
}
|
||||
|
||||
@ -158,7 +147,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
method(name: string): MethodFn {
|
||||
let functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
|
||||
const functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
|
||||
return o.${name}.apply(o, args);`;
|
||||
return <MethodFn>new Function('o', 'args', functionBody);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Type} from '../type';
|
||||
import {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||
import {ReflectorReader} from './reflector_reader';
|
||||
@ -60,10 +59,10 @@ export class Reflector extends ReflectorReader {
|
||||
* potential dead code.
|
||||
*/
|
||||
listUnusedKeys(): any[] {
|
||||
if (this._usedKeys == null) {
|
||||
if (!this._usedKeys) {
|
||||
throw new Error('Usage tracking is disabled');
|
||||
}
|
||||
var allTypes = MapWrapper.keys(this._injectableInfo);
|
||||
const allTypes = MapWrapper.keys(this._injectableInfo);
|
||||
return allTypes.filter(key => !this._usedKeys.has(key));
|
||||
}
|
||||
|
||||
@ -83,87 +82,73 @@ export class Reflector extends ReflectorReader {
|
||||
|
||||
factory(type: Type<any>): Function {
|
||||
if (this._containsReflectionInfo(type)) {
|
||||
var res = this._getReflectionInfo(type).factory;
|
||||
return isPresent(res) ? res : null;
|
||||
} else {
|
||||
return this.reflectionCapabilities.factory(type);
|
||||
return this._getReflectionInfo(type).factory || null;
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.factory(type);
|
||||
}
|
||||
|
||||
parameters(typeOrFunc: Type<any>): any[][] {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc).parameters;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.parameters(typeOrFunc);
|
||||
return this._getReflectionInfo(typeOrFunc).parameters || [];
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.parameters(typeOrFunc);
|
||||
}
|
||||
|
||||
annotations(typeOrFunc: Type<any>): any[] {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc).annotations;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.annotations(typeOrFunc);
|
||||
return this._getReflectionInfo(typeOrFunc).annotations || [];
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.annotations(typeOrFunc);
|
||||
}
|
||||
|
||||
propMetadata(typeOrFunc: Type<any>): {[key: string]: any[]} {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc).propMetadata;
|
||||
return isPresent(res) ? res : {};
|
||||
} else {
|
||||
return this.reflectionCapabilities.propMetadata(typeOrFunc);
|
||||
return this._getReflectionInfo(typeOrFunc).propMetadata || {};
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.propMetadata(typeOrFunc);
|
||||
}
|
||||
|
||||
interfaces(type: Type<any>): any[] {
|
||||
if (this._injectableInfo.has(type)) {
|
||||
var res = this._getReflectionInfo(type).interfaces;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.interfaces(type);
|
||||
return this._getReflectionInfo(type).interfaces || [];
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.interfaces(type);
|
||||
}
|
||||
|
||||
hasLifecycleHook(type: any, lcInterface: Type<any>, lcProperty: string): boolean {
|
||||
var interfaces = this.interfaces(type);
|
||||
if (interfaces.indexOf(lcInterface) !== -1) {
|
||||
if (this.interfaces(type).indexOf(lcInterface) !== -1) {
|
||||
return true;
|
||||
} else {
|
||||
return this.reflectionCapabilities.hasLifecycleHook(type, lcInterface, lcProperty);
|
||||
}
|
||||
|
||||
return this.reflectionCapabilities.hasLifecycleHook(type, lcInterface, lcProperty);
|
||||
}
|
||||
|
||||
getter(name: string): GetterFn {
|
||||
if (this._getters.has(name)) {
|
||||
return this._getters.get(name);
|
||||
} else {
|
||||
return this.reflectionCapabilities.getter(name);
|
||||
}
|
||||
return this._getters.has(name) ? this._getters.get(name) :
|
||||
this.reflectionCapabilities.getter(name);
|
||||
}
|
||||
|
||||
setter(name: string): SetterFn {
|
||||
if (this._setters.has(name)) {
|
||||
return this._setters.get(name);
|
||||
} else {
|
||||
return this.reflectionCapabilities.setter(name);
|
||||
}
|
||||
return this._setters.has(name) ? this._setters.get(name) :
|
||||
this.reflectionCapabilities.setter(name);
|
||||
}
|
||||
|
||||
method(name: string): MethodFn {
|
||||
if (this._methods.has(name)) {
|
||||
return this._methods.get(name);
|
||||
} else {
|
||||
return this.reflectionCapabilities.method(name);
|
||||
}
|
||||
return this._methods.has(name) ? this._methods.get(name) :
|
||||
this.reflectionCapabilities.method(name);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_getReflectionInfo(typeOrFunc: any): ReflectionInfo {
|
||||
if (isPresent(this._usedKeys)) {
|
||||
if (this._usedKeys) {
|
||||
this._usedKeys.add(typeOrFunc);
|
||||
}
|
||||
|
||||
return this._injectableInfo.get(typeOrFunc);
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {global, isFunction, stringify} from '../facade/lang';
|
||||
import {global, stringify} from '../facade/lang';
|
||||
import {Type} from '../type';
|
||||
|
||||
var _nextClassId = 0;
|
||||
let _nextClassId = 0;
|
||||
const Reflect = global.Reflect;
|
||||
|
||||
/**
|
||||
* Declares the interface to be used with {@link Class}.
|
||||
@ -87,7 +88,7 @@ export interface TypeDecorator {
|
||||
}
|
||||
|
||||
function extractAnnotation(annotation: any): any {
|
||||
if (isFunction(annotation) && annotation.hasOwnProperty('annotation')) {
|
||||
if (typeof annotation === 'function' && annotation.hasOwnProperty('annotation')) {
|
||||
// it is a decorator, extract annotation
|
||||
annotation = annotation.annotation;
|
||||
}
|
||||
@ -99,13 +100,16 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
fnOrArray === Number || fnOrArray === Array) {
|
||||
throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`);
|
||||
}
|
||||
if (isFunction(fnOrArray)) {
|
||||
return <Function>fnOrArray;
|
||||
} else if (fnOrArray instanceof Array) {
|
||||
|
||||
if (typeof fnOrArray === 'function') {
|
||||
return fnOrArray;
|
||||
}
|
||||
|
||||
if (Array.isArray(fnOrArray)) {
|
||||
const annotations: any[] = fnOrArray;
|
||||
const annoLength = annotations.length - 1;
|
||||
const fn: Function = fnOrArray[annoLength];
|
||||
if (!isFunction(fn)) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(
|
||||
`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`);
|
||||
}
|
||||
@ -118,11 +122,11 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
const paramAnnotations: any[] = [];
|
||||
paramsAnnotations.push(paramAnnotations);
|
||||
const annotation = annotations[i];
|
||||
if (annotation instanceof Array) {
|
||||
if (Array.isArray(annotation)) {
|
||||
for (let j = 0; j < annotation.length; j++) {
|
||||
paramAnnotations.push(extractAnnotation(annotation[j]));
|
||||
}
|
||||
} else if (isFunction(annotation)) {
|
||||
} else if (typeof annotation === 'function') {
|
||||
paramAnnotations.push(extractAnnotation(annotation));
|
||||
} else {
|
||||
paramAnnotations.push(annotation);
|
||||
@ -130,10 +134,10 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
}
|
||||
Reflect.defineMetadata('parameters', paramsAnnotations, fn);
|
||||
return fn;
|
||||
} else {
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,7 +187,7 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
*
|
||||
* ```
|
||||
* var MyService = ng.Class({
|
||||
* constructor: [String, [new Query(), QueryList], function(name, queryList) {
|
||||
* constructor: [String, [new Optional(), Service], function(name, myService) {
|
||||
* ...
|
||||
* }]
|
||||
* });
|
||||
@ -193,7 +197,7 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
*
|
||||
* ```
|
||||
* class MyService {
|
||||
* constructor(name: string, @Query() queryList: QueryList) {
|
||||
* constructor(name: string, @Optional() myService: Service) {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
@ -221,9 +225,11 @@ function applyParams(fnOrArray: (Function | any[]), key: string): Function {
|
||||
export function Class(clsDef: ClassDefinition): Type<any> {
|
||||
const constructor = applyParams(
|
||||
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
|
||||
|
||||
let proto = constructor.prototype;
|
||||
|
||||
if (clsDef.hasOwnProperty('extends')) {
|
||||
if (isFunction(clsDef.extends)) {
|
||||
if (typeof clsDef.extends === 'function') {
|
||||
(<Function>constructor).prototype = proto =
|
||||
Object.create((<Function>clsDef.extends).prototype);
|
||||
} else {
|
||||
@ -231,8 +237,9 @@ export function Class(clsDef: ClassDefinition): Type<any> {
|
||||
`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (let key in clsDef) {
|
||||
if (key != 'extends' && key != 'prototype' && clsDef.hasOwnProperty(key)) {
|
||||
if (key !== 'extends' && key !== 'prototype' && clsDef.hasOwnProperty(key)) {
|
||||
proto[key] = applyParams(<any>clsDef[key], key);
|
||||
}
|
||||
}
|
||||
@ -249,10 +256,8 @@ export function Class(clsDef: ClassDefinition): Type<any> {
|
||||
return <Type<any>>constructor;
|
||||
}
|
||||
|
||||
var Reflect = global.Reflect;
|
||||
|
||||
export function makeDecorator(
|
||||
name: string, props: {[key: string]: any}, parentClass?: any,
|
||||
name: string, props: {[name: string]: any}, parentClass?: any,
|
||||
chainFn: (fn: Function) => void = null): (...args: any[]) => (cls: any) => any {
|
||||
const metaCtor = makeMetadataCtor([props]);
|
||||
|
||||
@ -264,10 +269,11 @@ export function makeDecorator(
|
||||
if (this instanceof DecoratorFactory) {
|
||||
metaCtor.call(this, objOrType);
|
||||
return this;
|
||||
} else {
|
||||
}
|
||||
|
||||
const annotationInstance = new (<any>DecoratorFactory)(objOrType);
|
||||
const chainAnnotation =
|
||||
isFunction(this) && this.annotations instanceof Array ? this.annotations : [];
|
||||
typeof this === 'function' && Array.isArray(this.annotations) ? this.annotations : [];
|
||||
chainAnnotation.push(annotationInstance);
|
||||
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
|
||||
const annotations = Reflect.getOwnMetadata('annotations', cls) || [];
|
||||
@ -280,10 +286,11 @@ export function makeDecorator(
|
||||
if (chainFn) chainFn(TypeDecorator);
|
||||
return TypeDecorator;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentClass) {
|
||||
DecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
||||
DecoratorFactory.prototype.toString = () => `@${name}`;
|
||||
(<any>DecoratorFactory).annotationCls = DecoratorFactory;
|
||||
return DecoratorFactory;
|
||||
@ -295,12 +302,11 @@ function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any
|
||||
const argVal = args[i];
|
||||
if (Array.isArray(prop)) {
|
||||
// plain parameter
|
||||
const val = !argVal || argVal === undefined ? prop[1] : argVal;
|
||||
this[prop[0]] = val;
|
||||
this[prop[0]] = !argVal || argVal === undefined ? prop[1] : argVal;
|
||||
} else {
|
||||
for (let propName in prop) {
|
||||
const val = !argVal || argVal[propName] === undefined ? prop[propName] : argVal[propName];
|
||||
this[propName] = val;
|
||||
this[propName] =
|
||||
!argVal || argVal[propName] === undefined ? prop[propName] : argVal[propName];
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -309,7 +315,7 @@ function makeMetadataCtor(props: ([string, any] | {[key: string]: any})[]): any
|
||||
}
|
||||
|
||||
export function makeParamDecorator(
|
||||
name: string, props: ([string, any] | {[key: string]: any})[], parentClass?: any): any {
|
||||
name: string, props: ([string, any] | {[name: string]: any})[], parentClass?: any): any {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
function ParamDecoratorFactory(...args: any[]): any {
|
||||
if (this instanceof ParamDecoratorFactory) {
|
||||
@ -331,8 +337,7 @@ export function makeParamDecorator(
|
||||
}
|
||||
|
||||
parameters[index] = parameters[index] || [];
|
||||
var annotationsForParam: any[] = parameters[index];
|
||||
annotationsForParam.push(annotationInstance);
|
||||
parameters[index].push(annotationInstance);
|
||||
|
||||
Reflect.defineMetadata('parameters', parameters, cls);
|
||||
return cls;
|
||||
@ -353,8 +358,9 @@ export function makePropDecorator(
|
||||
if (this instanceof PropDecoratorFactory) {
|
||||
metaCtor.apply(this, args);
|
||||
return this;
|
||||
} else {
|
||||
var decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||
}
|
||||
|
||||
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
|
||||
return function PropDecorator(target: any, name: string) {
|
||||
const meta = Reflect.getOwnMetadata('propMetadata', target.constructor) || {};
|
||||
meta[name] = meta[name] || [];
|
||||
@ -362,7 +368,6 @@ export function makePropDecorator(
|
||||
Reflect.defineMetadata('propMetadata', meta, target.constructor);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (parentClass) {
|
||||
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
|
||||
}
|
||||
|
@ -8,25 +8,22 @@
|
||||
|
||||
import {EventEmitter} from '../facade/async';
|
||||
|
||||
import {NgZoneImpl} from './ng_zone_impl';
|
||||
|
||||
|
||||
/**
|
||||
* An injectable service for executing work inside or outside of the Angular zone.
|
||||
*
|
||||
* The most common use of this service is to optimize performance when starting a work consisting of
|
||||
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
|
||||
* Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
|
||||
* can reenter the Angular zone via {@link #run}.
|
||||
* Angular. Such tasks can be kicked off via {@link runOutsideAngular} and if needed, these tasks
|
||||
* can reenter the Angular zone via {@link run}.
|
||||
*
|
||||
* <!-- TODO: add/fix links to:
|
||||
* - docs explaining zones and the use of zones in Angular and change-detection
|
||||
* - link to runOutsideAngular/run (throughout this file!)
|
||||
* -->
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/lY9m8HLy7z06vDoUaSN2?p=preview))
|
||||
* ### Example
|
||||
* ```
|
||||
* import {Component, View, NgZone} from '@angular/core';
|
||||
* import {Component, NgZone} from '@angular/core';
|
||||
* import {NgIf} from '@angular/common';
|
||||
*
|
||||
* @Component({
|
||||
@ -67,7 +64,6 @@ import {NgZoneImpl} from './ng_zone_impl';
|
||||
* }}));
|
||||
* }
|
||||
*
|
||||
*
|
||||
* _increaseProgress(doneCallback: () => void) {
|
||||
* this.progress += 1;
|
||||
* console.log(`Current progress: ${this.progress}%`);
|
||||
@ -83,82 +79,85 @@ import {NgZoneImpl} from './ng_zone_impl';
|
||||
* @experimental
|
||||
*/
|
||||
export class NgZone {
|
||||
static isInAngularZone(): boolean { return NgZoneImpl.isInAngularZone(); }
|
||||
static assertInAngularZone(): void {
|
||||
if (!NgZoneImpl.isInAngularZone()) {
|
||||
throw new Error('Expected to be in Angular Zone, but it is not!');
|
||||
}
|
||||
}
|
||||
static assertNotInAngularZone(): void {
|
||||
if (NgZoneImpl.isInAngularZone()) {
|
||||
throw new Error('Expected to not be in Angular Zone, but it is!');
|
||||
}
|
||||
}
|
||||
|
||||
private _zoneImpl: NgZoneImpl;
|
||||
private outer: Zone;
|
||||
private inner: Zone;
|
||||
|
||||
private _hasPendingMicrotasks: boolean = false;
|
||||
private _hasPendingMacrotasks: boolean = false;
|
||||
|
||||
/** @internal */
|
||||
private _isStable = true;
|
||||
/** @internal */
|
||||
private _nesting = 0;
|
||||
/** @internal */
|
||||
private _nesting: number = 0;
|
||||
private _onUnstable: EventEmitter<any> = new EventEmitter(false);
|
||||
/** @internal */
|
||||
private _onMicrotaskEmpty: EventEmitter<any> = new EventEmitter(false);
|
||||
/** @internal */
|
||||
private _onStable: EventEmitter<any> = new EventEmitter(false);
|
||||
/** @internal */
|
||||
private _onErrorEvents: EventEmitter<any> = new EventEmitter(false);
|
||||
|
||||
constructor({enableLongStackTrace = false}) {
|
||||
this._zoneImpl = new NgZoneImpl({
|
||||
trace: enableLongStackTrace,
|
||||
onEnter: () => {
|
||||
// console.log('ZONE.enter', this._nesting, this._isStable);
|
||||
this._nesting++;
|
||||
if (this._isStable) {
|
||||
this._isStable = false;
|
||||
this._onUnstable.emit(null);
|
||||
}
|
||||
},
|
||||
onLeave: () => {
|
||||
this._nesting--;
|
||||
// console.log('ZONE.leave', this._nesting, this._isStable);
|
||||
this._checkStable();
|
||||
},
|
||||
setMicrotask: (hasMicrotasks: boolean) => {
|
||||
this._hasPendingMicrotasks = hasMicrotasks;
|
||||
this._checkStable();
|
||||
},
|
||||
setMacrotask: (hasMacrotasks: boolean) => { this._hasPendingMacrotasks = hasMacrotasks; },
|
||||
onError: (error: any) => this._onErrorEvents.emit(error)
|
||||
});
|
||||
if (typeof Zone == 'undefined') {
|
||||
throw new Error('Angular requires Zone.js prolyfill.');
|
||||
}
|
||||
|
||||
private _checkStable() {
|
||||
if (this._nesting == 0) {
|
||||
if (!this._hasPendingMicrotasks && !this._isStable) {
|
||||
try {
|
||||
// console.log('ZONE.microtaskEmpty');
|
||||
this._nesting++;
|
||||
this._onMicrotaskEmpty.emit(null);
|
||||
} finally {
|
||||
this._nesting--;
|
||||
if (!this._hasPendingMicrotasks) {
|
||||
try {
|
||||
// console.log('ZONE.stable', this._nesting, this._isStable);
|
||||
this.runOutsideAngular(() => this._onStable.emit(null));
|
||||
} finally {
|
||||
this._isStable = true;
|
||||
Zone.assertZonePatched();
|
||||
|
||||
this.outer = this.inner = Zone.current;
|
||||
|
||||
if ((Zone as any)['wtfZoneSpec']) {
|
||||
this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']);
|
||||
}
|
||||
|
||||
if (enableLongStackTrace && (Zone as any)['longStackTraceZoneSpec']) {
|
||||
this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']);
|
||||
}
|
||||
|
||||
this.forkInnerZoneWithAngularBehavior();
|
||||
}
|
||||
|
||||
static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; }
|
||||
|
||||
static assertInAngularZone(): void {
|
||||
if (!NgZone.isInAngularZone()) {
|
||||
throw new Error('Expected to be in Angular Zone, but it is not!');
|
||||
}
|
||||
}
|
||||
static assertNotInAngularZone(): void {
|
||||
if (NgZone.isInAngularZone()) {
|
||||
throw new Error('Expected to not be in Angular Zone, but it is!');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
|
||||
* outside of the Angular zone (typically started via {@link runOutsideAngular}).
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* within the Angular zone.
|
||||
*
|
||||
* If a synchronous error happens it will be rethrown and not reported via `onError`.
|
||||
*/
|
||||
run(fn: () => any): any { return this.inner.run(fn); }
|
||||
|
||||
/**
|
||||
* Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not
|
||||
* rethrown.
|
||||
*/
|
||||
runGuarded(fn: () => any): any { return this.inner.runGuarded(fn); }
|
||||
|
||||
/**
|
||||
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that
|
||||
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* outside of the Angular zone.
|
||||
*
|
||||
* Use {@link run} to reenter the Angular zone and do work that updates the application model.
|
||||
*/
|
||||
runOutsideAngular(fn: () => any): any { return this.outer.run(fn); }
|
||||
|
||||
/**
|
||||
* Notifies when code enters Angular Zone. This gets fired first on VM Turn.
|
||||
@ -185,51 +184,98 @@ export class NgZone {
|
||||
get onError(): EventEmitter<any> { return this._onErrorEvents; }
|
||||
|
||||
/**
|
||||
* Whether there are no outstanding microtasks or microtasks.
|
||||
* Whether there are no outstanding microtasks or macrotasks.
|
||||
*/
|
||||
get isStable(): boolean { return this._isStable; }
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding microtasks.
|
||||
*/
|
||||
get hasPendingMicrotasks(): boolean { return this._hasPendingMicrotasks; }
|
||||
|
||||
/**
|
||||
* Whether there are any outstanding microtasks.
|
||||
*/
|
||||
get hasPendingMacrotasks(): boolean { return this._hasPendingMacrotasks; }
|
||||
|
||||
/**
|
||||
* Executes the `fn` function synchronously within the Angular zone and returns value returned by
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
|
||||
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* within the Angular zone.
|
||||
*
|
||||
* If a synchronous error happens it will be rethrown and not reported via `onError`.
|
||||
*/
|
||||
run(fn: () => any): any { return this._zoneImpl.runInner(fn); }
|
||||
private checkStable() {
|
||||
if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) {
|
||||
try {
|
||||
this._nesting++;
|
||||
this._onMicrotaskEmpty.emit(null);
|
||||
} finally {
|
||||
this._nesting--;
|
||||
if (!this._hasPendingMicrotasks) {
|
||||
try {
|
||||
this.runOutsideAngular(() => this._onStable.emit(null));
|
||||
} finally {
|
||||
this._isStable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as #run, except that synchronous errors are caught and forwarded
|
||||
* via `onError` and not rethrown.
|
||||
*/
|
||||
runGuarded(fn: () => any): any { return this._zoneImpl.runInnerGuarded(fn); }
|
||||
private forkInnerZoneWithAngularBehavior() {
|
||||
this.inner = this.inner.fork({
|
||||
name: 'angular',
|
||||
properties: <any>{'isAngularZone': true},
|
||||
onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task,
|
||||
applyThis: any, applyArgs: any): any => {
|
||||
try {
|
||||
this.onEnter();
|
||||
return delegate.invokeTask(target, task, applyThis, applyArgs);
|
||||
} finally {
|
||||
this.onLeave();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
|
||||
* the function.
|
||||
*
|
||||
* Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that
|
||||
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
|
||||
*
|
||||
* Any future tasks or microtasks scheduled from within this function will continue executing from
|
||||
* outside of the Angular zone.
|
||||
*
|
||||
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
|
||||
*/
|
||||
runOutsideAngular(fn: () => any): any { return this._zoneImpl.runOuter(fn); }
|
||||
|
||||
onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
|
||||
applyThis: any, applyArgs: any[], source: string): any => {
|
||||
try {
|
||||
this.onEnter();
|
||||
return delegate.invoke(target, callback, applyThis, applyArgs, source);
|
||||
} finally {
|
||||
this.onLeave();
|
||||
}
|
||||
},
|
||||
|
||||
onHasTask:
|
||||
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
|
||||
delegate.hasTask(target, hasTaskState);
|
||||
if (current === target) {
|
||||
// We are only interested in hasTask events which originate from our zone
|
||||
// (A child hasTask event is not interesting to us)
|
||||
if (hasTaskState.change == 'microTask') {
|
||||
this.setHasMicrotask(hasTaskState.microTask);
|
||||
} else if (hasTaskState.change == 'macroTask') {
|
||||
this.setHasMacrotask(hasTaskState.macroTask);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
|
||||
delegate.handleError(target, error);
|
||||
this.triggerError(error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onEnter() {
|
||||
this._nesting++;
|
||||
if (this._isStable) {
|
||||
this._isStable = false;
|
||||
this._onUnstable.emit(null);
|
||||
}
|
||||
}
|
||||
|
||||
private onLeave() {
|
||||
this._nesting--;
|
||||
this.checkStable();
|
||||
}
|
||||
|
||||
private setHasMicrotask(hasMicrotasks: boolean) {
|
||||
this._hasPendingMicrotasks = hasMicrotasks;
|
||||
this.checkStable();
|
||||
}
|
||||
|
||||
private setHasMacrotask(hasMacrotasks: boolean) { this._hasPendingMacrotasks = hasMacrotasks; }
|
||||
|
||||
private triggerError(error: any) { this._onErrorEvents.emit(error); }
|
||||
}
|
||||
|
@ -1,98 +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 class NgZoneImpl {
|
||||
static isInAngularZone(): boolean { return Zone.current.get('isAngularZone') === true; }
|
||||
|
||||
/** @internal */
|
||||
private outer: Zone;
|
||||
/** @internal */
|
||||
private inner: Zone;
|
||||
|
||||
private onEnter: () => void;
|
||||
private onLeave: () => void;
|
||||
private setMicrotask: (hasMicrotasks: boolean) => void;
|
||||
private setMacrotask: (hasMacrotasks: boolean) => void;
|
||||
private onError: (error: any) => void;
|
||||
|
||||
constructor({trace, onEnter, onLeave, setMicrotask, setMacrotask, onError}: {
|
||||
trace: boolean,
|
||||
onEnter: () => void,
|
||||
onLeave: () => void,
|
||||
setMicrotask: (hasMicrotasks: boolean) => void,
|
||||
setMacrotask: (hasMacrotasks: boolean) => void,
|
||||
onError: (error: any) => void
|
||||
}) {
|
||||
this.onEnter = onEnter;
|
||||
this.onLeave = onLeave;
|
||||
this.setMicrotask = setMicrotask;
|
||||
this.setMacrotask = setMacrotask;
|
||||
this.onError = onError;
|
||||
|
||||
if (typeof Zone == 'undefined') {
|
||||
throw new Error('Angular requires Zone.js prolyfill.');
|
||||
}
|
||||
Zone.assertZonePatched();
|
||||
this.outer = this.inner = Zone.current;
|
||||
if ((Zone as any)['wtfZoneSpec']) {
|
||||
this.inner = this.inner.fork((Zone as any)['wtfZoneSpec']);
|
||||
}
|
||||
if (trace && (Zone as any)['longStackTraceZoneSpec']) {
|
||||
this.inner = this.inner.fork((Zone as any)['longStackTraceZoneSpec']);
|
||||
}
|
||||
this.inner = this.inner.fork({
|
||||
name: 'angular',
|
||||
properties: <any>{'isAngularZone': true},
|
||||
onInvokeTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task,
|
||||
applyThis: any, applyArgs: any): any => {
|
||||
try {
|
||||
this.onEnter();
|
||||
return delegate.invokeTask(target, task, applyThis, applyArgs);
|
||||
} finally {
|
||||
this.onLeave();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
onInvoke: (delegate: ZoneDelegate, current: Zone, target: Zone, callback: Function,
|
||||
applyThis: any, applyArgs: any[], source: string): any => {
|
||||
try {
|
||||
this.onEnter();
|
||||
return delegate.invoke(target, callback, applyThis, applyArgs, source);
|
||||
} finally {
|
||||
this.onLeave();
|
||||
}
|
||||
},
|
||||
|
||||
onHasTask:
|
||||
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
|
||||
delegate.hasTask(target, hasTaskState);
|
||||
if (current === target) {
|
||||
// We are only interested in hasTask events which originate from our zone
|
||||
// (A child hasTask event is not interesting to us)
|
||||
if (hasTaskState.change == 'microTask') {
|
||||
this.setMicrotask(hasTaskState.microTask);
|
||||
} else if (hasTaskState.change == 'macroTask') {
|
||||
this.setMacrotask(hasTaskState.macroTask);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any): boolean => {
|
||||
delegate.handleError(target, error);
|
||||
this.onError(error);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInner(fn: () => any): any { return this.inner.run(fn); };
|
||||
runInnerGuarded(fn: () => any): any { return this.inner.runGuarded(fn); };
|
||||
runOuter(fn: () => any): any { return this.outer.run(fn); };
|
||||
}
|
@ -146,6 +146,87 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(kf[1]).toEqual([1, {'background': 'blue'}]);
|
||||
}));
|
||||
|
||||
describe('animation aliases', () => {
|
||||
it('should animate the ":enter" animation alias as "void => *"', fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
':enter',
|
||||
[style({'opacity': 0}), animate('500ms', style({opacity: 1}))])])]
|
||||
}
|
||||
});
|
||||
|
||||
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||
let fixture = TestBed.createComponent(DummyIfCmp);
|
||||
var cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var animation = driver.log[0];
|
||||
expect(animation['duration']).toEqual(500);
|
||||
}));
|
||||
|
||||
it('should animate the ":leave" animation alias as "* => void"', fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(':leave', [animate('999ms', style({opacity: 0}))])])]
|
||||
}
|
||||
});
|
||||
|
||||
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||
let fixture = TestBed.createComponent(DummyIfCmp);
|
||||
var cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(driver.log.length).toEqual(0);
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var animation = driver.log[0];
|
||||
expect(animation['duration']).toEqual(999);
|
||||
}));
|
||||
|
||||
it('should throw an error when an unsupported alias is detected which is prefixed a colon value',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(':dont_leave_me', [animate('444ms', style({opacity: 0}))])])]
|
||||
}
|
||||
});
|
||||
|
||||
var message = '';
|
||||
try {
|
||||
let fixture = TestBed.createComponent(DummyIfCmp);
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
|
||||
expect(message).toMatch(
|
||||
/the transition alias value ":dont_leave_me" is not supported/);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should animate between * and void and back even when no expression is assigned',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
|
@ -74,7 +74,7 @@ export function main() {
|
||||
var cdRef = <any>new SpyChangeDetectorRef();
|
||||
try {
|
||||
ref.registerChangeDetector(cdRef);
|
||||
cdRef.spy('detectChanges').andCallFake(() => ref.tick());
|
||||
cdRef.spy('detectChanges').and.callFake(() => ref.tick());
|
||||
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
|
||||
} finally {
|
||||
ref.unregisterChangeDetector(cdRef);
|
||||
|
@ -31,17 +31,17 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should return the first suitable implementation', () => {
|
||||
factory1.spy('supports').andReturn(false);
|
||||
factory2.spy('supports').andReturn(true);
|
||||
factory3.spy('supports').andReturn(true);
|
||||
factory1.spy('supports').and.returnValue(false);
|
||||
factory2.spy('supports').and.returnValue(true);
|
||||
factory3.spy('supports').and.returnValue(true);
|
||||
|
||||
var differs = IterableDiffers.create(<any>[factory1, factory2, factory3]);
|
||||
expect(differs.find('some object')).toBe(factory2);
|
||||
});
|
||||
|
||||
it('should copy over differs from the parent repo', () => {
|
||||
factory1.spy('supports').andReturn(true);
|
||||
factory2.spy('supports').andReturn(false);
|
||||
factory1.spy('supports').and.returnValue(true);
|
||||
factory2.spy('supports').and.returnValue(false);
|
||||
|
||||
var parent = IterableDiffers.create(<any>[factory1]);
|
||||
var child = IterableDiffers.create(<any>[factory2], parent);
|
||||
|
@ -12,7 +12,6 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {iterateListLike} from '../../src/facade/collection';
|
||||
import {StringWrapper} from '../../src/facade/lang';
|
||||
|
||||
interface _JsQueryList {
|
||||
filter(c: any): any;
|
||||
@ -104,8 +103,8 @@ export function main() {
|
||||
it('should support toString', () => {
|
||||
queryList.reset(['one', 'two']);
|
||||
var listString = queryList.toString();
|
||||
expect(StringWrapper.contains(listString, 'one')).toBeTruthy();
|
||||
expect(StringWrapper.contains(listString, 'two')).toBeTruthy();
|
||||
expect(listString.indexOf('one') != -1).toBeTruthy();
|
||||
expect(listString.indexOf('two') != -1).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support first and last', () => {
|
||||
|
@ -66,7 +66,7 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('spy objects', () => {
|
||||
var spyObj: any /** TODO #9100 */;
|
||||
let spyObj: any;
|
||||
|
||||
beforeEach(() => { spyObj = <any>new SpyTestObj(); });
|
||||
|
||||
@ -74,7 +74,7 @@ export function main() {
|
||||
() => { expect(spyObj.spy('someFunc')).not.toHaveBeenCalled(); });
|
||||
|
||||
it('should record function calls', () => {
|
||||
spyObj.spy('someFunc').andCallFake((a: any, b: any) => a + b);
|
||||
spyObj.spy('someFunc').and.callFake((a: any, b: any) => a + b);
|
||||
|
||||
expect(spyObj.someFunc(1, 2)).toEqual(3);
|
||||
expect(spyObj.spy('someFunc')).toHaveBeenCalledWith(1, 2);
|
||||
@ -106,12 +106,6 @@ export function main() {
|
||||
|
||||
it('should create spys for all methods',
|
||||
() => { expect(() => spyObj.someFunc()).not.toThrow(); });
|
||||
|
||||
it('should create a default spy that does not fail for numbers', () => {
|
||||
// Previously needed for rtts_assert. Revisit this behavior.
|
||||
expect(spyObj.someFunc()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export {inject} from './test_bed';
|
||||
export * from './logger';
|
||||
export * from './ng_zone_mock';
|
||||
|
||||
export var proxy: ClassDecorator = (t: any /** TODO #9100 */) => t;
|
||||
export var proxy: ClassDecorator = (t: any) => t;
|
||||
|
||||
var _global = <any>(typeof window === 'undefined' ? global : window);
|
||||
|
||||
@ -35,7 +35,6 @@ var jsmIIt = _global.fit;
|
||||
var jsmXIt = _global.xit;
|
||||
|
||||
var runnerStack: BeforeEachRunner[] = [];
|
||||
var inIt = false;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
|
||||
var globalTimeOut = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
||||
|
||||
@ -123,7 +122,7 @@ function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: numbe
|
||||
var runner = runnerStack[runnerStack.length - 1];
|
||||
var timeOut = Math.max(globalTimeOut, testTimeOut);
|
||||
|
||||
jsmFn(name, (done: any /** TODO #9100 */) => {
|
||||
jsmFn(name, (done: any) => {
|
||||
var completerProvider = {
|
||||
provide: AsyncTestCompleter,
|
||||
useFactory: () => {
|
||||
@ -134,7 +133,6 @@ function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: numbe
|
||||
testBed.configureTestingModule({providers: [completerProvider]});
|
||||
runner.run();
|
||||
|
||||
inIt = true;
|
||||
if (testFn.length == 0) {
|
||||
let retVal = testFn();
|
||||
if (isPromise(retVal)) {
|
||||
@ -148,44 +146,26 @@ function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: numbe
|
||||
// Asynchronous test function that takes in 'done' parameter.
|
||||
testFn(done);
|
||||
}
|
||||
inIt = false;
|
||||
}, timeOut);
|
||||
}
|
||||
|
||||
export function it(
|
||||
name: any /** TODO #9100 */, fn: any /** TODO #9100 */,
|
||||
timeOut: any /** TODO #9100 */ = null): void {
|
||||
export function it(name: any, fn: any, timeOut: any = null): void {
|
||||
return _it(jsmIt, name, fn, timeOut);
|
||||
}
|
||||
|
||||
export function xit(
|
||||
name: any /** TODO #9100 */, fn: any /** TODO #9100 */,
|
||||
timeOut: any /** TODO #9100 */ = null): void {
|
||||
export function xit(name: any, fn: any, timeOut: any = null): void {
|
||||
return _it(jsmXIt, name, fn, timeOut);
|
||||
}
|
||||
|
||||
export function iit(
|
||||
name: any /** TODO #9100 */, fn: any /** TODO #9100 */,
|
||||
timeOut: any /** TODO #9100 */ = null): void {
|
||||
export function iit(name: any, fn: any, timeOut: any = null): void {
|
||||
return _it(jsmIIt, name, fn, timeOut);
|
||||
}
|
||||
|
||||
export interface GuinessCompatibleSpy extends jasmine.Spy {
|
||||
/** By chaining the spy with and.returnValue, all calls to the function will return a specific
|
||||
* value. */
|
||||
andReturn(val: any): void;
|
||||
/** By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied
|
||||
* function. */
|
||||
andCallFake(fn: Function): GuinessCompatibleSpy;
|
||||
/** removes all recorded calls */
|
||||
reset(): any /** TODO #9100 */;
|
||||
}
|
||||
|
||||
export class SpyObject {
|
||||
constructor(type: any /** TODO #9100 */ = null) {
|
||||
constructor(type?: any) {
|
||||
if (type) {
|
||||
for (var prop in type.prototype) {
|
||||
var m: any /** TODO #9100 */ = null;
|
||||
for (let prop in type.prototype) {
|
||||
let m: any = null;
|
||||
try {
|
||||
m = type.prototype[prop];
|
||||
} catch (e) {
|
||||
@ -200,23 +180,17 @@ export class SpyObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Noop so that SpyObject has the same interface as in Dart
|
||||
noSuchMethod(args: any /** TODO #9100 */) {}
|
||||
|
||||
spy(name: any /** TODO #9100 */) {
|
||||
if (!(this as any /** TODO #9100 */)[name]) {
|
||||
(this as any /** TODO #9100 */)[name] = this._createGuinnessCompatibleSpy(name);
|
||||
spy(name: string) {
|
||||
if (!(this as any)[name]) {
|
||||
(this as any)[name] = jasmine.createSpy(name);
|
||||
}
|
||||
return (this as any /** TODO #9100 */)[name];
|
||||
return (this as any)[name];
|
||||
}
|
||||
|
||||
prop(name: any /** TODO #9100 */, value: any /** TODO #9100 */) {
|
||||
(this as any /** TODO #9100 */)[name] = value;
|
||||
}
|
||||
prop(name: string, value: any) { (this as any)[name] = value; }
|
||||
|
||||
static stub(
|
||||
object: any /** TODO #9100 */ = null, config: any /** TODO #9100 */ = null,
|
||||
overrides: any /** TODO #9100 */ = null) {
|
||||
static stub(object: any = null, config: any = null, overrides: any = null) {
|
||||
if (!(object instanceof SpyObject)) {
|
||||
overrides = config;
|
||||
config = object;
|
||||
@ -224,18 +198,7 @@ export class SpyObject {
|
||||
}
|
||||
|
||||
var m = StringMapWrapper.merge(config, overrides);
|
||||
Object.keys(m).forEach(key => { object.spy(key).andReturn(m[key]); });
|
||||
Object.keys(m).forEach(key => { object.spy(key).and.returnValue(m[key]); });
|
||||
return object;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_createGuinnessCompatibleSpy(name: any /** TODO #9100 */): GuinessCompatibleSpy {
|
||||
var newSpy: GuinessCompatibleSpy = <any>jasmine.createSpy(name);
|
||||
newSpy.andCallFake = <any>newSpy.and.callFake;
|
||||
newSpy.andReturn = <any>newSpy.and.returnValue;
|
||||
newSpy.reset = <any>newSpy.calls.reset;
|
||||
// revisit return null here (previously needed for rtts_assert).
|
||||
newSpy.and.returnValue(null);
|
||||
return newSpy;
|
||||
}
|
||||
}
|
||||
|
@ -78,14 +78,14 @@ export class EventEmitter<T> extends Subject<T> {
|
||||
emit(value?: T) { super.next(value); }
|
||||
|
||||
subscribe(generatorOrNext?: any, error?: any, complete?: any): any {
|
||||
let schedulerFn: any /** TODO #9100 */;
|
||||
let errorFn = (err: any): any /** TODO #9100 */ => null;
|
||||
let completeFn = (): any /** TODO #9100 */ => null;
|
||||
let schedulerFn: (t: any) => any;
|
||||
let errorFn = (err: any): any => null;
|
||||
let completeFn = (): any => null;
|
||||
|
||||
if (generatorOrNext && typeof generatorOrNext === 'object') {
|
||||
schedulerFn = this.__isAsync ? (value: any /** TODO #9100 */) => {
|
||||
schedulerFn = this.__isAsync ? (value: any) => {
|
||||
setTimeout(() => generatorOrNext.next(value));
|
||||
} : (value: any /** TODO #9100 */) => { generatorOrNext.next(value); };
|
||||
} : (value: any) => { generatorOrNext.next(value); };
|
||||
|
||||
if (generatorOrNext.error) {
|
||||
errorFn = this.__isAsync ? (err) => { setTimeout(() => generatorOrNext.error(err)); } :
|
||||
@ -97,9 +97,8 @@ export class EventEmitter<T> extends Subject<T> {
|
||||
() => { generatorOrNext.complete(); };
|
||||
}
|
||||
} else {
|
||||
schedulerFn = this.__isAsync ? (value: any /** TODO #9100 */) => {
|
||||
setTimeout(() => generatorOrNext(value));
|
||||
} : (value: any /** TODO #9100 */) => { generatorOrNext(value); };
|
||||
schedulerFn = this.__isAsync ? (value: any) => { setTimeout(() => generatorOrNext(value)); } :
|
||||
(value: any) => { generatorOrNext(value); };
|
||||
|
||||
if (error) {
|
||||
errorFn =
|
||||
|
@ -137,73 +137,6 @@ export function stringify(token: any): string {
|
||||
return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
|
||||
}
|
||||
|
||||
export class StringWrapper {
|
||||
static fromCharCode(code: number): string { return String.fromCharCode(code); }
|
||||
|
||||
static charCodeAt(s: string, index: number): number { return s.charCodeAt(index); }
|
||||
|
||||
static split(s: string, regExp: RegExp): string[] { return s.split(regExp); }
|
||||
|
||||
static equals(s: string, s2: string): boolean { return s === s2; }
|
||||
|
||||
static stripLeft(s: string, charVal: string): string {
|
||||
if (s && s.length) {
|
||||
var pos = 0;
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
if (s[i] != charVal) break;
|
||||
pos++;
|
||||
}
|
||||
s = s.substring(pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static stripRight(s: string, charVal: string): string {
|
||||
if (s && s.length) {
|
||||
var pos = s.length;
|
||||
for (var i = s.length - 1; i >= 0; i--) {
|
||||
if (s[i] != charVal) break;
|
||||
pos--;
|
||||
}
|
||||
s = s.substring(0, pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static replace(s: string, from: string, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static replaceAll(s: string, from: RegExp, replace: string): string {
|
||||
return s.replace(from, replace);
|
||||
}
|
||||
|
||||
static slice<T>(s: string, from: number = 0, to: number = null): string {
|
||||
return s.slice(from, to === null ? undefined : to);
|
||||
}
|
||||
|
||||
static replaceAllMapped(s: string, from: RegExp, cb: (m: string[]) => string): string {
|
||||
return s.replace(from, function(...matches: any[]) {
|
||||
// Remove offset & string from the result array
|
||||
matches.splice(-2, 2);
|
||||
// The callback receives match, p1, ..., pn
|
||||
return cb(matches);
|
||||
});
|
||||
}
|
||||
|
||||
static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; }
|
||||
|
||||
static compare(a: string, b: string): number {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
constructor(public parts: string[] = []) {}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NumberWrapper, StringWrapper, escapeRegExp, hasConstructor} from '../src/lang';
|
||||
import {NumberWrapper, escapeRegExp, hasConstructor} from '../src/lang';
|
||||
|
||||
class MySuperclass {}
|
||||
class MySubclass extends MySuperclass {}
|
||||
@ -50,92 +50,4 @@ export function main() {
|
||||
() => { expect(NumberWrapper.isNumeric('2a')).toBe(false); });
|
||||
});
|
||||
});
|
||||
|
||||
describe('String', () => {
|
||||
var s: string;
|
||||
|
||||
describe('slice', () => {
|
||||
beforeEach(() => { s = 'abcdefghij'; });
|
||||
|
||||
it('should return the whole string if neither start nor end are specified',
|
||||
() => { expect(StringWrapper.slice(s)).toEqual('abcdefghij'); });
|
||||
|
||||
it('should return up to the end if end is not specified',
|
||||
() => { expect(StringWrapper.slice(s, 1)).toEqual('bcdefghij'); });
|
||||
|
||||
it('should support negative start',
|
||||
() => { expect(StringWrapper.slice(s, -1)).toEqual('j'); });
|
||||
|
||||
it('should support negative end',
|
||||
() => { expect(StringWrapper.slice(s, -3, -1)).toEqual('hi'); });
|
||||
|
||||
it('should return empty string if start is greater than end', () => {
|
||||
expect(StringWrapper.slice(s, 4, 2)).toEqual('');
|
||||
expect(StringWrapper.slice(s, -2, -4)).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripLeft', () => {
|
||||
it('should strip the first character of the string if it matches the provided input', () => {
|
||||
var input = '~angular2 is amazing';
|
||||
var expectedOutput = 'angular2 is amazing';
|
||||
|
||||
expect(StringWrapper.stripLeft(input, '~')).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('should keep stripping characters from the start until the first unmatched character',
|
||||
() => {
|
||||
var input = '#####hello';
|
||||
var expectedOutput = 'hello';
|
||||
expect(StringWrapper.stripLeft(input, '#')).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('should not alter the provided input if the first character does not match the provided input',
|
||||
() => {
|
||||
var input = '+angular2 is amazing';
|
||||
expect(StringWrapper.stripLeft(input, '*')).toEqual(input);
|
||||
});
|
||||
|
||||
it('should not do any alterations when an empty string or null value is passed in', () => {
|
||||
expect(StringWrapper.stripLeft('', 'S')).toEqual('');
|
||||
expect(StringWrapper.stripLeft(null, 'S')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stripRight', () => {
|
||||
it('should strip the first character of the string if it matches the provided input', () => {
|
||||
var input = 'angular2 is amazing!';
|
||||
var expectedOutput = 'angular2 is amazing';
|
||||
|
||||
expect(StringWrapper.stripRight(input, '!')).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('should not alter the provided input if the first character does not match the provided input',
|
||||
() => {
|
||||
var input = 'angular2 is amazing+';
|
||||
|
||||
expect(StringWrapper.stripRight(input, '*')).toEqual(input);
|
||||
});
|
||||
|
||||
it('should keep stripping characters from the end until the first unmatched character',
|
||||
() => {
|
||||
var input = 'hi&!&&&&&';
|
||||
var expectedOutput = 'hi&!';
|
||||
expect(StringWrapper.stripRight(input, '&')).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('should not do any alterations when an empty string or null value is passed in', () => {
|
||||
expect(StringWrapper.stripRight('', 'S')).toEqual('');
|
||||
expect(StringWrapper.stripRight(null, 'S')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasConstructor', () => {
|
||||
it('should be true when the type matches',
|
||||
() => { expect(hasConstructor(new MySuperclass(), MySuperclass)).toEqual(true); });
|
||||
|
||||
it('should be false for subtypes',
|
||||
() => { expect(hasConstructor(new MySubclass(), MySuperclass)).toEqual(false); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -48,7 +48,8 @@ const resolvedPromise = Promise.resolve(null);
|
||||
* sub-groups within the form.
|
||||
*
|
||||
* You can listen to the directive's `ngSubmit` event to be notified when the user has
|
||||
* triggered a form submission.
|
||||
* triggered a form submission. The `ngSubmit` event will be emitted with the original form
|
||||
* submission event.
|
||||
*
|
||||
* {@example forms/ts/simpleForm/simple_form_example.ts region='Component'}
|
||||
*
|
||||
@ -61,7 +62,7 @@ const resolvedPromise = Promise.resolve(null);
|
||||
@Directive({
|
||||
selector: 'form:not([ngNoForm]):not([formGroup]),ngForm,[ngForm]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {'(submit)': 'onSubmit()', '(reset)': 'onReset()'},
|
||||
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
|
||||
outputs: ['ngSubmit'],
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
@ -102,7 +103,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
|
||||
removeControl(dir: NgModel): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
const container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
@ -111,8 +112,8 @@ export class NgForm extends ControlContainer implements Form {
|
||||
|
||||
addFormGroup(dir: NgModelGroup): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
var group = new FormGroup({});
|
||||
const container = this._findContainer(dir.path);
|
||||
const group = new FormGroup({});
|
||||
setUpFormContainer(group, dir);
|
||||
container.registerControl(dir.name, group);
|
||||
group.updateValueAndValidity({emitEvent: false});
|
||||
@ -121,7 +122,7 @@ export class NgForm extends ControlContainer implements Form {
|
||||
|
||||
removeFormGroup(dir: NgModelGroup): void {
|
||||
resolvedPromise.then(() => {
|
||||
var container = this._findContainer(dir.path);
|
||||
const container = this._findContainer(dir.path);
|
||||
if (isPresent(container)) {
|
||||
container.removeControl(dir.name);
|
||||
}
|
||||
@ -132,16 +133,16 @@ export class NgForm extends ControlContainer implements Form {
|
||||
|
||||
updateModel(dir: NgControl, value: any): void {
|
||||
resolvedPromise.then(() => {
|
||||
var ctrl = <FormControl>this.form.get(dir.path);
|
||||
const ctrl = <FormControl>this.form.get(dir.path);
|
||||
ctrl.setValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
setValue(value: {[key: string]: any}): void { this.control.setValue(value); }
|
||||
|
||||
onSubmit(): boolean {
|
||||
onSubmit($event: Event): boolean {
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
this.ngSubmit.emit($event);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,10 @@ export const formDirectiveProvider: any = {
|
||||
* its {@link AbstractControl.statusChanges} event to be notified when the validation status is
|
||||
* re-calculated.
|
||||
*
|
||||
* Furthermore, you can listen to the directive's `ngSubmit` event to be notified when the user has
|
||||
* triggered a form submission. The `ngSubmit` event will be emitted with the original form
|
||||
* submission event.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* In this example, we create form controls for first name and last name.
|
||||
@ -59,7 +63,7 @@ export const formDirectiveProvider: any = {
|
||||
@Directive({
|
||||
selector: '[formGroup]',
|
||||
providers: [formDirectiveProvider],
|
||||
host: {'(submit)': 'onSubmit()', '(reset)': 'onReset()'},
|
||||
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
|
||||
exportAs: 'ngForm'
|
||||
})
|
||||
export class FormGroupDirective extends ControlContainer implements Form,
|
||||
@ -107,7 +111,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||
removeControl(dir: FormControlName): void { ListWrapper.remove(this.directives, dir); }
|
||||
|
||||
addFormGroup(dir: FormGroupName): void {
|
||||
var ctrl: any = this.form.get(dir.path);
|
||||
const ctrl: any = this.form.get(dir.path);
|
||||
setUpFormContainer(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
@ -117,7 +121,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||
getFormGroup(dir: FormGroupName): FormGroup { return <FormGroup>this.form.get(dir.path); }
|
||||
|
||||
addFormArray(dir: FormArrayName): void {
|
||||
var ctrl: any = this.form.get(dir.path);
|
||||
const ctrl: any = this.form.get(dir.path);
|
||||
setUpFormContainer(ctrl, dir);
|
||||
ctrl.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
@ -127,13 +131,13 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||
getFormArray(dir: FormArrayName): FormArray { return <FormArray>this.form.get(dir.path); }
|
||||
|
||||
updateModel(dir: FormControlName, value: any): void {
|
||||
var ctrl = <FormControl>this.form.get(dir.path);
|
||||
const ctrl = <FormControl>this.form.get(dir.path);
|
||||
ctrl.setValue(value);
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
onSubmit($event: Event): boolean {
|
||||
this._submitted = true;
|
||||
this.ngSubmit.emit(null);
|
||||
this.ngSubmit.emit($event);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, looseIdentical} from '../facade/lang';
|
||||
import {isBlank, isPresent, isPrimitive, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
@ -22,7 +22,7 @@ export const SELECT_VALUE_ACCESSOR: any = {
|
||||
function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
return `${id}: ${value}`.slice(0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
|
||||
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../facade/lang';
|
||||
import {isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../facade/lang';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||
|
||||
@ -23,7 +23,7 @@ function _buildValueString(id: string, value: any): string {
|
||||
if (isBlank(id)) return `${value}`;
|
||||
if (isString(value)) value = `'${value}'`;
|
||||
if (!isPrimitive(value)) value = 'Object';
|
||||
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
|
||||
return `${id}: ${value}`.slice(0, 50);
|
||||
}
|
||||
|
||||
function _extractId(valueString: string): string {
|
||||
|
@ -52,13 +52,14 @@ function _find(control: AbstractControl, path: Array<string|number>| string, del
|
||||
|
||||
return (<Array<string|number>>path).reduce((v, name) => {
|
||||
if (v instanceof FormGroup) {
|
||||
return isPresent(v.controls[name]) ? v.controls[name] : null;
|
||||
} else if (v instanceof FormArray) {
|
||||
var index = <number>name;
|
||||
return isPresent(v.at(index)) ? v.at(index) : null;
|
||||
} else {
|
||||
return null;
|
||||
return v.controls[name] || null;
|
||||
}
|
||||
|
||||
if (v instanceof FormArray) {
|
||||
return v.at(<number>name) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, control);
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,13 @@ import {toPromise} from 'rxjs/operator/toPromise';
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, isString} from './facade/lang';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {AbstractControl} from './model';
|
||||
import {isPromise} from './private_import_core';
|
||||
|
||||
|
||||
function isEmptyInputValue(value: any) {
|
||||
return value == null || typeof value === 'string' && value.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Providers for validators to be used for {@link FormControl}s in a form.
|
||||
@ -60,9 +62,7 @@ export class Validators {
|
||||
* Validator that requires controls to have a non-empty value.
|
||||
*/
|
||||
static required(control: AbstractControl): {[key: string]: boolean} {
|
||||
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
|
||||
{'required': true} :
|
||||
null;
|
||||
return isEmptyInputValue(control.value) ? {'required': true} : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,10 +70,12 @@ export class Validators {
|
||||
*/
|
||||
static minLength(minLength: number): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length < minLength ?
|
||||
{'minlength': {'requiredLength': minLength, 'actualLength': v.length}} :
|
||||
if (isEmptyInputValue(control.value)) {
|
||||
return null; // don't validate empty values to allow optional controls
|
||||
}
|
||||
const length = typeof control.value === 'string' ? control.value.length : 0;
|
||||
return length < minLength ?
|
||||
{'minlength': {'requiredLength': minLength, 'actualLength': length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
@ -83,10 +85,9 @@ export class Validators {
|
||||
*/
|
||||
static maxLength(maxLength: number): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
if (isPresent(Validators.required(control))) return null;
|
||||
var v: string = control.value;
|
||||
return v.length > maxLength ?
|
||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': v.length}} :
|
||||
const length = typeof control.value === 'string' ? control.value.length : 0;
|
||||
return length > maxLength ?
|
||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': length}} :
|
||||
null;
|
||||
};
|
||||
}
|
||||
@ -96,10 +97,14 @@ export class Validators {
|
||||
*/
|
||||
static pattern(pattern: string): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
let regex = new RegExp(`^${pattern}$`);
|
||||
let v: string = control.value;
|
||||
return regex.test(v) ? null :
|
||||
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': v}};
|
||||
if (isEmptyInputValue(control.value)) {
|
||||
return null; // don't validate empty values to allow optional controls
|
||||
}
|
||||
const regex = new RegExp(`^${pattern}$`);
|
||||
const value: string = control.value;
|
||||
return regex.test(value) ?
|
||||
null :
|
||||
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': value}};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -529,17 +529,17 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('submit and reset events', () => {
|
||||
it('should emit ngSubmit event on submit', () => {
|
||||
it('should emit ngSubmit event with the original submit event on submit', () => {
|
||||
const fixture = TestBed.createComponent(FormGroupComp);
|
||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||
fixture.componentInstance.data = 'should be changed';
|
||||
fixture.componentInstance.event = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||
dispatchEvent(formEl, 'submit');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.componentInstance.data).toEqual('submitted');
|
||||
expect(fixture.componentInstance.event.type).toEqual('submit');
|
||||
});
|
||||
|
||||
it('should mark formGroup as submitted on submit event', () => {
|
||||
@ -1760,7 +1760,7 @@ class FormControlComp {
|
||||
@Component({
|
||||
selector: 'form-group-comp',
|
||||
template: `
|
||||
<form [formGroup]="form" (ngSubmit)="data='submitted'">
|
||||
<form [formGroup]="form" (ngSubmit)="event=$event">
|
||||
<input type="text" formControlName="login">
|
||||
</form>
|
||||
`
|
||||
@ -1769,7 +1769,7 @@ class FormGroupComp {
|
||||
control: FormControl;
|
||||
form: FormGroup;
|
||||
myGroup: FormGroup;
|
||||
data: string;
|
||||
event: Event;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -22,7 +22,7 @@ export function main() {
|
||||
StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm,
|
||||
NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
|
||||
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
|
||||
NgModelValidationBindings
|
||||
NgModelValidationBindings, NgModelMultipleValidators
|
||||
],
|
||||
imports: [FormsModule]
|
||||
});
|
||||
@ -237,15 +237,15 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('submit and reset events', () => {
|
||||
it('should emit ngSubmit event on submit', fakeAsync(() => {
|
||||
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelForm);
|
||||
fixture.componentInstance.name = 'old';
|
||||
fixture.componentInstance.event = null;
|
||||
|
||||
const form = fixture.debugElement.query(By.css('form'));
|
||||
dispatchEvent(form.nativeElement, 'submit');
|
||||
tick();
|
||||
|
||||
expect(fixture.componentInstance.name).toEqual('submitted');
|
||||
expect(fixture.componentInstance.event.type).toEqual('submit');
|
||||
}));
|
||||
|
||||
it('should mark NgForm as submitted on submit event', fakeAsync(() => {
|
||||
@ -728,6 +728,50 @@ export function main() {
|
||||
expect(form.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should support optional fields with pattern validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.pattern = '[a-z]+';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = '';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeTruthy();
|
||||
|
||||
input.nativeElement.value = '1';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeFalsy();
|
||||
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should support optional fields with minlength validator', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelMultipleValidators);
|
||||
fixture.componentInstance.required = false;
|
||||
fixture.componentInstance.minLen = 2;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
|
||||
input.nativeElement.value = '';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeTruthy();
|
||||
|
||||
input.nativeElement.value = '1';
|
||||
dispatchEvent(input.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
expect(form.valid).toBeFalsy();
|
||||
expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('changes on bound properties should change the validation state of the form',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
||||
@ -854,13 +898,14 @@ class StandaloneNgModel {
|
||||
@Component({
|
||||
selector: 'ng-model-form',
|
||||
template: `
|
||||
<form (ngSubmit)="name='submitted'" (reset)="onReset()">
|
||||
<form (ngSubmit)="event=$event" (reset)="onReset()">
|
||||
<input name="name" [(ngModel)]="name" minlength="10" [ngModelOptions]="options">
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class NgModelForm {
|
||||
name: string;
|
||||
event: Event;
|
||||
options = {};
|
||||
|
||||
onReset() {}
|
||||
@ -1037,6 +1082,20 @@ class NgModelValidationBindings {
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-model-multiple-validators',
|
||||
template: `
|
||||
<form>
|
||||
<input name="tovalidate" ngModel [required]="required" [minlength]="minLen" [pattern]="pattern">
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class NgModelMultipleValidators {
|
||||
required: boolean;
|
||||
minLen: number;
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
function sortedClassList(el: HTMLElement) {
|
||||
const l = getDOM().classList(el);
|
||||
l.sort();
|
||||
|
@ -44,21 +44,24 @@ export function main() {
|
||||
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
|
||||
|
||||
it('should not error on a non-empty string',
|
||||
() => { expect(Validators.required(new FormControl('not empty'))).toEqual(null); });
|
||||
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
|
||||
|
||||
it('should accept zero as valid',
|
||||
() => { expect(Validators.required(new FormControl(0))).toEqual(null); });
|
||||
() => { expect(Validators.required(new FormControl(0))).toBeNull(); });
|
||||
});
|
||||
|
||||
describe('minLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(''))).toEqual(null); });
|
||||
() => { expect(Validators.minLength(2)(new FormControl(''))).toBeNull(); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toEqual(null); });
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toEqual(null); });
|
||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||
|
||||
it('should error on short strings', () => {
|
||||
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
|
||||
@ -69,13 +72,13 @@ export function main() {
|
||||
|
||||
describe('maxLength', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(''))).toEqual(null); });
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(''))).toBeNull(); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toEqual(null); });
|
||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toEqual(null); });
|
||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
|
||||
|
||||
it('should error on long strings', () => {
|
||||
expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({
|
||||
@ -86,29 +89,25 @@ export function main() {
|
||||
|
||||
describe('pattern', () => {
|
||||
it('should not error on an empty string',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new FormControl(''))).toEqual(null); });
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull(); });
|
||||
|
||||
it('should not error on null',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new FormControl(null))).toEqual(null); });
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on undefined',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on null value and "null" pattern',
|
||||
() => { expect(Validators.pattern('null')(new FormControl(null))).toEqual(null); });
|
||||
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
|
||||
|
||||
it('should not error on valid strings', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toEqual(null);
|
||||
});
|
||||
it('should not error on valid strings',
|
||||
() => { expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull(); });
|
||||
|
||||
it('should error on failure to match string', () => {
|
||||
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
|
||||
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on failure to match empty string', () => {
|
||||
expect(Validators.pattern('[a-zA-Z]+')(new FormControl(''))).toEqual({
|
||||
'pattern': {'requiredPattern': '^[a-zA-Z]+$', 'actualValue': ''}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('compose', () => {
|
||||
@ -127,7 +126,7 @@ export function main() {
|
||||
|
||||
it('should return null when no errors', () => {
|
||||
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
|
||||
expect(c(new FormControl(''))).toEqual(null);
|
||||
expect(c(new FormControl(''))).toBeNull();
|
||||
});
|
||||
|
||||
it('should ignore nulls', () => {
|
||||
@ -154,7 +153,7 @@ export function main() {
|
||||
}
|
||||
|
||||
it('should return null when given null',
|
||||
() => { expect(Validators.composeAsync(null)).toEqual(null); });
|
||||
() => { expect(Validators.composeAsync(null)).toBeNull(); });
|
||||
|
||||
it('should collect errors from all the validators', fakeAsync(() => {
|
||||
var c = Validators.composeAsync([
|
||||
@ -187,7 +186,7 @@ export function main() {
|
||||
(<Promise<any>>c(new FormControl('expected'))).then(v => value = v);
|
||||
tick(1);
|
||||
|
||||
expect(value).toEqual(null);
|
||||
expect(value).toBeNull();
|
||||
}));
|
||||
|
||||
it('should ignore nulls', fakeAsync(() => {
|
||||
|
@ -12,7 +12,7 @@ import {Observer} from 'rxjs/Observer';
|
||||
|
||||
import {ResponseOptions} from '../base_response_options';
|
||||
import {ReadyState, RequestMethod, ResponseType} from '../enums';
|
||||
import {StringWrapper, isPresent} from '../facade/lang';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Connection, ConnectionBackend} from '../interfaces';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
@ -75,7 +75,7 @@ export class JSONPConnection_ extends JSONPConnection {
|
||||
let callback = _dom.requestCallback(this._id);
|
||||
let url: string = req.url;
|
||||
if (url.indexOf('=JSONP_CALLBACK&') > -1) {
|
||||
url = StringWrapper.replace(url, '=JSONP_CALLBACK&', `=${callback}&`);
|
||||
url = url.replace('=JSONP_CALLBACK&', `=${callback}&`);
|
||||
} else if (url.lastIndexOf('=JSONP_CALLBACK') === url.length - '=JSONP_CALLBACK'.length) {
|
||||
url = url.substring(0, url.length - '=JSONP_CALLBACK'.length) + `=${callback}`;
|
||||
}
|
||||
|
@ -49,19 +49,16 @@ export class Headers {
|
||||
}
|
||||
|
||||
if (headers instanceof Headers) {
|
||||
headers._headers.forEach((value: string[], name: string) => {
|
||||
const lcName = name.toLowerCase();
|
||||
this._headers.set(lcName, value);
|
||||
this.mayBeSetNormalizedName(name);
|
||||
headers._headers.forEach((values: string[], name: string) => {
|
||||
values.forEach(value => this.append(name, value));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(headers).forEach((name: string) => {
|
||||
const value = headers[name];
|
||||
const lcName = name.toLowerCase();
|
||||
this._headers.set(lcName, Array.isArray(value) ? value : [value]);
|
||||
this.mayBeSetNormalizedName(name);
|
||||
const values: string[] = Array.isArray(headers[name]) ? headers[name] : [headers[name]];
|
||||
this.delete(name);
|
||||
values.forEach(value => this.append(name, value));
|
||||
});
|
||||
}
|
||||
|
||||
@ -88,7 +85,12 @@ export class Headers {
|
||||
*/
|
||||
append(name: string, value: string): void {
|
||||
const values = this.getAll(name);
|
||||
this.set(name, values === null ? [value] : [...values, value]);
|
||||
|
||||
if (values === null) {
|
||||
this.set(name, value);
|
||||
} else {
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,8 +134,13 @@ export class Headers {
|
||||
* Sets or overrides header value for given name.
|
||||
*/
|
||||
set(name: string, value: string|string[]): void {
|
||||
const strValue = Array.isArray(value) ? value.join(',') : value;
|
||||
this._headers.set(name.toLowerCase(), [strValue]);
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length) {
|
||||
this._headers.set(name.toLowerCase(), [value.join(',')]);
|
||||
}
|
||||
} else {
|
||||
this._headers.set(name.toLowerCase(), [value]);
|
||||
}
|
||||
this.mayBeSetNormalizedName(name);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StringWrapper, isPresent} from '../src/facade/lang';
|
||||
import {isPresent} from '../src/facade/lang';
|
||||
|
||||
import {Body} from './body';
|
||||
import {ContentType, RequestMethod, ResponseContentType} from './enums';
|
||||
@ -82,7 +82,7 @@ export class Request extends Body {
|
||||
let search = requestOptions.search.toString();
|
||||
if (search.length > 0) {
|
||||
let prefix = '?';
|
||||
if (StringWrapper.contains(this.url, '?')) {
|
||||
if (this.url.indexOf('?') != -1) {
|
||||
prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';
|
||||
}
|
||||
// TODO: just delete search-query-looking string in url?
|
||||
|
@ -37,8 +37,25 @@ export function main() {
|
||||
secondHeaders.append('Content-Type', 'image/jpeg');
|
||||
expect(firstHeaders.has('Content-Type')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should preserve the list of values', () => {
|
||||
const src = new Headers();
|
||||
src.append('foo', 'a');
|
||||
src.append('foo', 'b');
|
||||
src.append('foo', 'c');
|
||||
const dst = new Headers(src);
|
||||
expect(dst.getAll('foo')).toEqual(src.getAll('foo'));
|
||||
});
|
||||
|
||||
it('should keep the last value when initialized from an object', () => {
|
||||
const headers = new Headers({
|
||||
'foo': 'first',
|
||||
'fOo': 'second',
|
||||
});
|
||||
|
||||
expect(headers.getAll('foo')).toEqual(['second']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.set()', () => {
|
||||
it('should clear all values and re-set for the provided key', () => {
|
||||
@ -112,6 +129,14 @@ export function main() {
|
||||
});
|
||||
|
||||
describe('.append', () => {
|
||||
it('should append a value to the list', () => {
|
||||
const headers = new Headers();
|
||||
headers.append('foo', 'bar');
|
||||
headers.append('foo', 'baz');
|
||||
expect(headers.get('foo')).toEqual('bar');
|
||||
expect(headers.getAll('foo')).toEqual(['bar', 'baz']);
|
||||
});
|
||||
|
||||
it('should preserve the case of the first call', () => {
|
||||
const headers = new Headers();
|
||||
|
||||
@ -133,7 +158,6 @@ export function main() {
|
||||
ref = {'Accept': values};
|
||||
});
|
||||
|
||||
|
||||
it('should be serializable with toJSON',
|
||||
() => { expect(JSON.stringify(headers)).toEqual(JSON.stringify(ref)); });
|
||||
|
||||
@ -143,10 +167,8 @@ export function main() {
|
||||
expect(JSON.stringify(parsedHeaders)).toEqual(JSON.stringify(recreatedHeaders));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.fromResponseHeaderString()', () => {
|
||||
|
||||
it('should parse a response header string', () => {
|
||||
const response = `Date: Fri, 20 Nov 2015 01:45:26 GMT\n` +
|
||||
`Content-Type: application/json; charset=utf-8\n` +
|
||||
@ -159,4 +181,5 @@ export function main() {
|
||||
expect(headers.get('Connection')).toEqual('keep-alive');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user