Compare commits

..

16 Commits

Author SHA1 Message Date
712d1a7c37 chore(release): v2.0.1 2016-09-23 11:28:36 -07:00
16601f9359 docs(changelog): add changelog for 2.0.1 2016-09-23 10:50:42 -07:00
b81e2e7a31 fix(upgrade): allow attribute selectors for components in ng2 which are not part of upgrade (#11808)
fixes #11280
2016-09-23 10:48:47 -07:00
98fac36706 docs(Component): API docs for .encapsulation and .interpolation 2016-09-23 10:22:24 -07:00
3e780c032e refactor: misc cleanup 2016-09-23 10:22:05 -07:00
e09882180e refactor(common): cleanup (#11668) 2016-09-23 10:21:58 -07:00
0e18c57a17 docs(core): mark TestBed as stable api and add preliminary docs (#11767)
TestBed was accidentaly ommited from the 'stable' api list during the API sweep before final. We do consider it to be stable.
2016-09-23 10:21:50 -07:00
51e2b9c073 docs(contributing): remove preview references (#11795) 2016-09-23 10:21:44 -07:00
f218e240d3 ci(BrowserStack): add Safari 10 (#11796) 2016-09-23 10:21:31 -07:00
af6b219f8e refactor(TemplateParser): clearer error message for on* binding (#11802)
fixes #11756
2016-09-23 10:21:15 -07:00
20addf5f9f chore(ISSUE_TEMPLATE): update Angular version field (#11821) 2016-09-23 10:21:02 -07:00
2860418a3c fix(forms): disable all radios with disable() 2016-09-23 10:19:58 -07:00
39e251eea7 fix(forms): support unbound disabled in ngModel (#11736) 2016-09-23 10:19:46 -07:00
d7d716d5db chore(zone.js): update to 0.6.25 (#11725) 2016-09-23 10:19:30 -07:00
a95d65241c fix(compiler): Safe property access expressions work in event bindings (#11724) 2016-09-23 10:19:24 -07:00
fdb22bd185 refactor: misc cleanup (#11654) 2016-09-23 10:18:38 -07:00
463 changed files with 6619 additions and 7495 deletions

View File

@ -1,7 +1,3 @@
<!--
IF YOU DON'T FILL OUT THE FOLLOWING INFORMATION WE MIGHT CLOSE YOUR ISSUE WITHOUT INVESTIGATING
-->
**I'm submitting a ...** (check one with "x") **I'm submitting a ...** (check one with "x")
``` ```
[ ] bug report => search github for a similar issue or PR before submitting [ ] bug report => search github for a similar issue or PR before submitting
@ -15,12 +11,8 @@ IF YOU DON'T FILL OUT THE FOLLOWING INFORMATION WE MIGHT CLOSE YOUR ISSUE WITHOU
**Expected behavior** **Expected behavior**
<!-- Describe what the behavior would be without the bug. --> <!-- Describe what the behavior would be without the bug. -->
**Minimal reproduction of the problem with instructions** **Reproduction of the problem**
<!-- <!-- If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5). -->
If the current behavior is a bug or you can illustrate your feature request better with an example,
please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
-->
**What is the motivation / use case for changing the behavior?** **What is the motivation / use case for changing the behavior?**
<!-- Describe the motivation or the concrete use case --> <!-- Describe the motivation or the concrete use case -->

View File

@ -1,54 +1,3 @@
<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)
### Bug Fixes
* **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))
* **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 `[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)
* **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/co
* **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))
mmit/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))
* **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> <a name="2.0.1"></a>
## [2.0.1](https://github.com/angular/angular/compare/2.0.0...2.0.1) (2016-09-23) ## [2.0.1](https://github.com/angular/angular/compare/2.0.0...2.0.1) (2016-09-23)
@ -60,7 +9,7 @@ Note: 2.1.0-beta.0 release also contains all the changes present in the 2.0.1 re
* **compiler:** safe property access expressions work in event bindings ([#11724](https://github.com/angular/angular/issues/11724)) ([a95d652](https://github.com/angular/angular/commit/a95d652)) * **compiler:** safe property access expressions work in event bindings ([#11724](https://github.com/angular/angular/issues/11724)) ([a95d652](https://github.com/angular/angular/commit/a95d652))
* **compiler:** throw when Component.moduleId is not a string ([bd4045b](https://github.com/angular/angular/commit/bd4045b)), closes [#11590](https://github.com/angular/angular/issues/11590) * **compiler:** throw when Component.moduleId is not a string ([bd4045b](https://github.com/angular/angular/commit/bd4045b)), closes [#11590](https://github.com/angular/angular/issues/11590)
* **compiler:** do not provide I18N values when they're not specified ([03aedbe](https://github.com/angular/angular/commit/03aedbe)), closes [#11643](https://github.com/angular/angular/issues/11643) * **compiler:** do not provide I18N values when they're not specified ([03aedbe](https://github.com/angular/angular/commit/03aedbe)), closes [#11643](https://github.com/angular/angular/issues/11643)
* **core:** ContentChild descendants should be queried by default ([0dc15eb](https://github.com/angular/angular/commit/0dc15eb)), closes [#11645](https://github.com/angular/angular/issues/11645) * **core:** ContentChild descendants should be queried by default ([0dc15eb](https://github.com/angular/angular/commit/0dc15eb)), closes [#1645](https://github.com/angular/angular/issues/1645)
* **forms:** disable all radios with disable() ([2860418](https://github.com/angular/angular/commit/2860418)) * **forms:** disable all radios with disable() ([2860418](https://github.com/angular/angular/commit/2860418))
* **forms:** make setDisabledState optional for reactive form directives ([#11731](https://github.com/angular/angular/issues/11731)) ([51d73d3](https://github.com/angular/angular/commit/51d73d3)), closes [#11719](https://github.com/angular/angular/issues/11719) * **forms:** make setDisabledState optional for reactive form directives ([#11731](https://github.com/angular/angular/issues/11731)) ([51d73d3](https://github.com/angular/angular/commit/51d73d3)), closes [#11719](https://github.com/angular/angular/issues/11719)
* **forms:** support unbound disabled in ngModel ([#11736](https://github.com/angular/angular/issues/11736)) ([39e251e](https://github.com/angular/angular/commit/39e251e)) * **forms:** support unbound disabled in ngModel ([#11736](https://github.com/angular/angular/issues/11736)) ([39e251e](https://github.com/angular/angular/commit/39e251e))

View File

@ -114,7 +114,7 @@ You should execute the 3 test suites before submitting a PR to github.
All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass. All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass.
- CircleCI fails if your code is not formatted properly, - CircleCI fails if your code is not formatted properly,
- Travis CI fails if any of the test suites described above fails. - Travis CI fails if any of the test suite describe above fails.
## Update the public API tests ## Update the public API tests

View File

@ -4,6 +4,7 @@
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular) [![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular) [![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore) [![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore)
[![Downloads](http://img.shields.io/npm/dm/angular2.svg)](https://npmjs.org/package/angular2)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci) [![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
*Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].* *Safari (7+), iOS (7+), Edge (14) and IE mobile (11) are tested on [BrowserStack][browserstack].*

View File

@ -1,7 +1,7 @@
# Triage Process and Github Labels for Angular 2 # Triage Process and Github Labels for Angular 2
This document describes how the Angular team uses labels and milestones This document describes how the Angular team uses labels and milestones
to triage issues on github. The basic idea of the process is that to triage issues on github. The basic idea of the new process is that
caretaker only assigns a component and type (bug, feature) label. The 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 owner of the component than is in full control of how the issues should
be triaged further. be triaged further.
@ -17,9 +17,9 @@ with it.
* `comp: animations`: `@matsko` * `comp: animations`: `@matsko`
* `comp: benchpress`: `@tbosch` * `comp: benchpress`: `@tbosch`
* `comp: build & ci`: `@IgorMinar` -- All build and CI scripts * `comp: build/ci`: `@IgorMinar` -- All build and CI scripts
* `comp: common`: `@mhevery` -- This includes core components / pipes. * `comp: common`: `@mhevery` -- This includes core components / pipes.
* `comp: core & compiler`: `@tbosch` -- Because core and compiler are very * `comp: core/compiler`: `@tbosch` -- Because core and compiler are very
intertwined, we will be treating them as one. intertwined, we will be treating them as one.
* `comp: forms`: `@kara` * `comp: forms`: `@kara`
* `comp: http`: `@jeffbcross` * `comp: http`: `@jeffbcross`
@ -29,14 +29,14 @@ with it.
* `comp: testing`: `@juliemr` * `comp: testing`: `@juliemr`
* `comp: upgrade`: `@mhevery` * `comp: upgrade`: `@mhevery`
* `comp: web-worker`: `@vicb` * `comp: web-worker`: `@vicb`
* `comp: zones`: `@mhevery` * `comp: zone`: `@mhevery`
There are few components which are cross-cutting. They don't have 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 a clear location in the source tree. We will treat them as a component
even thought no specific source tree is associated with them. even thought no specific source tree is associated with them.
* `comp: docs`: `@naomiblack` * `comp: documentation`: `@naomiblack`
* `comp: packaging`: `@IgorMinar` * `comp: packaging`: `@mhevery`
* `comp: performance`: `@tbosch` * `comp: performance`: `@tbosch`
* `comp: security`: `@IgorMinar` * `comp: security`: `@IgorMinar`
@ -53,11 +53,11 @@ What kind of problem is this?
## Caretaker Triage Process ## Caretaker Triage Process
It is the caretaker's responsibility to assign `comp: *` to each new It is the caretaker's responsibility to assign `comp: *` and `type: *`
issue as they come in. The reason why we limit the responsibility of the to each new issue as they come in. The reason why we limit the
caretaker to this one label is that it is likely that without domain responsibility of the caretaker to these two labels is that it is
knowledge the caretaker could mislabel issues or lack knowledge of unlikely that without domain knowledge the caretaker could add any
duplicate issues. additional labels of value.
## Component's owner Triage Process ## Component's owner Triage Process
@ -68,37 +68,11 @@ process for their component.
It will be up to the component owner to determine the order in which the It will be up to the component owner to determine the order in which the
issues within the component will be resolved. 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 ### Assigning Issues to Milestones
Any issue that is being worked on must have: 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. * A `Milestone`: When we expect to complete this work.
We aim to only have at most three milestones open at a time: We aim to only have at most three milestones open at a time:

View File

@ -1,98 +1,242 @@
/** // Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL) and BrowserStack (BS).
* @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
*/
// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL)
// and BrowserStack (BS).
// If the target is set to null, then the browser is not run anywhere during CI. // If the target is set to null, then the browser is not run anywhere during CI.
// If a category becomes empty (e.g. BS and required), then the corresponding job must be commented // If a category becomes empty (e.g. BS and required), then the corresponding job must be commented out in Travis configuration.
// out in Travis configuration.
var CIconfiguration = { var CIconfiguration = {
'Chrome': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Firefox': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
// FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true // FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true
// Currently deactivated due to https://github.com/angular/angular/issues/7560 // Currently deactivated due to https://github.com/angular/angular/issues/7560
'ChromeBeta': {unitTest: {target: null, required: true}, e2e: {target: null, required: false}}, 'ChromeBeta': { unitTest: {target: null, required: true}, e2e: {target: null, required: false}},
'FirefoxBeta': {unitTest: {target: null, required: false}, e2e: {target: null, required: false}}, 'FirefoxBeta': { unitTest: {target: null, required: false}, e2e: {target: null, required: false}},
'ChromeDev': {unitTest: {target: null, required: true}, e2e: {target: null, required: true}}, 'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'FirefoxDev': {unitTest: {target: null, required: true}, e2e: {target: null, required: true}}, 'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE9': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'IE10': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'IE10': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'IE11': {unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}}, 'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Edge': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Android4.1': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.2': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.3': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.4': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android4.4': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android5': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}}, 'Android5': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari7': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari7': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'Safari10': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}}, 'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, 'iOS10': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}} 'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
}; };
var customLaunchers = { var customLaunchers = {
'DartiumWithWebPlatform': 'DartiumWithWebPlatform': {
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']}, base: 'Dartium',
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']}, flags: ['--enable-experimental-web-platform-features'] },
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'}, 'ChromeNoSandbox': {
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'}, base: 'Chrome',
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'}, flags: ['--no-sandbox'] },
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'}, 'SL_CHROME': {
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'}, base: 'SauceLabs',
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'}, browserName: 'chrome',
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'}, version: '52'
'SL_SAFARI8': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.10', version: '8.0'}, },
'SL_SAFARI9': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.11', version: '9.0'}, 'SL_CHROMEBETA': {
'SL_SAFARI10': base: 'SauceLabs',
{base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.12', version: '10.0'}, browserName: 'chrome',
'SL_IOS7': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '7.1'}, version: 'beta'
'SL_IOS8': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '8.4'}, },
'SL_IOS9': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '9.3'}, 'SL_CHROMEDEV': {
'SL_IOS10': {base: 'SauceLabs', browserName: 'iphone', platform: 'OS X 10.10', version: '10.0'}, base: 'SauceLabs',
'SL_IE9': browserName: 'chrome',
{base: 'SauceLabs', browserName: 'internet explorer', platform: 'Windows 2008', version: '9'}, version: 'dev'
},
'SL_FIREFOX': {
base: 'SauceLabs',
browserName: 'firefox',
version: '46'
},
'SL_FIREFOXBETA': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'beta'
},
'SL_FIREFOXDEV': {
base: 'SauceLabs',
browserName: 'firefox',
version: 'dev'
},
'SL_SAFARI7': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7.0'
},
'SL_SAFARI8': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.10',
version: '8.0'
},
'SL_SAFARI9': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.11',
version: '9.0'
},
'SL_SAFARI10': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.12',
version: '10.0'
},
'SL_IOS7': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '7.1'
},
'SL_IOS8': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '8.4'
},
'SL_IOS9': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '9.3'
},
'SL_IOS10': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '10.0'
},
'SL_IE9': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 2008',
version: '9'
},
'SL_IE10': { 'SL_IE10': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'internet explorer', browserName: 'internet explorer',
platform: 'Windows 2012', platform: 'Windows 2012',
version: '10' version: '10'
}, },
'SL_IE11': 'SL_IE11': {
{base: 'SauceLabs', browserName: 'internet explorer', platform: 'Windows 8.1', version: '11'}, base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'SL_EDGE': { 'SL_EDGE': {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'MicrosoftEdge', browserName: 'MicrosoftEdge',
platform: 'Windows 10', platform: 'Windows 10',
version: '13.10586' version: '13.10586'
}, },
'SL_ANDROID4.1': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.1'}, 'SL_ANDROID4.1': {
'SL_ANDROID4.2': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.2'}, base: 'SauceLabs',
'SL_ANDROID4.3': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.3'}, browserName: 'android',
'SL_ANDROID4.4': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '4.4'}, platform: 'Linux',
'SL_ANDROID5': {base: 'SauceLabs', browserName: 'android', platform: 'Linux', version: '5.1'}, version: '4.1'
},
'SL_ANDROID4.2': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.2'
},
'SL_ANDROID4.3': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.3'
},
'SL_ANDROID4.4': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.4'
},
'SL_ANDROID5': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '5.1'
},
'BS_CHROME': {base: 'BrowserStack', browser: 'chrome', os: 'OS X', os_version: 'Yosemite'}, 'BS_CHROME': {
'BS_FIREFOX': {base: 'BrowserStack', browser: 'firefox', os: 'Windows', os_version: '10'}, base: 'BrowserStack',
'BS_SAFARI7': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Mavericks'}, browser: 'chrome',
'BS_SAFARI8': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Yosemite'}, os: 'OS X',
'BS_SAFARI9': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'El Capitan'}, os_version: 'Yosemite'
'BS_SAFARI10': {base: 'BrowserStack', browser: 'safari', os: 'OS X', os_version: 'Sierra'}, },
'BS_IOS7': {base: 'BrowserStack', device: 'iPhone 5S', os: 'ios', os_version: '7.0'}, 'BS_FIREFOX': {
'BS_IOS8': {base: 'BrowserStack', device: 'iPhone 6', os: 'ios', os_version: '8.3'}, base: 'BrowserStack',
'BS_IOS9': {base: 'BrowserStack', device: 'iPhone 6S', os: 'ios', os_version: '9.1'}, browser: 'firefox',
'BS_IOS10': {base: 'BrowserStack', device: 'iPhone SE', os: 'ios', os_version: '10.0'}, os: 'Windows',
'BS_IE9': os_version: '10'
{base: 'BrowserStack', browser: 'ie', browser_version: '9.0', os: 'Windows', os_version: '7'}, },
'BS_SAFARI7': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mavericks'
},
'BS_SAFARI8': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_SAFARI9': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'El Capitan'
},
'BS_SAFARI10': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Sierra'
},
'BS_IOS7': {
base: 'BrowserStack',
device: 'iPhone 5S',
os: 'ios',
os_version: '7.0'
},
'BS_IOS8': {
base: 'BrowserStack',
device: 'iPhone 6',
os: 'ios',
os_version: '8.3'
},
'BS_IOS9': {
base: 'BrowserStack',
device: 'iPhone 6S',
os: 'ios',
os_version: '9.1'
},
'BS_IOS10': {
base: 'BrowserStack',
device: 'iPhone SE',
os: 'ios',
os_version: '10.0'
},
'BS_IE9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE10': { 'BS_IE10': {
base: 'BrowserStack', base: 'BrowserStack',
browser: 'ie', browser: 'ie',
@ -107,31 +251,54 @@ var customLaunchers = {
os: 'Windows', os: 'Windows',
os_version: '10' os_version: '10'
}, },
'BS_EDGE': {base: 'BrowserStack', browser: 'edge', os: 'Windows', os_version: '10'}, 'BS_EDGE': {
'BS_WINDOWSPHONE': base: 'BrowserStack',
{base: 'BrowserStack', device: 'Nokia Lumia 930', os: 'winphone', os_version: '8.1'}, browser: 'edge',
'BS_ANDROID5': {base: 'BrowserStack', device: 'Google Nexus 5', os: 'android', os_version: '5.0'}, os: 'Windows',
'BS_ANDROID4.4': {base: 'BrowserStack', device: 'HTC One M8', os: 'android', os_version: '4.4'}, os_version: '10'
'BS_ANDROID4.3': },
{base: 'BrowserStack', device: 'Samsung Galaxy S4', os: 'android', os_version: '4.3'}, 'BS_WINDOWSPHONE' : {
'BS_ANDROID4.2': base: 'BrowserStack',
{base: 'BrowserStack', device: 'Google Nexus 4', os: 'android', os_version: '4.2'}, device: 'Nokia Lumia 930',
'BS_ANDROID4.1': os: 'winphone',
{base: 'BrowserStack', device: 'Google Nexus 7', os: 'android', os_version: '4.1'} os_version: '8.1'
},
'BS_ANDROID5': {
base: 'BrowserStack',
device: 'Google Nexus 5',
os: 'android',
os_version: '5.0'
},
'BS_ANDROID4.4': {
base: 'BrowserStack',
device: 'HTC One M8',
os: 'android',
os_version: '4.4'
},
'BS_ANDROID4.3': {
base: 'BrowserStack',
device: 'Samsung Galaxy S4',
os: 'android',
os_version: '4.3'
},
'BS_ANDROID4.2': {
base: 'BrowserStack',
device: 'Google Nexus 4',
os: 'android',
os_version: '4.2'
},
'BS_ANDROID4.1': {
base: 'BrowserStack',
device: 'Google Nexus 7',
os: 'android',
os_version: '4.1'
}
}; };
var sauceAliases = { var sauceAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) { 'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}),
return customLaunchers[item].base == 'SauceLabs'; 'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10'],
}), 'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10'],
'DESKTOP': [
'SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7',
'SL_SAFARI8', 'SL_SAFARI9', 'SL_SAFARI10'
],
'MOBILE': [
'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5', 'SL_IOS7',
'SL_IOS8', 'SL_IOS9', 'SL_IOS10'
],
'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'], 'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'],
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'], 'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10'], 'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9', 'SL_IOS10'],
@ -143,16 +310,9 @@ var sauceAliases = {
}; };
var browserstackAliases = { var browserstackAliases = {
'ALL': Object.keys(customLaunchers).filter(function(item) { 'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}),
return customLaunchers[item].base == 'BrowserStack'; 'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9', 'BS_SAFARI10'],
}), 'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10', 'BS_WINDOWSPHONE'],
'DESKTOP': [
'BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7',
'BS_SAFARI8', 'BS_SAFARI9', 'BS_SAFARI10'
],
'MOBILE': [
'BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10', 'BS_WINDOWSPHONE'
],
'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'], 'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'], 'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10'], 'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_IOS10'],
@ -173,5 +333,7 @@ function buildConfiguration(type, target, required) {
var conf = CIconfiguration[item][type]; var conf = CIconfiguration[item][type];
return conf.required === required && conf.target === target; return conf.required === required && conf.target === target;
}) })
.map((item) => target + '_' + item.toUpperCase()); .map((item) => {
return target + '_' + item.toUpperCase();
});
} }

View File

@ -1,11 +1,3 @@
/**
* @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
*/
'use strict'; 'use strict';
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE // THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
@ -21,14 +13,12 @@ const os = require('os');
// clang-format entry points // clang-format entry points
const srcsToFmt = [ const srcsToFmt = [
'modules/@angular/**/*.{js,ts}', 'modules/@angular/**/*.ts',
'modules/benchmarks/**/*.{js,ts}', 'modules/benchmarks/**/*.ts',
'modules/e2e_util/**/*.{js,ts}', 'modules/e2e_util/**/*.ts',
'modules/playground/**/*.{js,ts}', 'modules/playground/**/*.ts',
'tools/**/*.{js,ts}', 'tools/**/*.ts',
'!tools/public_api_guard/**/*.d.ts', '!tools/public_api_guard/**/*.d.ts',
'./*.{js,ts}',
'!shims_for_IE.js',
]; ];
// Check source code for formatting errors (clang-format) // Check source code for formatting errors (clang-format)
@ -43,9 +33,8 @@ gulp.task('format:enforce', () => {
gulp.task('format', () => { gulp.task('format', () => {
const format = require('gulp-clang-format'); const format = require('gulp-clang-format');
const clangFormat = require('clang-format'); const clangFormat = require('clang-format');
return gulp.src(srcsToFmt, {base: '.'}) return gulp.src(srcsToFmt, { base: '.' }).pipe(
.pipe(format.format('file', clangFormat)) format.format('file', clangFormat)).pipe(gulp.dest('.'));
.pipe(gulp.dest('.'));
}); });
const entrypoints = [ const entrypoints = [
@ -73,18 +62,12 @@ const entrypoints = [
]; ];
const publicApiDir = path.normalize('tools/public_api_guard'); const publicApiDir = path.normalize('tools/public_api_guard');
const publicApiArgs = [ const publicApiArgs = [
'--rootDir', '--rootDir', 'dist/packages-dist',
'dist/packages-dist', '--stripExportPattern', '^__',
'--stripExportPattern', '--allowModuleIdentifiers', 'jasmine',
'^__', '--allowModuleIdentifiers', 'protractor',
'--allowModuleIdentifiers', '--allowModuleIdentifiers', 'angular',
'jasmine', '--onStabilityMissing', 'error',
'--allowModuleIdentifiers',
'protractor',
'--allowModuleIdentifiers',
'angular',
'--onStabilityMissing',
'error',
].concat(entrypoints); ].concat(entrypoints);
// Build angular // Build angular
@ -124,18 +107,14 @@ gulp.task('public-api:update', ['build.sh'], (done) => {
.on('close', done); .on('close', done);
}); });
// Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the // Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the focused tests is found.
// focused tests is found. // Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded tests in our code base.
// Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded
// tests in our code base.
gulp.task('check-tests', function() { gulp.task('check-tests', function() {
const ddescribeIit = require('gulp-ddescribe-iit'); const ddescribeIit = require('gulp-ddescribe-iit');
return gulp return gulp.src([
.src([
'modules/**/*.spec.ts', 'modules/**/*.spec.ts',
'modules/**/*_spec.ts', 'modules/**/*_spec.ts',
]) ]).pipe(ddescribeIit({allowDisabledTests: true}));
.pipe(ddescribeIit({allowDisabledTests: true}));
}); });
// Check the coding standards and programming errors // Check the coding standards and programming errors
@ -144,14 +123,7 @@ gulp.task('lint', ['check-tests', 'format:enforce', 'tools:build'], () => {
// Built-in rules are at // Built-in rules are at
// https://github.com/palantir/tslint#supported-rules // https://github.com/palantir/tslint#supported-rules
const tslintConfig = require('./tslint.json'); const tslintConfig = require('./tslint.json');
return gulp return gulp.src(['modules/@angular/**/*.ts', 'modules/benchpress/**/*.ts'])
.src([
// todo(vicb): add .js files when supported
// see https://github.com/palantir/tslint/pull/1515
'modules/@angular/**/*.ts',
'modules/benchpress/**/*.ts',
'./*.ts',
])
.pipe(tslint({ .pipe(tslint({
tslint: require('tslint').default, tslint: require('tslint').default,
configuration: tslintConfig, configuration: tslintConfig,
@ -170,7 +142,7 @@ gulp.task('check-cycle', (done) => {
const dependencyObject = madge(['dist/all/'], { const dependencyObject = madge(['dist/all/'], {
format: 'cjs', format: 'cjs',
extensions: ['.js'], extensions: ['.js'],
onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, '//'); } onParseFile: function(data) { data.src = data.src.replace(/\/\* circular \*\//g, "//"); }
}); });
const circularDependencies = dependencyObject.circular().getArray(); const circularDependencies = dependencyObject.circular().getArray();
if (circularDependencies.length > 0) { if (circularDependencies.length > 0) {
@ -215,7 +187,10 @@ gulp.task('changelog', () => {
const conventionalChangelog = require('gulp-conventional-changelog'); const conventionalChangelog = require('gulp-conventional-changelog');
return gulp.src('CHANGELOG.md') return gulp.src('CHANGELOG.md')
.pipe(conventionalChangelog({preset: 'angular', releaseCount: 1}, { .pipe(conventionalChangelog({
preset: 'angular',
releaseCount: 1
}, {
// Conventional Changelog Context // Conventional Changelog Context
// We have to manually set version number so it doesn't get prefixed with `v` // We have to manually set version number so it doesn't get prefixed with `v`
// See https://github.com/conventional-changelog/conventional-changelog-core/issues/10 // See https://github.com/conventional-changelog/conventional-changelog-core/issues/10
@ -230,7 +205,8 @@ function tsc(projectPath, done) {
childProcess childProcess
.spawn( .spawn(
path.normalize(platformScriptPath(`${__dirname}/node_modules/.bin/tsc`)), path.normalize(platformScriptPath(`${__dirname}/node_modules/.bin/tsc`)),
['-p', path.join(__dirname, projectPath)], {stdio: 'inherit'}) ['-p', path.join(__dirname, projectPath)],
{stdio: 'inherit'})
.on('close', done); .on('close', done);
} }

View File

@ -1,11 +1,3 @@
/**
* @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
*/
var browserProvidersConf = require('./browser-providers.conf.js'); var browserProvidersConf = require('./browser-providers.conf.js');
var internalAngularReporter = require('./tools/karma/reporter.js'); var internalAngularReporter = require('./tools/karma/reporter.js');
@ -25,25 +17,24 @@ module.exports = function(config) {
// include Angular v1 for upgrade module testing // include Angular v1 for upgrade module testing
'node_modules/angular/angular.min.js', 'node_modules/angular/angular.min.js',
'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/zone.js/dist/zone.js',
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js', 'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js', 'node_modules/zone.js/dist/fake-async-test.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces. // Including systemjs because it defines `__eval`, which produces correct stack traces.
'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js', 'shims_for_IE.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true}, {pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js', 'node_modules/reflect-metadata/Reflect.js',
{pattern: 'dist/all/empty.*', included: false, watched: false}, { 'tools/build/file2modulename.js',
pattern: 'modules/@angular/platform-browser/test/static_assets/**', 'test-main.js',
included: false, {pattern: 'dist/all/empty.*', included: false, watched: false},
watched: false {pattern: 'modules/@angular/platform-browser/test/static_assets/**', included: false, watched: false},
}, {pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**', included: false, watched: false}
{
pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**',
included: false,
watched: false,
}
], ],
exclude: [ exclude: [
@ -53,7 +44,7 @@ module.exports = function(config) {
'dist/all/@angular/benchpress/**', 'dist/all/@angular/benchpress/**',
'dist/all/angular1_router.js', 'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js', 'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/examples/**/e2e_test/**', 'dist/examples/**/e2e_test/**'
], ],
customLaunchers: browserProvidersConf.customLaunchers, customLaunchers: browserProvidersConf.customLaunchers,
@ -64,11 +55,11 @@ module.exports = function(config) {
'karma-sauce-launcher', 'karma-sauce-launcher',
'karma-chrome-launcher', 'karma-chrome-launcher',
'karma-sourcemap-loader', 'karma-sourcemap-loader',
internalAngularReporter, internalAngularReporter
], ],
preprocessors: { preprocessors: {
'**/*.js': ['sourcemap'], '**/*.js': ['sourcemap']
}, },
reporters: ['internal-angular'], reporters: ['internal-angular'],
@ -82,7 +73,7 @@ module.exports = function(config) {
'selenium-version': '2.53.0', 'selenium-version': '2.53.0',
'command-timeout': 600, 'command-timeout': 600,
'idle-timeout': 600, 'idle-timeout': 600,
'max-duration': 5400, 'max-duration': 5400
} }
}, },
@ -91,21 +82,20 @@ module.exports = function(config) {
startTunnel: false, startTunnel: false,
retryLimit: 3, retryLimit: 3,
timeout: 600, timeout: 600,
pollingTimeout: 10000, pollingTimeout: 10000
}, },
browsers: ['Chrome'], browsers: ['Chrome'],
port: 9876, port: 9876,
captureTimeout: 60000, captureTimeout: 60000,
browserDisconnectTimeout: 60000, browserDisconnectTimeout : 60000,
browserDisconnectTolerance: 3, browserDisconnectTolerance : 3,
browserNoActivityTimeout: 60000, browserNoActivityTimeout : 60000,
}); });
if (process.env.TRAVIS) { if (process.env.TRAVIS) {
var buildId = var buildId = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
if (process.env.CI_MODE.startsWith('saucelabs')) { if (process.env.CI_MODE.startsWith('saucelabs')) {
config.sauceLabs.build = buildId; config.sauceLabs.build = buildId;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;

View File

@ -9,6 +9,8 @@
import {OpaqueToken} from '@angular/core'; import {OpaqueToken} from '@angular/core';
import * as fs from 'fs'; import * as fs from 'fs';
import {DateWrapper} from './facade/lang';
export class Options { export class Options {
static SAMPLE_ID = new OpaqueToken('Options.sampleId'); static SAMPLE_ID = new OpaqueToken('Options.sampleId');
static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription'); static DEFAULT_DESCRIPTION = new OpaqueToken('Options.defaultDescription');
@ -32,7 +34,7 @@ export class Options {
{provide: Options.FORCE_GC, useValue: false}, {provide: Options.FORCE_GC, useValue: false},
{provide: Options.PREPARE, useValue: Options.NO_PREPARE}, {provide: Options.PREPARE, useValue: Options.NO_PREPARE},
{provide: Options.MICRO_METRICS, useValue: {}}, {provide: Options.USER_METRICS, useValue: {}}, {provide: Options.MICRO_METRICS, useValue: {}}, {provide: Options.USER_METRICS, useValue: {}},
{provide: Options.NOW, useValue: () => new Date()}, {provide: Options.NOW, useValue: () => DateWrapper.now()},
{provide: Options.RECEIVED_DATA, useValue: false}, {provide: Options.RECEIVED_DATA, useValue: false},
{provide: Options.REQUEST_COUNT, useValue: false}, {provide: Options.REQUEST_COUNT, useValue: false},
{provide: Options.CAPTURE_FRAMES, useValue: false}, {provide: Options.CAPTURE_FRAMES, useValue: false},

View File

@ -7,6 +7,7 @@
*/ */
import {Injector, OpaqueToken} from '@angular/core'; import {Injector, OpaqueToken} from '@angular/core';
import {StringMapWrapper} from '../facade/collection';
import {Metric} from '../metric'; import {Metric} from '../metric';
@ -56,7 +57,8 @@ export class MultiMetric extends Metric {
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} { function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
var result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); }); maps.forEach(
map => { StringMapWrapper.forEach(map, (value, prop) => { result[prop] = value; }); });
return result; return result;
} }

View File

@ -9,6 +9,7 @@
import {Inject, Injectable} from '@angular/core'; import {Inject, Injectable} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {StringMapWrapper} from '../facade/collection';
import {isNumber} from '../facade/lang'; import {isNumber} from '../facade/lang';
import {Metric} from '../metric'; import {Metric} from '../metric';
import {WebDriverAdapter} from '../web_driver_adapter'; import {WebDriverAdapter} from '../web_driver_adapter';
@ -39,7 +40,7 @@ export class UserMetric extends Metric {
reject = rej; reject = rej;
}); });
let adapter = this._wdAdapter; let adapter = this._wdAdapter;
let names = Object.keys(this._userMetrics); let names = StringMapWrapper.keys(this._userMetrics);
function getAndClearValues() { function getAndClearValues() {
Promise.all(names.map(name => adapter.executeScript(`return window.${name}`))) Promise.all(names.map(name => adapter.executeScript(`return window.${name}`)))

View File

@ -7,7 +7,10 @@
*/ */
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {print} from '../facade/lang';
import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {NumberWrapper, isBlank, isPresent, print} from '../facade/lang';
import {Math} from '../facade/math';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Reporter} from '../reporter'; import {Reporter} from '../reporter';
import {SampleDescription} from '../sample_description'; import {SampleDescription} from '../sample_description';

View File

@ -9,7 +9,7 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Options} from '../common_options'; import {Options} from '../common_options';
import {Json} from '../facade/lang'; import {DateWrapper, Json, isBlank, isPresent} from '../facade/lang';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Reporter} from '../reporter'; import {Reporter} from '../reporter';
import {SampleDescription} from '../sample_description'; import {SampleDescription} from '../sample_description';
@ -45,7 +45,8 @@ export class JsonFileReporter extends Reporter {
'completeSample': completeSample, 'completeSample': completeSample,
'validSample': validSample, 'validSample': validSample,
}); });
var filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`; var filePath =
`${this._path}/${this._description.id}_${DateWrapper.toMillis(this._now())}.json`;
return this._writeFile(filePath, content); return this._writeFile(filePath, content);
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StringMapWrapper} from '../facade/collection';
import {NumberWrapper} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
@ -17,7 +17,7 @@ export function formatNum(n: number) {
export function sortedProps(obj: {[key: string]: any}) { export function sortedProps(obj: {[key: string]: any}) {
var props: string[] = []; var props: string[] = [];
props.push(...Object.keys(obj)); StringMapWrapper.forEach(obj, (value, prop) => props.push(prop));
props.sort(); props.sort();
return props; return props;
} }

View File

@ -9,7 +9,7 @@
import {Provider, ReflectiveInjector} from '@angular/core'; import {Provider, ReflectiveInjector} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Metric} from './metric'; import {Metric} from './metric';
import {MultiMetric} from './metric/multi_metric'; import {MultiMetric} from './metric/multi_metric';
import {PerflogMetric} from './metric/perflog_metric'; import {PerflogMetric} from './metric/perflog_metric';

View File

@ -9,6 +9,7 @@
import {OpaqueToken} from '@angular/core'; import {OpaqueToken} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {StringMapWrapper} from './facade/collection';
import {Metric} from './metric'; import {Metric} from './metric';
import {Validator} from './validator'; import {Validator} from './validator';
@ -41,7 +42,7 @@ export class SampleDescription {
public metrics: {[key: string]: any}) { public metrics: {[key: string]: any}) {
this.description = {}; this.description = {};
descriptions.forEach(description => { descriptions.forEach(description => {
Object.keys(description).forEach(prop => { this.description[prop] = description[prop]; }); StringMapWrapper.forEach(description, (value, prop) => this.description[prop] = value);
}); });
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Inject, Injectable} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {isPresent} from './facade/lang'; import {Date, DateWrapper, isBlank, isPresent} from './facade/lang';
import {MeasureValues} from './measure_values'; import {MeasureValues} from './measure_values';
import {Metric} from './metric'; import {Metric} from './metric';
import {Reporter} from './reporter'; import {Reporter} from './reporter';

View File

@ -9,6 +9,7 @@
import {Injector, OpaqueToken} from '@angular/core'; import {Injector, OpaqueToken} from '@angular/core';
import {Options} from './common_options'; import {Options} from './common_options';
import {isBlank, isPresent} from './facade/lang';
export type PerfLogEvent = { export type PerfLogEvent = {
[key: string]: any [key: string]: any
@ -49,7 +50,7 @@ export abstract class WebDriverExtension {
delegate = extension; delegate = extension;
} }
}); });
if (!delegate) { if (isBlank(delegate)) {
throw new Error('Could not find a delegate for given capabilities!'); throw new Error('Could not find a delegate for given capabilities!');
} }
return delegate; return delegate;

View File

@ -169,7 +169,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
eventCategories: string[], eventName: string, expectedCategories: string[], eventCategories: string[], eventName: string, expectedCategories: string[],
expectedName: string = null): boolean { expectedName: string = null): boolean {
var hasCategories = expectedCategories.reduce( var hasCategories = expectedCategories.reduce(
(value, cat) => value && eventCategories.indexOf(cat) !== -1, true); (value, cat) => { return value && eventCategories.indexOf(cat) !== -1; }, true);
return !expectedName ? hasCategories : hasCategories && eventName === expectedName; return !expectedName ? hasCategories : hasCategories && eventName === expectedName;
} }

View File

@ -52,7 +52,7 @@ export class IOsDriverExtension extends WebDriverExtension {
/** @internal */ /** @internal */
private _convertPerfRecordsToEvents(records: any[], events: PerfLogEvent[] = null) { private _convertPerfRecordsToEvents(records: any[], events: PerfLogEvent[] = null) {
if (!events) { if (isBlank(events)) {
events = []; events = [];
} }
records.forEach((record) => { records.forEach((record) => {

View File

@ -6,14 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Metric, MultiMetric, ReflectiveInjector} from '../../index'; import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
export function main() { export function main() {
function createMetric(ids: any[]) { function createMetric(ids: any[]) {
var m = ReflectiveInjector var m = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockMetric(id)})), ids.map(id => { return {provide: id, useValue: new MockMetric(id)}; }),
MultiMetric.provideWith(ids) MultiMetric.provideWith(ids)
]) ])
.get(MultiMetric); .get(MultiMetric);

View File

@ -7,10 +7,11 @@
*/ */
import {Provider} from '@angular/core'; import {Provider} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index'; import {Metric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, ReflectiveInjector, WebDriverExtension} from '../../index';
import {isPresent} from '../../src/facade/lang'; import {StringMapWrapper} from '../../src/facade/collection';
import {isBlank, isPresent} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
@ -27,11 +28,11 @@ export function main() {
requestCount?: boolean requestCount?: boolean
} = {}): Metric { } = {}): Metric {
commandLog = []; commandLog = [];
if (!perfLogFeatures) { if (isBlank(perfLogFeatures)) {
perfLogFeatures = perfLogFeatures =
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true}); new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
} }
if (!microMetrics) { if (isBlank(microMetrics)) {
microMetrics = {}; microMetrics = {};
} }
var providers: Provider[] = [ var providers: Provider[] = [
@ -67,7 +68,7 @@ export function main() {
function sortedKeys(stringMap: {[key: string]: any}) { function sortedKeys(stringMap: {[key: string]: any}) {
var res: string[] = []; var res: string[] = [];
res.push(...Object.keys(stringMap)); StringMapWrapper.forEach(stringMap, (_, key) => { res.push(key); });
res.sort(); res.sort();
return res; return res;
} }

View File

@ -7,9 +7,11 @@
*/ */
import {Provider, ReflectiveInjector} from '@angular/core'; import {Provider, ReflectiveInjector} from '@angular/core';
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index'; import {Injector, Metric, MultiMetric, Options, PerfLogEvent, PerfLogFeatures, PerflogMetric, UserMetric, WebDriverAdapter, WebDriverExtension} from '../../index';
import {StringMapWrapper} from '../../src/facade/collection';
import {Json, isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
var wdAdapter: MockDriverAdapter; var wdAdapter: MockDriverAdapter;
@ -17,11 +19,11 @@ export function main() {
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
{userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric { {userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric {
if (!perfLogFeatures) { if (isBlank(perfLogFeatures)) {
perfLogFeatures = perfLogFeatures =
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true}); new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
} }
if (!userMetrics) { if (isBlank(userMetrics)) {
userMetrics = {}; userMetrics = {};
} }
wdAdapter = new MockDriverAdapter(); wdAdapter = new MockDriverAdapter();

View File

@ -7,10 +7,10 @@
*/ */
import {Provider} from '@angular/core'; import {Provider} from '@angular/core';
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {ConsoleReporter, MeasureValues, ReflectiveInjector, SampleDescription} from '../../index'; import {ConsoleReporter, MeasureValues, ReflectiveInjector, Reporter, SampleDescription, SampleState} from '../../index';
import {isBlank, isPresent} from '../../src/facade/lang'; import {Date, DateWrapper, isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('console reporter', () => { describe('console reporter', () => {
@ -25,7 +25,7 @@ export function main() {
metrics?: {[key: string]: any} metrics?: {[key: string]: any}
}) { }) {
log = []; log = [];
if (!descriptions) { if (isBlank(descriptions)) {
descriptions = []; descriptions = [];
} }
if (isBlank(sampleId)) { if (isBlank(sampleId)) {
@ -90,5 +90,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, new Date(time), values); return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index'; import {JsonFileReporter, MeasureValues, Options, ReflectiveInjector, SampleDescription} from '../../index';
import {Json, isPresent} from '../../src/facade/lang'; import {DateWrapper, Json, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('file reporter', () => { describe('file reporter', () => {
@ -27,7 +27,7 @@ export function main() {
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)
}, },
{provide: JsonFileReporter.PATH, useValue: path}, {provide: JsonFileReporter.PATH, useValue: path},
{provide: Options.NOW, useValue: () => new Date(1234)}, { {provide: Options.NOW, useValue: () => DateWrapper.fromMillis(1234)}, {
provide: Options.WRITE_FILE, provide: Options.WRITE_FILE,
useValue: (filename: string, content: string) => { useValue: (filename: string, content: string) => {
loggedFile = {'filename': filename, 'content': content}; loggedFile = {'filename': filename, 'content': content};
@ -77,5 +77,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, new Date(time), values); return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
} }

View File

@ -6,15 +6,16 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index'; import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../index';
import {DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
function createReporters(ids: any[]) { function createReporters(ids: any[]) {
var r = ReflectiveInjector var r = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockReporter(id)})), ids.map(id => { return {provide: id, useValue: new MockReporter(id)}; }),
MultiReporter.provideWith(ids) MultiReporter.provideWith(ids)
]) ])
.get(MultiReporter); .get(MultiReporter);
@ -25,7 +26,7 @@ export function main() {
it('should reportMeasureValues to all', it('should reportMeasureValues to all',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var mv = new MeasureValues(0, new Date(), {}); var mv = new MeasureValues(0, DateWrapper.now(), {});
createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => { createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => {
expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]); expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]);
@ -34,8 +35,9 @@ export function main() {
})); }));
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var completeSample = var completeSample = [
[new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})]; new MeasureValues(0, DateWrapper.now(), {}), new MeasureValues(1, DateWrapper.now(), {})
];
var validSample = [completeSample[1]]; var validSample = [completeSample[1]];
createReporters(['m1', 'm2']) createReporters(['m1', 'm2'])

View File

@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index'; import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
import {isBlank} from '../src/facade/lang';
export function main() { export function main() {
describe('runner', () => { describe('runner', () => {
@ -16,7 +17,7 @@ export function main() {
var runner: Runner; var runner: Runner;
function createRunner(defaultProviders: any[] = null): Runner { function createRunner(defaultProviders: any[] = null): Runner {
if (!defaultProviders) { if (isBlank(defaultProviders)) {
defaultProviders = []; defaultProviders = [];
} }
runner = new Runner([ runner = new Runner([

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index'; import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, Validator, WebDriverAdapter} from '../index';
import {isBlank, isPresent} from '../src/facade/lang'; import {Date, DateWrapper, isBlank, isPresent, stringify} from '../src/facade/lang';
export function main() { export function main() {
var EMPTY_EXECUTE = () => {}; var EMPTY_EXECUTE = () => {};
@ -26,10 +26,10 @@ export function main() {
execute?: any execute?: any
} = {}) { } = {}) {
var time = 1000; var time = 1000;
if (!metric) { if (isBlank(metric)) {
metric = new MockMetric([]); metric = new MockMetric([]);
} }
if (!reporter) { if (isBlank(reporter)) {
reporter = new MockReporter([]); reporter = new MockReporter([]);
} }
if (isBlank(driver)) { if (isBlank(driver)) {
@ -39,7 +39,7 @@ export function main() {
Options.DEFAULT_PROVIDERS, Sampler.PROVIDERS, {provide: Metric, useValue: metric}, Options.DEFAULT_PROVIDERS, Sampler.PROVIDERS, {provide: Metric, useValue: metric},
{provide: Reporter, useValue: reporter}, {provide: WebDriverAdapter, useValue: driver}, {provide: Reporter, useValue: reporter}, {provide: WebDriverAdapter, useValue: driver},
{provide: Options.EXECUTE, useValue: execute}, {provide: Validator, useValue: validator}, {provide: Options.EXECUTE, useValue: execute}, {provide: Validator, useValue: validator},
{provide: Options.NOW, useValue: () => new Date(time++)} {provide: Options.NOW, useValue: () => DateWrapper.fromMillis(time++)}
]; ];
if (isPresent(prepare)) { if (isPresent(prepare)) {
providers.push({provide: Options.PREPARE, useValue: prepare}); providers.push({provide: Options.PREPARE, useValue: prepare});
@ -60,8 +60,8 @@ export function main() {
createSampler({ createSampler({
driver: driver, driver: driver,
validator: createCountingValidator(2), validator: createCountingValidator(2),
prepare: () => count++, prepare: () => { return count++; },
execute: () => count++, execute: () => { return count++; }
}); });
sampler.sample().then((_) => { sampler.sample().then((_) => {
expect(count).toBe(4); expect(count).toBe(4);
@ -204,7 +204,7 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, new Date(time), values); return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
} }
function createCountingValidator( function createCountingValidator(
@ -221,7 +221,7 @@ function createCountingValidator(
function createCountingMetric(log: any[] = []) { function createCountingMetric(log: any[] = []) {
var scriptTime = 0; var scriptTime = 0;
return new MockMetric(log, () => ({'script': scriptTime++})); return new MockMetric(log, () => { return {'script': scriptTime++}; });
} }
class MockDriverAdapter extends WebDriverAdapter { class MockDriverAdapter extends WebDriverAdapter {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Statistic} from '../src/statistic'; import {Statistic} from '../src/statistic';
export function main() { export function main() {

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection'; import {ListWrapper} from '../../src/facade/collection';
import {Date, DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
@ -61,5 +62,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, new Date(time), values); return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
} }

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, SizeValidator, Validator} from '../../index';
import {ListWrapper} from '../../src/facade/collection'; import {ListWrapper} from '../../src/facade/collection';
import {Date, DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
@ -46,5 +47,5 @@ export function main() {
} }
function mv(runIndex: number, time: number, values: {[key: string]: number}) { function mv(runIndex: number, time: number, values: {[key: string]: number}) {
return new MeasureValues(runIndex, new Date(time), values); return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values);
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {Options, ReflectiveInjector, WebDriverExtension} from '../index'; import {Options, ReflectiveInjector, WebDriverExtension} from '../index';
import {StringWrapper, isPresent} from '../src/facade/lang'; import {StringWrapper, isPresent} from '../src/facade/lang';
@ -17,7 +17,7 @@ export function main() {
try { try {
res(ReflectiveInjector res(ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map((id) => ({provide: id, useValue: new MockExtension(id)})), ids.map((id) => { return {provide: id, useValue: new MockExtension(id)}; }),
{provide: Options.CAPABILITIES, useValue: caps}, {provide: Options.CAPABILITIES, useValue: caps},
WebDriverExtension.provideFirstSupported(ids) WebDriverExtension.provideFirstSupported(ids)
]) ])

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index'; import {ChromeDriverExtension, Options, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
import {Json, isBlank} from '../../src/facade/lang'; import {Json, isBlank} from '../../src/facade/lang';
@ -35,7 +35,7 @@ export function main() {
function createExtension( function createExtension(
perfRecords: any[] = null, userAgent: string = null, perfRecords: any[] = null, userAgent: string = null,
messageMethod = 'Tracing.dataCollected'): WebDriverExtension { messageMethod = 'Tracing.dataCollected'): WebDriverExtension {
if (!perfRecords) { if (isBlank(perfRecords)) {
perfRecords = []; perfRecords = [];
} }
if (isBlank(userAgent)) { if (isBlank(userAgent)) {
@ -396,11 +396,11 @@ class MockDriverAdapter extends WebDriverAdapter {
logs(type: string) { logs(type: string) {
this._log.push(['logs', type]); this._log.push(['logs', type]);
if (type === 'performance') { if (type === 'performance') {
return Promise.resolve(this._events.map( return Promise.resolve(this._events.map((event) => {
(event) => ({ return {
'message': 'message': Json.stringify({'message': {'method': this._messageMethod, 'params': event}})
Json.stringify({'message': {'method': this._messageMethod, 'params': event}}) };
}))); }));
} else { } else {
return null; return null;
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index'; import {IOsDriverExtension, ReflectiveInjector, WebDriverAdapter, WebDriverExtension} from '../../index';
import {Json} from '../../src/facade/lang'; import {Json, isBlank, isPresent} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
@ -20,7 +20,7 @@ export function main() {
var normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension(perfRecords: any[] = null): WebDriverExtension { function createExtension(perfRecords: any[] = null): WebDriverExtension {
if (!perfRecords) { if (isBlank(perfRecords)) {
perfRecords = []; perfRecords = [];
} }
log = []; log = [];
@ -156,7 +156,7 @@ function timeEndRecord(name: string, time: number) {
} }
function durationRecord(type: string, startTime: number, endTime: number, children: any[] = null) { function durationRecord(type: string, startTime: number, endTime: number, children: any[] = null) {
if (!children) { if (isBlank(children)) {
children = []; children = [];
} }
return {'type': type, 'startTime': startTime, 'endTime': endTime, 'children': children}; return {'type': type, 'startTime': startTime, 'endTime': endTime, 'children': children};

View File

@ -1,10 +1,3 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/common/testing/index.js', entry: '../../../dist/packages-dist/common/testing/index.js',
@ -17,4 +10,4 @@ export default {
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx' 'rxjs/Subject': 'Rx'
} }
}; }

View File

@ -1,10 +1,3 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/common/index.js', entry: '../../../dist/packages-dist/common/index.js',
@ -14,6 +7,6 @@ export default {
globals: { globals: {
'@angular/core': 'ng.core', '@angular/core': 'ng.core',
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx', 'rxjs/Subject': 'Rx'
} }
}; }

View File

@ -67,7 +67,7 @@ export class NgStyle implements DoCheck {
private _setStyle(nameAndUnit: string, value: string): void { private _setStyle(nameAndUnit: string, value: string): void {
const [name, unit] = nameAndUnit.split('.'); const [name, unit] = nameAndUnit.split('.');
value = value && unit ? `${value}${unit}` : value; value = value !== null && value !== void(0) && unit ? `${value}${unit}` : value;
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value); this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
} }

View File

@ -8,7 +8,7 @@
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('binding to CSS class list', () => { describe('binding to CSS class list', () => {

View File

@ -8,7 +8,7 @@
import {LOCALE_ID} from '@angular/core'; import {LOCALE_ID} from '@angular/core';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization'; import {NgLocaleLocalization, NgLocalization, getPluralCategory} from '../src/localization';

View File

@ -8,11 +8,12 @@
import {AsyncPipe} from '@angular/common'; import {AsyncPipe} from '@angular/common';
import {WrappedValue} from '@angular/core'; import {WrappedValue} from '@angular/core';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {EventEmitter} from '../../src/facade/async'; import {EventEmitter} from '../../src/facade/async';
import {isBlank} from '../../src/facade/lang';
import {SpyChangeDetectorRef} from '../spies'; import {SpyChangeDetectorRef} from '../spies';
export function main() { export function main() {
@ -111,7 +112,7 @@ export function main() {
var promise: Promise<any>; var promise: Promise<any>;
var ref: SpyChangeDetectorRef; var ref: SpyChangeDetectorRef;
// adds longer timers for passing tests in IE // adds longer timers for passing tests in IE
var timer = (getDOM() && browserDetection.isIE) ? 50 : 10; var timer = (!isBlank(getDOM()) && browserDetection.isIE) ? 50 : 10;
beforeEach(() => { beforeEach(() => {
promise = new Promise((res, rej) => { promise = new Promise((res, rej) => {

View File

@ -8,9 +8,11 @@
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {DateWrapper} from '../../src/facade/lang';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
var date: Date; var date: Date;
@ -25,7 +27,7 @@ export function main() {
// Tracking issue: https://github.com/angular/angular/issues/11187 // Tracking issue: https://github.com/angular/angular/issues/11187
beforeEach(() => { beforeEach(() => {
date = new Date(2015, 5, 15, 9, 3, 1); date = DateWrapper.create(2015, 6, 15, 9, 3, 1);
pipe = new DatePipe('en-US'); pipe = new DatePipe('en-US');
}); });

View File

@ -8,7 +8,7 @@
import {I18nPluralPipe, NgLocalization} from '@angular/common'; import {I18nPluralPipe, NgLocalization} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('I18nPluralPipe', () => { describe('I18nPluralPipe', () => {

View File

@ -8,7 +8,7 @@
import {I18nSelectPipe} from '@angular/common'; import {I18nSelectPipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('I18nSelectPipe', () => { describe('I18nSelectPipe', () => {

View File

@ -7,7 +7,7 @@
*/ */
import {LowerCasePipe} from '@angular/common'; import {LowerCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('LowerCasePipe', () => { describe('LowerCasePipe', () => {

View File

@ -7,7 +7,7 @@
*/ */
import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common'; import {CurrencyPipe, DecimalPipe, PercentPipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {

View File

@ -9,6 +9,7 @@
import {CommonModule, SlicePipe} from '@angular/common'; import {CommonModule, SlicePipe} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {TestBed, async} from '@angular/core/testing'; import {TestBed, async} from '@angular/core/testing';
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {

View File

@ -7,7 +7,7 @@
*/ */
import {UpperCasePipe} from '@angular/common'; import {UpperCasePipe} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
export function main() { export function main() {
describe('UpperCasePipe', () => { describe('UpperCasePipe', () => {

View File

@ -7,7 +7,7 @@
*/ */
import {ChangeDetectorRef} from '@angular/core/src/change_detection/change_detector_ref'; import {ChangeDetectorRef} from '@angular/core/src/change_detection/change_detector_ref';
import {SpyObject} from '@angular/core/testing/testing_internal'; import {SpyObject, proxy} from '@angular/core/testing/testing_internal';
export class SpyChangeDetectorRef extends SpyObject { export class SpyChangeDetectorRef extends SpyObject {
constructor() { constructor() {

View File

@ -36,8 +36,7 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<messagebundle> <messagebundle>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg> <msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg> <msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle> </messagebundle>`;
`;
const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?> const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
@ -55,8 +54,7 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>`;
`;
describe('template i18n extraction output', () => { describe('template i18n extraction output', () => {
const outDir = ''; const outDir = '';

View File

@ -1,14 +1,10 @@
/**
* @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
*/
module.exports = { module.exports = {
target: 'node', target: 'node',
entry: './test/all_spec.js', entry: './test/all_spec.js',
output: {filename: './all_spec.js'}, output: {
resolve: {extensions: ['.js']}, filename: './all_spec.js'
},
resolve: {
extensions: ['.js']
},
}; };

View File

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@angular/tsc-wrapped": "^0.3.0", "@angular/tsc-wrapped": "^0.3.0",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"parse5": "^2.2.1", "parse5": "1.3.2",
"minimist": "^1.2.0" "minimist": "^1.2.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -126,7 +126,7 @@ export class CodeGenerator {
static create( static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext, compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator { resourceLoader?: compiler.ResourceLoader): CodeGenerator {
resourceLoader = resourceLoader || { resourceLoader = resourceLoader || {
get: (s: string) => { get: (s: string) => {
if (!compilerHost.fileExists(s)) { if (!compilerHost.fileExists(s)) {
@ -148,12 +148,10 @@ export class CodeGenerator {
} }
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
reflectorHost = usePathMapping ? const reflectorHost = usePathMapping ?
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) : new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) :
new ReflectorHost(program, compilerHost, options, reflectorHostContext); new ReflectorHost(program, compilerHost, options, reflectorHostContext);
}
const staticReflector = new StaticReflector(reflectorHost); const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector); StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = const htmlParser =

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Query, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from './private_import_core'; import {ReflectorReader} from './private_import_core';

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal'; import {beforeEach, ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ReflectorHost} from '../src/reflector_host'; import {ReflectorHost} from '../src/reflector_host';

View File

@ -9,6 +9,7 @@
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector'; import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ListWrapper} from '@angular/facade/src/collection'; import {ListWrapper} from '@angular/facade/src/collection';
import {isBlank} from '@angular/facade/src/lang';
import {MetadataCollector} from '@angular/tsc-wrapped'; import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -453,7 +454,7 @@ class MockReflectorHost implements StaticReflectorHost {
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
var cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`; var cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`;
var result = this.staticTypeCache.get(cacheKey); var result = this.staticTypeCache.get(cacheKey);
if (!result) { if (isBlank(result)) {
result = new StaticSymbol(declarationFile, name, members); result = new StaticSymbol(declarationFile, name, members);
this.staticTypeCache.set(cacheKey, result); this.staticTypeCache.set(cacheKey, result);
} }

View File

@ -1,10 +1,3 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/compiler/testing/index.js', entry: '../../../dist/packages-dist/compiler/testing/index.js',
@ -18,4 +11,4 @@ export default {
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx' 'rxjs/Subject': 'Rx'
} }
}; }

View File

@ -1,10 +1,3 @@
/**
* @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 default { export default {
entry: '../../../dist/packages-dist/compiler/index.js', entry: '../../../dist/packages-dist/compiler/index.js',
@ -14,9 +7,9 @@ export default {
globals: { globals: {
'@angular/core': 'ng.core', '@angular/core': 'ng.core',
'rxjs/Observable': 'Rx', 'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx', 'rxjs/Subject': 'Rx'
}, },
plugins: [ plugins: [
// nodeResolve({ jsnext: true, main: true }), // nodeResolve({ jsnext: true, main: true }),
] ]
}; }

View File

@ -6,26 +6,75 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata} from '../compile_metadata';
import {isPresent} from '../facade/lang'; import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core'; import {ANY_STATE, AnimationOutput, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
import * as t from '../template_parser/template_ast';
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast'; import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry, parseAnimationOutputName} from './animation_parser';
export class AnimationEntryCompileResult { const animationCompilationCache =
constructor(public name: string, public statements: o.Statement[], public fnExp: o.Expression) {} new Map<CompileDirectiveMetadata, CompiledAnimationTriggerResult[]>();
export class CompiledAnimationTriggerResult {
constructor(
public name: string, public statesMapStatement: o.Statement,
public statesVariableName: string, public fnStatement: o.Statement,
public fnVariable: o.Expression) {}
}
export class CompiledComponentAnimationResult {
constructor(
public outputs: AnimationOutput[], public triggers: CompiledAnimationTriggerResult[]) {}
} }
export class AnimationCompiler { export class AnimationCompiler {
compile(factoryNamePrefix: string, parsedAnimations: AnimationEntryAst[]): compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]):
AnimationEntryCompileResult[] { CompiledComponentAnimationResult {
return parsedAnimations.map(entry => { var compiledAnimations: CompiledAnimationTriggerResult[] = [];
const factoryName = `${factoryNamePrefix}_${entry.name}`; var groupedErrors: string[] = [];
const visitor = new _AnimationBuilder(entry.name, factoryName); var triggerLookup: {[key: string]: CompiledAnimationTriggerResult} = {};
return visitor.build(entry); var componentName = component.type.name;
component.template.animations.forEach(entry => {
var result = parseAnimationEntry(entry);
var triggerName = entry.name;
if (result.errors.length > 0) {
var errorMessage =
`Unable to parse the animation sequence for "${triggerName}" due to the following errors:`;
result.errors.forEach(
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
groupedErrors.push(errorMessage);
}
if (triggerLookup[triggerName]) {
groupedErrors.push(
`The animation trigger "${triggerName}" has already been registered on "${componentName}"`);
} else {
var factoryName = `${componentName}_${entry.name}`;
var visitor = new _AnimationBuilder(triggerName, factoryName);
var compileResult = visitor.build(result.ast);
compiledAnimations.push(compileResult);
triggerLookup[entry.name] = compileResult;
}
}); });
var validatedProperties = _validateAnimationProperties(compiledAnimations, template);
validatedProperties.errors.forEach(error => { groupedErrors.push(error.msg); });
if (groupedErrors.length > 0) {
var errorMessageStr =
`Animation parsing for ${component.type.name} has failed due to the following errors:`;
groupedErrors.forEach(error => errorMessageStr += `\n- ${error}`);
throw new Error(errorMessageStr);
}
animationCompilationCache.set(component, compiledAnimations);
return new CompiledComponentAnimationResult(validatedProperties.outputs, compiledAnimations);
} }
} }
@ -61,7 +110,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
} }
ast.styles.forEach(entry => { ast.styles.forEach(entry => {
stylesArr.push(o.literalMap(Object.keys(entry).map(key => [key, o.literal(entry[key])]))); stylesArr.push(
o.literalMap(StringMapWrapper.keys(entry).map(key => [key, o.literal(entry[key])])));
}); });
return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([ return o.importExpr(resolveIdentifier(Identifiers.AnimationStyles)).instantiate([
@ -132,8 +182,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
visitAnimationStateDeclaration( visitAnimationStateDeclaration(
ast: AnimationStateDeclarationAst, context: _AnimationBuilderContext): void { ast: AnimationStateDeclarationAst, context: _AnimationBuilderContext): void {
var flatStyles: {[key: string]: string | number} = {}; var flatStyles: {[key: string]: string | number} = {};
_getStylesArray(ast).forEach( _getStylesArray(ast).forEach(entry => {
entry => { Object.keys(entry).forEach(key => { flatStyles[key] = entry[key]; }); }); StringMapWrapper.forEach(entry, (value: string, key: string) => { flatStyles[key] = value; });
});
context.stateMap.registerState(ast.stateName, flatStyles); context.stateMap.registerState(ast.stateName, flatStyles);
} }
@ -283,27 +334,28 @@ class _AnimationBuilder implements AnimationAstVisitor {
statements); statements);
} }
build(ast: AnimationAst): AnimationEntryCompileResult { build(ast: AnimationAst): CompiledAnimationTriggerResult {
var context = new _AnimationBuilderContext(); var context = new _AnimationBuilderContext();
var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName); var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
var fnVariable = o.variable(this._fnVarName); var fnVariable = o.variable(this._fnVarName);
var lookupMap: any[] = []; var lookupMap: any[] = [];
Object.keys(context.stateMap.states).forEach(stateName => { StringMapWrapper.forEach(
const value = context.stateMap.states[stateName]; context.stateMap.states, (value: {[key: string]: string}, stateName: string) => {
var variableValue = EMPTY_MAP; var variableValue = EMPTY_MAP;
if (isPresent(value)) { if (isPresent(value)) {
let styleMap: any[] = []; let styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); }); StringMapWrapper.forEach(value, (value: string, key: string) => {
styleMap.push([key, o.literal(value)]);
});
variableValue = o.literalMap(styleMap); variableValue = o.literalMap(styleMap);
} }
lookupMap.push([stateName, variableValue]); lookupMap.push([stateName, variableValue]);
}); });
const compiledStatesMapStmt = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt(); var compiledStatesMapExpr = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
const statements: o.Statement[] = [compiledStatesMapStmt, fnStatement]; return new CompiledAnimationTriggerResult(
this.animationName, compiledStatesMapExpr, this._statesMapVarName, fnStatement, fnVariable);
return new AnimationEntryCompileResult(this.animationName, statements, fnVariable);
} }
} }
@ -319,7 +371,7 @@ class _AnimationBuilderStateMap {
get states() { return this._states; } get states() { return this._states; }
registerState(name: string, value: {[prop: string]: string | number} = null): void { registerState(name: string, value: {[prop: string]: string | number} = null): void {
var existingEntry = this._states[name]; var existingEntry = this._states[name];
if (!existingEntry) { if (isBlank(existingEntry)) {
this._states[name] = value; this._states[name] = value;
} }
} }
@ -345,7 +397,7 @@ function _isEndStateAnimateStep(step: AnimationAst): boolean {
if (step instanceof AnimationStepAst && step.duration > 0 && step.keyframes.length == 2) { if (step instanceof AnimationStepAst && step.duration > 0 && step.keyframes.length == 2) {
var styles1 = _getStylesArray(step.keyframes[0])[0]; var styles1 = _getStylesArray(step.keyframes[0])[0];
var styles2 = _getStylesArray(step.keyframes[1])[0]; var styles2 = _getStylesArray(step.keyframes[1])[0];
return Object.keys(styles1).length === 0 && Object.keys(styles2).length === 0; return StringMapWrapper.isEmpty(styles1) && StringMapWrapper.isEmpty(styles2);
} }
return false; return false;
} }
@ -353,3 +405,99 @@ function _isEndStateAnimateStep(step: AnimationAst): boolean {
function _getStylesArray(obj: any): {[key: string]: any}[] { function _getStylesArray(obj: any): {[key: string]: any}[] {
return obj.styles.styles; return obj.styles.styles;
} }
function _validateAnimationProperties(
compiledAnimations: CompiledAnimationTriggerResult[],
template: t.TemplateAst[]): AnimationPropertyValidationOutput {
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
t.templateVisitAll(visitor, template);
return new AnimationPropertyValidationOutput(visitor.outputs, visitor.errors);
}
export class AnimationPropertyValidationOutput {
constructor(public outputs: AnimationOutput[], public errors: AnimationParseError[]) {}
}
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
private _animationRegistry: {[key: string]: boolean};
public errors: AnimationParseError[] = [];
public outputs: AnimationOutput[] = [];
constructor(animations: CompiledAnimationTriggerResult[]) {
this._animationRegistry = this._buildCompileAnimationLookup(animations);
}
private _buildCompileAnimationLookup(animations: CompiledAnimationTriggerResult[]):
{[key: string]: boolean} {
var map: {[key: string]: boolean} = {};
animations.forEach(entry => { map[entry.name] = true; });
return map;
}
private _validateAnimationInputOutputPairs(
inputAsts: t.BoundElementPropertyAst[], outputAsts: t.BoundEventAst[],
animationRegistry: {[key: string]: any}, isHostLevel: boolean): void {
var detectedAnimationInputs: {[key: string]: boolean} = {};
inputAsts.forEach(input => {
if (input.type == t.PropertyBindingType.Animation) {
var triggerName = input.name;
if (isPresent(animationRegistry[triggerName])) {
detectedAnimationInputs[triggerName] = true;
} else {
this.errors.push(
new AnimationParseError(`Couldn't find an animation entry for ${triggerName}`));
}
}
});
outputAsts.forEach(output => {
if (output.name[0] == '@') {
var normalizedOutputData = parseAnimationOutputName(output.name.substr(1), this.errors);
let triggerName = normalizedOutputData.name;
let triggerEventPhase = normalizedOutputData.phase;
if (!animationRegistry[triggerName]) {
this.errors.push(new AnimationParseError(
`Couldn't find the corresponding ${isHostLevel ? 'host-level ' : '' }animation trigger definition for (@${triggerName})`));
} else if (!detectedAnimationInputs[triggerName]) {
this.errors.push(new AnimationParseError(
`Unable to listen on (@${triggerName}.${triggerEventPhase}) because the animation trigger [@${triggerName}] isn't being used on the same element`));
} else {
this.outputs.push(normalizedOutputData);
}
}
});
}
visitElement(ast: t.ElementAst, ctx: any): any {
this._validateAnimationInputOutputPairs(
ast.inputs, ast.outputs, this._animationRegistry, false);
var componentOnElement: t.DirectiveAst =
ast.directives.find(directive => directive.directive.isComponent);
if (componentOnElement) {
let cachedComponentAnimations = animationCompilationCache.get(componentOnElement.directive);
if (cachedComponentAnimations) {
this._validateAnimationInputOutputPairs(
componentOnElement.hostProperties, componentOnElement.hostEvents,
this._buildCompileAnimationLookup(cachedComponentAnimations), true);
}
}
t.templateVisitAll(this, ast.children);
}
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst, ctx: any): any {
t.templateVisitAll(this, ast.children);
}
visitEvent(ast: t.BoundEventAst, ctx: any): any {}
visitBoundText(ast: t.BoundTextAst, ctx: any): any {}
visitText(ast: t.TextAst, ctx: any): any {}
visitNgContent(ast: t.NgContentAst, ctx: any): any {}
visitAttr(ast: t.AttrAst, ctx: any): any {}
visitDirective(ast: t.DirectiveAst, ctx: any): any {}
visitReference(ast: t.ReferenceAst, ctx: any): any {}
visitVariable(ast: t.VariableAst, ctx: any): any {}
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst, ctx: any): any {}
visitElementProperty(ast: t.BoundElementPropertyAst, ctx: any): any {}
}

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata'; import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata} from '../compile_metadata';
import {ListWrapper, StringMapWrapper} from '../facade/collection'; import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {isArray, isBlank, isPresent, isString, isStringMap} from '../facade/lang'; import {isArray, isBlank, isPresent, isString, isStringMap} from '../facade/lang';
import {Math} from '../facade/math'; import {Math} from '../facade/math';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
import {ANY_STATE, AnimationOutput, FILL_STYLE_FLAG} from '../private_import_core';
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast'; import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
import {StylesCollection} from './styles_collection'; import {StylesCollection} from './styles_collection';
@ -20,58 +20,21 @@ const _INITIAL_KEYFRAME = 0;
const _TERMINAL_KEYFRAME = 1; const _TERMINAL_KEYFRAME = 1;
const _ONE_SECOND = 1000; const _ONE_SECOND = 1000;
declare type Styles = {
[key: string]: string | number
};
export class AnimationParseError extends ParseError { export class AnimationParseError extends ParseError {
constructor(message: string) { super(null, message); } constructor(message: any /** TODO #9100 */) { super(null, message); }
toString(): string { return `${this.msg}`; } toString(): string { return `${this.msg}`; }
} }
export class AnimationEntryParseResult { export class ParsedAnimationResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {} constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
} }
export class AnimationParser { export function parseAnimationEntry(entry: CompileAnimationEntryMetadata): ParsedAnimationResult {
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
const errors: string[] = [];
const componentName = component.type.name;
const animationTriggerNames = new Set<string>();
const asts = component.template.animations.map(entry => {
const result = this.parseEntry(entry);
const ast = result.ast;
const triggerName = ast.name;
if (animationTriggerNames.has(triggerName)) {
result.errors.push(new AnimationParseError(
`The animation trigger "${triggerName}" has already been registered for the ${componentName} component`));
} else {
animationTriggerNames.add(triggerName);
}
if (result.errors.length > 0) {
let errorMessage =
`- Unable to parse the animation sequence for "${triggerName}" on the ${componentName} component due to the following errors:`;
result.errors.forEach(
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
errors.push(errorMessage);
}
return ast;
});
if (errors.length > 0) {
const errorString = errors.join('\n');
throw new Error(`Animation parse errors:\n${errorString}`);
}
return asts;
}
parseEntry(entry: CompileAnimationEntryMetadata): AnimationEntryParseResult {
var errors: AnimationParseError[] = []; var errors: AnimationParseError[] = [];
var stateStyles: {[key: string]: AnimationStylesAst} = {}; var stateStyles: {[key: string]: AnimationStylesAst} = {};
var transitions: CompileAnimationStateTransitionMetadata[] = []; var transitions: CompileAnimationStateTransitionMetadata[] = [];
var stateDeclarationAsts: AnimationStateDeclarationAst[] = []; var stateDeclarationAsts: any[] /** TODO #9100 */ = [];
entry.definitions.forEach(def => { entry.definitions.forEach(def => {
if (def instanceof CompileAnimationStateDeclarationMetadata) { if (def instanceof CompileAnimationStateDeclarationMetadata) {
_parseAnimationDeclarationStates(def, errors).forEach(ast => { _parseAnimationDeclarationStates(def, errors).forEach(ast => {
@ -87,18 +50,43 @@ export class AnimationParser {
transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors)); transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts); var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
return new AnimationEntryParseResult(ast, errors); return new ParsedAnimationResult(ast, errors);
}
export function parseAnimationOutputName(
outputName: string, errors: AnimationParseError[]): AnimationOutput {
var values = outputName.split('.');
var name: string;
var phase: string = '';
if (values.length > 1) {
name = values[0];
let parsedPhase = values[1];
switch (parsedPhase) {
case 'start':
case 'done':
phase = parsedPhase;
break;
default:
errors.push(new AnimationParseError(
`The provided animation output phase value "${parsedPhase}" for "@${name}" is not supported (use start or done)`));
} }
} else {
name = outputName;
errors.push(new AnimationParseError(
`The animation trigger output event (@${name}) is missing its phase value name (start or done are currently supported)`));
}
return new AnimationOutput(name, phase, outputName);
} }
function _parseAnimationDeclarationStates( function _parseAnimationDeclarationStates(
stateMetadata: CompileAnimationStateDeclarationMetadata, stateMetadata: CompileAnimationStateDeclarationMetadata,
errors: AnimationParseError[]): AnimationStateDeclarationAst[] { errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
var styleValues: Styles[] = []; var styleValues: {[key: string]: string | number}[] = [];
stateMetadata.styles.styles.forEach(stylesEntry => { stateMetadata.styles.styles.forEach(stylesEntry => {
// TODO (matsko): change this when we get CSS class integration support // TODO (matsko): change this when we get CSS class integration support
if (isStringMap(stylesEntry)) { if (isStringMap(stylesEntry)) {
styleValues.push(stylesEntry as Styles); styleValues.push(<{[key: string]: string | number}>stylesEntry);
} else { } else {
errors.push(new AnimationParseError( errors.push(new AnimationParseError(
`State based animations cannot contain references to other states`)); `State based animations cannot contain references to other states`));
@ -136,25 +124,9 @@ function _parseAnimationStateTransition(
return new AnimationStateTransitionAst(transitionExprs, stepsAst); 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( function _parseAnimationTransitionExpr(
eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] { eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
var expressions: AnimationStateTransitionExpression[] = []; var expressions: any[] /** TODO #9100 */ = [];
if (eventStr[0] == ':') {
eventStr = _parseAnimationAlias(eventStr, errors);
}
var match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/); var match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
if (!isPresent(match) || match.length < 4) { if (!isPresent(match) || match.length < 4) {
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`)); errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
@ -173,6 +145,16 @@ function _parseAnimationTransitionExpr(
return expressions; return expressions;
} }
function _fetchSylesFromState(stateName: string, stateStyles: {[key: string]: AnimationStylesAst}):
CompileAnimationStyleMetadata {
var entry = stateStyles[stateName];
if (isPresent(entry)) {
var styles = <{[key: string]: string | number}[]>entry.styles;
return new CompileAnimationStyleMetadata(0, styles);
}
return null;
}
function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnimationMetadata[]): function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnimationMetadata[]):
CompileAnimationMetadata { CompileAnimationMetadata {
return isArray(entry) ? new CompileAnimationSequenceMetadata(<CompileAnimationMetadata[]>entry) : return isArray(entry) ? new CompileAnimationSequenceMetadata(<CompileAnimationMetadata[]>entry) :
@ -228,7 +210,7 @@ function _normalizeStyleStepEntry(
} }
var newSteps: CompileAnimationMetadata[] = []; var newSteps: CompileAnimationMetadata[] = [];
var combinedStyles: Styles[]; var combinedStyles: {[key: string]: string | number}[];
steps.forEach(step => { steps.forEach(step => {
if (step instanceof CompileAnimationStyleMetadata) { if (step instanceof CompileAnimationStyleMetadata) {
// this occurs when a style step is followed by a previous style step // this occurs when a style step is followed by a previous style step
@ -284,7 +266,7 @@ function _normalizeStyleStepEntry(
function _resolveStylesFromState( function _resolveStylesFromState(
stateName: string, stateStyles: {[key: string]: AnimationStylesAst}, stateName: string, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]) { errors: AnimationParseError[]) {
var styles: Styles[] = []; var styles: {[key: string]: string | number}[] = [];
if (stateName[0] != ':') { if (stateName[0] != ':') {
errors.push(new AnimationParseError(`Animation states via styles must be prefixed with a ":"`)); errors.push(new AnimationParseError(`Animation states via styles must be prefixed with a ":"`));
} else { } else {
@ -296,7 +278,7 @@ function _resolveStylesFromState(
} else { } else {
value.styles.forEach(stylesEntry => { value.styles.forEach(stylesEntry => {
if (isStringMap(stylesEntry)) { if (isStringMap(stylesEntry)) {
styles.push(stylesEntry as Styles); styles.push(<{[key: string]: string | number}>stylesEntry);
} }
}); });
} }
@ -330,11 +312,13 @@ function _parseAnimationKeyframes(
var lastOffset = 0; var lastOffset = 0;
keyframeSequence.steps.forEach(styleMetadata => { keyframeSequence.steps.forEach(styleMetadata => {
var offset = styleMetadata.offset; var offset = styleMetadata.offset;
var keyframeStyles: Styles = {}; var keyframeStyles: {[key: string]: string | number} = {};
styleMetadata.styles.forEach(entry => { styleMetadata.styles.forEach(entry => {
Object.keys(entry).forEach(prop => { StringMapWrapper.forEach(
<{[key: string]: string | number}>entry,
(value: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
if (prop != 'offset') { if (prop != 'offset') {
keyframeStyles[prop] = (entry as Styles)[prop]; keyframeStyles[prop] = value;
} }
}); });
}); });
@ -373,7 +357,8 @@ function _parseAnimationKeyframes(
let entry = rawKeyframes[i]; let entry = rawKeyframes[i];
let styles = entry[1]; let styles = entry[1];
Object.keys(styles).forEach(prop => { StringMapWrapper.forEach(
styles, (value: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
if (!isPresent(firstKeyframeStyles[prop])) { if (!isPresent(firstKeyframeStyles[prop])) {
firstKeyframeStyles[prop] = FILL_STYLE_FLAG; firstKeyframeStyles[prop] = FILL_STYLE_FLAG;
} }
@ -384,9 +369,10 @@ function _parseAnimationKeyframes(
let entry = rawKeyframes[i]; let entry = rawKeyframes[i];
let styles = entry[1]; let styles = entry[1];
Object.keys(styles).forEach(prop => { StringMapWrapper.forEach(
styles, (value: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
if (!isPresent(lastKeyframeStyles[prop])) { if (!isPresent(lastKeyframeStyles[prop])) {
lastKeyframeStyles[prop] = styles[prop]; lastKeyframeStyles[prop] = value;
} }
}); });
} }
@ -412,9 +398,11 @@ function _parseTransitionAnimation(
if (entry instanceof CompileAnimationStyleMetadata) { if (entry instanceof CompileAnimationStyleMetadata) {
entry.styles.forEach(stylesEntry => { entry.styles.forEach(stylesEntry => {
// by this point we know that we only have stringmap values // by this point we know that we only have stringmap values
var map = stylesEntry as Styles; var map = <{[key: string]: string | number}>stylesEntry;
Object.keys(map).forEach( StringMapWrapper.forEach(
prop => { collectedStyles.insertAtTime(prop, time, map[prop]); }); map, (value: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
collectedStyles.insertAtTime(prop, time, value);
});
}); });
previousStyles = entry.styles; previousStyles = entry.styles;
return; return;
@ -460,7 +448,7 @@ function _parseTransitionAnimation(
} else { } else {
let styleData = <CompileAnimationStyleMetadata>styles; let styleData = <CompileAnimationStyleMetadata>styles;
let offset = _TERMINAL_KEYFRAME; let offset = _TERMINAL_KEYFRAME;
let styleAst = new AnimationStylesAst(styleData.styles as Styles[]); let styleAst = new AnimationStylesAst(<{[key: string]: string | number}[]>styleData.styles);
var keyframe = new AnimationKeyframeAst(offset, styleAst); var keyframe = new AnimationKeyframeAst(offset, styleAst);
keyframes = [keyframe]; keyframes = [keyframe];
} }
@ -472,8 +460,9 @@ function _parseTransitionAnimation(
keyframes.forEach( keyframes.forEach(
(keyframe: any /** TODO #9100 */) => keyframe.styles.styles.forEach( (keyframe: any /** TODO #9100 */) => keyframe.styles.styles.forEach(
(entry: any /** TODO #9100 */) => Object.keys(entry).forEach( (entry: any /** TODO #9100 */) => StringMapWrapper.forEach(
prop => { collectedStyles.insertAtTime(prop, currentTime, entry[prop]); }))); entry, (value: any /** TODO #9100 */, prop: any /** TODO #9100 */) =>
collectedStyles.insertAtTime(prop, currentTime, value))));
} else { } else {
// if the code reaches this stage then an error // if the code reaches this stage then an error
// has already been populated within the _normalizeStyleSteps() // has already been populated within the _normalizeStyleSteps()
@ -546,11 +535,10 @@ function _parseTimeExpression(
function _createStartKeyframeFromEndKeyframe( function _createStartKeyframeFromEndKeyframe(
endKeyframe: AnimationKeyframeAst, startTime: number, duration: number, endKeyframe: AnimationKeyframeAst, startTime: number, duration: number,
collectedStyles: StylesCollection, errors: AnimationParseError[]): AnimationKeyframeAst { collectedStyles: StylesCollection, errors: AnimationParseError[]): AnimationKeyframeAst {
var values: Styles = {}; var values: {[key: string]: string | number} = {};
var endTime = startTime + duration; var endTime = startTime + duration;
endKeyframe.styles.styles.forEach((styleData: Styles) => { endKeyframe.styles.styles.forEach((styleData: {[key: string]: string | number}) => {
Object.keys(styleData).forEach(prop => { StringMapWrapper.forEach(styleData, (val: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
const val = styleData[prop];
if (prop == 'offset') return; if (prop == 'offset') return;
var resultIndex = collectedStyles.indexOfAtOrBeforeTime(prop, startTime); var resultIndex = collectedStyles.indexOfAtOrBeforeTime(prop, startTime);

View File

@ -8,10 +8,11 @@
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ListWrapper, MapWrapper} from './facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper} from './facade/collection';
import {isPresent, isStringMap, normalizeBlank, normalizeBool} from './facade/lang'; import {isBlank, isPresent, isStringMap, normalizeBlank, normalizeBool} from './facade/lang';
import {LifecycleHooks} from './private_import_core'; import {LifecycleHooks, reflector} from './private_import_core';
import {CssSelector} from './selector'; import {CssSelector} from './selector';
import {getUrlScheme} from './url_resolver';
import {sanitizeIdentifier, splitAtColon} from './util'; import {sanitizeIdentifier, splitAtColon} from './util';
function unimplemented(): any { function unimplemented(): any {
@ -342,8 +343,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
var hostProperties: {[key: string]: string} = {}; var hostProperties: {[key: string]: string} = {};
var hostAttributes: {[key: string]: string} = {}; var hostAttributes: {[key: string]: string} = {};
if (isPresent(host)) { if (isPresent(host)) {
Object.keys(host).forEach(key => { StringMapWrapper.forEach(host, (value: string, key: string) => {
const value = host[key];
const matches = key.match(HOST_REG_EXP); const matches = key.match(HOST_REG_EXP);
if (matches === null) { if (matches === null) {
hostAttributes[key] = value; hostAttributes[key] = value;

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {COMPILER_OPTIONS, ClassProvider, Compiler, CompilerFactory, CompilerOptions, Component, ExistingProvider, FactoryProvider, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, TypeProvider, ValueProvider, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
export * from './template_parser/template_ast'; export * from './template_parser/template_ast';
export {TEMPLATE_TRANSFORMS} from './template_parser/template_parser'; export {TEMPLATE_TRANSFORMS} from './template_parser/template_parser';

View File

@ -9,7 +9,7 @@
import * as chars from '../chars'; import * as chars from '../chars';
import {BaseError} from '../facade/errors'; import {BaseError} from '../facade/errors';
import {StringWrapper, isPresent} from '../facade/lang'; import {StringWrapper, isPresent, resolveEnumToken} from '../facade/lang';
export enum CssTokenType { export enum CssTokenType {
EOF, EOF,
@ -223,8 +223,8 @@ export class CssScanner {
var error: CssScannerError = null; var error: CssScannerError = null;
if (!isMatchingType || (isPresent(value) && value != next.strValue)) { if (!isMatchingType || (isPresent(value) && value != next.strValue)) {
var errorMessage = var errorMessage = resolveEnumToken(CssTokenType, next.type) + ' does not match expected ' +
CssTokenType[next.type] + ' does not match expected ' + CssTokenType[type] + ' value'; resolveEnumToken(CssTokenType, type) + ' value';
if (isPresent(value)) { if (isPresent(value)) {
errorMessage += ' ("' + next.strValue + '" should match "' + value + '")'; errorMessage += ' ("' + next.strValue + '" should match "' + value + '")';

View File

@ -9,10 +9,14 @@
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core'; import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
import {StringMapWrapper} from './facade/collection'; import {StringMapWrapper} from './facade/collection';
import {stringify} from './facade/lang'; import {isPresent, stringify} from './facade/lang';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util'; import {splitAtColon} from './util';
function _isDirectiveMetadata(type: any): type is Directive {
return type instanceof Directive;
}
/* /*
* Resolve a `Type` for {@link Directive}. * Resolve a `Type` for {@link Directive}.
* *
@ -28,57 +32,54 @@ export class DirectiveResolver {
* Return {@link Directive} for a given `Type`. * Return {@link Directive} for a given `Type`.
*/ */
resolve(type: Type<any>, throwIfNotFound = true): Directive { resolve(type: Type<any>, throwIfNotFound = true): Directive {
const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); var typeMetadata = this._reflector.annotations(resolveForwardRef(type));
if (typeMetadata) { if (isPresent(typeMetadata)) {
const metadata = typeMetadata.find(isDirectiveMetadata); var metadata = typeMetadata.find(_isDirectiveMetadata);
if (metadata) { if (isPresent(metadata)) {
const propertyMetadata = this._reflector.propMetadata(type); var propertyMetadata = this._reflector.propMetadata(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type); return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
} }
} }
if (throwIfNotFound) { if (throwIfNotFound) {
throw new Error(`No Directive annotation found on ${stringify(type)}`); throw new Error(`No Directive annotation found on ${stringify(type)}`);
} }
return null; return null;
} }
private _mergeWithPropertyMetadata( private _mergeWithPropertyMetadata(
dm: Directive, propertyMetadata: {[key: string]: any[]}, dm: Directive, propertyMetadata: {[key: string]: any[]},
directiveType: Type<any>): Directive { directiveType: Type<any>): Directive {
const inputs: string[] = []; var inputs: string[] = [];
const outputs: string[] = []; var outputs: string[] = [];
const host: {[key: string]: string} = {}; var host: {[key: string]: string} = {};
const queries: {[key: string]: any} = {}; var queries: {[key: string]: any} = {};
Object.keys(propertyMetadata).forEach((propName: string) => { StringMapWrapper.forEach(propertyMetadata, (metadata: any[], propName: string) => {
metadata.forEach(a => {
propertyMetadata[propName].forEach(a => {
if (a instanceof Input) { if (a instanceof Input) {
if (a.bindingPropertyName) { if (isPresent(a.bindingPropertyName)) {
inputs.push(`${propName}: ${a.bindingPropertyName}`); inputs.push(`${propName}: ${a.bindingPropertyName}`);
} else { } else {
inputs.push(propName); inputs.push(propName);
} }
} else if (a instanceof Output) { } else if (a instanceof Output) {
const output: Output = a; const output: Output = a;
if (output.bindingPropertyName) { if (isPresent(output.bindingPropertyName)) {
outputs.push(`${propName}: ${output.bindingPropertyName}`); outputs.push(`${propName}: ${output.bindingPropertyName}`);
} else { } else {
outputs.push(propName); outputs.push(propName);
} }
} else if (a instanceof HostBinding) { } else if (a instanceof HostBinding) {
const hostBinding: HostBinding = a; const hostBinding: HostBinding = a;
if (hostBinding.hostPropertyName) { if (isPresent(hostBinding.hostPropertyName)) {
host[`[${hostBinding.hostPropertyName}]`] = propName; host[`[${hostBinding.hostPropertyName}]`] = propName;
} else { } else {
host[`[${propName}]`] = propName; host[`[${propName}]`] = propName;
} }
} else if (a instanceof HostListener) { } else if (a instanceof HostListener) {
const hostListener: HostListener = a; const hostListener: HostListener = a;
const args = hostListener.args || []; var args = isPresent(hostListener.args) ? (<any[]>hostListener.args).join(', ') : '';
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`; host[`(${hostListener.eventName})`] = `${propName}(${args})`;
} else if (a instanceof Query) { } else if (a instanceof Query) {
queries[propName] = a; queries[propName] = a;
} }
@ -90,14 +91,13 @@ export class DirectiveResolver {
private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); } private _extractPublicName(def: string) { return splitAtColon(def, [null, def])[1].trim(); }
private _merge( private _merge(
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string}, dm: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
queries: {[key: string]: any}, directiveType: Type<any>): Directive { queries: {[key: string]: any}, directiveType: Type<any>): Directive {
const mergedInputs: string[] = inputs; let mergedInputs: string[];
if (directive.inputs) { if (isPresent(dm.inputs)) {
const inputNames: string[] = const inputNames: string[] =
directive.inputs.map((def: string): string => this._extractPublicName(def)); dm.inputs.map((def: string): string => this._extractPublicName(def));
inputs.forEach((inputDef: string) => { inputs.forEach((inputDef: string) => {
const publicName = this._extractPublicName(inputDef); const publicName = this._extractPublicName(inputDef);
if (inputNames.indexOf(publicName) > -1) { if (inputNames.indexOf(publicName) > -1) {
@ -105,15 +105,16 @@ export class DirectiveResolver {
`Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`); `Input '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
} }
}); });
mergedInputs = dm.inputs.concat(inputs);
mergedInputs.unshift(...directive.inputs); } else {
mergedInputs = inputs;
} }
let mergedOutputs: string[] = outputs; let mergedOutputs: string[];
if (directive.outputs) { if (isPresent(dm.outputs)) {
const outputNames: string[] = const outputNames: string[] =
directive.outputs.map((def: string): string => this._extractPublicName(def)); dm.outputs.map((def: string): string => this._extractPublicName(def));
outputs.forEach((outputDef: string) => { outputs.forEach((outputDef: string) => {
const publicName = this._extractPublicName(outputDef); const publicName = this._extractPublicName(outputDef);
@ -122,48 +123,47 @@ export class DirectiveResolver {
`Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`); `Output event '${publicName}' defined multiple times in '${stringify(directiveType)}'`);
} }
}); });
mergedOutputs.unshift(...directive.outputs); mergedOutputs = dm.outputs.concat(outputs);
} else {
mergedOutputs = outputs;
} }
const mergedHost = directive.host ? StringMapWrapper.merge(directive.host, host) : host; var mergedHost = isPresent(dm.host) ? StringMapWrapper.merge(dm.host, host) : host;
const mergedQueries = var mergedQueries =
directive.queries ? StringMapWrapper.merge(directive.queries, queries) : queries; isPresent(dm.queries) ? StringMapWrapper.merge(dm.queries, queries) : queries;
if (directive instanceof Component) { if (dm instanceof Component) {
return new Component({ return new Component({
selector: directive.selector, selector: dm.selector,
inputs: mergedInputs, inputs: mergedInputs,
outputs: mergedOutputs, outputs: mergedOutputs,
host: mergedHost, host: mergedHost,
exportAs: directive.exportAs, exportAs: dm.exportAs,
moduleId: directive.moduleId, moduleId: dm.moduleId,
queries: mergedQueries, queries: mergedQueries,
changeDetection: directive.changeDetection, changeDetection: dm.changeDetection,
providers: directive.providers, providers: dm.providers,
viewProviders: directive.viewProviders, viewProviders: dm.viewProviders,
entryComponents: directive.entryComponents, entryComponents: dm.entryComponents,
template: directive.template, template: dm.template,
templateUrl: directive.templateUrl, templateUrl: dm.templateUrl,
styles: directive.styles, styles: dm.styles,
styleUrls: directive.styleUrls, styleUrls: dm.styleUrls,
encapsulation: directive.encapsulation, encapsulation: dm.encapsulation,
animations: directive.animations, animations: dm.animations,
interpolation: directive.interpolation interpolation: dm.interpolation
}); });
} else { } else {
return new Directive({ return new Directive({
selector: directive.selector, selector: dm.selector,
inputs: mergedInputs, inputs: mergedInputs,
outputs: mergedOutputs, outputs: mergedOutputs,
host: mergedHost, host: mergedHost,
exportAs: directive.exportAs, exportAs: dm.exportAs,
queries: mergedQueries, queries: mergedQueries,
providers: directive.providers providers: dm.providers
}); });
} }
} }
} }
function isDirectiveMetadata(type: any): type is Directive {
return type instanceof Directive;
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isBlank} from '../facade/lang'; import {isBlank} from '../facade/lang';
export class ParserError { export class ParserError {

View File

@ -27,6 +27,7 @@ const _PLACEHOLDER_TAG = 'x';
const _SOURCE_TAG = 'source'; const _SOURCE_TAG = 'source';
const _TARGET_TAG = 'target'; const _TARGET_TAG = 'target';
const _UNIT_TAG = 'trans-unit'; const _UNIT_TAG = 'trans-unit';
const _CR = (ws: number = 0) => new xml.Text(`\n${new Array(ws).join(' ')}`);
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html // http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
@ -43,37 +44,34 @@ export class Xliff implements Serializer {
let transUnit = new xml.Tag(_UNIT_TAG, {id: id, datatype: 'html'}); let transUnit = new xml.Tag(_UNIT_TAG, {id: id, datatype: 'html'});
transUnit.children.push( transUnit.children.push(
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), _CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)), _CR(8),
new xml.CR(8), new xml.Tag(_TARGET_TAG)); new xml.Tag(_TARGET_TAG));
if (message.description) { if (message.description) {
transUnit.children.push( transUnit.children.push(
new xml.CR(8), _CR(8),
new xml.Tag( new xml.Tag(
'note', {priority: '1', from: 'description'}, [new xml.Text(message.description)])); 'note', {priority: '1', from: 'description'}, [new xml.Text(message.description)]));
} }
if (message.meaning) { if (message.meaning) {
transUnit.children.push( transUnit.children.push(
new xml.CR(8), _CR(8),
new xml.Tag('note', {priority: '1', from: 'meaning'}, [new xml.Text(message.meaning)])); new xml.Tag('note', {priority: '1', from: 'meaning'}, [new xml.Text(message.meaning)]));
} }
transUnit.children.push(new xml.CR(6)); transUnit.children.push(_CR(6));
transUnits.push(new xml.CR(6), transUnit); transUnits.push(_CR(6), transUnit);
}); });
const body = new xml.Tag('body', {}, [...transUnits, new xml.CR(4)]); const body = new xml.Tag('body', {}, [...transUnits, _CR(4)]);
const file = new xml.Tag( const file = new xml.Tag(
'file', {'source-language': _SOURCE_LANG, datatype: 'plaintext', original: 'ng2.template'}, 'file', {'source-language': _SOURCE_LANG, datatype: 'plaintext', original: 'ng2.template'},
[new xml.CR(4), body, new xml.CR(2)]); [_CR(4), body, _CR(2)]);
const xliff = new xml.Tag( const xliff = new xml.Tag('xliff', {version: _VERSION, xmlns: _XMLNS}, [_CR(2), file, _CR()]);
'xliff', {version: _VERSION, xmlns: _XMLNS}, [new xml.CR(2), file, new xml.CR()]);
return xml.serialize([ return xml.serialize([new xml.Declaration({version: '1.0', encoding: 'UTF-8'}), _CR(), xliff]);
new xml.Declaration({version: '1.0', encoding: 'UTF-8'}), new xml.CR(), xliff, new xml.CR()
]);
} }
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} { load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
@ -139,15 +137,13 @@ class _WriteVisitor implements i18n.Visitor {
} }
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] { visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
const ctype = getCtypeForTag(ph.tag); const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {id: ph.startName, ctype: ph.tag});
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {id: ph.startName, ctype});
if (ph.isVoid) { if (ph.isVoid) {
// void tags have no children nor closing tags // void tags have no children nor closing tags
return [startTagPh]; return [startTagPh];
} }
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {id: ph.closeName, ctype}); const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {id: ph.closeName, ctype: ph.tag});
return [startTagPh, ...this.serialize(ph.children), closeTagPh]; return [startTagPh, ...this.serialize(ph.children), closeTagPh];
} }
@ -291,14 +287,3 @@ class _LoadVisitor implements ml.Visitor {
this._errors.push(new I18nError(node.sourceSpan, message)); this._errors.push(new I18nError(node.sourceSpan, message));
} }
} }
function getCtypeForTag(tag: string): string {
switch (tag.toLowerCase()) {
case 'br':
return 'lb';
case 'img':
return 'image';
default:
return `x-${tag}`;
}
}

View File

@ -43,6 +43,7 @@ export class Xmb implements Serializer {
write(messageMap: {[k: string]: i18n.Message}): string { write(messageMap: {[k: string]: i18n.Message}): string {
const visitor = new _Visitor(); const visitor = new _Visitor();
let rootNode = new xml.Tag(_MESSAGES_TAG); let rootNode = new xml.Tag(_MESSAGES_TAG);
rootNode.children.push(new xml.Text('\n'));
Object.keys(messageMap).forEach((id) => { Object.keys(messageMap).forEach((id) => {
const message = messageMap[id]; const message = messageMap[id];
@ -57,18 +58,16 @@ export class Xmb implements Serializer {
} }
rootNode.children.push( rootNode.children.push(
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes))); new xml.Text(' '), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)),
new xml.Text('\n'));
}); });
rootNode.children.push(new xml.CR());
return xml.serialize([ return xml.serialize([
new xml.Declaration({version: '1.0', encoding: 'UTF-8'}), new xml.Declaration({version: '1.0', encoding: 'UTF-8'}),
new xml.CR(), new xml.Text('\n'),
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE), new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
new xml.CR(), new xml.Text('\n'),
rootNode, rootNode,
new xml.CR(),
]); ]);
} }

View File

@ -88,10 +88,6 @@ export class Text implements Node {
visit(visitor: IVisitor): any { return visitor.visitText(this); } visit(visitor: IVisitor): any { return visitor.visitText(this); }
} }
export class CR extends Text {
constructor(ws: number = 0) { super(`\n${new Array(ws + 1).join(' ')}`); }
}
const _ESCAPED_CHARS: [RegExp, string][] = [ const _ESCAPED_CHARS: [RegExp, string][] = [
[/&/g, '&amp;'], [/&/g, '&amp;'],
[/"/g, '&quot;'], [/"/g, '&quot;'],

View File

@ -9,7 +9,7 @@
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core'; import {AnimationGroupPlayer, AnimationKeyframe, AnimationOutput, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core';
import {assetUrl} from './util'; import {assetUrl} from './util';
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
@ -266,6 +266,11 @@ export class Identifiers {
moduleUrl: assetUrl('core', 'i18n/tokens'), moduleUrl: assetUrl('core', 'i18n/tokens'),
runtime: TRANSLATIONS_FORMAT_ runtime: TRANSLATIONS_FORMAT_
}; };
static AnimationOutput: IdentifierSpec = {
name: 'AnimationOutput',
moduleUrl: assetUrl('core', 'animation/animation_output'),
runtime: AnimationOutput
};
} }
export function resolveIdentifier(identifier: IdentifierSpec) { export function resolveIdentifier(identifier: IdentifierSpec) {

View File

@ -123,7 +123,7 @@ class _TreeBuilder {
// read = // read =
while (this._peek.type === lex.TokenType.EXPANSION_CASE_VALUE) { while (this._peek.type === lex.TokenType.EXPANSION_CASE_VALUE) {
let expCase = this._parseExpansionCase(); let expCase = this._parseExpansionCase();
if (!expCase) return; // error if (isBlank(expCase)) return; // error
cases.push(expCase); cases.push(expCase);
} }
@ -154,7 +154,7 @@ class _TreeBuilder {
const start = this._advance(); const start = this._advance();
const exp = this._collectExpansionExpTokens(start); const exp = this._collectExpansionExpTokens(start);
if (!exp) return null; if (isBlank(exp)) return null;
const end = this._advance(); const end = this._advance();
exp.push(new lex.Token(lex.TokenType.EOF, [], end.sourceSpan)); exp.push(new lex.Token(lex.TokenType.EOF, [], end.sourceSpan));

View File

@ -9,8 +9,8 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util'; import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
@ -190,7 +190,7 @@ class _InjectorBuilder {
resolvedProviderValueExpr = providerValueExpressions[0]; resolvedProviderValueExpr = providerValueExpressions[0];
type = providerValueExpressions[0].type; type = providerValueExpressions[0].type;
} }
if (!type) { if (isBlank(type)) {
type = o.DYNAMIC_TYPE; type = o.DYNAMIC_TYPE;
} }
if (isEager) { if (isEager) {
@ -223,11 +223,11 @@ class _InjectorBuilder {
resolveIdentifierToken(Identifiers.ComponentFactoryResolver).reference)) { resolveIdentifierToken(Identifiers.ComponentFactoryResolver).reference)) {
result = o.THIS_EXPR; result = o.THIS_EXPR;
} }
if (!result) { if (isBlank(result)) {
result = this._instances.get(dep.token.reference); result = this._instances.get(dep.token.reference);
} }
} }
if (!result) { if (isBlank(result)) {
var args = [createDiTokenExpression(dep.token)]; var args = [createDiTokenExpression(dep.token)];
if (dep.isOptional) { if (dep.isOptional) {
args.push(o.NULL_EXPR); args.push(o.NULL_EXPR);

View File

@ -8,9 +8,7 @@
import {SchemaMetadata} from '@angular/core'; import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTokenMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from './metadata_resolver';
@ -30,9 +28,6 @@ export class NgModulesSummary {
} }
export class OfflineCompiler { export class OfflineCompiler {
private _animationParser = new AnimationParser();
private _animationCompiler = new AnimationCompiler();
constructor( constructor(
private _metadataResolver: CompileMetadataResolver, private _metadataResolver: CompileMetadataResolver,
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
@ -167,19 +162,14 @@ export class OfflineCompiler {
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[], compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet, pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet,
fileSuffix: string, targetStatements: o.Statement[]): string { fileSuffix: string, targetStatements: o.Statement[]): string {
const parsedAnimations = this._animationParser.parseComponent(compMeta);
const parsedTemplate = this._templateParser.parse( const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name); compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name);
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations = const viewResult =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations); this._viewCompiler.compileComponent(compMeta, parsedTemplate, stylesExpr, pipes);
const viewResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
if (componentStyles) { if (componentStyles) {
targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix)); targetStatements.push(..._resolveStyleStatements(componentStyles, fileSuffix));
} }
compiledAnimations.forEach(
entry => { entry.statements.forEach(statement => { targetStatements.push(statement); }); });
targetStatements.push(..._resolveViewStatements(viewResult)); targetStatements.push(..._resolveViewStatements(viewResult));
return viewResult.viewFactoryVar; return viewResult.viewFactoryVar;
} }

View File

@ -400,7 +400,7 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
} }
visitAllStatements(statements: o.Statement[], ctx: EmitterVisitorContext): void { visitAllStatements(statements: o.Statement[], ctx: EmitterVisitorContext): void {
statements.forEach((stmt) => stmt.visitStatement(this, ctx)); statements.forEach((stmt) => { return stmt.visitStatement(this, ctx); });
} }
} }

View File

@ -7,7 +7,7 @@
*/ */
import {isBlank, isPresent} from '../facade/lang'; import {StringWrapper, evalExpression, isBlank, isPresent, isString} from '../facade/lang';
import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter'; import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import {AbstractJsEmitterVisitor} from './abstract_js_emitter';

View File

@ -8,7 +8,9 @@
import {CompileIdentifierMetadata} from '../compile_metadata'; import {CompileIdentifierMetadata} from '../compile_metadata';
import {isPresent, isString} from '../facade/lang'; import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent, isString} from '../facade/lang';
import {ValueTransformer, visitValue} from '../util';
@ -19,7 +21,7 @@ export enum TypeModifier {
export abstract class Type { export abstract class Type {
constructor(public modifiers: TypeModifier[] = null) { constructor(public modifiers: TypeModifier[] = null) {
if (!modifiers) { if (isBlank(modifiers)) {
this.modifiers = []; this.modifiers = [];
} }
} }
@ -462,7 +464,7 @@ export enum StmtModifier {
export abstract class Statement { export abstract class Statement {
constructor(public modifiers: StmtModifier[] = null) { constructor(public modifiers: StmtModifier[] = null) {
if (!modifiers) { if (isBlank(modifiers)) {
this.modifiers = []; this.modifiers = [];
} }
} }
@ -517,7 +519,7 @@ export class ReturnStatement extends Statement {
export class AbstractClassPart { export class AbstractClassPart {
constructor(public type: Type = null, public modifiers: StmtModifier[]) { constructor(public type: Type = null, public modifiers: StmtModifier[]) {
if (!modifiers) { if (isBlank(modifiers)) {
this.modifiers = []; this.modifiers = [];
} }
} }

View File

@ -6,6 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {Math, isBlank, isPresent} from '../facade/lang';
// asset:<package-name>/<realm>/<path-to-module> // asset:<package-name>/<realm>/<path-to-module>
var _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/; var _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/;

View File

@ -75,22 +75,6 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
super.visitLiteralExpr(ast, ctx, '(null as any)'); 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 { visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
this._visitIdentifier(ast.value, ast.typeParams, ctx); this._visitIdentifier(ast.value, ast.typeParams, ctx);
return null; return null;

View File

@ -8,6 +8,7 @@
import {CompileIdentifierMetadata} from '../compile_metadata'; import {CompileIdentifierMetadata} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection';
import {ValueTransformer, visitValue} from '../util'; import {ValueTransformer, visitValue} from '../util';
import * as o from './output_ast'; import * as o from './output_ast';
@ -23,7 +24,9 @@ class _ValueOutputAstTransformer implements ValueTransformer {
visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression { visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression {
var entries: Array<string|o.Expression>[] = []; var entries: Array<string|o.Expression>[] = [];
Object.keys(map).forEach(key => { entries.push([key, visitValue(map[key], this, null)]); }); StringMapWrapper.forEach(map, (value: any, key: string) => {
entries.push([key, visitValue(value, this, null)]);
});
return o.literalMap(entries, type); return o.literalMap(entries, type);
} }

View File

@ -12,6 +12,7 @@ export const isDefaultChangeDetectionStrategy: typeof r.isDefaultChangeDetection
r.isDefaultChangeDetectionStrategy; r.isDefaultChangeDetectionStrategy;
export type ChangeDetectorStatus = typeof r._ChangeDetectorStatus; export type ChangeDetectorStatus = typeof r._ChangeDetectorStatus;
export const ChangeDetectorStatus: typeof r.ChangeDetectorStatus = r.ChangeDetectorStatus; export const ChangeDetectorStatus: typeof r.ChangeDetectorStatus = r.ChangeDetectorStatus;
r.CHANGE_DETECTION_STRATEGY_VALUES;
export type LifecycleHooks = typeof r._LifecycleHooks; export type LifecycleHooks = typeof r._LifecycleHooks;
export const LifecycleHooks: typeof r.LifecycleHooks = r.LifecycleHooks; export const LifecycleHooks: typeof r.LifecycleHooks = r.LifecycleHooks;
export const LIFECYCLE_HOOKS_VALUES: typeof r.LIFECYCLE_HOOKS_VALUES = r.LIFECYCLE_HOOKS_VALUES; export const LIFECYCLE_HOOKS_VALUES: typeof r.LIFECYCLE_HOOKS_VALUES = r.LIFECYCLE_HOOKS_VALUES;
@ -74,6 +75,8 @@ export type AnimationKeyframe = typeof r._AnimationKeyframe;
export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe; export const AnimationKeyframe: typeof r.AnimationKeyframe = r.AnimationKeyframe;
export type AnimationStyles = typeof r._AnimationStyles; export type AnimationStyles = typeof r._AnimationStyles;
export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles; export const AnimationStyles: typeof r.AnimationStyles = r.AnimationStyles;
export type AnimationOutput = typeof r._AnimationOutput;
export const AnimationOutput: typeof r.AnimationOutput = r.AnimationOutput;
export const ANY_STATE = r.ANY_STATE; export const ANY_STATE = r.ANY_STATE;
export const DEFAULT_STATE = r.DEFAULT_STATE; export const DEFAULT_STATE = r.DEFAULT_STATE;
export const EMPTY_STATE = r.EMPTY_STATE; export const EMPTY_STATE = r.EMPTY_STATE;

View File

@ -12,7 +12,7 @@ import {ListWrapper, MapWrapper} from './facade/collection';
import {isArray, isBlank, isPresent, normalizeBlank} from './facade/lang'; import {isArray, isBlank, isPresent, normalizeBlank} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast'; import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst, VariableAst} from './template_parser/template_ast';
export class ProviderError extends ParseError { export class ProviderError extends ParseError {
constructor(message: string, span: ParseSourceSpan) { super(span, message); } constructor(message: string, span: ParseSourceSpan) { super(span, message); }
@ -50,14 +50,14 @@ export class ProviderElementContext {
private _hasViewContainer: boolean = false; private _hasViewContainer: boolean = false;
constructor( constructor(
public viewContext: ProviderViewContext, private _parent: ProviderElementContext, private _viewContext: ProviderViewContext, private _parent: ProviderElementContext,
private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[], private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[],
refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) { refs: ReferenceAst[], private _sourceSpan: ParseSourceSpan) {
this._attrs = {}; this._attrs = {};
attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value); attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value);
var directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive); var directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive);
this._allProviders = this._allProviders =
_resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors); _resolveProvidersFromDirectives(directivesMeta, _sourceSpan, _viewContext.errors);
this._contentQueries = _getContentQueries(directivesMeta); this._contentQueries = _getContentQueries(directivesMeta);
var queriedTokens = new Map<any, boolean>(); var queriedTokens = new Map<any, boolean>();
MapWrapper.values(this._allProviders).forEach((provider) => { MapWrapper.values(this._allProviders).forEach((provider) => {
@ -124,7 +124,7 @@ export class ProviderElementContext {
} }
currentEl = currentEl._parent; currentEl = currentEl._parent;
} }
queries = this.viewContext.viewQueries.get(token.reference); queries = this._viewContext.viewQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
ListWrapper.addAll(result, queries); ListWrapper.addAll(result, queries);
} }
@ -136,7 +136,8 @@ export class ProviderElementContext {
requestingProviderType: ProviderAstType, token: CompileTokenMetadata, requestingProviderType: ProviderAstType, token: CompileTokenMetadata,
eager: boolean): ProviderAst { eager: boolean): ProviderAst {
var resolvedProvider = this._allProviders.get(token.reference); var resolvedProvider = this._allProviders.get(token.reference);
if (!resolvedProvider || ((requestingProviderType === ProviderAstType.Directive || if (isBlank(resolvedProvider) ||
((requestingProviderType === ProviderAstType.Directive ||
requestingProviderType === ProviderAstType.PublicService) && requestingProviderType === ProviderAstType.PublicService) &&
resolvedProvider.providerType === ProviderAstType.PrivateService) || resolvedProvider.providerType === ProviderAstType.PrivateService) ||
((requestingProviderType === ProviderAstType.PrivateService || ((requestingProviderType === ProviderAstType.PrivateService ||
@ -149,7 +150,7 @@ export class ProviderElementContext {
return transformedProviderAst; return transformedProviderAst;
} }
if (isPresent(this._seenProviders.get(token.reference))) { if (isPresent(this._seenProviders.get(token.reference))) {
this.viewContext.errors.push(new ProviderError( this._viewContext.errors.push(new ProviderError(
`Cannot instantiate cyclic dependency! ${token.name}`, this._sourceSpan)); `Cannot instantiate cyclic dependency! ${token.name}`, this._sourceSpan));
return null; return null;
} }
@ -238,12 +239,12 @@ export class ProviderElementContext {
result = this._getLocalDependency(requestingProviderType, dep, eager); result = this._getLocalDependency(requestingProviderType, dep, eager);
} }
if (dep.isSelf) { if (dep.isSelf) {
if (!result && dep.isOptional) { if (isBlank(result) && dep.isOptional) {
result = new CompileDiDependencyMetadata({isValue: true, value: null}); result = new CompileDiDependencyMetadata({isValue: true, value: null});
} }
} else { } else {
// check parent elements // check parent elements
while (!result && isPresent(currElement._parent)) { while (isBlank(result) && isPresent(currElement._parent)) {
var prevElement = currElement; var prevElement = currElement;
currElement = currElement._parent; currElement = currElement._parent;
if (prevElement._isViewRoot) { if (prevElement._isViewRoot) {
@ -252,10 +253,10 @@ export class ProviderElementContext {
result = currElement._getLocalDependency(ProviderAstType.PublicService, dep, currEager); result = currElement._getLocalDependency(ProviderAstType.PublicService, dep, currEager);
} }
// check @Host restriction // check @Host restriction
if (!result) { if (isBlank(result)) {
if (!dep.isHost || this.viewContext.component.type.isHost || if (!dep.isHost || this._viewContext.component.type.isHost ||
this.viewContext.component.type.reference === dep.token.reference || this._viewContext.component.type.reference === dep.token.reference ||
isPresent(this.viewContext.viewProviders.get(dep.token.reference))) { isPresent(this._viewContext.viewProviders.get(dep.token.reference))) {
result = dep; result = dep;
} else { } else {
result = dep.isOptional ? result = dep.isOptional ?
@ -264,8 +265,8 @@ export class ProviderElementContext {
} }
} }
} }
if (!result) { if (isBlank(result)) {
this.viewContext.errors.push( this._viewContext.errors.push(
new ProviderError(`No provider for ${dep.token.name}`, this._sourceSpan)); new ProviderError(`No provider for ${dep.token.name}`, this._sourceSpan));
} }
return result; return result;
@ -310,7 +311,7 @@ export class NgModuleProviderAnalyzer {
private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst { private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst {
var resolvedProvider = this._allProviders.get(token.reference); var resolvedProvider = this._allProviders.get(token.reference);
if (!resolvedProvider) { if (isBlank(resolvedProvider)) {
return null; return null;
} }
var transformedProviderAst = this._transformedProviders.get(token.reference); var transformedProviderAst = this._transformedProviders.get(token.reference);
@ -413,7 +414,7 @@ function _normalizeProviders(
providers: Array<CompileProviderMetadata|CompileTypeMetadata|any[]>, providers: Array<CompileProviderMetadata|CompileTypeMetadata|any[]>,
sourceSpan: ParseSourceSpan, targetErrors: ParseError[], sourceSpan: ParseSourceSpan, targetErrors: ParseError[],
targetProviders: CompileProviderMetadata[] = null): CompileProviderMetadata[] { targetProviders: CompileProviderMetadata[] = null): CompileProviderMetadata[] {
if (!targetProviders) { if (isBlank(targetProviders)) {
targetProviders = []; targetProviders = [];
} }
if (isPresent(providers)) { if (isPresent(providers)) {
@ -478,7 +479,7 @@ function _resolveProviders(
`Mixing multi and non multi provider is not possible for token ${resolvedProvider.token.name}`, `Mixing multi and non multi provider is not possible for token ${resolvedProvider.token.name}`,
sourceSpan)); sourceSpan));
} }
if (!resolvedProvider) { if (isBlank(resolvedProvider)) {
const lifecycleHooks = const lifecycleHooks =
provider.token.identifier && provider.token.identifier instanceof CompileTypeMetadata ? provider.token.identifier && provider.token.identifier instanceof CompileTypeMetadata ?
provider.token.identifier.lifecycleHooks : provider.token.identifier.lifecycleHooks :
@ -529,7 +530,7 @@ function _getContentQueries(directives: CompileDirectiveMetadata[]):
function _addQueryToTokenMap(map: Map<any, CompileQueryMetadata[]>, query: CompileQueryMetadata) { function _addQueryToTokenMap(map: Map<any, CompileQueryMetadata[]>, query: CompileQueryMetadata) {
query.selectors.forEach((token: CompileTokenMetadata) => { query.selectors.forEach((token: CompileTokenMetadata) => {
var entry = map.get(token.reference); var entry = map.get(token.reference);
if (!entry) { if (isBlank(entry)) {
entry = []; entry = [];
map.set(token.reference, entry); map.set(token.reference, entry);
} }

View File

@ -6,13 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core'; import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, Optional, Provider, SchemaMetadata, SkipSelf, Type} from '@angular/core';
import {AnimationCompiler} from './animation/animation_compiler';
import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {stringify} from './facade/lang'; import {isBlank, stringify} from './facade/lang';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from './ng_module_compiler';
import * as ir from './output/output_ast'; import * as ir from './output/output_ast';
@ -24,6 +23,8 @@ import {TemplateParser} from './template_parser/template_parser';
import {SyncAsyncResult} from './util'; import {SyncAsyncResult} from './util';
import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
/** /**
* An internal module of the Angular compiler that begins with component types, * An internal module of the Angular compiler that begins with component types,
* extracts templates, and eventually produces a compiled version of the component * extracts templates, and eventually produces a compiled version of the component
@ -38,8 +39,6 @@ export class RuntimeCompiler implements Compiler {
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>(); private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
private _animationParser = new AnimationParser();
private _animationCompiler = new AnimationCompiler();
constructor( constructor(
private _injector: Injector, private _metadataResolver: CompileMetadataResolver, private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
@ -191,7 +190,7 @@ export class RuntimeCompiler implements Compiler {
private _createCompiledHostTemplate(compType: Type<any>): CompiledTemplate { private _createCompiledHostTemplate(compType: Type<any>): CompiledTemplate {
var compiledTemplate = this._compiledHostTemplateCache.get(compType); var compiledTemplate = this._compiledHostTemplateCache.get(compType);
if (!compiledTemplate) { if (isBlank(compiledTemplate)) {
var compMeta = this._metadataResolver.getDirectiveMetadata(compType); var compMeta = this._metadataResolver.getDirectiveMetadata(compType);
assertComponent(compMeta); assertComponent(compMeta);
var hostMeta = createHostComponentMeta(compMeta); var hostMeta = createHostComponentMeta(compMeta);
@ -206,7 +205,7 @@ export class RuntimeCompiler implements Compiler {
private _createCompiledTemplate( private _createCompiledTemplate(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata): CompiledTemplate { compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata): CompiledTemplate {
var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.reference); var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.reference);
if (!compiledTemplate) { if (isBlank(compiledTemplate)) {
assertComponent(compMeta); assertComponent(compMeta);
compiledTemplate = new CompiledTemplate( compiledTemplate = new CompiledTemplate(
false, compMeta.selector, compMeta.type, ngModule.transitiveModule.directives, false, compMeta.selector, compMeta.type, ngModule.transitiveModule.directives,
@ -254,15 +253,12 @@ export class RuntimeCompiler implements Compiler {
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl); stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
const viewCompMetas = template.viewComponentTypes.map( const viewCompMetas = template.viewComponentTypes.map(
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta); (compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
const parsedAnimations = this._animationParser.parseComponent(compMeta);
const parsedTemplate = this._templateParser.parse( const parsedTemplate = this._templateParser.parse(
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas), compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
template.viewPipes, template.schemas, compMeta.type.name); template.viewPipes, template.schemas, compMeta.type.name);
const compiledAnimations =
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
const compileResult = this._viewCompiler.compileComponent( const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar), compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
template.viewPipes, compiledAnimations); template.viewPipes);
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
let depTemplate: CompiledTemplate; let depTemplate: CompiledTemplate;
if (dep instanceof ViewFactoryDependency) { if (dep instanceof ViewFactoryDependency) {
@ -279,8 +275,6 @@ export class RuntimeCompiler implements Compiler {
}); });
const statements = const statements =
stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements); stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements);
compiledAnimations.forEach(
entry => { entry.statements.forEach(statement => { statements.push(statement); }); });
let factory: any; let factory: any;
if (!this._compilerConfig.useJit) { if (!this._compilerConfig.useJit) {
factory = interpretStatements(statements, compileResult.viewFactoryVar); factory = interpretStatements(statements, compileResult.viewFactoryVar);

View File

@ -344,26 +344,4 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
getMappedPropName(propName: string): string { return _ATTR_TO_PROP[propName] || propName; } getMappedPropName(propName: string): string { return _ATTR_TO_PROP[propName] || propName; }
getDefaultComponentElementName(): string { return 'ng-component'; } getDefaultComponentElementName(): string { return 'ng-component'; }
validateProperty(name: string): {error: boolean, msg?: string} {
if (name.toLowerCase().startsWith('on')) {
const msg = `Binding to event property '${name}' is disallowed for security reasons, ` +
`please use (${name.slice(2)})=...` +
`\nIf '${name}' is a directive input, make sure the directive is imported by the` +
` current module.`;
return {error: true, msg: msg};
} else {
return {error: false};
}
}
validateAttribute(name: string): {error: boolean, msg?: string} {
if (name.toLowerCase().startsWith('on')) {
const msg = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
`please use (${name.slice(2)})=...`;
return {error: true, msg: msg};
} else {
return {error: false};
}
}
} }

View File

@ -14,6 +14,4 @@ export abstract class ElementSchemaRegistry {
abstract securityContext(tagName: string, propName: string): any; abstract securityContext(tagName: string, propName: string): any;
abstract getMappedPropName(propName: string): string; abstract getMappedPropName(propName: string): string;
abstract getDefaultComponentElementName(): string; abstract getDefaultComponentElementName(): string;
abstract validateProperty(name: string): {error: boolean, msg?: string};
abstract validateAttribute(name: string): {error: boolean, msg?: string};
} }

View File

@ -6,13 +6,18 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from './facade/collection';
import {StringWrapper, isBlank, isPresent} from './facade/lang';
import {getHtmlTagDefinition} from './ml_parser/html_tags'; import {getHtmlTagDefinition} from './ml_parser/html_tags';
const _EMPTY_ATTR_VALUE = '';
const _SELECTOR_REGEXP = new RegExp( const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not(" '(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag" '([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class" '(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" '(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(\\))|' + // ")" '(\\))|' + // ")"
'(\\s*,\\s*)', // "," '(\\s*,\\s*)', // ","
'g'); 'g');
@ -29,21 +34,21 @@ export class CssSelector {
notSelectors: CssSelector[] = []; notSelectors: CssSelector[] = [];
static parse(selector: string): CssSelector[] { static parse(selector: string): CssSelector[] {
const results: CssSelector[] = []; var results: CssSelector[] = [];
const _addResult = (res: CssSelector[], cssSel: CssSelector) => { var _addResult = (res: CssSelector[], cssSel: CssSelector) => {
if (cssSel.notSelectors.length > 0 && !cssSel.element && cssSel.classNames.length == 0 && if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
cssSel.attrs.length == 0) { ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = '*'; cssSel.element = '*';
} }
res.push(cssSel); res.push(cssSel);
}; };
let cssSelector = new CssSelector(); var cssSelector = new CssSelector();
let match: string[]; var match: string[];
let current = cssSelector; var current = cssSelector;
let inNot = false; var inNot = false;
_SELECTOR_REGEXP.lastIndex = 0; _SELECTOR_REGEXP.lastIndex = 0;
while (match = _SELECTOR_REGEXP.exec(selector)) { while (isPresent(match = _SELECTOR_REGEXP.exec(selector))) {
if (match[1]) { if (isPresent(match[1])) {
if (inNot) { if (inNot) {
throw new Error('Nesting :not is not allowed in a selector'); throw new Error('Nesting :not is not allowed in a selector');
} }
@ -51,20 +56,20 @@ export class CssSelector {
current = new CssSelector(); current = new CssSelector();
cssSelector.notSelectors.push(current); cssSelector.notSelectors.push(current);
} }
if (match[2]) { if (isPresent(match[2])) {
current.setElement(match[2]); current.setElement(match[2]);
} }
if (match[3]) { if (isPresent(match[3])) {
current.addClassName(match[3]); current.addClassName(match[3]);
} }
if (match[4]) { if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]); current.addAttribute(match[4], match[5]);
} }
if (match[6]) { if (isPresent(match[6])) {
inNot = false; inNot = false;
current = cssSelector; current = cssSelector;
} }
if (match[7]) { if (isPresent(match[7])) {
if (inNot) { if (inNot) {
throw new Error('Multiple selectors in :not are not supported'); throw new Error('Multiple selectors in :not are not supported');
} }
@ -101,22 +106,37 @@ export class CssSelector {
`<${tagName}${classAttr}${attrs}></${tagName}>`; `<${tagName}${classAttr}${attrs}></${tagName}>`;
} }
addAttribute(name: string, value: string = '') { addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
this.attrs.push(name, value && value.toLowerCase() || ''); this.attrs.push(name);
if (isPresent(value)) {
value = value.toLowerCase();
} else {
value = _EMPTY_ATTR_VALUE;
}
this.attrs.push(value);
} }
addClassName(name: string) { this.classNames.push(name.toLowerCase()); } addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
toString(): string { toString(): string {
let res: string = this.element || ''; var res = '';
if (this.classNames) { if (isPresent(this.element)) {
this.classNames.forEach(klass => res += `.${klass}`); res += this.element;
} }
if (this.attrs) { if (isPresent(this.classNames)) {
for (let i = 0; i < this.attrs.length; i += 2) { for (var i = 0; i < this.classNames.length; i++) {
const name = this.attrs[i]; res += '.' + this.classNames[i];
const value = this.attrs[i + 1]; }
res += `[${name}${value ? '=' + value : ''}]`; }
if (isPresent(this.attrs)) {
for (var i = 0; i < this.attrs.length;) {
var attrName = this.attrs[i++];
var attrValue = this.attrs[i++];
res += '[' + attrName;
if (attrValue.length > 0) {
res += '=' + attrValue;
}
res += ']';
} }
} }
this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`); this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
@ -130,26 +150,26 @@ export class CssSelector {
*/ */
export class SelectorMatcher { export class SelectorMatcher {
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher { static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
const notMatcher = new SelectorMatcher(); var notMatcher = new SelectorMatcher();
notMatcher.addSelectables(notSelectors, null); notMatcher.addSelectables(notSelectors, null);
return notMatcher; return notMatcher;
} }
private _elementMap: {[k: string]: SelectorContext[]} = {}; private _elementMap = new Map<string, SelectorContext[]>();
private _elementPartialMap: {[k: string]: SelectorMatcher} = {}; private _elementPartialMap = new Map<string, SelectorMatcher>();
private _classMap: {[k: string]: SelectorContext[]} = {}; private _classMap = new Map<string, SelectorContext[]>();
private _classPartialMap: {[k: string]: SelectorMatcher} = {}; private _classPartialMap = new Map<string, SelectorMatcher>();
private _attrValueMap: {[k: string]: {[k: string]: SelectorContext[]}} = {}; private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
private _attrValuePartialMap: {[k: string]: {[k: string]: SelectorMatcher}} = {}; private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
private _listContexts: SelectorListContext[] = []; private _listContexts: SelectorListContext[] = [];
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) { addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
let listContext: SelectorListContext = null; var listContext: SelectorListContext = null;
if (cssSelectors.length > 1) { if (cssSelectors.length > 1) {
listContext = new SelectorListContext(cssSelectors); listContext = new SelectorListContext(cssSelectors);
this._listContexts.push(listContext); this._listContexts.push(listContext);
} }
for (let i = 0; i < cssSelectors.length; i++) { for (var i = 0; i < cssSelectors.length; i++) {
this._addSelectable(cssSelectors[i], callbackCtxt, listContext); this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
} }
} }
@ -161,14 +181,14 @@ export class SelectorMatcher {
*/ */
private _addSelectable( private _addSelectable(
cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) { cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) {
let matcher: SelectorMatcher = this; var matcher: SelectorMatcher = this;
const element = cssSelector.element; var element = cssSelector.element;
const classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
const attrs = cssSelector.attrs; var attrs = cssSelector.attrs;
const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext); var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (element) { if (isPresent(element)) {
const isTerminal = attrs.length === 0 && classNames.length === 0; var isTerminal = attrs.length === 0 && classNames.length === 0;
if (isTerminal) { if (isTerminal) {
this._addTerminal(matcher._elementMap, element, selectable); this._addTerminal(matcher._elementMap, element, selectable);
} else { } else {
@ -176,10 +196,10 @@ export class SelectorMatcher {
} }
} }
if (classNames) { if (isPresent(classNames)) {
for (let i = 0; i < classNames.length; i++) { for (var index = 0; index < classNames.length; index++) {
const isTerminal = attrs.length === 0 && i === classNames.length - 1; var isTerminal = attrs.length === 0 && index === classNames.length - 1;
const className = classNames[i]; var className = classNames[index];
if (isTerminal) { if (isTerminal) {
this._addTerminal(matcher._classMap, className, selectable); this._addTerminal(matcher._classMap, className, selectable);
} else { } else {
@ -188,47 +208,47 @@ export class SelectorMatcher {
} }
} }
if (attrs) { if (isPresent(attrs)) {
for (let i = 0; i < attrs.length; i += 2) { for (var index = 0; index < attrs.length;) {
const isTerminal = i === attrs.length - 2; var isTerminal = index === attrs.length - 2;
const name = attrs[i]; var attrName = attrs[index++];
const value = attrs[i + 1]; var attrValue = attrs[index++];
if (isTerminal) { if (isTerminal) {
const terminalMap = matcher._attrValueMap; var terminalMap = matcher._attrValueMap;
let terminalValuesMap = terminalMap[name]; var terminalValuesMap = terminalMap.get(attrName);
if (!terminalValuesMap) { if (isBlank(terminalValuesMap)) {
terminalValuesMap = {}; terminalValuesMap = new Map<string, SelectorContext[]>();
terminalMap[name] = terminalValuesMap; terminalMap.set(attrName, terminalValuesMap);
} }
this._addTerminal(terminalValuesMap, value, selectable); this._addTerminal(terminalValuesMap, attrValue, selectable);
} else { } else {
let partialMap = matcher._attrValuePartialMap; var parttialMap = matcher._attrValuePartialMap;
let partialValuesMap = partialMap[name]; var partialValuesMap = parttialMap.get(attrName);
if (!partialValuesMap) { if (isBlank(partialValuesMap)) {
partialValuesMap = {}; partialValuesMap = new Map<string, SelectorMatcher>();
partialMap[name] = partialValuesMap; parttialMap.set(attrName, partialValuesMap);
} }
matcher = this._addPartial(partialValuesMap, value); matcher = this._addPartial(partialValuesMap, attrValue);
} }
} }
} }
} }
private _addTerminal( private _addTerminal(
map: {[k: string]: SelectorContext[]}, name: string, selectable: SelectorContext) { map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) {
let terminalList = map[name]; var terminalList = map.get(name);
if (!terminalList) { if (isBlank(terminalList)) {
terminalList = []; terminalList = [];
map[name] = terminalList; map.set(name, terminalList);
} }
terminalList.push(selectable); terminalList.push(selectable);
} }
private _addPartial(map: {[k: string]: SelectorMatcher}, name: string): SelectorMatcher { private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
let matcher = map[name]; var matcher = map.get(name);
if (!matcher) { if (isBlank(matcher)) {
matcher = new SelectorMatcher(); matcher = new SelectorMatcher();
map[name] = matcher; map.set(name, matcher);
} }
return matcher; return matcher;
} }
@ -241,12 +261,12 @@ export class SelectorMatcher {
* @return boolean true if a match was found * @return boolean true if a match was found
*/ */
match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean { match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean {
let result = false; var result = false;
const element = cssSelector.element; var element = cssSelector.element;
const classNames = cssSelector.classNames; var classNames = cssSelector.classNames;
const attrs = cssSelector.attrs; var attrs = cssSelector.attrs;
for (let i = 0; i < this._listContexts.length; i++) { for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false; this._listContexts[i].alreadyMatched = false;
} }
@ -254,9 +274,9 @@ export class SelectorMatcher {
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
result; result;
if (classNames) { if (isPresent(classNames)) {
for (let i = 0; i < classNames.length; i++) { for (var index = 0; index < classNames.length; index++) {
const className = classNames[i]; var className = classNames[index];
result = result =
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result; this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
result = result =
@ -265,25 +285,28 @@ export class SelectorMatcher {
} }
} }
if (attrs) { if (isPresent(attrs)) {
for (let i = 0; i < attrs.length; i += 2) { for (var index = 0; index < attrs.length;) {
const name = attrs[i]; var attrName = attrs[index++];
const value = attrs[i + 1]; var attrValue = attrs[index++];
const terminalValuesMap = this._attrValueMap[name]; var terminalValuesMap = this._attrValueMap.get(attrName);
if (value) { if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
result = result = this._matchTerminal(
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result; terminalValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
result;
}
result = this._matchTerminal(terminalValuesMap, attrValue, cssSelector, matchedCallback) ||
result;
var partialValuesMap = this._attrValuePartialMap.get(attrName);
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
result = this._matchPartial(
partialValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
result;
} }
result = result =
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result; this._matchPartial(partialValuesMap, attrValue, cssSelector, matchedCallback) || result;
const partialValuesMap = this._attrValuePartialMap[name];
if (value) {
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
}
result =
this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result;
} }
} }
return result; return result;
@ -291,24 +314,24 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchTerminal( _matchTerminal(
map: {[k: string]: SelectorContext[]}, name: string, cssSelector: CssSelector, map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (isBlank(map) || isBlank(name)) {
return false; return false;
} }
let selectables = map[name]; var selectables = map.get(name);
const starSelectables = map['*']; var starSelectables = map.get('*');
if (starSelectables) { if (isPresent(starSelectables)) {
selectables = selectables.concat(starSelectables); selectables = selectables.concat(starSelectables);
} }
if (!selectables) { if (isBlank(selectables)) {
return false; return false;
} }
let selectable: SelectorContext; var selectable: SelectorContext;
let result = false; var result = false;
for (let i = 0; i < selectables.length; i++) { for (var index = 0; index < selectables.length; index++) {
selectable = selectables[i]; selectable = selectables[index];
result = selectable.finalize(cssSelector, matchedCallback) || result; result = selectable.finalize(cssSelector, matchedCallback) || result;
} }
return result; return result;
@ -316,14 +339,13 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchPartial( _matchPartial(
map: {[k: string]: SelectorMatcher}, name: string, cssSelector: CssSelector, map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (isBlank(map) || isBlank(name)) {
return false; return false;
} }
var nestedSelector = map.get(name);
const nestedSelector = map[name]; if (isBlank(nestedSelector)) {
if (!nestedSelector) {
return false; return false;
} }
// TODO(perf): get rid of recursion and measure again // TODO(perf): get rid of recursion and measure again
@ -351,13 +373,15 @@ export class SelectorContext {
} }
finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean { finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean {
let result = true; var result = true;
if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) { if (this.notSelectors.length > 0 &&
const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors); (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
result = !notMatcher.match(cssSelector, null); result = !notMatcher.match(cssSelector, null);
} }
if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) { if (result && isPresent(callback) &&
if (this.listContext) { (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true; this.listContext.alreadyMatched = true;
} }
callback(this.selector, this.cbContext); callback(this.selector, this.cbContext);

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StringWrapper, isBlank, isPresent} from './facade/lang';
/** /**
* This file is a port of shadowCSS from webcomponents.js to TypeScript. * This file is a port of shadowCSS from webcomponents.js to TypeScript.
* *
@ -173,8 +175,9 @@ export class ShadowCss {
**/ **/
private _insertPolyfillDirectivesInCssText(cssText: string): string { private _insertPolyfillDirectivesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments // Difference with webcomponents.js: does not handle comments
return cssText.replace( return StringWrapper.replaceAllMapped(
_cssContentNextSelectorRe, function(...m: string[]) { return m[2] + '{'; }); cssText, _cssContentNextSelectorRe,
function(m: any /** TODO #9100 */) { return m[1] + '{'; });
} }
/* /*
@ -194,9 +197,12 @@ export class ShadowCss {
**/ **/
private _insertPolyfillRulesInCssText(cssText: string): string { private _insertPolyfillRulesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments // Difference with webcomponents.js: does not handle comments
return cssText.replace(_cssContentRuleRe, (...m: string[]) => { return StringWrapper.replaceAllMapped(
const rule = m[0].replace(m[1], '').replace(m[2], ''); cssText, _cssContentRuleRe, function(m: any /** TODO #9100 */) {
return m[4] + rule; let rule = m[0];
rule = StringWrapper.replace(rule, m[1], '');
rule = StringWrapper.replace(rule, m[2], '');
return m[3] + rule;
}); });
} }
@ -209,16 +215,15 @@ export class ShadowCss {
* scopeName .foo { ... } * scopeName .foo { ... }
*/ */
private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
const unscopedRules = this._extractUnscopedRulesFromCssText(cssText); const unscoped = this._extractUnscopedRulesFromCssText(cssText);
// replace :host and :host-context -shadowcsshost and -shadowcsshost respectively
cssText = this._insertPolyfillHostInCssText(cssText); cssText = this._insertPolyfillHostInCssText(cssText);
cssText = this._convertColonHost(cssText); cssText = this._convertColonHost(cssText);
cssText = this._convertColonHostContext(cssText); cssText = this._convertColonHostContext(cssText);
cssText = this._convertShadowDOMSelectors(cssText); cssText = this._convertShadowDOMSelectors(cssText);
if (scopeSelector) { if (isPresent(scopeSelector)) {
cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector); cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector);
} }
cssText = cssText + '\n' + unscopedRules; cssText = cssText + '\n' + unscoped;
return cssText.trim(); return cssText.trim();
} }
@ -243,7 +248,9 @@ export class ShadowCss {
let m: RegExpExecArray; let m: RegExpExecArray;
_cssContentUnscopedRuleRe.lastIndex = 0; _cssContentUnscopedRuleRe.lastIndex = 0;
while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) { while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) {
const rule = m[0].replace(m[2], '').replace(m[1], m[4]); let rule = m[0];
rule = StringWrapper.replace(rule, m[2], '');
rule = StringWrapper.replace(rule, m[1], m[3]);
r += rule + '\n\n'; r += rule + '\n\n';
} }
return r; return r;
@ -254,7 +261,7 @@ export class ShadowCss {
* *
* to * to
* *
* .foo<scopeName> > .bar * scopeName.foo > .bar
*/ */
private _convertColonHost(cssText: string): string { private _convertColonHost(cssText: string): string {
return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer); return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer);
@ -265,7 +272,7 @@ export class ShadowCss {
* *
* to * to
* *
* .foo<scopeName> > .bar, .foo scopeName > .bar { } * scopeName.foo > .bar, .foo scopeName > .bar { }
* *
* and * and
* *
@ -273,7 +280,7 @@ export class ShadowCss {
* *
* to * to
* *
* .foo<scopeName> .bar { ... } * scopeName.foo .bar { ... }
*/ */
private _convertColonHostContext(cssText: string): string { private _convertColonHostContext(cssText: string): string {
return this._convertColonRule( return this._convertColonRule(
@ -281,14 +288,14 @@ export class ShadowCss {
} }
private _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string { private _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string {
// m[1] = :host(-context), m[2] = contents of (), m[3] rest of rule // p1 = :host, p2 = contents of (), p3 rest of rule
return cssText.replace(regExp, function(...m: string[]) { return StringWrapper.replaceAllMapped(cssText, regExp, function(m: any /** TODO #9100 */) {
if (m[2]) { if (isPresent(m[2])) {
const parts = m[2].split(','); const parts = m[2].split(','), r: any[] /** TODO #9100 */ = [];
const r: string[] = [];
for (let i = 0; i < parts.length; i++) { for (let i = 0; i < parts.length; i++) {
let p = parts[i].trim(); let p = parts[i];
if (!p) break; if (isBlank(p)) break;
p = p.trim();
r.push(partReplacer(_polyfillHostNoCombinator, p, m[3])); r.push(partReplacer(_polyfillHostNoCombinator, p, m[3]));
} }
return r.join(','); return r.join(',');
@ -299,7 +306,7 @@ export class ShadowCss {
} }
private _colonHostContextPartReplacer(host: string, part: string, suffix: string): string { private _colonHostContextPartReplacer(host: string, part: string, suffix: string): string {
if (part.indexOf(_polyfillHost) > -1) { if (StringWrapper.contains(part, _polyfillHost)) {
return this._colonHostPartReplacer(host, part, suffix); return this._colonHostPartReplacer(host, part, suffix);
} else { } else {
return host + part + suffix + ', ' + part + ' ' + host + suffix; return host + part + suffix + ', ' + part + ' ' + host + suffix;
@ -307,7 +314,7 @@ export class ShadowCss {
} }
private _colonHostPartReplacer(host: string, part: string, suffix: string): string { private _colonHostPartReplacer(host: string, part: string, suffix: string): string {
return host + part.replace(_polyfillHost, '') + suffix; return host + StringWrapper.replace(part, _polyfillHost, '') + suffix;
} }
/* /*
@ -315,7 +322,8 @@ export class ShadowCss {
* by replacing with space. * by replacing with space.
*/ */
private _convertShadowDOMSelectors(cssText: string): string { private _convertShadowDOMSelectors(cssText: string): string {
return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText); return _shadowDOMSelectorsRe.reduce(
(result, pattern) => { return StringWrapper.replaceAll(result, pattern, ' '); }, cssText);
} }
// change a selector like 'div' to 'name div' // change a selector like 'div' to 'name div'
@ -323,12 +331,10 @@ export class ShadowCss {
return processRules(cssText, (rule: CssRule) => { return processRules(cssText, (rule: CssRule) => {
let selector = rule.selector; let selector = rule.selector;
let content = rule.content; let content = rule.content;
if (rule.selector[0] != '@') { if (rule.selector[0] != '@' || rule.selector.startsWith('@page')) {
selector = selector =
this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling); this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling);
} else if ( } else if (rule.selector.startsWith('@media') || rule.selector.startsWith('@supports')) {
rule.selector.startsWith('@media') || rule.selector.startsWith('@supports') ||
rule.selector.startsWith('@page') || rule.selector.startsWith('@document')) {
content = this._scopeSelectors(rule.content, scopeSelector, hostSelector); content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
} }
return new CssRule(selector, content); return new CssRule(selector, content);
@ -363,7 +369,8 @@ export class ShadowCss {
private _makeScopeMatcher(scopeSelector: string): RegExp { private _makeScopeMatcher(scopeSelector: string): RegExp {
const lre = /\[/g; const lre = /\[/g;
const rre = /\]/g; const rre = /\]/g;
scopeSelector = scopeSelector.replace(lre, '\\[').replace(rre, '\\]'); scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\[');
scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]');
return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm'); return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
} }
@ -378,15 +385,14 @@ export class ShadowCss {
string { string {
// In Android browser, the lastIndex is not reset when the regex is used in String.replace() // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
_polyfillHostRe.lastIndex = 0; _polyfillHostRe.lastIndex = 0;
if (_polyfillHostRe.test(selector)) { if (_polyfillHostRe.test(selector)) {
const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector; const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
return selector.replace(_polyfillHostNoCombinatorRe, (hnc, selector) => selector + replaceBy) selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy);
.replace(_polyfillHostRe, replaceBy + ' '); return StringWrapper.replaceAll(selector, _polyfillHostRe, replaceBy + ' ');
} } else {
return scopeSelector + ' ' + selector; return scopeSelector + ' ' + selector;
} }
}
// return a selector with [name] suffix on each simple selector // return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */ // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
@ -398,9 +404,9 @@ export class ShadowCss {
const attrName = '[' + scopeSelector + ']'; const attrName = '[' + scopeSelector + ']';
const _scopeSelectorPart = (p: string) => { const _scopeSelectorPart = (p: string) => {
let scopedP = p.trim(); var scopedP = p.trim();
if (!scopedP) { if (scopedP.length == 0) {
return ''; return '';
} }
@ -420,38 +426,23 @@ export class ShadowCss {
return scopedP; return scopedP;
}; };
let attrSelectorIndex = 0;
const attrSelectors: string[] = [];
// replace attribute selectors with placeholders to avoid issue with white space being treated
// as separator
selector = selector.replace(/\[[^\]]*\]/g, (attrSelector) => {
const replaceBy = `__attr_sel_${attrSelectorIndex}__`;
attrSelectors.push(attrSelector);
attrSelectorIndex++;
return replaceBy;
});
let scopedSelector = '';
let startIndex = 0;
let res: RegExpExecArray;
const sep = /( |>|\+|~(?!=))\s*/g; const sep = /( |>|\+|~(?!=))\s*/g;
const scopeAfter = selector.indexOf(_polyfillHostNoCombinator); const scopeAfter = selector.indexOf(_polyfillHostNoCombinator);
let scoped = '';
let startIndex = 0;
let res: RegExpExecArray;
while ((res = sep.exec(selector)) !== null) { while ((res = sep.exec(selector)) !== null) {
const separator = res[1]; const separator = res[1];
const part = selector.slice(startIndex, res.index).trim(); const part = selector.slice(startIndex, res.index).trim();
// if a selector appears before :host-context it should not be shimmed as it // if a selector appears before :host-context it should not be shimmed as it
// matches on ancestor elements and not on elements in the host's shadow // matches on ancestor elements and not on elements in the host's shadow
const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part;
scopedSelector += `${scopedPart} ${separator} `; scoped += `${scopedPart} ${separator} `;
startIndex = sep.lastIndex; startIndex = sep.lastIndex;
} }
return scoped + _scopeSelectorPart(selector.substring(startIndex));
scopedSelector += _scopeSelectorPart(selector.substring(startIndex));
// replace the placeholders with their original values
return scopedSelector.replace(/__attr_sel_(\d+)__/g, (ph, index) => attrSelectors[+index]);
} }
private _insertPolyfillHostInCssText(selector: string): string { private _insertPolyfillHostInCssText(selector: string): string {
@ -460,10 +451,10 @@ export class ShadowCss {
} }
} }
const _cssContentNextSelectorRe = const _cssContentNextSelectorRe =
/polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim; /polyfill-next-selector[^}]*content:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim;
const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
const _cssContentUnscopedRuleRe = const _cssContentUnscopedRuleRe =
/(polyfill-unscoped-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; /(polyfill-unscoped-rule)[^}]*(content:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim;
const _polyfillHost = '-shadowcsshost'; const _polyfillHost = '-shadowcsshost';
// note: :host-context pre-processed to -shadowcsshostcontext. // note: :host-context pre-processed to -shadowcsshostcontext.
const _polyfillHostContext = '-shadowcsscontext'; const _polyfillHostContext = '-shadowcsscontext';
@ -473,7 +464,6 @@ const _parenSuffix = ')(?:\\((' +
const _cssColonHostRe = new RegExp('(' + _polyfillHost + _parenSuffix, 'gim'); const _cssColonHostRe = new RegExp('(' + _polyfillHost + _parenSuffix, 'gim');
const _cssColonHostContextRe = new RegExp('(' + _polyfillHostContext + _parenSuffix, 'gim'); const _cssColonHostContextRe = new RegExp('(' + _polyfillHostContext + _parenSuffix, 'gim');
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator'; const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
const _shadowDOMSelectorsRe = [ const _shadowDOMSelectorsRe = [
/::shadow/g, /::shadow/g,
/::content/g, /::content/g,
@ -490,7 +480,7 @@ const _colonHostContextRe = /:host-context/gim;
const _commentRe = /\/\*\s*[\s\S]*?\*\//g; const _commentRe = /\/\*\s*[\s\S]*?\*\//g;
function stripComments(input: string): string { function stripComments(input: string): string {
return input.replace(_commentRe, ''); return StringWrapper.replaceAllMapped(input, _commentRe, (_: any /** TODO #9100 */) => '');
} }
// all comments except inline source mapping // all comments except inline source mapping
@ -511,17 +501,18 @@ export class CssRule {
constructor(public selector: string, public content: string) {} constructor(public selector: string, public content: string) {}
} }
export function processRules(input: string, ruleCallback: (rule: CssRule) => CssRule): string { export function processRules(input: string, ruleCallback: Function): string {
const inputWithEscapedBlocks = escapeBlocks(input); const inputWithEscapedBlocks = escapeBlocks(input);
let nextBlockIndex = 0; let nextBlockIndex = 0;
return inputWithEscapedBlocks.escapedString.replace(_ruleRe, function(...m: string[]) { return StringWrapper.replaceAllMapped(
inputWithEscapedBlocks.escapedString, _ruleRe, function(m: any /** TODO #9100 */) {
const selector = m[2]; const selector = m[2];
let content = ''; let content = '';
let suffix = m[4]; let suffix = m[4];
let contentPrefix = ''; let contentPrefix = '';
if (suffix && suffix.startsWith('{' + BLOCK_PLACEHOLDER)) { if (isPresent(m[4]) && m[4].startsWith('{' + BLOCK_PLACEHOLDER)) {
content = inputWithEscapedBlocks.blocks[nextBlockIndex++]; content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
suffix = suffix.substring(BLOCK_PLACEHOLDER.length + 1); suffix = m[4].substring(BLOCK_PLACEHOLDER.length + 1);
contentPrefix = '{'; contentPrefix = '{';
} }
const rule = ruleCallback(new CssRule(selector, content)); const rule = ruleCallback(new CssRule(selector, content));
@ -534,11 +525,11 @@ class StringWithEscapedBlocks {
} }
function escapeBlocks(input: string): StringWithEscapedBlocks { function escapeBlocks(input: string): StringWithEscapedBlocks {
const inputParts = input.split(_curlyRe); const inputParts = StringWrapper.split(input, _curlyRe);
const resultParts: string[] = []; const resultParts: any[] /** TODO #9100 */ = [];
const escapedBlocks: string[] = []; const escapedBlocks: any[] /** TODO #9100 */ = [];
let bracketCount = 0; let bracketCount = 0;
let currentBlockParts: string[] = []; let currentBlockParts: any[] /** TODO #9100 */ = [];
for (let partIndex = 0; partIndex < inputParts.length; partIndex++) { for (let partIndex = 0; partIndex < inputParts.length; partIndex++) {
const part = inputParts[partIndex]; const part = inputParts[partIndex];
if (part == CLOSE_CURLY) { if (part == CLOSE_CURLY) {

View File

@ -12,8 +12,11 @@ import {CompileDirectiveMetadata, CompileProviderMetadata, CompileTokenMetadata}
import {AST} from '../expression_parser/ast'; import {AST} from '../expression_parser/ast';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {ParseSourceSpan} from '../parse_util'; import {ParseSourceSpan} from '../parse_util';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
/** /**
* An Abstract Syntax Tree node representing part of a parsed Angular template. * An Abstract Syntax Tree node representing part of a parsed Angular template.
*/ */
@ -58,8 +61,7 @@ export class AttrAst implements TemplateAst {
} }
/** /**
* A binding for an element property (e.g. `[property]="expression"`) or an animation trigger (e.g. * A binding for an element property (e.g. `[property]="expression"`).
* `[@trigger]="stateExp"`)
*/ */
export class BoundElementPropertyAst implements TemplateAst { export class BoundElementPropertyAst implements TemplateAst {
constructor( constructor(
@ -69,16 +71,14 @@ export class BoundElementPropertyAst implements TemplateAst {
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context); return visitor.visitElementProperty(this, context);
} }
get isAnimation(): boolean { return this.type === PropertyBindingType.Animation; }
} }
/** /**
* A binding for an element event (e.g. `(event)="handler()"`) or an animation trigger event (e.g. * A binding for an element event (e.g. `(event)="handler()"`).
* `(@trigger.phase)="callback($event)"`).
*/ */
export class BoundEventAst implements TemplateAst { export class BoundEventAst implements TemplateAst {
constructor( constructor(
public name: string, public target: string, public phase: string, public handler: AST, public name: string, public target: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context); return visitor.visitEvent(this, context);
@ -90,7 +90,6 @@ export class BoundEventAst implements TemplateAst {
return this.name; return this.name;
} }
} }
get isAnimation(): boolean { return !!this.phase; }
} }
/** /**

View File

@ -8,10 +8,11 @@
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core'; import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core';
import {CompileDirectiveMetadata, CompilePipeMetadata, CompileTemplateMetadata, CompileTokenMetadata, removeIdentifierDuplicates} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata, CompileTokenMetadata, removeIdentifierDuplicates} from '../compile_metadata';
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent, isString} from '../facade/lang'; import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent, isString} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser'; import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {Identifiers, identifierToken, resolveIdentifierToken} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifierToken} from '../identifiers';
import * as html from '../ml_parser/ast'; import * as html from '../ml_parser/ast';
@ -25,13 +26,12 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector'; import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver'; import {isStyleUrlResolvable} from '../style_url_resolver';
import {splitAtColon, splitAtPeriod} from '../util'; import {splitAtColon} from '../util';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser'; import {PreparsedElementType, preparseElement} from './template_preparser';
// Group 1 = "bind-" // Group 1 = "bind-"
// Group 2 = "let-" // Group 2 = "let-"
// Group 3 = "ref-/#" // Group 3 = "ref-/#"
@ -142,6 +142,7 @@ export class TemplateParser {
const parseVisitor = new TemplateParseVisitor( const parseVisitor = new TemplateParseVisitor(
providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser, providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser,
this._schemaRegistry); this._schemaRegistry);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...parseVisitor.errors, ...providerViewContext.errors); errors.push(...parseVisitor.errors, ...providerViewContext.errors);
} else { } else {
@ -443,15 +444,6 @@ class TemplateParseVisitor implements html.Visitor {
providerContext.transformedDirectiveAsts, providerContext.transformProviders, providerContext.transformedDirectiveAsts, providerContext.transformProviders,
providerContext.transformedHasViewContainer, children, providerContext.transformedHasViewContainer, children,
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan); hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
this._findComponentDirectives(directiveAsts)
.forEach(
componentDirectiveAst => this._validateElementAnimationInputOutputs(
componentDirectiveAst.hostProperties, componentDirectiveAst.hostEvents,
componentDirectiveAst.directive.template));
const componentTemplate = providerContext.viewContext.component.template;
this._validateElementAnimationInputOutputs(elementProps, events, componentTemplate);
} }
if (hasInlineTemplates) { if (hasInlineTemplates) {
@ -477,36 +469,9 @@ class TemplateParseVisitor implements html.Visitor {
templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex, templateProviderContext.transformedHasViewContainer, [parsedElement], ngContentIndex,
element.sourceSpan); element.sourceSpan);
} }
return parsedElement; return parsedElement;
} }
private _validateElementAnimationInputOutputs(
inputs: BoundElementPropertyAst[], outputs: BoundEventAst[],
template: CompileTemplateMetadata) {
const triggerLookup = new Set<string>();
template.animations.forEach(entry => { triggerLookup.add(entry.name); });
const animationInputs = inputs.filter(input => input.isAnimation);
animationInputs.forEach(input => {
const name = input.name;
if (!triggerLookup.has(name)) {
this._reportError(`Couldn't find an animation entry for "${name}"`, input.sourceSpan);
}
});
outputs.forEach(output => {
if (output.isAnimation) {
const found = animationInputs.find(input => input.name == output.name);
if (!found) {
this._reportError(
`Unable to listen on (@${output.name}.${output.phase}) because the animation trigger [@${output.name}] isn't being used on the same element`,
output.sourceSpan);
}
}
});
}
private _parseInlineTemplateBinding( private _parseInlineTemplateBinding(
attr: html.Attribute, targetMatchableAttrs: string[][], attr: html.Attribute, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean { targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean {
@ -568,7 +533,7 @@ class TemplateParseVisitor implements html.Visitor {
this._parseReference(identifier, value, srcSpan, targetRefs); this._parseReference(identifier, value, srcSpan, targetRefs);
} else if (bindParts[KW_ON_IDX]) { } else if (bindParts[KW_ON_IDX]) {
this._parseEventOrAnimationEvent( this._parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_BINDON_IDX]) { } else if (bindParts[KW_BINDON_IDX]) {
@ -579,7 +544,7 @@ class TemplateParseVisitor implements html.Visitor {
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_AT_IDX]) { } else if (bindParts[KW_AT_IDX]) {
if (_isAnimationLabel(name) && isPresent(value) && value.length > 0) { if (name[0] == '@' && isPresent(value) && value.length > 0) {
this._reportError( this._reportError(
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` + `Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, ` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
@ -600,7 +565,7 @@ class TemplateParseVisitor implements html.Visitor {
targetAnimationProps); targetAnimationProps);
} else if (bindParts[IDENT_EVENT_IDX]) { } else if (bindParts[IDENT_EVENT_IDX]) {
this._parseEventOrAnimationEvent( this._parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} }
} else { } else {
@ -643,7 +608,7 @@ class TemplateParseVisitor implements html.Visitor {
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[], targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[],
targetAnimationProps: BoundElementPropertyAst[]) { targetAnimationProps: BoundElementPropertyAst[]) {
const animatePropLength = ANIMATE_PROP_PREFIX.length; const animatePropLength = ANIMATE_PROP_PREFIX.length;
var isAnimationProp = _isAnimationLabel(name); var isAnimationProp = name[0] == '@';
var animationPrefixLength = 1; var animationPrefixLength = 1;
if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) { if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) {
isAnimationProp = true; isAnimationProp = true;
@ -670,7 +635,6 @@ class TemplateParseVisitor implements html.Visitor {
if (!isPresent(expression) || expression.length == 0) { if (!isPresent(expression) || expression.length == 0) {
expression = 'null'; expression = 'null';
} }
const ast = this._parseBinding(expression, sourceSpan); const ast = this._parseBinding(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]); targetMatchableAttrs.push([name, ast.source]);
targetAnimationProps.push(new BoundElementPropertyAst( targetAnimationProps.push(new BoundElementPropertyAst(
@ -698,56 +662,20 @@ class TemplateParseVisitor implements html.Visitor {
private _parseAssignmentEvent( private _parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) { targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
this._parseEventOrAnimationEvent( this._parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents); `${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
} }
private _parseEventOrAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
if (_isAnimationLabel(name)) {
name = name.substr(1);
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
} else {
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
}
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetEvents: BoundEventAst[]) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
if (phase) {
switch (phase) {
case 'start':
case 'done':
const ast = this._parseAction(expression, sourceSpan);
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
break;
default:
this._reportError(
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
sourceSpan);
break;
}
} else {
this._reportError(
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
sourceSpan);
}
}
private _parseEvent( private _parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) { targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName' // long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null, name]); const parts = splitAtColon(name, [null, name]);
const target = parts[0];
const eventName = parts[1];
const ast = this._parseAction(expression, sourceSpan); const ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]); targetMatchableAttrs.push([name, ast.source]);
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan)); targetEvents.push(new BoundEventAst(eventName, target, ast, sourceSpan));
// Don't detect directives for event names for now, // Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs // so don't add the event name to the matchableAttrs
} }
@ -831,8 +759,7 @@ class TemplateParseVisitor implements html.Visitor {
elementName: string, hostProps: {[key: string]: string}, sourceSpan: ParseSourceSpan, elementName: string, hostProps: {[key: string]: string}, sourceSpan: ParseSourceSpan,
targetPropertyAsts: BoundElementPropertyAst[]) { targetPropertyAsts: BoundElementPropertyAst[]) {
if (hostProps) { if (hostProps) {
Object.keys(hostProps).forEach(propName => { StringMapWrapper.forEach(hostProps, (expression: string, propName: string) => {
const expression = hostProps[propName];
if (isString(expression)) { if (isString(expression)) {
const exprAst = this._parseBinding(expression, sourceSpan); const exprAst = this._parseBinding(expression, sourceSpan);
targetPropertyAsts.push( targetPropertyAsts.push(
@ -850,10 +777,9 @@ class TemplateParseVisitor implements html.Visitor {
hostListeners: {[key: string]: string}, sourceSpan: ParseSourceSpan, hostListeners: {[key: string]: string}, sourceSpan: ParseSourceSpan,
targetEventAsts: BoundEventAst[]) { targetEventAsts: BoundEventAst[]) {
if (hostListeners) { if (hostListeners) {
Object.keys(hostListeners).forEach(propName => { StringMapWrapper.forEach(hostListeners, (expression: string, propName: string) => {
const expression = hostListeners[propName];
if (isString(expression)) { if (isString(expression)) {
this._parseEventOrAnimationEvent(propName, expression, sourceSpan, [], targetEventAsts); this._parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
} else { } else {
this._reportError( this._reportError(
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, `Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
@ -870,14 +796,13 @@ class TemplateParseVisitor implements html.Visitor {
const boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>(); const boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>();
boundProps.forEach(boundProp => { boundProps.forEach(boundProp => {
const prevValue = boundPropsByName.get(boundProp.name); const prevValue = boundPropsByName.get(boundProp.name);
if (!prevValue || prevValue.isLiteral) { if (isBlank(prevValue) || prevValue.isLiteral) {
// give [a]="b" a higher precedence than a="b" on the same element // give [a]="b" a higher precedence than a="b" on the same element
boundPropsByName.set(boundProp.name, boundProp); boundPropsByName.set(boundProp.name, boundProp);
} }
}); });
Object.keys(directiveProperties).forEach(dirProp => { StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => {
const elProp = directiveProperties[dirProp];
const boundProp = boundPropsByName.get(elProp); const boundProp = boundPropsByName.get(elProp);
// Bindings are optional, so this binding only needs to be set up if an expression is given. // Bindings are optional, so this binding only needs to be set up if an expression is given.
@ -902,7 +827,7 @@ class TemplateParseVisitor implements html.Visitor {
}); });
props.forEach((prop: BoundElementOrDirectiveProperty) => { props.forEach((prop: BoundElementOrDirectiveProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) { if (!prop.isLiteral && isBlank(boundDirectivePropsIndex.get(prop.name))) {
boundElementProps.push(this._createElementPropertyAst( boundElementProps.push(this._createElementPropertyAst(
elementName, prop.name, prop.expression, prop.sourceSpan)); elementName, prop.name, prop.expression, prop.sourceSpan));
} }
@ -921,7 +846,7 @@ class TemplateParseVisitor implements html.Visitor {
if (parts.length === 1) { if (parts.length === 1) {
var partValue = parts[0]; var partValue = parts[0];
if (_isAnimationLabel(partValue)) { if (partValue[0] == '@') {
boundPropertyName = partValue.substr(1); boundPropertyName = partValue.substr(1);
bindingType = PropertyBindingType.Animation; bindingType = PropertyBindingType.Animation;
securityContext = SecurityContext.NONE; securityContext = SecurityContext.NONE;
@ -929,7 +854,7 @@ class TemplateParseVisitor implements html.Visitor {
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue); boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName); securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
bindingType = PropertyBindingType.Property; bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, false); this._assertNoEventBinding(boundPropertyName, sourceSpan, false);
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) { if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) {
let errorMsg = let errorMsg =
`Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`; `Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`;
@ -944,7 +869,7 @@ class TemplateParseVisitor implements html.Visitor {
} else { } else {
if (parts[0] == ATTRIBUTE_PREFIX) { if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1]; boundPropertyName = parts[1];
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, true); this._assertNoEventBinding(boundPropertyName, sourceSpan, true);
// NB: For security purposes, use the mapped property name, not the attribute name. // NB: For security purposes, use the mapped property name, not the attribute name.
const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName); const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName);
securityContext = this._schemaRegistry.securityContext(elementName, mapPropName); securityContext = this._schemaRegistry.securityContext(elementName, mapPropName);
@ -977,29 +902,35 @@ class TemplateParseVisitor implements html.Visitor {
boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan); boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan);
} }
/** /**
* @param propName the name of the property / attribute * @param propName the name of the property / attribute
* @param sourceSpan * @param sourceSpan
* @param isAttr true when binding to an attribute * @param isAttr true when binding to an attribute
* @private * @private
*/ */
private _validatePropertyOrAttributeName( private _assertNoEventBinding(propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean):
propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean): void { void {
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) : if (propName.toLowerCase().startsWith('on')) {
this._schemaRegistry.validateProperty(propName); let msg = `Binding to event attribute '${propName}' is disallowed for security reasons, ` +
if (report.error) { `please use (${propName.slice(2)})=...`;
this._reportError(report.msg, sourceSpan, ParseErrorLevel.FATAL); if (!isAttr) {
msg +=
`\nIf '${propName}' is a directive input, make sure the directive is imported by the` +
` current module.`;
} }
this._reportError(msg, sourceSpan, ParseErrorLevel.FATAL);
} }
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
return directives.filter(directive => directive.directive.isComponent);
} }
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] { private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
return this._findComponentDirectives(directives) const componentTypeNames: string[] = [];
.map(directive => directive.directive.type.name); directives.forEach(directive => {
const typeName = directive.directive.type.name;
if (directive.directive.isComponent) {
componentTypeNames.push(typeName);
}
});
return componentTypeNames;
} }
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) { private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
@ -1049,8 +980,7 @@ class TemplateParseVisitor implements html.Visitor {
const allDirectiveEvents = new Set<string>(); const allDirectiveEvents = new Set<string>();
directives.forEach(directive => { directives.forEach(directive => {
Object.keys(directive.directive.outputs).forEach(k => { StringMapWrapper.forEach(directive.directive.outputs, (eventName: string) => {
const eventName = directive.directive.outputs[k];
allDirectiveEvents.add(eventName); allDirectiveEvents.add(eventName);
}); });
}); });
@ -1184,7 +1114,3 @@ export class PipeCollector extends RecursiveAstVisitor {
return null; return null;
} }
} }
function _isAnimationLabel(name: string): boolean {
return name[0] == '@';
}

View File

@ -7,6 +7,7 @@
*/ */
import {CompileTokenMetadata} from './compile_metadata'; import {CompileTokenMetadata} from './compile_metadata';
import {StringMapWrapper} from './facade/collection';
import {StringWrapper, isArray, isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang'; import {StringWrapper, isArray, isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
@ -16,21 +17,13 @@ var CAMEL_CASE_REGEXP = /([A-Z])/g;
export function camelCaseToDashCase(input: string): string { export function camelCaseToDashCase(input: string): string {
return StringWrapper.replaceAllMapped( return StringWrapper.replaceAllMapped(
input, CAMEL_CASE_REGEXP, (m: string[]) => '-' + m[1].toLowerCase()); input, CAMEL_CASE_REGEXP, (m: string[]) => { return '-' + m[1].toLowerCase(); });
} }
export function splitAtColon(input: string, defaultValues: string[]): string[] { export function splitAtColon(input: string, defaultValues: string[]): string[] {
return _splitAt(input, ':', defaultValues); const colonIndex = input.indexOf(':');
} if (colonIndex == -1) return defaultValues;
return [input.slice(0, colonIndex).trim(), input.slice(colonIndex + 1).trim()];
export function splitAtPeriod(input: string, defaultValues: string[]): string[] {
return _splitAt(input, '.', defaultValues);
}
function _splitAt(input: string, character: string, defaultValues: string[]): string[] {
const characterIndex = input.indexOf(character);
if (characterIndex == -1) return defaultValues;
return [input.slice(0, characterIndex).trim(), input.slice(characterIndex + 1).trim()];
} }
export function sanitizeIdentifier(name: string): string { export function sanitizeIdentifier(name: string): string {
@ -62,8 +55,9 @@ export class ValueTransformer implements ValueVisitor {
} }
visitStringMap(map: {[key: string]: any}, context: any): any { visitStringMap(map: {[key: string]: any}, context: any): any {
var result = {}; var result = {};
Object.keys(map).forEach( StringMapWrapper.forEach(map, (value: any /** TODO #9100 */, key: any /** TODO #9100 */) => {
key => { (result as any /** TODO #9100 */)[key] = visitValue(map[key], this, context); }); (result as any /** TODO #9100 */)[key] = visitValue(value, this, context);
});
return result; return result;
} }
visitPrimitive(value: any, context: any): any { return value; } visitPrimitive(value: any, context: any): any { return value; }

View File

@ -8,8 +8,8 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {ListWrapper, MapWrapper} from '../facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util'; import {convertValueToOutputAst} from '../output/value_util';
@ -27,7 +27,7 @@ export class CompileNode {
public parent: CompileElement, public view: CompileView, public nodeIndex: number, public parent: CompileElement, public view: CompileView, public nodeIndex: number,
public renderNode: o.Expression, public sourceAst: TemplateAst) {} public renderNode: o.Expression, public sourceAst: TemplateAst) {}
isNull(): boolean { return !this.renderNode; } isNull(): boolean { return isBlank(this.renderNode); }
isRootElement(): boolean { return this.view != this.parent.view; } isRootElement(): boolean { return this.view != this.parent.view; }
} }
@ -192,7 +192,7 @@ export class CompileElement extends CompileNode {
queriesWithReads, queriesWithReads,
queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token))); queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
}); });
Object.keys(this.referenceTokens).forEach(varName => { StringMapWrapper.forEach(this.referenceTokens, (_: CompileTokenMetadata, varName: string) => {
var token = this.referenceTokens[varName]; var token = this.referenceTokens[varName];
var varValue: o.Expression; var varValue: o.Expression;
if (isPresent(token)) { if (isPresent(token)) {
@ -313,12 +313,12 @@ export class CompileElement extends CompileNode {
requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression {
var result: o.Expression = null; var result: o.Expression = null;
// constructor content query // constructor content query
if (!result && isPresent(dep.query)) { if (isBlank(result) && isPresent(dep.query)) {
result = this._addQuery(dep.query, null).queryList; result = this._addQuery(dep.query, null).queryList;
} }
// constructor view query // constructor view query
if (!result && isPresent(dep.viewQuery)) { if (isBlank(result) && isPresent(dep.viewQuery)) {
result = createQueryList( result = createQueryList(
dep.viewQuery, null, dep.viewQuery, null,
`_viewQuery_${dep.viewQuery.selectors[0].name}_${this.nodeIndex}_${this._componentConstructorViewQueryLists.length}`, `_viewQuery_${dep.viewQuery.selectors[0].name}_${this.nodeIndex}_${this._componentConstructorViewQueryLists.length}`,
@ -328,7 +328,7 @@ export class CompileElement extends CompileNode {
if (isPresent(dep.token)) { if (isPresent(dep.token)) {
// access builtins with special visibility // access builtins with special visibility
if (!result) { if (isBlank(result)) {
if (dep.token.reference === if (dep.token.reference ===
resolveIdentifierToken(Identifiers.ChangeDetectorRef).reference) { resolveIdentifierToken(Identifiers.ChangeDetectorRef).reference) {
if (requestingProviderType === ProviderAstType.Component) { if (requestingProviderType === ProviderAstType.Component) {
@ -339,7 +339,7 @@ export class CompileElement extends CompileNode {
} }
} }
// access regular providers on the element // access regular providers on the element
if (!result) { if (isBlank(result)) {
let resolvedProvider = this._resolvedProviders.get(dep.token.reference); let resolvedProvider = this._resolvedProviders.get(dep.token.reference);
// don't allow directives / public services to access private services. // don't allow directives / public services to access private services.
// only components and private services can access private services. // only components and private services can access private services.
@ -361,20 +361,20 @@ export class CompileElement extends CompileNode {
if (dep.isValue) { if (dep.isValue) {
result = o.literal(dep.value); result = o.literal(dep.value);
} }
if (!result && !dep.isSkipSelf) { if (isBlank(result) && !dep.isSkipSelf) {
result = this._getLocalDependency(requestingProviderType, dep); result = this._getLocalDependency(requestingProviderType, dep);
} }
// check parent elements // check parent elements
while (!result && !currElement.parent.isNull()) { while (isBlank(result) && !currElement.parent.isNull()) {
currElement = currElement.parent; currElement = currElement.parent;
result = currElement._getLocalDependency( result = currElement._getLocalDependency(
ProviderAstType.PublicService, new CompileDiDependencyMetadata({token: dep.token})); ProviderAstType.PublicService, new CompileDiDependencyMetadata({token: dep.token}));
} }
if (!result) { if (isBlank(result)) {
result = injectFromViewParentInjector(dep.token, dep.isOptional); result = injectFromViewParentInjector(dep.token, dep.isOptional);
} }
if (!result) { if (isBlank(result)) {
result = o.NULL_EXPR; result = o.NULL_EXPR;
} }
return getPropertyInView(result, this.view, currElement.view); return getPropertyInView(result, this.view, currElement.view);
@ -411,7 +411,7 @@ function createProviderProperty(
resolvedProviderValueExpr = providerValueExpressions[0]; resolvedProviderValueExpr = providerValueExpressions[0];
type = providerValueExpressions[0].type; type = providerValueExpressions[0].type;
} }
if (!type) { if (isBlank(type)) {
type = o.DYNAMIC_TYPE; type = o.DYNAMIC_TYPE;
} }
if (isEager) { if (isEager) {

View File

@ -8,7 +8,8 @@
import {CompilePipeMetadata} from '../compile_metadata'; import {CompilePipeMetadata} from '../compile_metadata';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
@ -22,7 +23,7 @@ export class CompilePipe {
if (meta.pure) { if (meta.pure) {
// pure pipes live on the component view // pure pipes live on the component view
pipe = compView.purePipes.get(name); pipe = compView.purePipes.get(name);
if (!pipe) { if (isBlank(pipe)) {
pipe = new CompilePipe(compView, meta); pipe = new CompilePipe(compView, meta);
compView.purePipes.set(name, pipe); compView.purePipes.set(name, pipe);
compView.pipes.push(pipe); compView.pipes.push(pipe);
@ -84,7 +85,7 @@ function _findPipeMeta(view: CompileView, name: string): CompilePipeMetadata {
break; break;
} }
} }
if (!pipeMeta) { if (isBlank(pipeMeta)) {
throw new Error( throw new Error(
`Illegal state: Could not find pipe ${name} although the parser should have detected this error!`); `Illegal state: Could not find pipe ${name} although the parser should have detected this error!`);
} }

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileQueryMetadata} from '../compile_metadata'; import {CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -101,8 +101,9 @@ function createQueryValues(viewValues: ViewQueryValues): o.Expression[] {
function mapNestedViews( function mapNestedViews(
declarationAppElement: o.Expression, view: CompileView, declarationAppElement: o.Expression, view: CompileView,
expressions: o.Expression[]): o.Expression { expressions: o.Expression[]): o.Expression {
var adjustedExpressions: o.Expression[] = expressions.map( var adjustedExpressions: o.Expression[] = expressions.map((expr) => {
(expr) => o.replaceVarInExpression(o.THIS_EXPR.name, o.variable('nestedView'), expr)); return o.replaceVarInExpression(o.THIS_EXPR.name, o.variable('nestedView'), expr);
});
return declarationAppElement.callMethod('mapNestedViews', [ return declarationAppElement.callMethod('mapNestedViews', [
o.variable(view.className), o.variable(view.className),
o.fn( o.fn(
@ -128,7 +129,7 @@ export function createQueryList(
export function addQueryToTokenMap(map: Map<any, CompileQuery[]>, query: CompileQuery) { export function addQueryToTokenMap(map: Map<any, CompileQuery[]>, query: CompileQuery) {
query.meta.selectors.forEach((selector) => { query.meta.selectors.forEach((selector) => {
var entry = map.get(selector.reference); var entry = map.get(selector.reference);
if (!entry) { if (isBlank(entry)) {
entry = []; entry = [];
map.set(selector.reference, entry); map.set(selector.reference, entry);
} }

View File

@ -6,14 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {CompiledAnimationTriggerResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata, CompileTokenMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {ListWrapper, MapWrapper} from '../facade/collection'; import {ListWrapper, MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
import {createDiTokenExpression} from '../util';
import {CompileBinding} from './compile_binding'; import {CompileBinding} from './compile_binding';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
@ -22,7 +23,7 @@ import {CompilePipe} from './compile_pipe';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {EventHandlerVars} from './constants'; import {EventHandlerVars} from './constants';
import {NameResolver} from './expression_converter'; import {NameResolver} from './expression_converter';
import {createPureProxy, getPropertyInView, getViewFactoryName} from './util'; import {createPureProxy, getPropertyInView, getViewFactoryName, injectFromViewParentInjector} from './util';
export class CompileView implements NameResolver { export class CompileView implements NameResolver {
public viewType: ViewType; public viewType: ViewType;
@ -71,7 +72,7 @@ export class CompileView implements NameResolver {
constructor( constructor(
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig, public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression, public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
public animations: AnimationEntryCompileResult[], public viewIndex: number, public animations: CompiledAnimationTriggerResult[], public viewIndex: number,
public declarationElement: CompileElement, public templateVariableBindings: string[][]) { public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
this.createMethod = new CompileMethod(this); this.createMethod = new CompileMethod(this);
this.animationBindingsMethod = new CompileMethod(this); this.animationBindingsMethod = new CompileMethod(this);
@ -138,7 +139,7 @@ export class CompileView implements NameResolver {
} }
var currView: CompileView = this; var currView: CompileView = this;
var result = currView.locals.get(name); var result = currView.locals.get(name);
while (!result && isPresent(currView.declarationElement.view)) { while (isBlank(result) && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view; currView = currView.declarationElement.view;
result = currView.locals.get(name); result = currView.locals.get(name);
} }

View File

@ -9,6 +9,7 @@
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {CompileIdentifierMetadata} from '../compile_metadata'; import {CompileIdentifierMetadata} from '../compile_metadata';
import {isBlank} from '../facade/lang';
import {Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';

View File

@ -7,9 +7,11 @@
*/ */
import {CompileDirectiveMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata} from '../compile_metadata';
import {StringWrapper, isPresent} from '../facade/lang'; import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {identifierToken} from '../identifiers'; import {StringWrapper, isBlank, isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {AnimationOutput} from '../private_import_core';
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileBinding} from './compile_binding'; import {CompileBinding} from './compile_binding';
@ -18,6 +20,10 @@ import {CompileMethod} from './compile_method';
import {EventHandlerVars, ViewProperties} from './constants'; import {EventHandlerVars, ViewProperties} from './constants';
import {convertCdStatementToIr} from './expression_converter'; import {convertCdStatementToIr} from './expression_converter';
export class CompileElementAnimationOutput {
constructor(public listener: CompileEventListener, public output: AnimationOutput) {}
}
export class CompileEventListener { export class CompileEventListener {
private _method: CompileMethod; private _method: CompileMethod;
private _hasComponentHostListener: boolean = false; private _hasComponentHostListener: boolean = false;
@ -26,14 +32,13 @@ export class CompileEventListener {
private _actionResultExprs: o.Expression[] = []; private _actionResultExprs: o.Expression[] = [];
static getOrCreate( static getOrCreate(
compileElement: CompileElement, eventTarget: string, eventName: string, eventPhase: string, compileElement: CompileElement, eventTarget: string, eventName: string,
targetEventListeners: CompileEventListener[]): CompileEventListener { targetEventListeners: CompileEventListener[]): CompileEventListener {
var listener = targetEventListeners.find( var listener = targetEventListeners.find(
listener => listener.eventTarget == eventTarget && listener.eventName == eventName && listener => listener.eventTarget == eventTarget && listener.eventName == eventName);
listener.eventPhase == eventPhase); if (isBlank(listener)) {
if (!listener) {
listener = new CompileEventListener( listener = new CompileEventListener(
compileElement, eventTarget, eventName, eventPhase, targetEventListeners.length); compileElement, eventTarget, eventName, targetEventListeners.length);
targetEventListeners.push(listener); targetEventListeners.push(listener);
} }
return listener; return listener;
@ -43,7 +48,7 @@ export class CompileEventListener {
constructor( constructor(
public compileElement: CompileElement, public eventTarget: string, public eventName: string, public compileElement: CompileElement, public eventTarget: string, public eventName: string,
public eventPhase: string, listenerIndex: number) { listenerIndex: number) {
this._method = new CompileMethod(compileElement.view); this._method = new CompileMethod(compileElement.view);
this._methodName = this._methodName =
`_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`; `_handle_${santitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
@ -114,7 +119,7 @@ export class CompileEventListener {
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private])); disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
} }
listenToAnimation() { listenToAnimation(output: AnimationOutput) {
var outputListener = o.THIS_EXPR.callMethod( var outputListener = o.THIS_EXPR.callMethod(
'eventHandler', 'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]); [o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
@ -124,8 +129,11 @@ export class CompileEventListener {
.callMethod( .callMethod(
'registerAnimationOutput', 'registerAnimationOutput',
[ [
this.compileElement.renderNode, o.literal(this.eventName), this.compileElement.renderNode,
o.literal(this.eventPhase), outputListener o.importExpr(resolveIdentifier(Identifiers.AnimationOutput)).instantiate([
o.literal(output.name), o.literal(output.phase)
]),
outputListener
]) ])
.toStmt(); .toStmt();
this.compileElement.view.createMethod.addStmt(stmt); this.compileElement.view.createMethod.addStmt(stmt);
@ -152,7 +160,7 @@ export function collectEventListeners(
hostEvents.forEach((hostEvent) => { hostEvents.forEach((hostEvent) => {
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent)); compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
var listener = CompileEventListener.getOrCreate( var listener = CompileEventListener.getOrCreate(
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners); compileElement, hostEvent.target, hostEvent.name, eventListeners);
listener.addAction(hostEvent, null, null); listener.addAction(hostEvent, null, null);
}); });
dirs.forEach((directiveAst) => { dirs.forEach((directiveAst) => {
@ -161,7 +169,7 @@ export function collectEventListeners(
directiveAst.hostEvents.forEach((hostEvent) => { directiveAst.hostEvents.forEach((hostEvent) => {
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent)); compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
var listener = CompileEventListener.getOrCreate( var listener = CompileEventListener.getOrCreate(
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners); compileElement, hostEvent.target, hostEvent.name, eventListeners);
listener.addAction(hostEvent, directiveAst.directive, directiveInstance); listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
}); });
}); });
@ -172,8 +180,9 @@ export function collectEventListeners(
export function bindDirectiveOutputs( export function bindDirectiveOutputs(
directiveAst: DirectiveAst, directiveInstance: o.Expression, directiveAst: DirectiveAst, directiveInstance: o.Expression,
eventListeners: CompileEventListener[]) { eventListeners: CompileEventListener[]) {
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => { StringMapWrapper.forEach(
const eventName = directiveAst.directive.outputs[observablePropName]; directiveAst.directive.outputs,
(eventName: any /** TODO #9100 */, observablePropName: any /** TODO #9100 */) => {
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => { eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
listener.listenToDirective(directiveInstance, observablePropName); listener.listenToDirective(directiveInstance, observablePropName);
}); });
@ -181,13 +190,11 @@ export function bindDirectiveOutputs(
} }
export function bindRenderOutputs(eventListeners: CompileEventListener[]) { export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
eventListeners.forEach(listener => { eventListeners.forEach(listener => listener.listenToRenderer());
if (listener.eventPhase) { }
listener.listenToAnimation();
} else { export function bindAnimationOutputs(eventListeners: CompileElementAnimationOutput[]) {
listener.listenToRenderer(); eventListeners.forEach(entry => { entry.listener.listenToAnimation(entry.output); });
}
});
} }
function convertStmtIntoExpression(stmt: o.Statement): o.Expression { function convertStmtIntoExpression(stmt: o.Statement): o.Expression {

View File

@ -9,7 +9,7 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core';
@ -31,13 +31,15 @@ function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: ` return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: `
} }
const _animationViewCheckedFlagMap = new Map<CompileView, boolean>();
function bind( function bind(
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr, view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[], parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
method: CompileMethod, bindingIndex: number) { method: CompileMethod, bindingIndex: number) {
var checkExpression = convertCdExpressionToIr( var checkExpression = convertCdExpressionToIr(
view, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex); view, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex);
if (!checkExpression.expression) { if (isBlank(checkExpression.expression)) {
// e.g. an empty expression was given // e.g. an empty expression was given
return; return;
} }

View File

@ -8,7 +8,7 @@
import {CompileDirectiveMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileTokenMetadata} from '../compile_metadata';
import {isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {createDiTokenExpression} from '../util'; import {createDiTokenExpression} from '../util';
@ -84,7 +84,7 @@ export function createPureProxy(
view.fields.push(new o.ClassField(pureProxyProp.name, null)); view.fields.push(new o.ClassField(pureProxyProp.name, null));
var pureProxyId = var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null; argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (!pureProxyId) { if (isBlank(pureProxyId)) {
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`); throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
} }
view.createMethod.addStmt(o.THIS_EXPR.prop(pureProxyProp.name) view.createMethod.addStmt(o.THIS_EXPR.prop(pureProxyProp.name)

View File

@ -5,17 +5,20 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {identifierToken} from '../identifiers';
import {AnimationOutput} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder'; import {CompileElementAnimationOutput, CompileEventListener, bindAnimationOutputs, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder'; import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void { export function bindView(
var visitor = new ViewBinderVisitor(view); view: CompileView, parsedTemplate: TemplateAst[], animationOutputs: AnimationOutput[]): void {
var visitor = new ViewBinderVisitor(view, animationOutputs);
templateVisitAll(visitor, parsedTemplate); templateVisitAll(visitor, parsedTemplate);
view.pipes.forEach( view.pipes.forEach(
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); }); (pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
@ -23,8 +26,12 @@ export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void
class ViewBinderVisitor implements TemplateAstVisitor { class ViewBinderVisitor implements TemplateAstVisitor {
private _nodeIndex: number = 0; private _nodeIndex: number = 0;
private _animationOutputsMap: {[key: string]: AnimationOutput} = {};
constructor(public view: CompileView) {} constructor(public view: CompileView, public animationOutputs: AnimationOutput[]) {
animationOutputs.forEach(
entry => { this._animationOutputsMap[entry.fullPropertyName] = entry; });
}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any { visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
var node = this.view.nodes[this._nodeIndex++]; var node = this.view.nodes[this._nodeIndex++];
@ -41,9 +48,22 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners: CompileEventListener[] = []; var eventListeners: CompileEventListener[] = [];
var animationEventListeners: CompileElementAnimationOutput[] = [];
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => { collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
// TODO: figure out how to abstract this `if` statement elsewhere
if (entry.eventName[0] == '@') {
let animationOutputName = entry.eventName.substr(1);
let output = this._animationOutputsMap[animationOutputName];
// no need to report an error here since the parser will
// have caught the missing animation trigger definition
if (output) {
animationEventListeners.push(new CompileElementAnimationOutput(entry, output));
}
} else {
eventListeners.push(entry); eventListeners.push(entry);
}
}); });
bindAnimationOutputs(animationEventListeners);
bindRenderInputs(ast.inputs, compileElement); bindRenderInputs(ast.inputs, compileElement);
bindRenderOutputs(eventListeners); bindRenderOutputs(eventListeners);
ast.directives.forEach((directiveAst) => { ast.directives.forEach((directiveAst) => {
@ -88,7 +108,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement); bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
}); });
bindView(compileElement.embeddedView, ast.children); bindView(compileElement.embeddedView, ast.children, this.animationOutputs);
return null; return null;
} }

View File

@ -6,15 +6,16 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {AnimationCompiler} from '../animation/animation_compiler';
import {ListWrapper} from '../facade/collection'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata, CompileTypeMetadata} from '../compile_metadata';
import {ListWrapper, SetWrapper, StringMapWrapper} from '../facade/collection';
import {StringWrapper, isPresent} from '../facade/lang'; import {StringWrapper, isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ProviderAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util'; import {createDiTokenExpression} from '../util';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
@ -64,6 +65,8 @@ export function finishView(view: CompileView, targetStatements: o.Statement[]) {
class ViewBuilderVisitor implements TemplateAstVisitor { class ViewBuilderVisitor implements TemplateAstVisitor {
nestedViewCount: number = 0; nestedViewCount: number = 0;
private _animationCompiler = new AnimationCompiler();
constructor( constructor(
public view: CompileView, public view: CompileView,
public targetDependencies: Array<ViewFactoryDependency|ComponentFactoryDependency>) {} public targetDependencies: Array<ViewFactoryDependency|ComponentFactoryDependency>) {}
@ -276,10 +279,12 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
ast.hasViewContainer, true, ast.references); ast.hasViewContainer, true, ast.references);
this.view.nodes.push(compileElement); this.view.nodes.push(compileElement);
var compiledAnimations = this._animationCompiler.compileComponent(this.view.component, [ast]);
this.nestedViewCount++; this.nestedViewCount++;
var embeddedView = new CompileView( var embeddedView = new CompileView(
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR, this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
this.view.animations, this.view.viewIndex + this.nestedViewCount, compileElement, compiledAnimations.triggers, this.view.viewIndex + this.nestedViewCount, compileElement,
templateVariableBindings); templateVariableBindings);
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies); this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
@ -346,10 +351,10 @@ function _mergeHtmlAndDirectiveAttrs(
declaredHtmlAttrs: {[key: string]: string}, declaredHtmlAttrs: {[key: string]: string},
directives: CompileDirectiveMetadata[]): string[][] { directives: CompileDirectiveMetadata[]): string[][] {
var result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
Object.keys(declaredHtmlAttrs).forEach(key => { result[key] = declaredHtmlAttrs[key]; }); StringMapWrapper.forEach(
declaredHtmlAttrs, (value: string, key: string) => { result[key] = value; });
directives.forEach(directiveMeta => { directives.forEach(directiveMeta => {
Object.keys(directiveMeta.hostAttributes).forEach(name => { StringMapWrapper.forEach(directiveMeta.hostAttributes, (value: string, name: string) => {
const value = directiveMeta.hostAttributes[name];
var prevValue = result[name]; var prevValue = result[name];
result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
}); });
@ -373,7 +378,9 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
function mapToKeyValueArray(data: {[key: string]: string}): string[][] { function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
var entryArray: string[][] = []; var entryArray: string[][] = [];
Object.keys(data).forEach(name => { entryArray.push([name, data[name]]); }); StringMapWrapper.forEach(data, (value: string, name: string) => {
entryArray.push([name, value]);
});
// We need to sort to get a defined output order // We need to sort to get a defined output order
// for tests and for caching generated artifacts... // for tests and for caching generated artifacts...
ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
@ -419,8 +426,8 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
if (isPresent(compileElement.component)) { if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type)); componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
} }
Object.keys(compileElement.referenceTokens).forEach(varName => { StringMapWrapper.forEach(
const token = compileElement.referenceTokens[varName]; compileElement.referenceTokens, (token: CompileTokenMetadata, varName: string) => {
varTokenEntries.push( varTokenEntries.push(
[varName, isPresent(token) ? createDiTokenExpression(token) : o.NULL_EXPR]); [varName, isPresent(token) ? createDiTokenExpression(token) : o.NULL_EXPR]);
}); });
@ -511,20 +518,19 @@ function createViewFactory(
templateUrlInfo = view.component.template.templateUrl; templateUrlInfo = view.component.template.templateUrl;
} }
if (view.viewIndex === 0) { if (view.viewIndex === 0) {
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnExp])); var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnVariable]));
initRenderCompTypeStmts = [new o.IfStmt( initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
renderCompTypeVar.identical(o.NULL_EXPR), renderCompTypeVar
[renderCompTypeVar
.set(ViewConstructorVars.viewUtils.callMethod( .set(ViewConstructorVars.viewUtils.callMethod(
'createRenderComponentType', 'createRenderComponentType',
[ [
view.genConfig.genDebugInfo ? o.literal(templateUrlInfo) : o.literal(''), o.literal(templateUrlInfo),
o.literal(view.component.template.ngContentSelectors.length), o.literal(view.component.template.ngContentSelectors.length),
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles,
view.styles, animationsExpr
animationsExpr,
])) ]))
.toStmt()])]; .toStmt()
])];
} }
return o return o
.fn(viewFactoryArgs, initRenderCompTypeStmts.concat([new o.ReturnStatement( .fn(viewFactoryArgs, initRenderCompTypeStmts.concat([new o.ReturnStatement(
@ -596,15 +602,15 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
var varStmts: any[] = []; var varStmts: any[] = [];
var readVars = o.findReadVarNames(stmts); var readVars = o.findReadVarNames(stmts);
if (readVars.has(DetectChangesVars.changed.name)) { if (SetWrapper.has(readVars, DetectChangesVars.changed.name)) {
varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)); varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
} }
if (readVars.has(DetectChangesVars.changes.name)) { if (SetWrapper.has(readVars, DetectChangesVars.changes.name)) {
varStmts.push( varStmts.push(
DetectChangesVars.changes.set(o.NULL_EXPR) DetectChangesVars.changes.set(o.NULL_EXPR)
.toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange))))); .toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange)))));
} }
if (readVars.has(DetectChangesVars.valUnwrapper.name)) { if (SetWrapper.has(readVars, DetectChangesVars.valUnwrapper.name)) {
varStmts.push( varStmts.push(
DetectChangesVars.valUnwrapper DetectChangesVars.valUnwrapper
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([])) .set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))

View File

@ -8,7 +8,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {AnimationCompiler, AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -34,18 +34,22 @@ export class ViewCompiler {
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
pipes: CompilePipeMetadata[], pipes: CompilePipeMetadata[]): ViewCompileResult {
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { var dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = [];
const dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = []; var compiledAnimations = this._animationCompiler.compileComponent(component, template);
const view = new CompileView( var statements: o.Statement[] = [];
component, this._genConfig, pipes, styles, compiledAnimations, 0, var animationTriggers = compiledAnimations.triggers;
animationTriggers.forEach(entry => {
statements.push(entry.statesMapStatement);
statements.push(entry.fnStatement);
});
var view = new CompileView(
component, this._genConfig, pipes, styles, animationTriggers, 0,
CompileElement.createNull(), []); CompileElement.createNull(), []);
const statements: o.Statement[] = [];
buildView(view, template, dependencies); buildView(view, template, dependencies);
// Need to separate binding from creation to be able to refer to // Need to separate binding from creation to be able to refer to
// variables that have been declared after usage. // variables that have been declared after usage.
bindView(view, template); bindView(view, template, compiledAnimations.outputs);
finishView(view, statements); finishView(view, statements);
return new ViewCompileResult(statements, view.viewFactory.name, dependencies); return new ViewCompileResult(statements, view.viewFactory.name, dependencies);

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationMetadata, animate, sequence, style, transition, trigger} from '@angular/core'; import {AnimationMetadata, animate, group, sequence, style, transition, trigger} from '@angular/core';
import {beforeEach, describe, expect, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
import {AnimationCompiler, AnimationEntryCompileResult} from '../../src/animation/animation_compiler';
import {AnimationParser} from '../../src/animation/animation_parser'; import {StringMapWrapper} from '../../../platform-browser-dynamic/src/facade/collection';
import {AnimationCompiler, CompiledAnimationTriggerResult} from '../../src/animation/animation_compiler';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
import {CompileMetadataResolver} from '../../src/metadata_resolver'; import {CompileMetadataResolver} from '../../src/metadata_resolver';
@ -19,13 +20,12 @@ export function main() {
beforeEach( beforeEach(
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; })); inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
const parser = new AnimationParser(); var compiler = new AnimationCompiler();
const compiler = new AnimationCompiler();
var compileAnimations = var compileAnimations =
(component: CompileDirectiveMetadata): AnimationEntryCompileResult[] => { (component: CompileDirectiveMetadata): CompiledAnimationTriggerResult => {
const parsedAnimations = parser.parseComponent(component); var result = compiler.compileComponent(component, []);
return compiler.compile(component.type.name, parsedAnimations); return result.triggers[0];
}; };
var compileTriggers = (input: any[]) => { var compileTriggers = (input: any[]) => {
@ -66,5 +66,14 @@ export function main() {
expect(capturedErrorMessage) expect(capturedErrorMessage)
.toMatch(/Animation states via styles must be prefixed with a ":"/); .toMatch(/Animation states via styles must be prefixed with a ":"/);
}); });
it('should throw an error when two or more animation triggers contain the same name', () => {
var t1Data: any[] = [];
var t2Data: any[] = [];
expect(() => {
compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
}).toThrowError(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
});
}); });
} }

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AnimationMetadata, animate, group, keyframes, sequence, style, transition, trigger} from '@angular/core'; import {AnimationAnimateMetadata, AnimationGroupMetadata, AnimationMetadata, AnimationSequenceMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal'; import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
import {AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStepAst, AnimationStylesAst} from '../../src/animation/animation_ast'; import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from '../../src/animation/animation_ast';
import {AnimationParser} from '../../src/animation/animation_parser'; import {parseAnimationEntry} from '../../src/animation/animation_parser';
import {StringMapWrapper} from '../../src/facade/collection';
import {CompileMetadataResolver} from '../../src/metadata_resolver'; import {CompileMetadataResolver} from '../../src/metadata_resolver';
import {FILL_STYLE_FLAG, flattenStyles} from '../private_import_core'; import {FILL_STYLE_FLAG, flattenStyles} from '../private_import_core';
@ -20,13 +21,15 @@ export function main() {
var combineStyles = (styles: AnimationStylesAst): {[key: string]: string | number} => { var combineStyles = (styles: AnimationStylesAst): {[key: string]: string | number} => {
var flatStyles: {[key: string]: string | number} = {}; var flatStyles: {[key: string]: string | number} = {};
styles.styles.forEach( styles.styles.forEach(
entry => Object.keys(entry).forEach(prop => { flatStyles[prop] = entry[prop]; })); entry => StringMapWrapper.forEach(
entry, (val: any /** TODO #9100 */, prop: any /** TODO #9100 */) => {
flatStyles[prop] = val;
}));
return flatStyles; return flatStyles;
}; };
var collectKeyframeStyles = var collectKeyframeStyles = (keyframe: AnimationKeyframeAst):
(keyframe: AnimationKeyframeAst): {[key: string]: string | number} => {[key: string]: string | number} => { return combineStyles(keyframe.styles); };
combineStyles(keyframe.styles);
var collectStepStyles = (step: AnimationStepAst): Array<{[key: string]: string | number}> => { var collectStepStyles = (step: AnimationStepAst): Array<{[key: string]: string | number}> => {
var keyframes = step.keyframes; var keyframes = step.keyframes;
@ -43,17 +46,17 @@ export function main() {
inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; })); inject([CompileMetadataResolver], (res: CompileMetadataResolver) => { resolver = res; }));
var parseAnimation = (data: AnimationMetadata[]) => { var parseAnimation = (data: AnimationMetadata[]) => {
const entry = trigger('myAnimation', [transition('state1 => state2', sequence(data))]); var entry = trigger('myAnimation', [transition('state1 => state2', sequence(data))]);
const compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry); var compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry);
const parser = new AnimationParser(); return parseAnimationEntry(compiledAnimationEntry);
return parser.parseEntry(compiledAnimationEntry);
}; };
var getAnimationAstFromEntryAst = var getAnimationAstFromEntryAst =
(ast: AnimationEntryAst) => { return ast.stateTransitions[0].animation; }; (ast: AnimationEntryAst) => { return ast.stateTransitions[0].animation; };
var parseAnimationAst = (data: AnimationMetadata[]) => var parseAnimationAst = (data: AnimationMetadata[]) => {
getAnimationAstFromEntryAst(parseAnimation(data).ast); return getAnimationAstFromEntryAst(parseAnimation(data).ast);
};
var parseAnimationAndGetErrors = (data: AnimationMetadata[]) => parseAnimation(data).errors; var parseAnimationAndGetErrors = (data: AnimationMetadata[]) => parseAnimation(data).errors;

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