Compare commits

..

85 Commits
2.2.2 ... 2.1.x

Author SHA1 Message Date
7bd6cf9f0a fix: update public api master file 2016-11-07 10:22:39 -08:00
70a6c54dcf fix(router): routerLink should not prevent default on non-link elements 2016-11-03 10:29:18 -07:00
9245e9ec4f fix(router): CanDeactivate receives a wrong component
Closes #12592
2016-11-03 10:29:18 -07:00
e76f33f3cc fix(compiler): support multiple components in a view container 2016-11-03 10:29:17 -07:00
b60f03e074 refactor(compiler): remove unused constructor query support 2016-11-03 10:29:17 -07:00
4571dffbc9 refactor(compiler): make view.disposable array null if empty 2016-11-03 10:29:17 -07:00
d9c0eb7c78 refactor(compiler): remove view.rootNodes and view.projectableNodes
They are replaced by generated visitor functions `view.visitRootNodes` / `view.visitProjectableNodes`.
2016-11-03 10:29:17 -07:00
a40e883799 refactor(compiler): inline view.contentChildren 2016-11-03 10:29:17 -07:00
b225f03174 refactor(compiler): inline view.viewChildren in generated code 2016-11-03 10:29:17 -07:00
369945d4d6 refactor(compiler): remove unused subscriptions in view 2016-11-03 10:29:17 -07:00
7968539a17 fix(router): call data observers when the path changes 2016-11-03 10:29:16 -07:00
56b6df8e78 fix(compiler): dedupe NgModule declarations, …
This is important so that we don’t generate things multiple times.
2016-11-03 10:29:16 -07:00
fa4c3ab5b0 fix(compiler): Don’t throw on empty property bindings
Closes #12583
2016-11-03 10:29:16 -07:00
515befbefc style(platform-browser): clean up hammer gestures 2016-11-03 10:29:16 -07:00
4d8620cb91 refactor(playground): update gestures playground to use latest hammer.js 2016-11-03 10:29:16 -07:00
ac801898bf style: make internal members accessibility explicit 2016-11-03 10:29:16 -07:00
e107a13aba style: merge imports from the same modules 2016-11-03 10:29:16 -07:00
3655182d63 style: add missing semicolons 2016-11-03 10:29:15 -07:00
927fb0493b style: add missing copyright headers 2016-11-03 10:29:15 -07:00
3c28aeec4a chore(lint): extend linting to all modules and tools 2016-11-03 10:29:15 -07:00
dbd91473fc fix(core): improve error when multiple components match the same element
Closes #7067
2016-11-03 10:29:15 -07:00
e6a5dc96b0 chore(lint): replace enforce-copyright-header rule with the native equivalent 2016-11-03 10:29:15 -07:00
fae2316a7f chore(lint): deduplicate tslint dependency 2016-11-03 10:29:15 -07:00
9f65684214 chore(package): remove unused lodash and sorted-object 2016-11-03 10:29:14 -07:00
6249680344 chore(npm): clean up clean-shrinkwrap script 2016-11-03 10:29:14 -07:00
9f9949296a style(tests): clean up testing_public_spec (#11452) 2016-11-03 10:29:14 -07:00
07ab92f037 docs(pipes): minor fix and improvements 2016-11-03 10:29:14 -07:00
35f551da35 chore(lint): replace gulp check-task with tslint no-jasmine-focus rule
fixes #11800
2016-11-03 10:29:14 -07:00
621685be4b chore(lint): replace duplicate-module-import rule with no-duplicate-imports 2016-11-03 10:29:14 -07:00
ecf71f74aa chore(lint): add vrsource tslint rules dependency 2016-11-03 10:29:14 -07:00
a9692b6e59 refactor(facade): cleanup Intl facade 2016-11-03 10:29:13 -07:00
23402c4b68 refactor(core): removed extraneous interface from QueryList test 2016-11-03 10:22:10 -07:00
ba5c6b5e7c fix(router): rerun resolvers when url changes
Closes #12603
2016-11-03 10:21:53 -07:00
6676073a4f fix(compiler): don’t double bind functions
This fixes a performance regressions introduced by 178fb79b5c.

Also makes properties in the directive wrapper private
so that closure compiler can minify them better.
2016-11-03 10:20:17 -07:00
1aabfbb312 refactor(compiler): generate host listeners in DirectiveWrappers
Part of #11683
2016-11-03 10:20:11 -07:00
124b3dfa91 build: ensure necessary symlinks created on windows
Bash scripts create and tear down symlinks on Windows. These use the
packages.txt file as input to identify the symlink locations. The
scripts ignored the last line in packages.txt if it didn't end with a
newline. Also, one location was missing. Resolve both issues.

Closes #12422
2016-11-03 10:20:06 -07:00
6a79e6caf9 refactor(upgrade): spec cleanup 2016-11-03 10:20:01 -07:00
7275dfba15 fix(upgrade): silent bootstrap failures
fixes #12062
2016-11-03 10:19:54 -07:00
19273a89b6 chore(release): cut the 2.1.2 release 2016-10-27 20:28:13 +02:00
957192db6d docs(changelog): update changelog with changes in v2.1.2 2016-10-27 20:28:00 +02:00
4546f09b0e chore(release): cut the 2.2.0-beta.0 release 2016-10-27 20:16:38 +02:00
c7e4b86bf1 fix: remove double exports of template_ast 2016-10-27 20:15:18 +02:00
3a67339945 docs(reset): change semi-colon to colon in code example
The first code example for the reset function was invalid as it has a semi-colon instead of a colon for the last property in the json object.  Change the semi-colon to a colon.

Closes https://github.com/angular/angular/issues/12531
2016-10-27 20:14:59 +02:00
80fef43334 fix(compiler-cli): fix types 2016-10-27 20:14:51 +02:00
80efa4b26b fix(selectors): use Maps instead of objects 2016-10-27 20:14:46 +02:00
92103606e1 fix(xsrf): overwrite already set xsrf header 2016-10-27 20:14:40 +02:00
487970f6f2 refactor(compiler): move host properties into DirectiveWrapper
Part of #11683
2016-10-27 20:14:29 +02:00
89a493934f refactor(compiler): make arguments in InlineArray optional. 2016-10-27 20:14:23 +02:00
6f54ed01a8 refactor(compiler): extract createCheckBindingStmt into compiler_util
Part of #11683
2016-10-27 20:14:15 +02:00
ef99670343 refactor(compiler): minor cleanups 2016-10-27 20:14:06 +02:00
808e4cf1b6 refactor(compiler): extract expression evaluation and writing to renderer from view_compiler
This is needed to that `DirectiveWrapper`s can also use them later on.

Part of #11683
2016-10-27 20:13:59 +02:00
9cb71fc7c9 refactor(compiler): introduce ClassBuilder.
Part of #11683
2016-10-27 20:13:52 +02:00
6f082f93f5 refactor(compiler): set element attributes via one call
This makes the cost of using directives that have host attributes
smaller.

Part of #11683
2016-10-27 20:13:45 +02:00
0d7124e7cb refactor(compiler): extract BindingParser
Needed so that we can parse directive host bindings independent of templates.

Part of #11683
2016-10-27 20:13:39 +02:00
52896591e6 fix(router): preserve resolve data
Closes #12306
2016-10-27 20:13:26 +02:00
07c44a76f9 tests(router): add a test showing how to handle resovle errors 2016-10-27 20:13:11 +02:00
3b5fc56cc1 fix(router): change router not to deactivate aux routes when navigating from a componentless routes 2016-10-27 20:13:03 +02:00
2c30365496 fix(router): disallow component routes with named outlets
Closes #11208, #11082
2016-10-27 20:12:57 +02:00
8c6b376c07 fix(router): add a test to make sure canDeactivate guards are called for aux routes
Closes #11345
2016-10-27 20:12:48 +02:00
089d4533b3 fix(router): canDeactivate guards are not triggered for componentless routes
Closes #12375
2016-10-27 20:12:37 +02:00
8113a03c21 fix(CompilerCli): assert that all pipes and directives are declared by a module 2016-10-27 20:12:26 +02:00
93854e00e0 docs(common): minor corrections/improvements for NgClass (#12327) 2016-10-27 20:12:20 +02:00
fddf30cfaf doc(compiler-cli): align example with style guide (#12414)
See The Angular Style Guide, [Section 2.2 - Separate File Names with Dots and Dashes](https://angular.io/docs/ts/latest/guide/style-guide.html#!#02-02)
2016-10-27 20:12:12 +02:00
0fd859ca0b fix(compiler): walk third party modules (#12453)
fixes #11889
fixes #12428
2016-10-27 20:12:03 +02:00
f274eeb9fc refactor(i18n): extract Extractor from extract_i18n (#12417)
I put an extractor into your extract so you can extract while you
extract.

This allows integrators to call Extractor as a library. Also refactors
Extractor a bit so that callers need fewer arguments or arguments that
are at the right semantic level.

The refactoring causes no function change.
2016-10-27 20:11:51 +02:00
675e3d4244 refactor: remove most facades (#12399) 2016-10-27 20:08:06 +02:00
767841b434 docs(changelog): fix minor typo (#12429) 2016-10-27 19:48:35 +02:00
7bce0153bb fix(compiler): don't access view local variables nor pipes in host expressions (#12396)
Fixes #12004
Closes #12071
2016-10-27 19:47:51 +02:00
7a1f964201 chore(release): cut the 2.1.1 release 2016-10-20 15:36:02 -07:00
3dffbb6cf1 docs(changelog): add 2.1.1 changelog 2016-10-20 15:34:35 -07:00
79f1798b68 chore(release): cut the 2.2.0-beta.0 release and add changelog 2016-10-20 15:31:47 -07:00
dd08d421a1 fix(router): do not update primary route if only secondary outlet is given (#11797) 2016-10-20 15:31:24 -07:00
a2d4299f2c fix(router): module loader should start compiling modules when stubbedModules are set (#11742) 2016-10-20 15:31:11 -07:00
2598b59de7 cleanup(router): add a test verifying than NavigationEnd is not emitted after NavigationCancel 2016-10-20 15:31:04 -07:00
20b4617289 fix(router): fix lazy loading triggered by redirects from wildcard routes
Closes #12183
2016-10-20 15:30:50 -07:00
958bb0da04 refactor(compiler): introduce directive wrappers to generate less code
- for now only wraps the `@Input` properties and calls
  to `ngOnInit`, `ngDoCheck` and `ngOnChanges` of directives.
- also groups eval sources by NgModule.

Part of #11683
2016-10-20 15:30:27 -07:00
4ba8f1989b refactor(compiler): don’t use the OfflineCompiler in extract_i18n 2016-10-20 15:29:43 -07:00
c04b4d795a refactor(compiler): remove private exports
All of `@angular/compiler` is private, so we can export
everything we need directly.
2016-10-20 15:29:25 -07:00
f1b5ba9231 chore(ci): re-enable browserstack tests in ci 2016-10-20 15:27:39 -07:00
50b524ba1e chore(tslint.json): semicolon rule expects an array 2016-10-20 15:27:25 -07:00
680ceb7d65 refactor: remove some facades (#12335) 2016-10-20 15:25:05 -07:00
ea186d5ccd refactor(forms): remove ListWrapper facades
originally cherry-picked from 445e5922ec
2016-10-20 15:15:32 -07:00
10455044f1 chore(ci): make browserstack tests optional until they are fixed 2016-10-20 14:49:01 -07:00
31150fe6e8 feat(benchmark): add large form benchmark
This benchmark tracks the generated file size for large forms
as well as the time to create and destroy many form fields.
2016-10-20 14:48:02 -07:00
9223066123 fix(benchmarks): fix method name in targetable spec 2016-10-20 14:47:42 -07:00
670 changed files with 10755 additions and 20948 deletions

View File

@ -1,169 +1,5 @@
<a name="2.2.2"></a>
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
<a name="2.3.0-beta.0"></a>
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
### Bug Fixes
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
### Features
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
<a name="2.2.1"></a>
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
### Bug Fixes
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
<a name="2.2.0"></a>
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
### Features (summary of all features from 2.2.0-beta.0 - 2.2.0-rc.0 releases)
* **common:** support narrow forms for month and weekdays in DatePipe ([#12297](https://github.com/angular/angular/issues/12297)) ([f77ab6a](https://github.com/angular/angular/commit/f77ab6a)), closes [#12294](https://github.com/angular/angular/issues/12294)
* **core:** map 'for' attribute to 'htmlFor' property ([#10546](https://github.com/angular/angular/issues/10546)) ([634b3bb](https://github.com/angular/angular/commit/634b3bb)), closes [#7516](https://github.com/angular/angular/issues/7516)
* **core:** add the find method to QueryList ([7c16ef9](https://github.com/angular/angular/commit/7c16ef9))
* **forms:** add hasError and getError to AbstractControlDirective ([#11985](https://github.com/angular/angular/issues/11985)) ([592f40a](https://github.com/angular/angular/commit/592f40a)), closes [#7255](https://github.com/angular/angular/issues/7255)
* **forms:** add ng-pending CSS class during async validation ([#11243](https://github.com/angular/angular/issues/11243)) ([97bc971](https://github.com/angular/angular/commit/97bc971)), closes [#10336](https://github.com/angular/angular/issues/10336)
* **forms:** add emitEvent to AbstractControl methods ([#11949](https://github.com/angular/angular/issues/11949)) ([b9fc090](https://github.com/angular/angular/commit/b9fc090))
* **forms:** make 'parent' a public property of 'AbstractControl' ([#11855](https://github.com/angular/angular/issues/11855)) ([445e592](https://github.com/angular/angular/commit/445e592))
* **forms:** Validator.pattern accepts a RegExp ([#12323](https://github.com/angular/angular/issues/12323)) ([bf60418](https://github.com/angular/angular/commit/bf60418))
* **router:** add a provider making angular1/angular2 integration easier ([#12769](https://github.com/angular/angular/issues/12769)) ([6e35d13](https://github.com/angular/angular/commit/6e35d13))
* **router:** add support for custom url matchers ([7340735](https://github.com/angular/angular/commit/7340735)), closes [#12442](https://github.com/angular/angular/issues/12442) [#12772](https://github.com/angular/angular/issues/12772)
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
* **router:** add support for ng1/ng2 migration ([#12160](https://github.com/angular/angular/issues/12160)) ([8b9ab44](https://github.com/angular/angular/commit/8b9ab44))
* **upgrade:** add support for AoT compiled upgrade applications ([d6791ff](https://github.com/angular/angular/commit/d6791ff)), closes [#12239](https://github.com/angular/angular/issues/12239)
* **upgrade:** add support for `require` in UpgradeComponent ([fe1d0e2](https://github.com/angular/angular/commit/fe1d0e2))
* **upgrade:** add/improve support for lifecycle hooks in UpgradeComponent ([469010e](https://github.com/angular/angular/commit/469010e))
### Performance Improvements
* **compiler:** introduce direct rendering ([9c23884](https://github.com/angular/angular/commit/9c23884))
* **core:** dont use `DomAdapter` nor zone for regular events ([648ce59](https://github.com/angular/angular/commit/648ce59))
* **core:** use `array.push` / `array.pop` instead of `splice` if possible ([0fc11a4](https://github.com/angular/angular/commit/0fc11a4))
* **platform-browser:** cache plugin resolution in the EventManager ([73593d4](https://github.com/angular/angular/commit/73593d4)), closes [#12824](https://github.com/angular/angular/issues/12824)
* **platform-browser:** dont use `DomAdapter` any more ([d708a88](https://github.com/angular/angular/commit/d708a88))
### Bug Fixes
* **animations:** allow animations to be destroyed manually ([#12719](https://github.com/angular/angular/issues/12719)) ([fe35bc3](https://github.com/angular/angular/commit/fe35bc3)), closes [#12456](https://github.com/angular/angular/issues/12456)
* **animations:** always normalize style properties and values during compilation ([#12755](https://github.com/angular/angular/issues/12755)) ([a0e9fde](https://github.com/angular/angular/commit/a0e9fde)), closes [#11582](https://github.com/angular/angular/issues/11582) [#12481](https://github.com/angular/angular/issues/12481)
* **animations:** always trigger animations after the change detection check ([#12713](https://github.com/angular/angular/issues/12713)) ([383f23b](https://github.com/angular/angular/commit/383f23b))
* **animations:** ensure animations work with web-workers ([#12656](https://github.com/angular/angular/issues/12656)) ([19e869e](https://github.com/angular/angular/commit/19e869e))
* **animations:** ensure web-animations are caught within the Angular zone ([f80a157](https://github.com/angular/angular/commit/f80a157)), closes [#11881](https://github.com/angular/angular/issues/11881) [#11712](https://github.com/angular/angular/issues/11712) [#12355](https://github.com/angular/angular/issues/12355) [#11881](https://github.com/angular/angular/issues/11881) [#12546](https://github.com/angular/angular/issues/12546) [#12707](https://github.com/angular/angular/issues/12707) [#12774](https://github.com/angular/angular/issues/12774)
* **common:** `NgSwitch` - dont create the default case if another case matches ([#12726](https://github.com/angular/angular/issues/12726)) ([d8f23f4](https://github.com/angular/angular/commit/d8f23f4)), closes [#11297](https://github.com/angular/angular/issues/11297) [#9420](https://github.com/angular/angular/issues/9420)
* **common:** I18nSelectPipe selects other case on default ([4708b24](https://github.com/angular/angular/commit/4708b24))
* **common:** no TZ Offset added by DatePipe for dates without time ([#12380](https://github.com/angular/angular/issues/12380)) ([2aba8b0](https://github.com/angular/angular/commit/2aba8b0))
* **common:** NgClass should throw a descriptive error when CSS class is not a string ([#12662](https://github.com/angular/angular/issues/12662)) ([f3793b5](https://github.com/angular/angular/commit/f3793b5)), closes [#12586](https://github.com/angular/angular/issues/12586)
* **common:** DatePipe should handle empty string ([#12374](https://github.com/angular/angular/issues/12374)) ([3dc6177](https://github.com/angular/angular/commit/3dc6177))
* **compiler:** don't convert undefined to null literals ([#11503](https://github.com/angular/angular/issues/11503)) ([f0cdb42](https://github.com/angular/angular/commit/f0cdb42)), closes [#11493](https://github.com/angular/angular/issues/11493)
* **compiler:** generate safe access strictNullChecks compatible code ([#12800](https://github.com/angular/angular/issues/12800)) ([a965d11](https://github.com/angular/angular/commit/a965d11)), closes [#12795](https://github.com/angular/angular/issues/12795)
* **compiler:** support more than 9 interpolations ([#12710](https://github.com/angular/angular/issues/12710)) ([22c021c](https://github.com/angular/angular/commit/22c021c)), closes [#10253](https://github.com/angular/angular/issues/10253)
* **compiler:** use the other case by default in ICU messages ([55dc0e4](https://github.com/angular/angular/commit/55dc0e4))
* **compiler-cli:** suppress closure compiler suspiciousCode check in codegen ([#12666](https://github.com/angular/angular/issues/12666)) ([7103754](https://github.com/angular/angular/commit/7103754))
* **compiler-cli:** suppress two more closure compiler checks in codegen ([#12698](https://github.com/angular/angular/issues/12698)) ([77cbf7f](https://github.com/angular/angular/commit/77cbf7f))
* **core:** allow to query content of templates that are stamped out at a different place ([f2bbef3](https://github.com/angular/angular/commit/f2bbef3)), closes [#12283](https://github.com/angular/angular/issues/12283) [#12094](https://github.com/angular/angular/issues/12094)
* **core:** apply host attributes to root elements ([#12761](https://github.com/angular/angular/issues/12761)) ([ad3bf6c](https://github.com/angular/angular/commit/ad3bf6c)), closes [#12744](https://github.com/angular/angular/issues/12744)
* **core:** ensure that component views that have no bindings recurse into nested components / view containers. ([051d748](https://github.com/angular/angular/commit/051d748))
* **core:** fix pseudo-selector shimming ([#12754](https://github.com/angular/angular/issues/12754)) ([acbf1d8](https://github.com/angular/angular/commit/acbf1d8)), closes [#12730](https://github.com/angular/angular/issues/12730) [#12354](https://github.com/angular/angular/issues/12354)
* **forms:** check if registerOnValidatorChange exists on validator before trying to invoke it ([#12801](https://github.com/angular/angular/issues/12801)) ([ef88147](https://github.com/angular/angular/commit/ef88147)), closes [#12593](https://github.com/angular/angular/issues/12593)
* **forms:** getRawValue returns any instead of Object ([#12599](https://github.com/angular/angular/issues/12599)) ([09092ac](https://github.com/angular/angular/commit/09092ac))
* **http:** preserve header case when copying headers ([#12697](https://github.com/angular/angular/issues/12697)) ([121e508](https://github.com/angular/angular/commit/121e508))
* **router:** advance a route only after its children have been deactivated ([#12676](https://github.com/angular/angular/issues/12676)) ([9ddf9b3](https://github.com/angular/angular/commit/9ddf9b3)), closes [#11715](https://github.com/angular/angular/issues/11715)
* **router:** avoid router initialization for non root components ([2a4bf9a](https://github.com/angular/angular/commit/2a4bf9a)), closes [#12338](https://github.com/angular/angular/issues/12338) [#12814](https://github.com/angular/angular/issues/12814)
* **router:** check if windows.console exists before using it ([#12348](https://github.com/angular/angular/issues/12348)) ([7886561](https://github.com/angular/angular/commit/7886561))
* **router:** correctly export concatMap operator in es5 ([#12430](https://github.com/angular/angular/issues/12430)) ([e25baa0](https://github.com/angular/angular/commit/e25baa0))
* **router:** do not require the creation of empty-path routes when no url left ([2c11093](https://github.com/angular/angular/commit/2c11093)), closes [#12133](https://github.com/angular/angular/issues/12133)
* **router:** ignore null or undefined query parameters ([#12333](https://github.com/angular/angular/issues/12333)) ([3052fb2](https://github.com/angular/angular/commit/3052fb2))
* **router:** incorrect injector is used when instantiating components loaded lazily ([#12817](https://github.com/angular/angular/issues/12817)) ([52be848](https://github.com/angular/angular/commit/52be848))
* **router:** resolve guard observables on the first emit ([#10412](https://github.com/angular/angular/issues/10412)) ([2e78b76](https://github.com/angular/angular/commit/2e78b76))
* **router:** Route.isActive also compares query params ([#12321](https://github.com/angular/angular/issues/12321)) ([785b7b6](https://github.com/angular/angular/commit/785b7b6))
* **router:** router should not swallow "unhandled" errors ([e5a753e](https://github.com/angular/angular/commit/e5a753e)), closes [#12802](https://github.com/angular/angular/issues/12802)
* **router:** throw an error when encounter undefined route ([#12389](https://github.com/angular/angular/issues/12389)) ([77dc1ab](https://github.com/angular/angular/commit/77dc1ab))
* **platform-browser:** enableDebugTools should create AngularTools by merging into context.ng ([#12003](https://github.com/angular/angular/issues/12003)) ([b2cf379](https://github.com/angular/angular/commit/b2cf379)), closes [#12002](https://github.com/angular/angular/issues/12002)
* **platform-browser:** provide the ability to register global hammer.js events ([768cddb](https://github.com/angular/angular/commit/768cddb)), closes [#12797](https://github.com/angular/angular/issues/12797)
* **tsc-wrapped:** harden collector against invalid asts ([#12793](https://github.com/angular/angular/issues/12793)) ([69f87ca](https://github.com/angular/angular/commit/69f87ca))
<a name="2.2.0-rc.0"></a>
# [2.2.0-rc.0](https://github.com/angular/angular/compare/2.2.0-beta.1...2.2.0-rc.0) (2016-11-02)
### Bug Fixes
* **compiler:** dedupe NgModule declarations, … ([a178bc6](https://github.com/angular/angular/commit/a178bc6))
* **compiler:** dont double bind functions ([e391cac](https://github.com/angular/angular/commit/e391cac))
* **compiler:** Dont throw on empty property bindings ([642c1db](https://github.com/angular/angular/commit/642c1db)), closes [#12583](https://github.com/angular/angular/issues/12583)
* **compiler:** support multiple components in a view container ([6fda972](https://github.com/angular/angular/commit/6fda972))
* **core:** improve error when multiple components match the same element ([e9fd864](https://github.com/angular/angular/commit/e9fd864)), closes [#7067](https://github.com/angular/angular/issues/7067)
* **router:** call data observers when the path changes ([1de04b2](https://github.com/angular/angular/commit/1de04b2))
* **router:** CanDeactivate receives a wrong component ([830a780](https://github.com/angular/angular/commit/830a780)), closes [#12592](https://github.com/angular/angular/issues/12592)
* **router:** rerun resolvers when url changes ([fe47e6b](https://github.com/angular/angular/commit/fe47e6b)), closes [#12603](https://github.com/angular/angular/issues/12603)
* **router:** reset URL to the stable state when a navigation gets canceled ([d509ee0](https://github.com/angular/angular/commit/d509ee0)), closes [#10321](https://github.com/angular/angular/issues/10321)
* **router:** routerLink should not prevent default on non-link elements ([8e221b8](https://github.com/angular/angular/commit/8e221b8))
* **router:** run navigations serially ([091c390](https://github.com/angular/angular/commit/091c390)), closes [#11754](https://github.com/angular/angular/issues/11754)
* **upgrade:** silent bootstrap failures ([fa93fd6](https://github.com/angular/angular/commit/fa93fd6)), closes [#12062](https://github.com/angular/angular/issues/12062)
### Features
* **core:** add the find method to QueryList ([7c16ef9](https://github.com/angular/angular/commit/7c16ef9))
<a name="2.1.2"></a> <a name="2.1.2"></a>
# [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27) ## [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27)
### Bug Fixes ### Bug Fixes
@ -195,7 +31,7 @@ Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf)) * **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
### BREAKING CHANGES (only for beta version users) ### BREAKING CHANGES
* upgrade: Four newly added APIs in 2.2.0-beta: * upgrade: Four newly added APIs in 2.2.0-beta:
downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule are no longer exported by @angular/upgrade. downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule are no longer exported by @angular/upgrade.

View File

@ -17,7 +17,7 @@ Help us keep Angular open and inclusive. Please read and follow our [Code of Con
## <a name="question"></a> Got a Question or Problem? ## <a name="question"></a> Got a Question or Problem?
Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](https://stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`. Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](stackoverflow.com/questions/tagged/angular) where the questions should be tagged with tag `angular`.
StackOverflow is a much better place to ask questions since: StackOverflow is a much better place to ask questions since:

View File

@ -14,16 +14,12 @@ PACKAGES=(core
platform-webworker platform-webworker
platform-webworker-dynamic platform-webworker-dynamic
http http
upgrade
router router
upgrade
compiler-cli compiler-cli
benchpress) benchpress)
BUILD_ALL=true BUILD_ALL=true
BUNDLE=true BUNDLE=true
VERSION_PREFIX=$(node -p "require('./package.json').version")
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
REMOVE_BENCHPRESS=false
for ARG in "$@"; do for ARG in "$@"; do
case "$ARG" in case "$ARG" in
@ -35,10 +31,6 @@ for ARG in "$@"; do
--bundle=*) --bundle=*)
BUNDLE=( "${ARG#--bundle=}" ) BUNDLE=( "${ARG#--bundle=}" )
;; ;;
--publish)
VERSION_SUFFIX=""
REMOVE_BENCHPRESS=true
;;
*) *)
echo "Unknown option $ARG." echo "Unknown option $ARG."
exit 1 exit 1
@ -46,10 +38,6 @@ for ARG in "$@"; do
esac esac
done done
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main" TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
@ -75,7 +63,7 @@ if [[ ${BUILD_ALL} == true ]]; then
ln -s ../../../../node_modules/zone.js/dist/zone.js . ln -s ../../../../node_modules/zone.js/dist/zone.js .
ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js . ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js . ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js . ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js . ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs . ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js . ln -s ../../../../node_modules/angular/angular.js .
@ -91,6 +79,7 @@ if [[ ${BUILD_ALL} == true ]]; then
ln -s ../../../../node_modules/zone.js/dist/zone.js . ln -s ../../../../node_modules/zone.js/dist/zone.js .
ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js . ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js . ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js . ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs . ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js . ln -s ../../../../node_modules/angular/angular.js .
@ -113,29 +102,14 @@ do
DESTDIR=${PWD}/dist/packages-dist/${PACKAGE} DESTDIR=${PWD}/dist/packages-dist/${PACKAGE}
UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js
UMD_TESTING_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-testing.umd.js UMD_TESTING_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-testing.umd.js
UMD_STATIC_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.js
UMD_UPGRADE_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.js
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
if [[ ${PACKAGE} != router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
fi
if [[ ${PACKAGE} == router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
fi
rm -rf ${DESTDIR} rm -rf ${DESTDIR}
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json =====" echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-build.json ====="
$TSC -p ${SRCDIR}/tsconfig-build.json $TSC -p ${SRCDIR}/tsconfig-build.json
if [[ -e ${SRCDIR}/tsconfig-upgrade.json ]]; then
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-upgrade.json ====="
$TSC -p ${SRCDIR}/tsconfig-upgrade.json
fi
cp ${SRCDIR}/package.json ${DESTDIR}/ cp ${SRCDIR}/package.json ${DESTDIR}/
cp ${PWD}/modules/@angular/README.md ${DESTDIR}/ cp ${PWD}/modules/@angular/README.md ${DESTDIR}/
@ -184,49 +158,9 @@ do
cat ${UMD_TESTING_ES5_PATH} >> ${UMD_TESTING_ES5_PATH}.tmp cat ${UMD_TESTING_ES5_PATH} >> ${UMD_TESTING_ES5_PATH}.tmp
mv ${UMD_TESTING_ES5_PATH}.tmp ${UMD_TESTING_ES5_PATH} mv ${UMD_TESTING_ES5_PATH}.tmp ${UMD_TESTING_ES5_PATH}
fi fi
if [[ -e rollup-static.config.js ]]; then
echo "====== Rollup ${PACKAGE} static"
../../../node_modules/.bin/rollup -c rollup-static.config.js
# create dir because it doesn't exist yet, we should move the src code here and remove this line
mkdir ${DESTDIR}/static
echo "{\"main\": \"../bundles/${PACKAGE}-static.umd.js\"}" > ${DESTDIR}/static/package.json
cat ${LICENSE_BANNER} > ${UMD_STATIC_ES5_PATH}.tmp
cat ${UMD_STATIC_ES5_PATH} >> ${UMD_STATIC_ES5_PATH}.tmp
mv ${UMD_STATIC_ES5_PATH}.tmp ${UMD_STATIC_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_STATIC_ES5_MIN_PATH} ${UMD_STATIC_ES5_PATH}
fi
if [[ -e rollup-upgrade.config.js ]]; then
echo "====== Rollup ${PACKAGE} upgrade"
../../../node_modules/.bin/rollup -c rollup-upgrade.config.js
# create dir because it doesn't exist yet, we should move the src code here and remove this line
mkdir ${DESTDIR}/upgrade
echo "{\"main\": \"../bundles/${PACKAGE}-upgrade.umd.js\"}" > ${DESTDIR}/upgrade/package.json
cat ${LICENSE_BANNER} > ${UMD_UPGRADE_ES5_PATH}.tmp
cat ${UMD_UPGRADE_ES5_PATH} >> ${UMD_UPGRADE_ES5_PATH}.tmp
mv ${UMD_UPGRADE_ES5_PATH}.tmp ${UMD_UPGRADE_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi
) 2>&1 | grep -v "as external dependency" ) 2>&1 | grep -v "as external dependency"
fi
( fi
echo "====== VERSION: Updating version references"
cd ${DESTDIR}
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
)
done done
echo ""
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
./modules/@angular/examples/build.sh ./modules/@angular/examples/build.sh
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
echo ""
echo "==== Removing benchpress from publication"
rm -r dist/packages-dist/benchpress
fi

View File

@ -4,7 +4,7 @@ machine:
dependencies: dependencies:
pre: pre:
- npm install -g npm@3.6.0 - npm install -g npm
test: test:
override: override:

View File

@ -58,7 +58,6 @@ const entrypoints = [
//'dist/packages-dist/compiler/index.d.ts', //'dist/packages-dist/compiler/index.d.ts',
//'dist/packages-dist/compiler/testing.d.ts', //'dist/packages-dist/compiler/testing.d.ts',
'dist/packages-dist/upgrade/index.d.ts', 'dist/packages-dist/upgrade/index.d.ts',
'dist/packages-dist/upgrade/static.d.ts',
'dist/packages-dist/platform-browser/index.d.ts', 'dist/packages-dist/platform-browser/index.d.ts',
'dist/packages-dist/platform-browser/testing/index.d.ts', 'dist/packages-dist/platform-browser/testing/index.d.ts',
'dist/packages-dist/platform-browser-dynamic/index.d.ts', 'dist/packages-dist/platform-browser-dynamic/index.d.ts',

View File

@ -23,7 +23,7 @@ module.exports = function(config) {
'node_modules/core-js/client/core.js', 'node_modules/core-js/client/core.js',
// include Angular v1 for upgrade module testing // include Angular v1 for upgrade module testing
'node_modules/angular/angular.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/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js', 'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',
@ -50,7 +50,6 @@ module.exports = function(config) {
'dist/all/@angular/**/e2e_test/**', 'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/router/**', 'dist/all/@angular/router/**',
'dist/all/@angular/compiler-cli/**', 'dist/all/@angular/compiler-cli/**',
'dist/all/@angular/compiler/test/aot/**',
'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',
@ -91,17 +90,17 @@ module.exports = function(config) {
project: 'Angular2', project: 'Angular2',
startTunnel: false, startTunnel: false,
retryLimit: 3, retryLimit: 3,
timeout: 1800, timeout: 600,
pollingTimeout: 10000, pollingTimeout: 10000,
}, },
browsers: ['Chrome'], browsers: ['Chrome'],
port: 9876, port: 9876,
captureTimeout: 180000, captureTimeout: 60000,
browserDisconnectTimeout: 180000, browserDisconnectTimeout: 60000,
browserDisconnectTolerance: 3, browserDisconnectTolerance: 3,
browserNoActivityTimeout: 300000, browserNoActivityTimeout: 60000,
}); });
if (process.env.TRAVIS) { if (process.env.TRAVIS) {

View File

@ -10,7 +10,7 @@ declare var exportFunction: any;
declare var unsafeWindow: any; declare var unsafeWindow: any;
exportFunction(function() { exportFunction(function() {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('startProfiler', curTime); (<any>self).port.emit('startProfiler', curTime);
}, unsafeWindow, {defineAs: 'startProfiler'}); }, unsafeWindow, {defineAs: 'startProfiler'});
@ -28,11 +28,11 @@ exportFunction(function() {
}, unsafeWindow, {defineAs: 'forceGC'}); }, unsafeWindow, {defineAs: 'forceGC'});
exportFunction(function(name: string) { exportFunction(function(name: string) {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('markStart', name, curTime); (<any>self).port.emit('markStart', name, curTime);
}, unsafeWindow, {defineAs: 'markStart'}); }, unsafeWindow, {defineAs: 'markStart'});
exportFunction(function(name: string) { exportFunction(function(name: string) {
const curTime = unsafeWindow.performance.now(); var curTime = unsafeWindow.performance.now();
(<any>self).port.emit('markEnd', name, curTime); (<any>self).port.emit('markEnd', name, curTime);
}, unsafeWindow, {defineAs: 'markEnd'}); }, unsafeWindow, {defineAs: 'markEnd'});

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
*/ */
const {Cc, Ci, Cu} = require('chrome'); var {Cc, Ci, Cu} = require('chrome');
const os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService); var os = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const ParserUtil = require('./parser_util'); var ParserUtil = require('./parser_util');
class Profiler { class Profiler {
private _profiler: any; private _profiler: any;
@ -26,8 +26,8 @@ class Profiler {
stop() { this._profiler.StopProfiler(); } stop() { this._profiler.StopProfiler(); }
getProfilePerfEvents() { getProfilePerfEvents() {
const profileData = this._profiler.getProfileData(); var profileData = this._profiler.getProfileData();
let perfEvents = ParserUtil.convertPerfProfileToEvents(profileData); var perfEvents = ParserUtil.convertPerfProfileToEvents(profileData);
perfEvents = this._mergeMarkerEvents(perfEvents); perfEvents = this._mergeMarkerEvents(perfEvents);
perfEvents.sort(function(event1: any, event2: any) { perfEvents.sort(function(event1: any, event2: any) {
return event1.ts - event2.ts; return event1.ts - event2.ts;
@ -55,9 +55,9 @@ function forceGC() {
os.notifyObservers(null, 'child-gc-request', null); os.notifyObservers(null, 'child-gc-request', null);
}; };
const mod = require('sdk/page-mod'); var mod = require('sdk/page-mod');
const data = require('sdk/self').data; var data = require('sdk/self').data;
const profiler = new Profiler(); var profiler = new Profiler();
mod.PageMod({ mod.PageMod({
include: ['*'], include: ['*'],
contentScriptFile: data.url('installed_script.js'), contentScriptFile: data.url('installed_script.js'),

View File

@ -12,11 +12,11 @@
* within the perf profile. * within the perf profile.
*/ */
export function convertPerfProfileToEvents(perfProfile: any): any[] { export function convertPerfProfileToEvents(perfProfile: any): any[] {
const inProgressEvents = new Map(); // map from event name to start time var inProgressEvents = new Map(); // map from event name to start time
const finishedEvents: {[key: string]: any}[] = []; // Event[] finished events var finishedEvents: {[key: string]: any}[] = []; // Event[] finished events
const addFinishedEvent = function(eventName: string, startTime: number, endTime: number) { var addFinishedEvent = function(eventName: string, startTime: number, endTime: number) {
const categorizedEventName = categorizeEvent(eventName); var categorizedEventName = categorizeEvent(eventName);
let args: {[key: string]: any} = undefined; var args: {[key: string]: any} = undefined;
if (categorizedEventName == 'gc') { if (categorizedEventName == 'gc') {
// TODO: We cannot measure heap size at the moment // TODO: We cannot measure heap size at the moment
args = {usedHeapSize: 0}; args = {usedHeapSize: 0};
@ -31,17 +31,17 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
} }
}; };
const samples = perfProfile.threads[0].samples; var samples = perfProfile.threads[0].samples;
// In perf profile, firefox samples all the frames in set time intervals. Here // In perf profile, firefox samples all the frames in set time intervals. Here
// we go through all the samples and construct the start and end time for each // we go through all the samples and construct the start and end time for each
// event. // event.
for (let i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
const sample = samples[i]; var sample = samples[i];
const sampleTime = sample.time; var sampleTime = sample.time;
// Add all the frames into a set so it's easier/faster to find the set // Add all the frames into a set so it's easier/faster to find the set
// differences // differences
const sampleFrames = new Set(); var sampleFrames = new Set();
sample.frames.forEach(function(frame: {[key: string]: any}) { sample.frames.forEach(function(frame: {[key: string]: any}) {
sampleFrames.add(frame['location']); sampleFrames.add(frame['location']);
}); });
@ -49,7 +49,7 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
// If an event is in the inProgressEvents map, but not in the current sample, // If an event is in the inProgressEvents map, but not in the current sample,
// then it must have just finished. We add this event to the finishedEvents // then it must have just finished. We add this event to the finishedEvents
// array and remove it from the inProgressEvents map. // array and remove it from the inProgressEvents map.
const previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time); var previousSampleTime = (i == 0 ? /* not used */ -1 : samples[i - 1].time);
inProgressEvents.forEach(function(startTime, eventName) { inProgressEvents.forEach(function(startTime, eventName) {
if (!(sampleFrames.has(eventName))) { if (!(sampleFrames.has(eventName))) {
addFinishedEvent(eventName, startTime, previousSampleTime); addFinishedEvent(eventName, startTime, previousSampleTime);
@ -69,7 +69,7 @@ export function convertPerfProfileToEvents(perfProfile: any): any[] {
// If anything is still in progress, we need to included it as a finished event // If anything is still in progress, we need to included it as a finished event
// since recording ended. // since recording ended.
const lastSampleTime = samples[samples.length - 1].time; var lastSampleTime = samples[samples.length - 1].time;
inProgressEvents.forEach(function(startTime, eventName) { inProgressEvents.forEach(function(startTime, eventName) {
addFinishedEvent(eventName, startTime, lastSampleTime); addFinishedEvent(eventName, startTime, lastSampleTime);
}); });

View File

@ -6,15 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
const q = require('q'); var q = require('q');
const FirefoxProfile = require('firefox-profile'); var FirefoxProfile = require('firefox-profile');
const jpm = require('jpm/lib/xpi'); var jpm = require('jpm/lib/xpi');
const pathUtil = require('path'); var pathUtil = require('path');
const PERF_ADDON_PACKAGE_JSON_DIR = '..'; var PERF_ADDON_PACKAGE_JSON_DIR = '..';
exports.getAbsolutePath = function(path: string) { exports.getAbsolutePath = function(path: string) {
const normalizedPath = pathUtil.normalize(path); var normalizedPath = pathUtil.normalize(path);
if (pathUtil.resolve(normalizedPath) == normalizedPath) { if (pathUtil.resolve(normalizedPath) == normalizedPath) {
// Already absolute path // Already absolute path
return normalizedPath; return normalizedPath;
@ -24,12 +24,12 @@ exports.getAbsolutePath = function(path: string) {
}; };
exports.getFirefoxProfile = function(extensionPath: string) { exports.getFirefoxProfile = function(extensionPath: string) {
const deferred = q.defer(); var deferred = q.defer();
const firefoxProfile = new FirefoxProfile(); var firefoxProfile = new FirefoxProfile();
firefoxProfile.addExtensions([extensionPath], () => { firefoxProfile.addExtensions([extensionPath], () => {
firefoxProfile.encoded((encodedProfile: any) => { firefoxProfile.encoded((encodedProfile: any) => {
const multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}]; var multiCapabilities = [{browserName: 'firefox', firefox_profile: encodedProfile}];
deferred.resolve(multiCapabilities); deferred.resolve(multiCapabilities);
}); });
}); });
@ -38,10 +38,10 @@ exports.getFirefoxProfile = function(extensionPath: string) {
}; };
exports.getFirefoxProfileWithExtension = function() { exports.getFirefoxProfileWithExtension = function() {
const absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR); var absPackageJsonDir = pathUtil.join(__dirname, PERF_ADDON_PACKAGE_JSON_DIR);
const packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json')); var packageJson = require(pathUtil.join(absPackageJsonDir, 'package.json'));
const savedCwd = process.cwd(); var savedCwd = process.cwd();
process.chdir(absPackageJsonDir); process.chdir(absPackageJsonDir);
return jpm(packageJson).then((xpiPath: string) => { return jpm(packageJson).then((xpiPath: string) => {

View File

@ -55,9 +55,9 @@ export class MultiMetric extends Metric {
} }
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} { function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
const result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); }); maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); });
return result; return result;
} }
const _CHILDREN = new OpaqueToken('MultiMetric.children'); var _CHILDREN = new OpaqueToken('MultiMetric.children');

View File

@ -56,7 +56,7 @@ export class PerflogMetric extends Metric {
} }
describe(): {[key: string]: string} { describe(): {[key: string]: string} {
const res: {[key: string]: any} = { var res: {[key: string]: any} = {
'scriptTime': 'script execution time in ms, including gc and render', 'scriptTime': 'script execution time in ms, including gc and render',
'pureScriptTime': 'script execution time in ms, without gc nor render' 'pureScriptTime': 'script execution time in ms, without gc nor render'
}; };
@ -80,7 +80,7 @@ export class PerflogMetric extends Metric {
} }
if (this._captureFrames) { if (this._captureFrames) {
if (!this._perfLogFeatures.frameCapture) { if (!this._perfLogFeatures.frameCapture) {
const warningMsg = 'WARNING: Metric requested, but not supported by driver'; var warningMsg = 'WARNING: Metric requested, but not supported by driver';
// using dot syntax for metric name to keep them grouped together in console reporter // using dot syntax for metric name to keep them grouped together in console reporter
res['frameTime.mean'] = warningMsg; res['frameTime.mean'] = warningMsg;
res['frameTime.worst'] = warningMsg; res['frameTime.worst'] = warningMsg;
@ -93,14 +93,14 @@ export class PerflogMetric extends Metric {
res['frameTime.smooth'] = 'percentage of frames that hit 60fps'; res['frameTime.smooth'] = 'percentage of frames that hit 60fps';
} }
} }
for (const name in this._microMetrics) { for (let name in this._microMetrics) {
res[name] = this._microMetrics[name]; res[name] = this._microMetrics[name];
} }
return res; return res;
} }
beginMeasure(): Promise<any> { beginMeasure(): Promise<any> {
let resultPromise = Promise.resolve(null); var resultPromise = Promise.resolve(null);
if (this._forceGc) { if (this._forceGc) {
resultPromise = resultPromise.then((_) => this._driverExtension.gc()); resultPromise = resultPromise.then((_) => this._driverExtension.gc());
} }
@ -119,7 +119,7 @@ export class PerflogMetric extends Metric {
private _endPlainMeasureAndMeasureForceGc(restartMeasure: boolean) { private _endPlainMeasureAndMeasureForceGc(restartMeasure: boolean) {
return this._endMeasure(true).then((measureValues) => { return this._endMeasure(true).then((measureValues) => {
// disable frame capture for measurements during forced gc // disable frame capture for measurements during forced gc
const originalFrameCaptureValue = this._captureFrames; var originalFrameCaptureValue = this._captureFrames;
this._captureFrames = false; this._captureFrames = false;
return this._driverExtension.gc() return this._driverExtension.gc()
.then((_) => this._endMeasure(restartMeasure)) .then((_) => this._endMeasure(restartMeasure))
@ -137,8 +137,8 @@ export class PerflogMetric extends Metric {
} }
private _endMeasure(restart: boolean): Promise<{[key: string]: number}> { private _endMeasure(restart: boolean): Promise<{[key: string]: number}> {
const markName = this._markName(this._measureCount - 1); var markName = this._markName(this._measureCount - 1);
const nextMarkName = restart ? this._markName(this._measureCount++) : null; var nextMarkName = restart ? this._markName(this._measureCount++) : null;
return this._driverExtension.timeEnd(markName, nextMarkName) return this._driverExtension.timeEnd(markName, nextMarkName)
.then((_) => this._readUntilEndMark(markName)); .then((_) => this._readUntilEndMark(markName));
} }
@ -150,26 +150,26 @@ export class PerflogMetric extends Metric {
} }
return this._driverExtension.readPerfLog().then((events) => { return this._driverExtension.readPerfLog().then((events) => {
this._addEvents(events); this._addEvents(events);
const result = this._aggregateEvents(this._remainingEvents, markName); var result = this._aggregateEvents(this._remainingEvents, markName);
if (result) { if (result) {
this._remainingEvents = events; this._remainingEvents = events;
return result; return result;
} }
let resolve: (result: any) => void; var resolve: (result: any) => void;
const promise = new Promise(res => { resolve = res; }); var promise = new Promise(res => { resolve = res; });
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100); this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
return promise; return promise;
}); });
} }
private _addEvents(events: PerfLogEvent[]) { private _addEvents(events: PerfLogEvent[]) {
let needSort = false; var needSort = false;
events.forEach(event => { events.forEach(event => {
if (event['ph'] === 'X') { if (event['ph'] === 'X') {
needSort = true; needSort = true;
const startEvent: PerfLogEvent = {}; var startEvent: PerfLogEvent = {};
const endEvent: PerfLogEvent = {}; var endEvent: PerfLogEvent = {};
for (const prop in event) { for (let prop in event) {
startEvent[prop] = event[prop]; startEvent[prop] = event[prop];
endEvent[prop] = event[prop]; endEvent[prop] = event[prop];
} }
@ -185,14 +185,14 @@ export class PerflogMetric extends Metric {
if (needSort) { if (needSort) {
// Need to sort because of the ph==='X' events // Need to sort because of the ph==='X' events
this._remainingEvents.sort((a, b) => { this._remainingEvents.sort((a, b) => {
const diff = a['ts'] - b['ts']; var diff = a['ts'] - b['ts'];
return diff > 0 ? 1 : diff < 0 ? -1 : 0; return diff > 0 ? 1 : diff < 0 ? -1 : 0;
}); });
} }
} }
private _aggregateEvents(events: PerfLogEvent[], markName: string): {[key: string]: number} { private _aggregateEvents(events: PerfLogEvent[], markName: string): {[key: string]: number} {
const result: {[key: string]: number} = {'scriptTime': 0, 'pureScriptTime': 0}; var result: {[key: string]: number} = {'scriptTime': 0, 'pureScriptTime': 0};
if (this._perfLogFeatures.gc) { if (this._perfLogFeatures.gc) {
result['gcTime'] = 0; result['gcTime'] = 0;
result['majorGcTime'] = 0; result['majorGcTime'] = 0;
@ -207,7 +207,7 @@ export class PerflogMetric extends Metric {
result['frameTime.worst'] = 0; result['frameTime.worst'] = 0;
result['frameTime.smooth'] = 0; result['frameTime.smooth'] = 0;
} }
for (const name in this._microMetrics) { for (let name in this._microMetrics) {
result[name] = 0; result[name] = 0;
} }
if (this._receivedData) { if (this._receivedData) {
@ -217,11 +217,11 @@ export class PerflogMetric extends Metric {
result['requestCount'] = 0; result['requestCount'] = 0;
} }
let markStartEvent: PerfLogEvent = null; var markStartEvent: PerfLogEvent = null;
let markEndEvent: PerfLogEvent = null; var markEndEvent: PerfLogEvent = null;
events.forEach((event) => { events.forEach((event) => {
const ph = event['ph']; var ph = event['ph'];
const name = event['name']; var name = event['name'];
if (ph === 'B' && name === markName) { if (ph === 'B' && name === markName) {
markStartEvent = event; markStartEvent = event;
} else if (ph === 'I' && name === 'navigationStart') { } else if (ph === 'I' && name === 'navigationStart') {
@ -237,23 +237,23 @@ export class PerflogMetric extends Metric {
return null; return null;
} }
let gcTimeInScript = 0; var gcTimeInScript = 0;
let renderTimeInScript = 0; var renderTimeInScript = 0;
const frameTimestamps: number[] = []; var frameTimestamps: number[] = [];
const frameTimes: number[] = []; var frameTimes: number[] = [];
let frameCaptureStartEvent: PerfLogEvent = null; var frameCaptureStartEvent: PerfLogEvent = null;
let frameCaptureEndEvent: PerfLogEvent = null; var frameCaptureEndEvent: PerfLogEvent = null;
const intervalStarts: {[key: string]: PerfLogEvent} = {}; var intervalStarts: {[key: string]: PerfLogEvent} = {};
const intervalStartCount: {[key: string]: number} = {}; var intervalStartCount: {[key: string]: number} = {};
let inMeasureRange = false; var inMeasureRange = false;
events.forEach((event) => { events.forEach((event) => {
const ph = event['ph']; var ph = event['ph'];
let name = event['name']; var name = event['name'];
let microIterations = 1; var microIterations = 1;
const microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX); var microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
if (microIterationsMatch) { if (microIterationsMatch) {
name = microIterationsMatch[1]; name = microIterationsMatch[1];
microIterations = parseInt(microIterationsMatch[2], 10); microIterations = parseInt(microIterationsMatch[2], 10);
@ -307,15 +307,15 @@ export class PerflogMetric extends Metric {
} else if ((ph === 'E') && intervalStarts[name]) { } else if ((ph === 'E') && intervalStarts[name]) {
intervalStartCount[name]--; intervalStartCount[name]--;
if (intervalStartCount[name] === 0) { if (intervalStartCount[name] === 0) {
const startEvent = intervalStarts[name]; var startEvent = intervalStarts[name];
const duration = (event['ts'] - startEvent['ts']); var duration = (event['ts'] - startEvent['ts']);
intervalStarts[name] = null; intervalStarts[name] = null;
if (name === 'gc') { if (name === 'gc') {
result['gcTime'] += duration; result['gcTime'] += duration;
const amount = var amount =
(startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000; (startEvent['args']['usedHeapSize'] - event['args']['usedHeapSize']) / 1000;
result['gcAmount'] += amount; result['gcAmount'] += amount;
const majorGc = event['args']['majorGc']; var majorGc = event['args']['majorGc'];
if (majorGc && majorGc) { if (majorGc && majorGc) {
result['majorGcTime'] += duration; result['majorGcTime'] += duration;
} }
@ -351,7 +351,7 @@ export class PerflogMetric extends Metric {
private _addFrameMetrics(result: {[key: string]: number}, frameTimes: any[]) { private _addFrameMetrics(result: {[key: string]: number}, frameTimes: any[]) {
result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length; result['frameTime.mean'] = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
const firstFrame = frameTimes[0]; var firstFrame = frameTimes[0];
result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame); result['frameTime.worst'] = frameTimes.reduce((a, b) => a > b ? a : b, firstFrame);
result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame); result['frameTime.best'] = frameTimes.reduce((a, b) => a < b ? a : b, firstFrame);
result['frameTime.smooth'] = result['frameTime.smooth'] =
@ -361,11 +361,11 @@ export class PerflogMetric extends Metric {
private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; } private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; }
} }
const _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/; var _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/;
const _MAX_RETRY_COUNT = 20; var _MAX_RETRY_COUNT = 20;
const _MARK_NAME_PREFIX = 'benchpress'; var _MARK_NAME_PREFIX = 'benchpress';
const _MARK_NAME_FRAME_CAPUTRE = 'frameCapture'; var _MARK_NAME_FRAME_CAPUTRE = 'frameCapture';
// using 17ms as a somewhat looser threshold, instead of 16.6666ms // using 17ms as a somewhat looser threshold, instead of 16.6666ms
const _FRAME_TIME_SMOOTH_THRESHOLD = 17; var _FRAME_TIME_SMOOTH_THRESHOLD = 17;

View File

@ -33,12 +33,12 @@ export class UserMetric extends Metric {
endMeasure(restart: boolean): Promise<{[key: string]: any}> { endMeasure(restart: boolean): Promise<{[key: string]: any}> {
let resolve: (result: any) => void; let resolve: (result: any) => void;
let reject: (error: any) => void; let reject: (error: any) => void;
const promise = new Promise((res, rej) => { let promise = new Promise((res, rej) => {
resolve = res; resolve = res;
reject = rej; reject = rej;
}); });
const adapter = this._wdAdapter; let adapter = this._wdAdapter;
const names = Object.keys(this._userMetrics); let names = Object.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}`)))
@ -46,7 +46,7 @@ export class UserMetric extends Metric {
if (values.every(v => typeof v === 'number')) { if (values.every(v => typeof v === 'number')) {
Promise.all(names.map(name => adapter.executeScript(`delete window.${name}`))) Promise.all(names.map(name => adapter.executeScript(`delete window.${name}`)))
.then((_: any[]) => { .then((_: any[]) => {
const map: {[k: string]: any} = {}; let map: {[k: string]: any} = {};
for (let i = 0, n = names.length; i < n; i++) { for (let i = 0, n = names.length; i < n; i++) {
map[names[i]] = values[i]; map[names[i]] = values[i];
} }

View File

@ -28,8 +28,8 @@ export class ConsoleReporter extends Reporter {
]; ];
private static _lpad(value: string, columnWidth: number, fill = ' ') { private static _lpad(value: string, columnWidth: number, fill = ' ') {
let result = ''; var result = '';
for (let i = 0; i < columnWidth - value.length; i++) { for (var i = 0; i < columnWidth - value.length; i++) {
result += fill; result += fill;
} }
return result + value; return result + value;
@ -49,7 +49,7 @@ export class ConsoleReporter extends Reporter {
private _printDescription(sampleDescription: SampleDescription) { private _printDescription(sampleDescription: SampleDescription) {
this._print(`BENCHMARK ${sampleDescription.id}`); this._print(`BENCHMARK ${sampleDescription.id}`);
this._print('Description:'); this._print('Description:');
const props = sortedProps(sampleDescription.description); var props = sortedProps(sampleDescription.description);
props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); }); props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); });
this._print('Metrics:'); this._print('Metrics:');
this._metricNames.forEach((metricName) => { this._metricNames.forEach((metricName) => {
@ -61,8 +61,8 @@ export class ConsoleReporter extends Reporter {
} }
reportMeasureValues(measureValues: MeasureValues): Promise<any> { reportMeasureValues(measureValues: MeasureValues): Promise<any> {
const formattedValues = this._metricNames.map(metricName => { var formattedValues = this._metricNames.map(metricName => {
const value = measureValues.values[metricName]; var value = measureValues.values[metricName];
return formatNum(value); return formatNum(value);
}); });
this._printStringRow(formattedValues); this._printStringRow(formattedValues);

View File

@ -38,7 +38,7 @@ export class JsonFileReporter extends Reporter {
sortedProps(this._description.metrics).forEach((metricName) => { sortedProps(this._description.metrics).forEach((metricName) => {
stats[metricName] = formatStats(validSample, metricName); stats[metricName] = formatStats(validSample, metricName);
}); });
const content = JSON.stringify( var content = JSON.stringify(
{ {
'description': this._description, 'description': this._description,
'stats': stats, 'stats': stats,
@ -46,7 +46,7 @@ export class JsonFileReporter extends Reporter {
'validSample': validSample, 'validSample': validSample,
}, },
null, 2); null, 2);
const filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`; var filePath = `${this._path}/${this._description.id}_${this._now().getTime()}.json`;
return this._writeFile(filePath, content); return this._writeFile(filePath, content);
} }
} }

View File

@ -39,4 +39,4 @@ export class MultiReporter extends Reporter {
} }
} }
const _CHILDREN = new OpaqueToken('MultiReporter.children'); var _CHILDREN = new OpaqueToken('MultiReporter.children');

View File

@ -18,10 +18,10 @@ export function sortedProps(obj: {[key: string]: any}) {
} }
export function formatStats(validSamples: MeasureValues[], metricName: string): string { export function formatStats(validSamples: MeasureValues[], metricName: string): string {
const samples = validSamples.map(measureValues => measureValues.values[metricName]); var samples = validSamples.map(measureValues => measureValues.values[metricName]);
const mean = Statistic.calculateMean(samples); var mean = Statistic.calculateMean(samples);
const cv = Statistic.calculateCoefficientOfVariation(samples, mean); var cv = Statistic.calculateCoefficientOfVariation(samples, mean);
const formattedMean = formatNum(mean); var formattedMean = formatNum(mean);
// Note: Don't use the unicode character for +- as it might cause // Note: Don't use the unicode character for +- as it might cause
// hickups for consoles... // hickups for consoles...
return isNaN(cv) ? formattedMean : `${formattedMean}+-${Math.floor(cv)}%`; return isNaN(cv) ? formattedMean : `${formattedMean}+-${Math.floor(cv)}%`;

View File

@ -45,7 +45,7 @@ export class Runner {
providers?: Provider[], providers?: Provider[],
userMetrics?: {[key: string]: string} userMetrics?: {[key: string]: string}
}): Promise<SampleState> { }): Promise<SampleState> {
const sampleProviders: Provider[] = [ var sampleProviders: Provider[] = [
_DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id}, _DEFAULT_PROVIDERS, this._defaultProviders, {provide: Options.SAMPLE_ID, useValue: id},
{provide: Options.EXECUTE, useValue: execute} {provide: Options.EXECUTE, useValue: execute}
]; ];
@ -62,33 +62,33 @@ export class Runner {
sampleProviders.push(providers); sampleProviders.push(providers);
} }
const inj = ReflectiveInjector.resolveAndCreate(sampleProviders); var inj = ReflectiveInjector.resolveAndCreate(sampleProviders);
const adapter: WebDriverAdapter = inj.get(WebDriverAdapter); var adapter: WebDriverAdapter = inj.get(WebDriverAdapter);
return Promise return Promise
.all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')]) .all([adapter.capabilities(), adapter.executeScript('return window.navigator.userAgent;')])
.then((args) => { .then((args) => {
const capabilities = args[0]; var capabilities = args[0];
const userAgent = args[1]; var userAgent = args[1];
// This might still create instances twice. We are creating a new injector with all the // This might still create instances twice. We are creating a new injector with all the
// providers. // providers.
// Only WebDriverAdapter is reused. // Only WebDriverAdapter is reused.
// TODO vsavkin consider changing it when toAsyncFactory is added back or when child // TODO vsavkin consider changing it when toAsyncFactory is added back or when child
// injectors are handled better. // injectors are handled better.
const injector = ReflectiveInjector.resolveAndCreate([ var injector = ReflectiveInjector.resolveAndCreate([
sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities}, sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities},
{provide: Options.USER_AGENT, useValue: userAgent}, {provide: Options.USER_AGENT, useValue: userAgent},
{provide: WebDriverAdapter, useValue: adapter} {provide: WebDriverAdapter, useValue: adapter}
]); ]);
const sampler = injector.get(Sampler); var sampler = injector.get(Sampler);
return sampler.sample(); return sampler.sample();
}); });
} }
} }
const _DEFAULT_PROVIDERS = [ var _DEFAULT_PROVIDERS = [
Options.DEFAULT_PROVIDERS, Options.DEFAULT_PROVIDERS,
Sampler.PROVIDERS, Sampler.PROVIDERS,
ConsoleReporter.PROVIDERS, ConsoleReporter.PROVIDERS,

View File

@ -49,7 +49,7 @@ export class Sampler {
} }
private _iterate(lastState: SampleState): Promise<SampleState> { private _iterate(lastState: SampleState): Promise<SampleState> {
let resultPromise: Promise<SampleState>; var resultPromise: Promise<SampleState>;
if (this._prepare !== Options.NO_PREPARE) { if (this._prepare !== Options.NO_PREPARE) {
resultPromise = this._driver.waitFor(this._prepare); resultPromise = this._driver.waitFor(this._prepare);
} else { } else {
@ -64,10 +64,10 @@ export class Sampler {
} }
private _report(state: SampleState, metricValues: {[key: string]: any}): Promise<SampleState> { private _report(state: SampleState, metricValues: {[key: string]: any}): Promise<SampleState> {
const measureValues = new MeasureValues(state.completeSample.length, this._now(), metricValues); var measureValues = new MeasureValues(state.completeSample.length, this._now(), metricValues);
const completeSample = state.completeSample.concat([measureValues]); var completeSample = state.completeSample.concat([measureValues]);
const validSample = this._validator.validate(completeSample); var validSample = this._validator.validate(completeSample);
let resultPromise = this._reporter.reportMeasureValues(measureValues); var resultPromise = this._reporter.reportMeasureValues(measureValues);
if (isPresent(validSample)) { if (isPresent(validSample)) {
resultPromise = resultPromise =
resultPromise.then((_) => this._reporter.reportSample(completeSample, validSample)); resultPromise.then((_) => this._reporter.reportSample(completeSample, validSample));

View File

@ -12,14 +12,14 @@ export class Statistic {
} }
static calculateMean(samples: number[]) { static calculateMean(samples: number[]) {
let total = 0; var total = 0;
// TODO: use reduce // TODO: use reduce
samples.forEach(x => total += x); samples.forEach(x => total += x);
return total / samples.length; return total / samples.length;
} }
static calculateStandardDeviation(samples: number[], mean: number) { static calculateStandardDeviation(samples: number[], mean: number) {
let deviation = 0; var deviation = 0;
// TODO: use reduce // TODO: use reduce
samples.forEach(x => deviation += Math.pow(x - mean, 2)); samples.forEach(x => deviation += Math.pow(x - mean, 2));
deviation = deviation / (samples.length); deviation = deviation / (samples.length);
@ -30,9 +30,9 @@ export class Statistic {
static calculateRegressionSlope( static calculateRegressionSlope(
xValues: number[], xMean: number, yValues: number[], yMean: number) { xValues: number[], xMean: number, yValues: number[], yMean: number) {
// See http://en.wikipedia.org/wiki/Simple_linear_regression // See http://en.wikipedia.org/wiki/Simple_linear_regression
let dividendSum = 0; var dividendSum = 0;
let divisorSum = 0; var divisorSum = 0;
for (let i = 0; i < xValues.length; i++) { for (var i = 0; i < xValues.length; i++) {
dividendSum += (xValues[i] - xMean) * (yValues[i] - yMean); dividendSum += (xValues[i] - xMean) * (yValues[i] - yMean);
divisorSum += Math.pow(xValues[i] - xMean, 2); divisorSum += Math.pow(xValues[i] - xMean, 2);
} }

View File

@ -34,7 +34,7 @@ export type PerfLogEvent = {
*/ */
export abstract class WebDriverExtension { export abstract class WebDriverExtension {
static provideFirstSupported(childTokens: any[]): any[] { static provideFirstSupported(childTokens: any[]): any[] {
const res = [ var res = [
{ {
provide: _CHILDREN, provide: _CHILDREN,
useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)), useFactory: (injector: Injector) => childTokens.map(token => injector.get(token)),
@ -43,7 +43,7 @@ export abstract class WebDriverExtension {
{ {
provide: WebDriverExtension, provide: WebDriverExtension,
useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => { useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => {
let delegate: WebDriverExtension; var delegate: WebDriverExtension;
children.forEach(extension => { children.forEach(extension => {
if (extension.supports(capabilities)) { if (extension.supports(capabilities)) {
delegate = extension; delegate = extension;
@ -101,4 +101,4 @@ export class PerfLogFeatures {
} }
} }
const _CHILDREN = new OpaqueToken('WebDriverExtension.children'); var _CHILDREN = new OpaqueToken('WebDriverExtension.children');

View File

@ -34,7 +34,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
if (!userAgent) { if (!userAgent) {
return -1; return -1;
} }
let v = userAgent.split(/Chrom(e|ium)\//g)[2]; var v = userAgent.split(/Chrom(e|ium)\//g)[2];
if (!v) { if (!v) {
return -1; return -1;
} }
@ -52,7 +52,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = `console.timeEnd('${name}');`; var script = `console.timeEnd('${name}');`;
if (restartName) { if (restartName) {
script += `console.time('${restartName}');`; script += `console.time('${restartName}');`;
} }
@ -67,9 +67,9 @@ export class ChromeDriverExtension extends WebDriverExtension {
return this._driver.executeScript('1+1') return this._driver.executeScript('1+1')
.then((_) => this._driver.logs('performance')) .then((_) => this._driver.logs('performance'))
.then((entries) => { .then((entries) => {
const events: PerfLogEvent[] = []; var events: PerfLogEvent[] = [];
entries.forEach(entry => { entries.forEach(entry => {
const message = JSON.parse(entry['message'])['message']; var message = JSON.parse(entry['message'])['message'];
if (message['method'] === 'Tracing.dataCollected') { if (message['method'] === 'Tracing.dataCollected') {
events.push(message['params']); events.push(message['params']);
} }
@ -95,8 +95,8 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
private _convertEvent(event: {[key: string]: any}, categories: string[]) { private _convertEvent(event: {[key: string]: any}, categories: string[]) {
const name = event['name']; var name = event['name'];
const args = event['args']; var args = event['args'];
if (this._isEvent(categories, name, ['blink.console'])) { if (this._isEvent(categories, name, ['blink.console'])) {
return normalizeEvent(event, {'name': name}); return normalizeEvent(event, {'name': name});
} else if (this._isEvent( } else if (this._isEvent(
@ -109,7 +109,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
// new surfaces framework (not broadly enabled yet) // new surfaces framework (not broadly enabled yet)
// 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is // 3rd choice: BenchmarkInstrumentation::ImplThreadRenderingStats - fallback event that is
// always available if something is rendered // always available if something is rendered
const frameCount = event['args']['data']['frame_count']; var frameCount = event['args']['data']['frame_count'];
if (frameCount > 1) { if (frameCount > 1) {
throw new Error('multi-frame render stats not supported'); throw new Error('multi-frame render stats not supported');
} }
@ -122,14 +122,14 @@ export class ChromeDriverExtension extends WebDriverExtension {
categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) { categories, name, ['disabled-by-default-devtools.timeline'], 'CompositeLayers')) {
return normalizeEvent(event, {'name': 'render'}); return normalizeEvent(event, {'name': 'render'});
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) { } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MajorGC')) {
const normArgs = { var normArgs = {
'majorGc': true, 'majorGc': true,
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] : 'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
args['usedHeapSizeBefore'] args['usedHeapSizeBefore']
}; };
return normalizeEvent(event, {'name': 'gc', 'args': normArgs}); return normalizeEvent(event, {'name': 'gc', 'args': normArgs});
} else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) { } else if (this._isEvent(categories, name, ['devtools.timeline', 'v8'], 'MinorGC')) {
const normArgs = { var normArgs = {
'majorGc': false, 'majorGc': false,
'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] : 'usedHeapSize': args['usedHeapSizeAfter'] !== undefined ? args['usedHeapSizeAfter'] :
args['usedHeapSizeBefore'] args['usedHeapSizeBefore']
@ -151,11 +151,11 @@ export class ChromeDriverExtension extends WebDriverExtension {
this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) { this._isEvent(categories, name, ['devtools.timeline'], 'Paint')) {
return normalizeEvent(event, {'name': 'render'}); return normalizeEvent(event, {'name': 'render'});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) { } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceReceivedData')) {
const normArgs = {'encodedDataLength': args['data']['encodedDataLength']}; let normArgs = {'encodedDataLength': args['data']['encodedDataLength']};
return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs}); return normalizeEvent(event, {'name': 'receivedData', 'args': normArgs});
} else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) { } else if (this._isEvent(categories, name, ['devtools.timeline'], 'ResourceSendRequest')) {
const data = args['data']; let data = args['data'];
const normArgs = {'url': data['url'], 'method': data['requestMethod']}; let normArgs = {'url': data['url'], 'method': data['requestMethod']};
return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs}); return normalizeEvent(event, {'name': 'sendRequest', 'args': normArgs});
} else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) { } else if (this._isEvent(categories, name, ['blink.user_timing'], 'navigationStart')) {
return normalizeEvent(event, {'name': 'navigationStart'}); return normalizeEvent(event, {'name': 'navigationStart'});
@ -168,7 +168,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
private _isEvent( private _isEvent(
eventCategories: string[], eventName: string, expectedCategories: string[], eventCategories: string[], eventName: string, expectedCategories: string[],
expectedName: string = null): boolean { expectedName: string = null): boolean {
const hasCategories = expectedCategories.reduce( var hasCategories = expectedCategories.reduce(
(value, cat) => value && eventCategories.indexOf(cat) !== -1, true); (value, cat) => value && eventCategories.indexOf(cat) !== -1, true);
return !expectedName ? hasCategories : hasCategories && eventName === expectedName; return !expectedName ? hasCategories : hasCategories && eventName === expectedName;
} }
@ -183,7 +183,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
} }
function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent { function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent): PerfLogEvent {
let ph = chromeEvent['ph'].toUpperCase(); var ph = chromeEvent['ph'].toUpperCase();
if (ph === 'S') { if (ph === 'S') {
ph = 'B'; ph = 'B';
} else if (ph === 'F') { } else if (ph === 'F') {
@ -192,16 +192,16 @@ function normalizeEvent(chromeEvent: {[key: string]: any}, data: PerfLogEvent):
// mark events from navigation timing // mark events from navigation timing
ph = 'I'; ph = 'I';
} }
const result: {[key: string]: any} = var result: {[key: string]: any} =
{'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000}; {'pid': chromeEvent['pid'], 'ph': ph, 'cat': 'timeline', 'ts': chromeEvent['ts'] / 1000};
if (ph === 'X') { if (ph === 'X') {
let dur = chromeEvent['dur']; var dur = chromeEvent['dur'];
if (dur === undefined) { if (dur === undefined) {
dur = chromeEvent['tdur']; dur = chromeEvent['tdur'];
} }
result['dur'] = !dur ? 0.0 : dur / 1000; result['dur'] = !dur ? 0.0 : dur / 1000;
} }
for (const prop in data) { for (let prop in data) {
result[prop] = data[prop]; result[prop] = data[prop];
} }
return result; return result;

View File

@ -34,7 +34,7 @@ export class FirefoxDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = 'window.markEnd("' + name + '");'; var script = 'window.markEnd("' + name + '");';
if (isPresent(restartName)) { if (isPresent(restartName)) {
script += 'window.markStart("' + restartName + '");'; script += 'window.markStart("' + restartName + '");';
} }

View File

@ -25,7 +25,7 @@ export class IOsDriverExtension extends WebDriverExtension {
} }
timeEnd(name: string, restartName: string = null): Promise<any> { timeEnd(name: string, restartName: string = null): Promise<any> {
let script = `console.timeEnd('${name}');`; var script = `console.timeEnd('${name}');`;
if (isPresent(restartName)) { if (isPresent(restartName)) {
script += `console.time('${restartName}');`; script += `console.time('${restartName}');`;
} }
@ -39,9 +39,9 @@ export class IOsDriverExtension extends WebDriverExtension {
return this._driver.executeScript('1+1') return this._driver.executeScript('1+1')
.then((_) => this._driver.logs('performance')) .then((_) => this._driver.logs('performance'))
.then((entries) => { .then((entries) => {
const records: any[] = []; var records: any[] = [];
entries.forEach(entry => { entries.forEach(entry => {
const message = JSON.parse(entry['message'])['message']; var message = JSON.parse(entry['message'])['message'];
if (message['method'] === 'Timeline.eventRecorded') { if (message['method'] === 'Timeline.eventRecorded') {
records.push(message['params']['record']); records.push(message['params']['record']);
} }
@ -56,11 +56,11 @@ export class IOsDriverExtension extends WebDriverExtension {
events = []; events = [];
} }
records.forEach((record) => { records.forEach((record) => {
let endEvent: PerfLogEvent = null; var endEvent: PerfLogEvent = null;
const type = record['type']; var type = record['type'];
const data = record['data']; var data = record['data'];
const startTime = record['startTime']; var startTime = record['startTime'];
const endTime = record['endTime']; var endTime = record['endTime'];
if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) { if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) {
events.push(createStartEvent('script', startTime)); events.push(createStartEvent('script', startTime));
@ -95,7 +95,7 @@ export class IOsDriverExtension extends WebDriverExtension {
function createEvent( function createEvent(
ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) { ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) {
const result: PerfLogEvent = { var result: PerfLogEvent = {
'cat': 'timeline', 'cat': 'timeline',
'name': name, 'name': name,
'ts': time, 'ts': time,

View File

@ -8,7 +8,7 @@
require('core-js'); require('core-js');
require('reflect-metadata'); require('reflect-metadata');
const testHelper = require('../../src/firefox_extension/lib/test_helper.js'); var testHelper = require('../../src/firefox_extension/lib/test_helper.js');
exports.config = { exports.config = {
specs: ['spec.js', 'sample_benchmark.js'], specs: ['spec.js', 'sample_benchmark.js'],

View File

@ -10,10 +10,10 @@ import {convertPerfProfileToEvents} from '../../src/firefox_extension/lib/parser
function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) { function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
expect(actualEvents.length == expectedEvents.length); expect(actualEvents.length == expectedEvents.length);
for (let i = 0; i < actualEvents.length; ++i) { for (var i = 0; i < actualEvents.length; ++i) {
const actualEvent = actualEvents[i]; var actualEvent = actualEvents[i];
const expectedEvent = expectedEvents[i]; var expectedEvent = expectedEvents[i];
for (const key in actualEvent) { for (var key in actualEvent) {
expect(actualEvent[key]).toEqual(expectedEvent[key]); expect(actualEvent[key]).toEqual(expectedEvent[key]);
} }
} }
@ -22,17 +22,17 @@ function assertEventsEqual(actualEvents: any[], expectedEvents: any[]) {
export function main() { export function main() {
describe('convertPerfProfileToEvents', function() { describe('convertPerfProfileToEvents', function() {
it('should convert single instantaneous event', function() { it('should convert single instantaneous event', function() {
const profileData = { var profileData = {
threads: [ threads: [
{samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]} {samples: [{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}]}
] ]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
}); });
it('should convert single non-instantaneous event', function() { it('should convert single non-instantaneous event', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -41,13 +41,13 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual( assertEventsEqual(
perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]); perfEvents, [{ph: 'B', ts: 1, name: 'script'}, {ph: 'E', ts: 100, name: 'script'}]);
}); });
it('should convert multiple instantaneous events', function() { it('should convert multiple instantaneous events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -55,13 +55,13 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual( assertEventsEqual(
perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]); perfEvents, [{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}]);
}); });
it('should convert multiple mixed events', function() { it('should convert multiple mixed events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -71,7 +71,7 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [ assertEventsEqual(perfEvents, [
{ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'}, {ph: 'X', ts: 1, name: 'script'}, {ph: 'X', ts: 2, name: 'render'},
{ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'} {ph: 'B', ts: 5, name: 'script'}, {ph: 'E', ts: 10, name: 'script'}
@ -79,13 +79,13 @@ export function main() {
}); });
it('should add args to gc events', function() { it('should add args to gc events', function() {
const profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]}; var profileData = {threads: [{samples: [{time: 1, frames: [{location: 'forceGC'}]}]}]};
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'gc', args: {usedHeapSize: 0}}]);
}); });
it('should skip unknown events', function() { it('should skip unknown events', function() {
const profileData = { var profileData = {
threads: [{ threads: [{
samples: [ samples: [
{time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]}, {time: 1, frames: [{location: 'FirefoxDriver.prototype.executeScript'}]},
@ -93,7 +93,7 @@ export function main() {
] ]
}] }]
}; };
const perfEvents = convertPerfProfileToEvents(profileData); var perfEvents = convertPerfProfileToEvents(profileData);
assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]); assertEventsEqual(perfEvents, [{ph: 'X', ts: 1, name: 'script'}]);
}); });
}); });

View File

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {$, browser} from 'protractor'; var benchpress = require('../../index.js');
var runner = new benchpress.Runner([
const benchpress = require('../../index.js');
const runner = new benchpress.Runner([
// use protractor as Webdriver client // use protractor as Webdriver client
benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS, benchpress.SeleniumWebDriverAdapter.PROTRACTOR_PROVIDERS,
// use RegressionSlopeValidator to validate samples // use RegressionSlopeValidator to validate samples

View File

@ -6,11 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {browser} from 'protractor'; var assertEventsContainsName = function(events: any[], eventName: string) {
var found = false;
const assertEventsContainsName = function(events: any[], eventName: string) { for (var i = 0; i < events.length; ++i) {
let found = false;
for (let i = 0; i < events.length; ++i) {
if (events[i].name == eventName) { if (events[i].name == eventName) {
found = true; found = true;
break; break;
@ -20,7 +18,7 @@ const assertEventsContainsName = function(events: any[], eventName: string) {
}; };
describe('firefox extension', function() { describe('firefox extension', function() {
const TEST_URL = 'http://localhost:8001/playground/src/hello_world/index.html'; var TEST_URL = 'http://localhost:8001/playground/src/hello_world/index.html';
it('should measure performance', function() { it('should measure performance', function() {
browser.sleep(3000); // wait for extension to load browser.sleep(3000); // wait for extension to load

View File

@ -11,7 +11,7 @@ import {Metric, MultiMetric, ReflectiveInjector} from '../../index';
export function main() { export function main() {
function createMetric(ids: any[]) { function createMetric(ids: any[]) {
const m = ReflectiveInjector var m = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockMetric(id)})), ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
MultiMetric.provideWith(ids) MultiMetric.provideWith(ids)
@ -56,13 +56,13 @@ class MockMetric extends Metric {
beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); } beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); }
endMeasure(restart: boolean): Promise<{[key: string]: any}> { endMeasure(restart: boolean): Promise<{[key: string]: any}> {
const result: {[key: string]: any} = {}; var result: {[key: string]: any} = {};
result[this._id] = {'restart': restart}; result[this._id] = {'restart': restart};
return Promise.resolve(result); return Promise.resolve(result);
} }
describe(): {[key: string]: string} { describe(): {[key: string]: string} {
const result: {[key: string]: string} = {}; var result: {[key: string]: string} = {};
result[this._id] = 'describe'; result[this._id] = 'describe';
return result; return result;
} }

View File

@ -14,8 +14,8 @@ import {isPresent} from '../../src/facade/lang';
import {TraceEventFactory} from '../trace_event_factory'; import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
let commandLog: any[]; var commandLog: any[];
const eventFactory = new TraceEventFactory('timeline', 'pid0'); var eventFactory = new TraceEventFactory('timeline', 'pid0');
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
@ -34,7 +34,7 @@ export function main() {
if (!microMetrics) { if (!microMetrics) {
microMetrics = {}; microMetrics = {};
} }
const providers: Provider[] = [ var providers: Provider[] = [
Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS, Options.DEFAULT_PROVIDERS, PerflogMetric.PROVIDERS,
{provide: Options.MICRO_METRICS, useValue: microMetrics}, { {provide: Options.MICRO_METRICS, useValue: microMetrics}, {
provide: PerflogMetric.SET_TIMEOUT, provide: PerflogMetric.SET_TIMEOUT,
@ -66,7 +66,7 @@ export function main() {
describe('perflog metric', () => { describe('perflog metric', () => {
function sortedKeys(stringMap: {[key: string]: any}) { function sortedKeys(stringMap: {[key: string]: any}) {
const res: string[] = []; var res: string[] = [];
res.push(...Object.keys(stringMap)); res.push(...Object.keys(stringMap));
res.sort(); res.sort();
return res; return res;
@ -102,13 +102,13 @@ export function main() {
}); });
it('should describe itself based on micro metrics', () => { it('should describe itself based on micro metrics', () => {
const description = var description =
createMetric([[]], null, {microMetrics: {'myMicroMetric': 'someDesc'}}).describe(); createMetric([[]], null, {microMetrics: {'myMicroMetric': 'someDesc'}}).describe();
expect(description['myMicroMetric']).toEqual('someDesc'); expect(description['myMicroMetric']).toEqual('someDesc');
}); });
it('should describe itself if frame capture is requested and available', () => { it('should describe itself if frame capture is requested and available', () => {
const description = createMetric([[]], new PerfLogFeatures({frameCapture: true}), { var description = createMetric([[]], new PerfLogFeatures({frameCapture: true}), {
captureFrames: true captureFrames: true
}).describe(); }).describe();
expect(description['frameTime.mean']).not.toContain('WARNING'); expect(description['frameTime.mean']).not.toContain('WARNING');
@ -118,7 +118,7 @@ export function main() {
}); });
it('should describe itself if frame capture is requested and not available', () => { it('should describe itself if frame capture is requested and not available', () => {
const description = createMetric([[]], new PerfLogFeatures({frameCapture: false}), { var description = createMetric([[]], new PerfLogFeatures({frameCapture: false}), {
captureFrames: true captureFrames: true
}).describe(); }).describe();
expect(description['frameTime.mean']).toContain('WARNING'); expect(description['frameTime.mean']).toContain('WARNING');
@ -131,7 +131,7 @@ export function main() {
it('should not force gc and mark the timeline', it('should not force gc and mark the timeline',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric([[]], null); var metric = createMetric([[]], null);
metric.beginMeasure().then((_) => { metric.beginMeasure().then((_) => {
expect(commandLog).toEqual([['timeBegin', 'benchpress0']]); expect(commandLog).toEqual([['timeBegin', 'benchpress0']]);
@ -141,7 +141,7 @@ export function main() {
it('should force gc and mark the timeline', it('should force gc and mark the timeline',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric([[]], null, {forceGc: true}); var metric = createMetric([[]], null, {forceGc: true});
metric.beginMeasure().then((_) => { metric.beginMeasure().then((_) => {
expect(commandLog).toEqual([['gc'], ['timeBegin', 'benchpress0']]); expect(commandLog).toEqual([['gc'], ['timeBegin', 'benchpress0']]);
@ -155,11 +155,11 @@ export function main() {
it('should mark and aggregate events in between the marks', it('should mark and aggregate events in between the marks',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [[ var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10) eventFactory.end('script', 6), eventFactory.markEnd('benchpress0', 10)
]]; ]];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog' ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog'
@ -172,13 +172,13 @@ export function main() {
it('should mark and aggregate events since navigationStart', it('should mark and aggregate events since navigationStart',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [[ var events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7), eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7),
eventFactory.start('script', 8), eventFactory.end('script', 9), eventFactory.start('script', 8), eventFactory.end('script', 9),
eventFactory.markEnd('benchpress0', 10) eventFactory.markEnd('benchpress0', 10)
]]; ]];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(data['scriptTime']).toBe(1); expect(data['scriptTime']).toBe(1);
@ -187,7 +187,7 @@ export function main() {
})); }));
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[ [
eventFactory.markStart('benchpress0', 0), eventFactory.markStart('benchpress0', 0),
eventFactory.markEnd('benchpress0', 1), eventFactory.markEnd('benchpress0', 1),
@ -195,7 +195,7 @@ export function main() {
], ],
[eventFactory.markEnd('benchpress1', 3)] [eventFactory.markEnd('benchpress1', 3)]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure() metric.beginMeasure()
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
@ -211,7 +211,7 @@ export function main() {
it('should loop and aggregate until the end mark is present', it('should loop and aggregate until the end mark is present',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 1)], [eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 1)],
[eventFactory.end('script', 2)], [eventFactory.end('script', 2)],
[ [
@ -219,7 +219,7 @@ export function main() {
eventFactory.markEnd('benchpress0', 10) eventFactory.markEnd('benchpress0', 10)
] ]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog', ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', null], 'readPerfLog',
@ -233,7 +233,7 @@ export function main() {
it('should store events after the end mark for the next call', it('should store events after the end mark for the next call',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [ var events = [
[ [
eventFactory.markStart('benchpress0', 0), eventFactory.markEnd('benchpress0', 1), eventFactory.markStart('benchpress0', 0), eventFactory.markEnd('benchpress0', 1),
eventFactory.markStart('benchpress1', 1), eventFactory.start('script', 1), eventFactory.markStart('benchpress1', 1), eventFactory.start('script', 1),
@ -244,7 +244,7 @@ export function main() {
eventFactory.markEnd('benchpress1', 6) eventFactory.markEnd('benchpress1', 6)
] ]
]; ];
const metric = createMetric(events, null); var metric = createMetric(events, null);
metric.beginMeasure() metric.beginMeasure()
.then((_) => metric.endMeasure(true)) .then((_) => metric.endMeasure(true))
.then((data) => { .then((data) => {
@ -263,7 +263,7 @@ export function main() {
})); }));
describe('with forced gc', () => { describe('with forced gc', () => {
let events: PerfLogEvent[][]; var events: PerfLogEvent[][];
beforeEach(() => { beforeEach(() => {
events = [[ events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
@ -276,7 +276,7 @@ export function main() {
}); });
it('should measure forced gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should measure forced gc', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric(events, null, {forceGc: true}); var metric = createMetric(events, null, {forceGc: true});
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(commandLog).toEqual([ expect(commandLog).toEqual([
['gc'], ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'], ['gc'], ['timeBegin', 'benchpress0'], ['timeEnd', 'benchpress0', 'benchpress1'],
@ -291,7 +291,7 @@ export function main() {
it('should restart after the forced gc if needed', it('should restart after the forced gc if needed',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric(events, null, {forceGc: true}); var metric = createMetric(events, null, {forceGc: true});
metric.beginMeasure().then((_) => metric.endMeasure(true)).then((data) => { metric.beginMeasure().then((_) => metric.endMeasure(true)).then((data) => {
expect(commandLog[5]).toEqual(['timeEnd', 'benchpress1', 'benchpress2']); expect(commandLog[5]).toEqual(['timeEnd', 'benchpress1', 'benchpress2']);
@ -313,7 +313,7 @@ export function main() {
} = {}) { } = {}) {
events.unshift(eventFactory.markStart('benchpress0', 0)); events.unshift(eventFactory.markStart('benchpress0', 0));
events.push(eventFactory.markEnd('benchpress0', 10)); events.push(eventFactory.markEnd('benchpress0', 10));
const metric = createMetric([events], null, { var metric = createMetric([events], null, {
microMetrics: microMetrics, microMetrics: microMetrics,
captureFrames: captureFrames, captureFrames: captureFrames,
receivedData: receivedData, receivedData: receivedData,
@ -502,8 +502,8 @@ export function main() {
it('should ignore events from different processed as the start mark', it('should ignore events from different processed as the start mark',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const otherProcessEventFactory = new TraceEventFactory('timeline', 'pid1'); var otherProcessEventFactory = new TraceEventFactory('timeline', 'pid1');
const metric = createMetric( var metric = createMetric(
[[ [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 0, null), eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 0, null),
eventFactory.end('script', 5, null), eventFactory.end('script', 5, null),
@ -685,7 +685,7 @@ class MockDriverExtension extends WebDriverExtension {
readPerfLog(): Promise<any> { readPerfLog(): Promise<any> {
this._commandLog.push('readPerfLog'); this._commandLog.push('readPerfLog');
if (this._perfLogs.length > 0) { if (this._perfLogs.length > 0) {
const next = this._perfLogs[0]; var next = this._perfLogs[0];
this._perfLogs.shift(); this._perfLogs.shift();
return Promise.resolve(next); return Promise.resolve(next);
} else { } else {

View File

@ -12,7 +12,7 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index'; import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index';
export function main() { export function main() {
let wdAdapter: MockDriverAdapter; var wdAdapter: MockDriverAdapter;
function createMetric( function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures, perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
@ -25,7 +25,7 @@ export function main() {
userMetrics = {}; userMetrics = {};
} }
wdAdapter = new MockDriverAdapter(); wdAdapter = new MockDriverAdapter();
const providers: Provider[] = [ var providers: Provider[] = [
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS, Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
{provide: Options.USER_METRICS, useValue: userMetrics}, {provide: Options.USER_METRICS, useValue: userMetrics},
{provide: WebDriverAdapter, useValue: wdAdapter} {provide: WebDriverAdapter, useValue: wdAdapter}
@ -45,7 +45,7 @@ export function main() {
describe('endMeasure', () => { describe('endMeasure', () => {
it('should stop measuring when all properties have numeric values', it('should stop measuring when all properties have numeric values',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const metric = createMetric( let metric = createMetric(
[[]], new PerfLogFeatures(), [[]], new PerfLogFeatures(),
{userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}}); {userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}});
metric.beginMeasure() metric.beginMeasure()
@ -71,7 +71,7 @@ class MockDriverAdapter extends WebDriverAdapter {
executeScript(script: string): any { executeScript(script: string): any {
// Just handles `return window.propName` ignores `delete window.propName`. // Just handles `return window.propName` ignores `delete window.propName`.
if (script.indexOf('return window.') == 0) { if (script.indexOf('return window.') == 0) {
const metricName = script.substring('return window.'.length); let metricName = script.substring('return window.'.length);
return Promise.resolve(this.data[metricName]); return Promise.resolve(this.data[metricName]);
} else if (script.indexOf('delete window.') == 0) { } else if (script.indexOf('delete window.') == 0) {
return Promise.resolve(null); return Promise.resolve(null);

View File

@ -14,8 +14,8 @@ import {isBlank, isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('console reporter', () => { describe('console reporter', () => {
let reporter: ConsoleReporter; var reporter: ConsoleReporter;
let log: string[]; var log: string[];
function createReporter( function createReporter(
{columnWidth = null, sampleId = null, descriptions = null, metrics = null}: { {columnWidth = null, sampleId = null, descriptions = null, metrics = null}: {
@ -31,7 +31,7 @@ export function main() {
if (sampleId == null) { if (sampleId == null) {
sampleId = 'null'; sampleId = 'null';
} }
const providers: Provider[] = [ var providers: Provider[] = [
ConsoleReporter.PROVIDERS, { ConsoleReporter.PROVIDERS, {
provide: SampleDescription, provide: SampleDescription,
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)

View File

@ -13,7 +13,7 @@ import {isPresent} from '../../src/facade/lang';
export function main() { export function main() {
describe('file reporter', () => { describe('file reporter', () => {
let loggedFile: any; var loggedFile: any;
function createReporter({sampleId, descriptions, metrics, path}: { function createReporter({sampleId, descriptions, metrics, path}: {
sampleId: string, sampleId: string,
@ -21,7 +21,7 @@ export function main() {
metrics: {[key: string]: string}, metrics: {[key: string]: string},
path: string path: string
}) { }) {
const providers = [ var providers = [
JsonFileReporter.PROVIDERS, { JsonFileReporter.PROVIDERS, {
provide: SampleDescription, provide: SampleDescription,
useValue: new SampleDescription(sampleId, descriptions, metrics) useValue: new SampleDescription(sampleId, descriptions, metrics)
@ -49,9 +49,9 @@ export function main() {
.reportSample( .reportSample(
[mv(0, 0, {'a': 3, 'b': 6})], [mv(0, 0, {'a': 3, 'b': 6})],
[mv(0, 0, {'a': 3, 'b': 6}), mv(1, 1, {'a': 5, 'b': 9})]); [mv(0, 0, {'a': 3, 'b': 6}), mv(1, 1, {'a': 5, 'b': 9})]);
const regExp = /somePath\/someId_\d+\.json/; var regExp = /somePath\/someId_\d+\.json/;
expect(isPresent(loggedFile['filename'].match(regExp))).toBe(true); expect(isPresent(loggedFile['filename'].match(regExp))).toBe(true);
const parsedContent = JSON.parse(loggedFile['content']); var parsedContent = JSON.parse(loggedFile['content']);
expect(parsedContent).toEqual({ expect(parsedContent).toEqual({
'description': { 'description': {
'id': 'someId', 'id': 'someId',

View File

@ -12,7 +12,7 @@ import {MeasureValues, MultiReporter, ReflectiveInjector, Reporter} from '../../
export function main() { export function main() {
function createReporters(ids: any[]) { function createReporters(ids: any[]) {
const r = ReflectiveInjector var r = ReflectiveInjector
.resolveAndCreate([ .resolveAndCreate([
ids.map(id => ({provide: id, useValue: new MockReporter(id)})), ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
MultiReporter.provideWith(ids) MultiReporter.provideWith(ids)
@ -25,7 +25,7 @@ export function main() {
it('should reportMeasureValues to all', it('should reportMeasureValues to all',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const mv = new MeasureValues(0, new Date(), {}); var mv = new MeasureValues(0, new Date(), {});
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,9 +34,9 @@ export function main() {
})); }));
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const completeSample = var completeSample =
[new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})]; [new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})];
const validSample = [completeSample[1]]; var validSample = [completeSample[1]];
createReporters(['m1', 'm2']) createReporters(['m1', 'm2'])
.then((r) => r.reportSample(completeSample, validSample)) .then((r) => r.reportSample(completeSample, validSample))

View File

@ -12,8 +12,8 @@ import {Injector, Metric, Options, ReflectiveInjector, Runner, SampleDescription
export function main() { export function main() {
describe('runner', () => { describe('runner', () => {
let injector: ReflectiveInjector; var injector: ReflectiveInjector;
let runner: Runner; var runner: Runner;
function createRunner(defaultProviders: any[] = null): Runner { function createRunner(defaultProviders: any[] = null): Runner {
if (!defaultProviders) { if (!defaultProviders) {
@ -76,7 +76,7 @@ export function main() {
it('should provide Options.EXECUTE', it('should provide Options.EXECUTE',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const execute = () => {}; var execute = () => {};
createRunner().sample({id: 'someId', execute: execute}).then((_) => { createRunner().sample({id: 'someId', execute: execute}).then((_) => {
expect(injector.get(Options.EXECUTE)).toEqual(execute); expect(injector.get(Options.EXECUTE)).toEqual(execute);
async.done(); async.done();
@ -85,7 +85,7 @@ export function main() {
it('should provide Options.PREPARE', it('should provide Options.PREPARE',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const prepare = () => {}; var prepare = () => {};
createRunner().sample({id: 'someId', prepare: prepare}).then((_) => { createRunner().sample({id: 'someId', prepare: prepare}).then((_) => {
expect(injector.get(Options.PREPARE)).toEqual(prepare); expect(injector.get(Options.PREPARE)).toEqual(prepare);
async.done(); async.done();

View File

@ -12,10 +12,10 @@ import {MeasureValues, Metric, Options, ReflectiveInjector, Reporter, Sampler, V
import {isBlank, isPresent} from '../src/facade/lang'; import {isBlank, isPresent} from '../src/facade/lang';
export function main() { export function main() {
const EMPTY_EXECUTE = () => {}; var EMPTY_EXECUTE = () => {};
describe('sampler', () => { describe('sampler', () => {
let sampler: Sampler; var sampler: Sampler;
function createSampler({driver, metric, reporter, validator, prepare, execute}: { function createSampler({driver, metric, reporter, validator, prepare, execute}: {
driver?: any, driver?: any,
@ -25,7 +25,7 @@ export function main() {
prepare?: any, prepare?: any,
execute?: any execute?: any
} = {}) { } = {}) {
let time = 1000; var time = 1000;
if (!metric) { if (!metric) {
metric = new MockMetric([]); metric = new MockMetric([]);
} }
@ -35,7 +35,7 @@ export function main() {
if (isBlank(driver)) { if (isBlank(driver)) {
driver = new MockDriverAdapter([]); driver = new MockDriverAdapter([]);
} }
const providers = [ var providers = [
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},
@ -50,10 +50,10 @@ export function main() {
it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor', it('should call the prepare and execute callbacks using WebDriverAdapter.waitFor',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
let count = 0; var count = 0;
const driver = new MockDriverAdapter([], (callback: Function) => { var driver = new MockDriverAdapter([], (callback: Function) => {
const result = callback(); var result = callback();
log.push(result); log.push(result);
return Promise.resolve(result); return Promise.resolve(result);
}); });
@ -73,8 +73,8 @@ export function main() {
it('should call prepare, beginMeasure, execute, endMeasure for every iteration', it('should call prepare, beginMeasure, execute, endMeasure for every iteration',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let workCount = 0; var workCount = 0;
const log: any[] = []; var log: any[] = [];
createSampler({ createSampler({
metric: createCountingMetric(log), metric: createCountingMetric(log),
validator: createCountingValidator(2), validator: createCountingValidator(2),
@ -98,8 +98,8 @@ export function main() {
it('should call execute, endMeasure for every iteration if there is no prepare callback', it('should call execute, endMeasure for every iteration if there is no prepare callback',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
let workCount = 0; var workCount = 0;
createSampler({ createSampler({
metric: createCountingMetric(log), metric: createCountingMetric(log),
validator: createCountingValidator(2), validator: createCountingValidator(2),
@ -120,14 +120,14 @@ export function main() {
it('should only collect metrics for execute and ignore metrics from prepare', it('should only collect metrics for execute and ignore metrics from prepare',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
let scriptTime = 0; var scriptTime = 0;
let iterationCount = 1; var iterationCount = 1;
createSampler({ createSampler({
validator: createCountingValidator(2), validator: createCountingValidator(2),
metric: new MockMetric( metric: new MockMetric(
[], [],
() => { () => {
const result = Promise.resolve({'script': scriptTime}); var result = Promise.resolve({'script': scriptTime});
scriptTime = 0; scriptTime = 0;
return result; return result;
}), }),
@ -147,8 +147,8 @@ export function main() {
it('should call the validator for every execution and store the valid sample', it('should call the validator for every execution and store the valid sample',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
const validSample = [mv(null, null, {})]; var validSample = [mv(null, null, {})];
createSampler({ createSampler({
metric: createCountingMetric(), metric: createCountingMetric(),
@ -174,8 +174,8 @@ export function main() {
it('should report the metric values', it('should report the metric values',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const log: any[] = []; var log: any[] = [];
const validSample = [mv(null, null, {})]; var validSample = [mv(null, null, {})];
createSampler({ createSampler({
validator: createCountingValidator(2, validSample), validator: createCountingValidator(2, validSample),
metric: createCountingMetric(), metric: createCountingMetric(),
@ -220,7 +220,7 @@ function createCountingValidator(
} }
function createCountingMetric(log: any[] = []) { function createCountingMetric(log: any[] = []) {
let scriptTime = 0; var scriptTime = 0;
return new MockMetric(log, () => ({'script': scriptTime++})); return new MockMetric(log, () => ({'script': scriptTime++}));
} }
@ -239,8 +239,7 @@ class MockDriverAdapter extends WebDriverAdapter {
class MockValidator extends Validator { class MockValidator extends Validator {
constructor(private _log: any[] = [], private _validate: Function = null) { super(); } constructor(private _log: any[] = [], private _validate: Function = null) { super(); }
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
const stableSample = var stableSample = isPresent(this._validate) ? this._validate(completeSample) : completeSample;
isPresent(this._validate) ? this._validate(completeSample) : completeSample;
this._log.push(['validate', completeSample, stableSample]); this._log.push(['validate', completeSample, stableSample]);
return stableSample; return stableSample;
} }
@ -253,7 +252,7 @@ class MockMetric extends Metric {
return Promise.resolve(null); return Promise.resolve(null);
} }
endMeasure(restart: boolean) { endMeasure(restart: boolean) {
const measureValues = isPresent(this._endMeasure) ? this._endMeasure() : {}; var measureValues = isPresent(this._endMeasure) ? this._endMeasure() : {};
this._log.push(['endMeasure', restart, measureValues]); this._log.push(['endMeasure', restart, measureValues]);
return Promise.resolve(measureValues); return Promise.resolve(measureValues);
} }

View File

@ -13,7 +13,7 @@ export class TraceEventFactory {
constructor(private _cat: string, private _pid: string) {} constructor(private _cat: string, private _pid: string) {}
create(ph: any, name: string, time: number, args: any = null) { create(ph: any, name: string, time: number, args: any = null) {
const res: var res:
PerfLogEvent = {'name': name, 'cat': this._cat, 'ph': ph, 'ts': time, 'pid': this._pid}; PerfLogEvent = {'name': name, 'cat': this._cat, 'ph': ph, 'ts': time, 'pid': this._pid};
if (isPresent(args)) { if (isPresent(args)) {
res['args'] = args; res['args'] = args;
@ -34,7 +34,7 @@ export class TraceEventFactory {
} }
complete(name: string, time: number, duration: number, args: any = null) { complete(name: string, time: number, duration: number, args: any = null) {
const res = this.create('X', name, time, args); var res = this.create('X', name, time, args);
res['dur'] = duration; res['dur'] = duration;
return res; return res;
} }

View File

@ -12,7 +12,7 @@ import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../..
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
let validator: RegressionSlopeValidator; var validator: RegressionSlopeValidator;
function createValidator({size, metric}: {size: number, metric: string}) { function createValidator({size, metric}: {size: number, metric: string}) {
validator = ReflectiveInjector validator = ReflectiveInjector
@ -42,14 +42,14 @@ export function main() {
it('should return the last sampleSize runs when the regression slope is ==0', () => { it('should return the last sampleSize runs when the regression slope is ==0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
const sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
}); });
it('should return the last sampleSize runs when the regression slope is >0', () => { it('should return the last sampleSize runs when the regression slope is >0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
const sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
}); });

View File

@ -12,7 +12,7 @@ import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
let validator: SizeValidator; var validator: SizeValidator;
function createValidator(size: number) { function createValidator(size: number) {
validator = validator =
@ -35,7 +35,7 @@ export function main() {
it('should return the last sampleSize runs when it has at least the given size', () => { it('should return the last sampleSize runs when it has at least the given size', () => {
createValidator(2); createValidator(2);
const sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})]; var sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
}); });

View File

@ -14,23 +14,23 @@ import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
describe('chrome driver extension', () => { describe('chrome driver extension', () => {
const CHROME45_USER_AGENT = var CHROME45_USER_AGENT =
'"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2499.0 Safari/537.36"'; '"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2499.0 Safari/537.36"';
let log: any[]; var log: any[];
let extension: ChromeDriverExtension; var extension: ChromeDriverExtension;
const blinkEvents = new TraceEventFactory('blink.console', 'pid0'); var blinkEvents = new TraceEventFactory('blink.console', 'pid0');
const v8Events = new TraceEventFactory('v8', 'pid0'); var v8Events = new TraceEventFactory('v8', 'pid0');
const v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1'); var v8EventsOtherProcess = new TraceEventFactory('v8', 'pid1');
const chromeTimelineEvents = var chromeTimelineEvents =
new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0'); new TraceEventFactory('disabled-by-default-devtools.timeline', 'pid0');
const chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0'); var chrome45TimelineEvents = new TraceEventFactory('devtools.timeline', 'pid0');
const chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0'); var chromeTimelineV8Events = new TraceEventFactory('devtools.timeline,v8', 'pid0');
const chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0'); var chromeBlinkTimelineEvents = new TraceEventFactory('blink,devtools.timeline', 'pid0');
const chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0'); var chromeBlinkUserTimingEvents = new TraceEventFactory('blink.user_timing', 'pid0');
const benchmarkEvents = new TraceEventFactory('benchmark', 'pid0'); var benchmarkEvents = new TraceEventFactory('benchmark', 'pid0');
const normEvents = new TraceEventFactory('timeline', 'pid0'); var normEvents = new TraceEventFactory('timeline', 'pid0');
function createExtension( function createExtension(
perfRecords: any[] = null, userAgent: string = null, perfRecords: any[] = null, userAgent: string = null,
@ -101,7 +101,7 @@ export function main() {
it('should normalize "tdur" to "dur"', it('should normalize "tdur" to "dur"',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null); var event: any = chromeTimelineV8Events.create('X', 'FunctionCall', 1100, null);
event['tdur'] = 5500; event['tdur'] = 5500;
createExtension([event]).readPerfLog().then((events) => { createExtension([event]).readPerfLog().then((events) => {
expect(events).toEqual([ expect(events).toEqual([

View File

@ -13,10 +13,10 @@ import {TraceEventFactory} from '../trace_event_factory';
export function main() { export function main() {
describe('ios driver extension', () => { describe('ios driver extension', () => {
let log: any[]; var log: any[];
let extension: IOsDriverExtension; var extension: IOsDriverExtension;
const 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 (!perfRecords) {

View File

@ -9,7 +9,7 @@
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core'; import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent, stringify} from '../facade/lang'; import {isPresent} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -108,14 +108,8 @@ export class NgClass implements DoCheck {
} }
private _applyIterableChanges(changes: any): void { private _applyIterableChanges(changes: any): void {
changes.forEachAddedItem((record: CollectionChangeRecord) => { changes.forEachAddedItem(
if (typeof record.item === 'string') { (record: CollectionChangeRecord) => this._toggleClass(record.item, true));
this._toggleClass(record.item, true);
} else {
throw new Error(
`NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`);
}
});
changes.forEachRemovedItem( changes.forEachRemovedItem(
(record: CollectionChangeRecord) => this._toggleClass(record.item, false)); (record: CollectionChangeRecord) => this._toggleClass(record.item, false));

View File

@ -150,13 +150,13 @@ export class NgFor implements DoCheck, OnChanges {
} }
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) { for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i); let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
viewRef.context.index = i; viewRef.context.index = i;
viewRef.context.count = ilen; viewRef.context.count = ilen;
} }
changes.forEachIdentityChange((record: any) => { changes.forEachIdentityChange((record: any) => {
const viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex); let viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item; viewRef.context.$implicit = record.item;
}); });
} }

View File

@ -25,7 +25,7 @@ import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDif
* @description * @description
* *
* The styles are updated according to the value of the expression evaluation: * The styles are updated according to the value of the expression evaluation:
* - keys are style names with an optional `.<unit>` suffix (ie 'top.px', 'font-style.em'), * - keys are style names with an option `.<unit>` suffix (ie 'top.px', 'font-style.em'),
* - values are the values assigned to those properties (expressed in the given unit). * - values are the values assigned to those properties (expressed in the given unit).
* *
* @stable * @stable

View File

@ -6,31 +6,19 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, DoCheck, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core'; import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {ListWrapper} from '../facade/collection';
const _CASE_DEFAULT = {};
export class SwitchView { export class SwitchView {
private _created = false;
constructor( constructor(
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {} private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}
create(): void { create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
this._created = true;
this._viewContainerRef.createEmbeddedView(this._templateRef);
}
destroy(): void { destroy(): void { this._viewContainerRef.clear(); }
this._created = false;
this._viewContainerRef.clear();
}
enforceState(created: boolean) {
if (created && !this._created) {
this.create();
} else if (!created && this._created) {
this.destroy();
}
}
} }
/** /**
@ -76,52 +64,92 @@ export class SwitchView {
*/ */
@Directive({selector: '[ngSwitch]'}) @Directive({selector: '[ngSwitch]'})
export class NgSwitch { export class NgSwitch {
private _defaultViews: SwitchView[]; private _switchValue: any;
private _defaultUsed = false; private _useDefault: boolean = false;
private _caseCount = 0; private _valueViews = new Map<any, SwitchView[]>();
private _lastCaseCheckIndex = 0; private _activeViews: SwitchView[] = [];
private _lastCasesMatched = false;
private _ngSwitch: any;
@Input() @Input()
set ngSwitch(newValue: any) { set ngSwitch(value: any) {
this._ngSwitch = newValue; // Set of views to display for this value
if (this._caseCount === 0) { let views = this._valueViews.get(value);
this._updateDefaultCases(true);
if (views) {
this._useDefault = false;
} else {
// No view to display for the current value -> default case
// Nothing to do if the default case was already active
if (this._useDefault) {
return;
}
this._useDefault = true;
views = this._valueViews.get(_CASE_DEFAULT);
}
this._emptyAllActiveViews();
this._activateViews(views);
this._switchValue = value;
}
/** @internal */
_onCaseValueChanged(oldCase: any, newCase: any, view: SwitchView): void {
this._deregisterView(oldCase, view);
this._registerView(newCase, view);
if (oldCase === this._switchValue) {
view.destroy();
ListWrapper.remove(this._activeViews, view);
} else if (newCase === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._emptyAllActiveViews();
}
view.create();
this._activeViews.push(view);
}
// Switch to default when there is no more active ViewContainers
if (this._activeViews.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViews(this._valueViews.get(_CASE_DEFAULT));
}
}
private _emptyAllActiveViews(): void {
const activeContainers = this._activeViews;
for (var i = 0; i < activeContainers.length; i++) {
activeContainers[i].destroy();
}
this._activeViews = [];
}
private _activateViews(views?: SwitchView[]): void {
if (views) {
for (var i = 0; i < views.length; i++) {
views[i].create();
}
this._activeViews = views;
} }
} }
/** @internal */ /** @internal */
_addCase(): number { return this._caseCount++; } _registerView(value: any, view: SwitchView): void {
let views = this._valueViews.get(value);
/** @internal */ if (!views) {
_addDefault(view: SwitchView) { views = [];
if (!this._defaultViews) { this._valueViews.set(value, views);
this._defaultViews = [];
} }
this._defaultViews.push(view); views.push(view);
} }
/** @internal */ private _deregisterView(value: any, view: SwitchView): void {
_matchCase(value: any): boolean { // `_CASE_DEFAULT` is used a marker for non-registered cases
const matched = value == this._ngSwitch; if (value === _CASE_DEFAULT) return;
this._lastCasesMatched = this._lastCasesMatched || matched; const views = this._valueViews.get(value);
this._lastCaseCheckIndex++; if (views.length == 1) {
if (this._lastCaseCheckIndex === this._caseCount) { this._valueViews.delete(value);
this._updateDefaultCases(!this._lastCasesMatched); } else {
this._lastCaseCheckIndex = 0; ListWrapper.remove(views, view);
this._lastCasesMatched = false;
}
return matched;
}
private _updateDefaultCases(useDefault: boolean) {
if (this._defaultViews && useDefault !== this._defaultUsed) {
this._defaultUsed = useDefault;
for (let i = 0; i < this._defaultViews.length; i++) {
const defaultView = this._defaultViews[i];
defaultView.enforceState(useDefault);
}
} }
} }
} }
@ -151,20 +179,24 @@ export class NgSwitch {
* @stable * @stable
*/ */
@Directive({selector: '[ngSwitchCase]'}) @Directive({selector: '[ngSwitchCase]'})
export class NgSwitchCase implements DoCheck { export class NgSwitchCase {
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
private _value: any = _CASE_DEFAULT;
private _view: SwitchView; private _view: SwitchView;
private _switch: NgSwitch;
@Input()
ngSwitchCase: any;
constructor( constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>, viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() private ngSwitch: NgSwitch) { @Host() ngSwitch: NgSwitch) {
ngSwitch._addCase(); this._switch = ngSwitch;
this._view = new SwitchView(viewContainer, templateRef); this._view = new SwitchView(viewContainer, templateRef);
} }
ngDoCheck() { this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase)); } @Input()
set ngSwitchCase(value: any) {
this._switch._onCaseValueChanged(this._value, value, this._view);
this._value = value;
}
} }
/** /**
@ -194,7 +226,7 @@ export class NgSwitchCase implements DoCheck {
export class NgSwitchDefault { export class NgSwitchDefault {
constructor( constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>, viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) { @Host() sswitch: NgSwitch) {
ngSwitch._addDefault(new SwitchView(viewContainer, templateRef)); sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef));
} }
} }

View File

@ -64,19 +64,19 @@ export class HashLocationStrategy extends LocationStrategy {
path(includeHash: boolean = false): string { path(includeHash: boolean = false): string {
// the hash value is always prefixed with a `#` // the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty // and if it is empty then it will stay empty
let path = this._platformLocation.hash; var path = this._platformLocation.hash;
if (!isPresent(path)) path = '#'; if (!isPresent(path)) path = '#';
return path.length > 0 ? path.substring(1) : path; return path.length > 0 ? path.substring(1) : path;
} }
prepareExternalUrl(internal: string): string { prepareExternalUrl(internal: string): string {
const url = Location.joinWithSlash(this._baseHref, internal); var url = Location.joinWithSlash(this._baseHref, internal);
return url.length > 0 ? ('#' + url) : url; return url.length > 0 ? ('#' + url) : url;
} }
pushState(state: any, title: string, path: string, queryParams: string) { pushState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) { if (url.length == 0) {
url = this._platformLocation.pathname; url = this._platformLocation.pathname;
} }
@ -84,7 +84,7 @@ export class HashLocationStrategy extends LocationStrategy {
} }
replaceState(state: any, title: string, path: string, queryParams: string) { replaceState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams)); var url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) { if (url.length == 0) {
url = this._platformLocation.pathname; url = this._platformLocation.pathname;
} }

View File

@ -156,7 +156,7 @@ export class Location {
if (end.length == 0) { if (end.length == 0) {
return start; return start;
} }
let slashes = 0; var slashes = 0;
if (start.endsWith('/')) { if (start.endsWith('/')) {
slashes++; slashes++;
} }

View File

@ -79,12 +79,12 @@ export class PathLocationStrategy extends LocationStrategy {
} }
pushState(state: any, title: string, url: string, queryParams: string) { pushState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.pushState(state, title, externalUrl); this._platformLocation.pushState(state, title, externalUrl);
} }
replaceState(state: any, title: string, url: string, queryParams: string) { replaceState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams)); var externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.replaceState(state, title, externalUrl); this._platformLocation.replaceState(state, title, externalUrl);
} }

View File

@ -8,7 +8,7 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {DateFormatter} from '../facade/intl'; import {DateFormatter} from '../facade/intl';
import {NumberWrapper, isDate} from '../facade/lang'; import {NumberWrapper, isBlank, isDate} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -33,30 +33,27 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`) * - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
* *
* *
* | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit | * | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------|--------------|-------------------|-----------|-----------| * |-----------|:------:|--------------|-------------------|-----------|-----------|
* | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - | * | era | G | G (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | - | y (2015) | yy (15) | * | year | y | - | - | y (2015) | yy (15) |
* | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) | * | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | - | d (3) | dd (03) | * | day | d | - | - | d (3) | dd (03) |
* | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - | * | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - |
* | hour | j | - | - | - | j (13) | jj (13) | * | hour | j | - | - | j (13) | jj (13) |
* | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)| * | hour12 | h | - | - | h (1 PM) | hh (01 PM)|
* | hour24 | H | - | - | - | H (13) | HH (13) | * | hour24 | H | - | - | H (13) | HH (13) |
* | minute | m | - | - | - | m (5) | mm (05) | * | minute | m | - | - | m (5) | mm (05) |
* | second | s | - | - | - | s (9) | ss (09) | * | second | s | - | - | s (9) | ss (09) |
* | timezone | z | - | - | z (Pacific Standard Time)| - | - | * | timezone | z | - | z (Pacific Standard Time)| - | - |
* | timezone | Z | - | Z (GMT-8:00) | - | - | - | * | timezone | Z | Z (GMT-8:00) | - | - | - |
* | timezone | a | - | a (PM) | - | - | - | * | timezone | a | a (PM) | - | - | - |
* *
* In javascript, only the components specified will be respected (not the ordering, * In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the formatting will be dependent on the locale. * punctuations, ...) and details of the formatting will be dependent on the locale.
* *
* Timezone of the formatted text will be the local system timezone of the end-user's machine. * Timezone of the formatted text will be the local system timezone of the end-user's machine.
* *
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
* applied and the formatted text will have the same day, month and year of the expression.
*
* WARNINGS: * WARNINGS:
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated. * - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* Instead users should treat the date as an immutable object and change the reference when the * Instead users should treat the date as an immutable object and change the reference when the
@ -98,42 +95,22 @@ export class DatePipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {} constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(value: any, pattern: string = 'mediumDate'): string { transform(value: any, pattern: string = 'mediumDate'): string {
let date: Date;
if (isBlank(value)) return null; if (isBlank(value)) return null;
if (typeof value === 'string') { if (!this.supports(value)) {
value = value.trim();
}
if (isDate(value)) {
date = value;
} else if (NumberWrapper.isNumeric(value)) {
date = new Date(parseFloat(value));
} else if (typeof value === 'string' && /^(\d{4}-\d{1,2}-\d{1,2})$/.test(value)) {
/**
* For ISO Strings without time the day, month and year must be extracted from the ISO String
* before Date creation to avoid time offset and errors in the new Date.
* If we only replace '-' with ',' in the ISO String ("2015,01,01"), and try to create a new
* date, some browsers (e.g. IE 9) will throw an invalid Date error
* If we leave the '-' ("2015-01-01") and try to create a new Date("2015-01-01") the timeoffset
* is applied
* Note: ISO months are 0 for January, 1 for February, ...
*/
const [y, m, d] = value.split('-').map((val: string) => parseInt(val, 10));
date = new Date(y, m - 1, d);
} else {
date = new Date(value);
}
if (!isDate(date)) {
throw new InvalidPipeArgumentError(DatePipe, value); throw new InvalidPipeArgumentError(DatePipe, value);
} }
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern); if (NumberWrapper.isNumeric(value)) {
value = parseFloat(value);
}
return DateFormatter.format(
new Date(value), this._locale, DatePipe._ALIASES[pattern] || pattern);
}
private supports(obj: any): boolean {
return isDate(obj) || NumberWrapper.isNumeric(obj) ||
(typeof obj === 'string' && isDate(new Date(obj)));
} }
} }
function isBlank(obj: any): boolean {
return obj == null || obj === '';
}

View File

@ -7,6 +7,7 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
@ -15,10 +16,9 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | i18nSelect:mapping` * @howToUse `expression | i18nSelect:mapping`
* @description * @description
* *
* Where `mapping` is an object that indicates the text that should be displayed * Where:
* - `mapping`: is an object that indicates the text that should be displayed
* for different values of the provided `expression`. * for different values of the provided `expression`.
* If none of the keys of the mapping match the value of the `expression`, then the content
* of the `other` key is returned when present, otherwise an empty string is returned.
* *
* ## Example * ## Example
* *
@ -29,20 +29,12 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@Pipe({name: 'i18nSelect', pure: true}) @Pipe({name: 'i18nSelect', pure: true})
export class I18nSelectPipe implements PipeTransform { export class I18nSelectPipe implements PipeTransform {
transform(value: string, mapping: {[key: string]: string}): string { transform(value: string, mapping: {[key: string]: string}): string {
if (value == null) return ''; if (isBlank(value)) return '';
if (typeof mapping !== 'object' || typeof value !== 'string') { if (typeof mapping !== 'object' || mapping === null) {
throw new InvalidPipeArgumentError(I18nSelectPipe, mapping); throw new InvalidPipeArgumentError(I18nSelectPipe, mapping);
} }
if (mapping.hasOwnProperty(value)) { return mapping[value] || '';
return mapping[value];
}
if (mapping.hasOwnProperty('other')) {
return mapping['other'];
}
return '';
} }
} }

View File

@ -37,7 +37,7 @@ function formatNumber(
} }
if (digits) { if (digits) {
const parts = digits.match(_NUMBER_FORMAT_REGEXP); let parts = digits.match(_NUMBER_FORMAT_REGEXP);
if (parts === null) { if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`); throw new Error(`${digits} is not a valid digit info for number pipes`);
} }

View File

@ -8,6 +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';
export function main() { export function main() {
describe('binding to CSS class list', () => { describe('binding to CSS class list', () => {
@ -65,7 +66,7 @@ export function main() {
it('should add and remove classes based on changes to the expression object', async(() => { it('should add and remove classes based on changes to the expression object', async(() => {
fixture = createTestComponent('<div [ngClass]="objExpr"></div>'); fixture = createTestComponent('<div [ngClass]="objExpr"></div>');
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
detectChangesAndExpectClassName('foo'); detectChangesAndExpectClassName('foo');
@ -134,7 +135,7 @@ export function main() {
it('should add and remove classes based on changes to the expression', async(() => { it('should add and remove classes based on changes to the expression', async(() => {
fixture = createTestComponent('<div [ngClass]="arrExpr"></div>'); fixture = createTestComponent('<div [ngClass]="arrExpr"></div>');
const arrExpr = getComponent().arrExpr; let arrExpr = getComponent().arrExpr;
detectChangesAndExpectClassName('foo'); detectChangesAndExpectClassName('foo');
arrExpr.push('bar'); arrExpr.push('bar');
@ -186,13 +187,6 @@ export function main() {
getComponent().arrExpr = ['foo bar baz foobar']; getComponent().arrExpr = ['foo bar baz foobar'];
detectChangesAndExpectClassName('foo bar baz foobar'); detectChangesAndExpectClassName('foo bar baz foobar');
})); }));
it('should throw with descriptive error message when CSS class is not a string', () => {
fixture = createTestComponent(`<div [ngClass]="['foo', {}]"></div>`);
expect(() => fixture.detectChanges())
.toThrowError(
/NgClass can only toggle CSS classes expressed as strings, got \[object Object\]/);
});
}); });
describe('expressions evaluating to sets', () => { describe('expressions evaluating to sets', () => {
@ -259,7 +253,7 @@ export function main() {
it('should co-operate with the class attribute', async(() => { it('should co-operate with the class attribute', async(() => {
fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>'); fixture = createTestComponent('<div [ngClass]="objExpr" class="init foo"></div>');
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName('init foo bar'); detectChangesAndExpectClassName('init foo bar');
@ -273,7 +267,7 @@ export function main() {
it('should co-operate with the interpolated class attribute', async(() => { it('should co-operate with the interpolated class attribute', async(() => {
fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`); fixture = createTestComponent(`<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName(`init foo bar`); detectChangesAndExpectClassName(`init foo bar`);
@ -288,7 +282,7 @@ export function main() {
it('should co-operate with the class attribute and binding to it', async(() => { it('should co-operate with the class attribute and binding to it', async(() => {
fixture = fixture =
createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`); createTestComponent(`<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
objExpr['bar'] = true; objExpr['bar'] = true;
detectChangesAndExpectClassName(`init foo bar`); detectChangesAndExpectClassName(`init foo bar`);
@ -304,7 +298,7 @@ export function main() {
const template = const template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>'; '<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
const objExpr = getComponent().objExpr; let objExpr = getComponent().objExpr;
detectChangesAndExpectClassName('init foo baz'); detectChangesAndExpectClassName('init foo baz');
@ -322,7 +316,7 @@ export function main() {
async(() => { async(() => {
const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>'; const template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
const cmp = getComponent(); let cmp = getComponent();
detectChangesAndExpectClassName('init foo'); detectChangesAndExpectClassName('init foo');

View File

@ -7,8 +7,8 @@
*/ */
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Attribute, Component, Directive} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
@ -32,7 +32,7 @@ export function main() {
}); });
describe('switch value changes', () => { describe('switch value changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a</li></template>' + '<template ngSwitchCase="a"><li>when a</li></template>' +
@ -48,9 +48,9 @@ export function main() {
getComponent().switchValue = 'b'; getComponent().switchValue = 'b';
detectChangesAndExpectText('when b'); detectChangesAndExpectText('when b');
}); }));
it('should switch amongst when values with fallback to default', () => { it('should switch amongst when values with fallback to default', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchCase \'a\'">when a</li>' + '<li template="ngSwitchCase \'a\'">when a</li>' +
@ -68,9 +68,9 @@ export function main() {
getComponent().switchValue = 'c'; getComponent().switchValue = 'c';
detectChangesAndExpectText('when default'); detectChangesAndExpectText('when default');
}); }));
it('should support multiple whens with the same value', () => { it('should support multiple whens with the same value', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<template ngSwitchCase="a"><li>when a1;</li></template>' +
@ -89,11 +89,11 @@ export function main() {
getComponent().switchValue = 'b'; getComponent().switchValue = 'b';
detectChangesAndExpectText('when b1;when b2;'); detectChangesAndExpectText('when b1;when b2;');
}); }));
}); });
describe('when values changes', () => { describe('when values changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', async(() => {
const template = '<div>' + const template = '<div>' +
'<ul [ngSwitch]="switchValue">' + '<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' + '<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
@ -118,67 +118,7 @@ export function main() {
getComponent().when1 = 'd'; getComponent().when1 = 'd';
detectChangesAndExpectText('when default;'); detectChangesAndExpectText('when default;');
}); }));
});
describe('corner cases', () => {
it('should not create the default case if another case matches', () => {
const log: string[] = [];
@Directive({selector: '[test]'})
class TestDirective {
constructor(@Attribute('test') test: string) { log.push(test); }
}
const template = '<div [ngSwitch]="switchValue">' +
'<div *ngSwitchCase="\'a\'" test="aCase"></div>' +
'<div *ngSwitchDefault test="defaultCase"></div>' +
'</div>';
TestBed.configureTestingModule({declarations: [TestDirective]});
TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(log).toEqual(['aCase']);
});
it('should create the default case if there is no other case', () => {
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
});
it('should allow defaults before cases', () => {
const template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
'</ul></div>';
fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;');
getComponent().switchValue = 'a';
detectChangesAndExpectText('when a1;when a2;');
getComponent().switchValue = 'b';
detectChangesAndExpectText('when b1;when b2;');
});
}); });
}); });
} }

View File

@ -19,10 +19,10 @@ export function main() {
describe('AsyncPipe', () => { describe('AsyncPipe', () => {
describe('Observable', () => { describe('Observable', () => {
let emitter: EventEmitter<any>; var emitter: EventEmitter<any>;
let pipe: AsyncPipe; var pipe: AsyncPipe;
let ref: any; var ref: any;
const message = {}; var message = {};
beforeEach(() => { beforeEach(() => {
emitter = new EventEmitter(); emitter = new EventEmitter();
@ -62,7 +62,7 @@ export function main() {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
pipe.transform(emitter); pipe.transform(emitter);
const newEmitter = new EventEmitter(); var newEmitter = new EventEmitter();
expect(pipe.transform(newEmitter)).toBe(null); expect(pipe.transform(newEmitter)).toBe(null);
emitter.emit(message); emitter.emit(message);
@ -104,14 +104,14 @@ export function main() {
}); });
describe('Promise', () => { describe('Promise', () => {
const message = new Object(); var message = new Object();
let pipe: AsyncPipe; var pipe: AsyncPipe;
let resolve: (result: any) => void; var resolve: (result: any) => void;
let reject: (error: any) => void; var reject: (error: any) => void;
let promise: Promise<any>; var promise: Promise<any>;
let ref: SpyChangeDetectorRef; var ref: SpyChangeDetectorRef;
// adds longer timers for passing tests in IE // adds longer timers for passing tests in IE
const timer = (getDOM() && browserDetection.isIE) ? 50 : 10; var timer = (getDOM() && browserDetection.isIE) ? 50 : 10;
beforeEach(() => { beforeEach(() => {
promise = new Promise((res, rej) => { promise = new Promise((res, rej) => {
@ -154,7 +154,7 @@ export function main() {
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
pipe.transform(promise); pipe.transform(promise);
promise = new Promise<any>(() => {}); var promise = new Promise<any>(() => {});
expect(pipe.transform(promise)).toBe(null); expect(pipe.transform(promise)).toBe(null);
// this should not affect the pipe, so it should return WrappedValue // this should not affect the pipe, so it should return WrappedValue
@ -168,7 +168,7 @@ export function main() {
it('should request a change detection check upon receiving a new value', it('should request a change detection check upon receiving a new value',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const markForCheck = ref.spy('markForCheck'); var markForCheck = ref.spy('markForCheck');
pipe.transform(promise); pipe.transform(promise);
resolve(message); resolve(message);
@ -202,14 +202,14 @@ export function main() {
describe('null', () => { describe('null', () => {
it('should return null when given null', () => { it('should return null when given null', () => {
const pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(pipe.transform(null)).toEqual(null); expect(pipe.transform(null)).toEqual(null);
}); });
}); });
describe('other types', () => { describe('other types', () => {
it('should throw when given an invalid object', () => { it('should throw when given an invalid object', () => {
const pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(() => pipe.transform(<any>'some bogus object')).toThrowError(); expect(() => pipe.transform(<any>'some bogus object')).toThrowError();
}); });
}); });

View File

@ -13,14 +13,8 @@ import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
let date: Date; let date: Date;
const isoStringWithoutTime = '2015-01-01';
let pipe: DatePipe; let pipe: DatePipe;
// Check the transformation of a date into a pattern
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
expect(pipe.transform(date, pattern)).toEqual(output);
}
// TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs // TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs
// In some old versions of Chrome in Android emulators, time formatting returns dates in the // In some old versions of Chrome in Android emulators, time formatting returns dates in the
// timezone of the VM host, // timezone of the VM host,
@ -49,143 +43,85 @@ export function main() {
() => { expect(() => pipe.transform('123456789.11')).not.toThrow(); }); () => { expect(() => pipe.transform('123456789.11')).not.toThrow(); });
it('should support ISO string', it('should support ISO string',
() => expect(() => pipe.transform('2015-06-15T21:43:11Z')).not.toThrow()); () => { expect(() => pipe.transform('2015-06-15T21:43:11Z')).not.toThrow(); });
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null)); it('should not support other objects', () => {
expect(() => pipe.transform({})).toThrow();
it('should support ISO string without time', expect(() => pipe.transform('')).toThrow();
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); }); });
it('should not support other objects',
() => { expect(() => pipe.transform({})).toThrowError(); });
}); });
describe('transform', () => { describe('transform', () => {
it('should format each component correctly', () => { it('should format each component correctly', () => {
const dateFixtures: any = { expect(pipe.transform(date, 'y')).toEqual('2015');
'y': '2015', expect(pipe.transform(date, 'yy')).toEqual('15');
'yy': '15', expect(pipe.transform(date, 'M')).toEqual('6');
'M': '6', expect(pipe.transform(date, 'MM')).toEqual('06');
'MM': '06', expect(pipe.transform(date, 'MMM')).toEqual('Jun');
'MMM': 'Jun', expect(pipe.transform(date, 'MMMM')).toEqual('June');
'MMMM': 'June', expect(pipe.transform(date, 'd')).toEqual('15');
'd': '15', expect(pipe.transform(date, 'E')).toEqual('Mon');
'dd': '15', expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
'EEE': 'Mon',
'EEEE': 'Monday'
};
const isoStringWithoutTimeFixtures: any = {
'y': '2015',
'yy': '15',
'M': '1',
'MM': '01',
'MMM': 'Jan',
'MMMM': 'January',
'd': '1',
'dd': '01',
'EEE': 'Thu',
'EEEE': 'Thursday'
};
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['h'] = '9'; expect(pipe.transform(date, 'h')).toEqual('9');
dateFixtures['hh'] = '09'; expect(pipe.transform(date, 'hh')).toEqual('09');
dateFixtures['j'] = '9 AM'; expect(pipe.transform(date, 'j')).toEqual('9 AM');
isoStringWithoutTimeFixtures['h'] = '12';
isoStringWithoutTimeFixtures['hh'] = '12';
isoStringWithoutTimeFixtures['j'] = '12 AM';
} }
// IE and Edge can't format a date to minutes and seconds without hours // IE and Edge can't format a date to minutes and seconds without hours
if (!browserDetection.isEdge && !browserDetection.isIE || if (!browserDetection.isEdge && !browserDetection.isIE ||
!browserDetection.supportsNativeIntlApi) { !browserDetection.supportsNativeIntlApi) {
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['HH'] = '09'; expect(pipe.transform(date, 'HH')).toEqual('09');
isoStringWithoutTimeFixtures['HH'] = '00';
} }
dateFixtures['E'] = 'M'; expect(pipe.transform(date, 'm')).toEqual('3');
dateFixtures['L'] = 'J'; expect(pipe.transform(date, 's')).toEqual('1');
dateFixtures['m'] = '3'; expect(pipe.transform(date, 'mm')).toEqual('03');
dateFixtures['s'] = '1'; expect(pipe.transform(date, 'ss')).toEqual('01');
dateFixtures['mm'] = '03';
dateFixtures['ss'] = '01';
isoStringWithoutTimeFixtures['m'] = '0';
isoStringWithoutTimeFixtures['s'] = '0';
isoStringWithoutTimeFixtures['mm'] = '00';
isoStringWithoutTimeFixtures['ss'] = '00';
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
});
expect(pipe.transform(date, 'Z')).toBeDefined(); expect(pipe.transform(date, 'Z')).toBeDefined();
}); });
it('should format common multi component patterns', () => { it('should format common multi component patterns', () => {
const dateFixtures: any = { expect(pipe.transform(date, 'E, M/d/y')).toEqual('Mon, 6/15/2015');
'EEE, M/d/y': 'Mon, 6/15/2015', expect(pipe.transform(date, 'E, M/d')).toEqual('Mon, 6/15');
'EEE, M/d': 'Mon, 6/15', expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15');
'MMM d': 'Jun 15', expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015');
'dd/MM/yyyy': '15/06/2015', expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
'MM/dd/yyyy': '06/15/2015', expect(pipe.transform(date, 'yMEd')).toEqual('20156Mon15');
'yMEEEd': '20156Mon15', expect(pipe.transform(date, 'MEd')).toEqual('6Mon15');
'MEEEd': '6Mon15', expect(pipe.transform(date, 'MMMd')).toEqual('Jun15');
'MMMd': 'Jun15', expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015');
'yMMMMEEEEd': 'Monday, June 15, 2015'
};
// IE and Edge can't format a date to minutes and seconds without hours // IE and Edge can't format a date to minutes and seconds without hours
if (!browserDetection.isEdge && !browserDetection.isIE || if (!browserDetection.isEdge && !browserDetection.isIE ||
!browserDetection.supportsNativeIntlApi) { !browserDetection.supportsNativeIntlApi) {
dateFixtures['ms'] = '31'; expect(pipe.transform(date, 'ms')).toEqual('31');
} }
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['jm'] = '9:03 AM'; expect(pipe.transform(date, 'jm')).toEqual('9:03 AM');
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
}); });
it('should format with pattern aliases', () => { it('should format with pattern aliases', () => {
const dateFixtures: any = {
'MM/dd/yyyy': '06/15/2015',
'fullDate': 'Monday, June 15, 2015',
'longDate': 'June 15, 2015',
'mediumDate': 'Jun 15, 2015',
'shortDate': '6/15/2015'
};
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
// IE and Edge do not add a coma after the year in these 2 cases // IE and Edge do not add a coma after the year in these 2 cases
if ((browserDetection.isEdge || browserDetection.isIE) && if ((browserDetection.isEdge || browserDetection.isIE) &&
browserDetection.supportsNativeIntlApi) { browserDetection.supportsNativeIntlApi) {
dateFixtures['medium'] = 'Jun 15, 2015 9:03:01 AM'; expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015 9:03:01 AM');
dateFixtures['short'] = '6/15/2015 9:03 AM'; expect(pipe.transform(date, 'short')).toEqual('6/15/2015 9:03 AM');
} else { } else {
dateFixtures['medium'] = 'Jun 15, 2015, 9:03:01 AM'; expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015, 9:03:01 AM');
dateFixtures['short'] = '6/15/2015, 9:03 AM'; expect(pipe.transform(date, 'short')).toEqual('6/15/2015, 9:03 AM');
} }
} }
expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
expect(pipe.transform(date, 'fullDate')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, 'longDate')).toEqual('June 15, 2015');
expect(pipe.transform(date, 'mediumDate')).toEqual('Jun 15, 2015');
expect(pipe.transform(date, 'shortDate')).toEqual('6/15/2015');
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
dateFixtures['mediumTime'] = '9:03:01 AM'; expect(pipe.transform(date, 'mediumTime')).toEqual('9:03:01 AM');
dateFixtures['shortTime'] = '9:03 AM'; expect(pipe.transform(date, 'shortTime')).toEqual('9:03 AM');
} }
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
}); });
it('should remove bidi control characters', it('should remove bidi control characters',

View File

@ -12,10 +12,10 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
export function main() { export function main() {
describe('I18nPluralPipe', () => { describe('I18nPluralPipe', () => {
let localization: NgLocalization; var localization: NgLocalization;
let pipe: I18nPluralPipe; var pipe: I18nPluralPipe;
const mapping = { var mapping = {
'=0': 'No messages.', '=0': 'No messages.',
'=1': 'One message.', '=1': 'One message.',
'many': 'Many messages.', 'many': 'Many messages.',
@ -32,27 +32,27 @@ export function main() {
describe('transform', () => { describe('transform', () => {
it('should return 0 text if value is 0', () => { it('should return 0 text if value is 0', () => {
const val = pipe.transform(0, mapping); var val = pipe.transform(0, mapping);
expect(val).toEqual('No messages.'); expect(val).toEqual('No messages.');
}); });
it('should return 1 text if value is 1', () => { it('should return 1 text if value is 1', () => {
const val = pipe.transform(1, mapping); var val = pipe.transform(1, mapping);
expect(val).toEqual('One message.'); expect(val).toEqual('One message.');
}); });
it('should return category messages', () => { it('should return category messages', () => {
const val = pipe.transform(4, mapping); var val = pipe.transform(4, mapping);
expect(val).toEqual('Many messages.'); expect(val).toEqual('Many messages.');
}); });
it('should interpolate the value into the text where indicated', () => { it('should interpolate the value into the text where indicated', () => {
const val = pipe.transform(6, mapping); var val = pipe.transform(6, mapping);
expect(val).toEqual('There are 6 messages, that is 6.'); expect(val).toEqual('There are 6 messages, that is 6.');
}); });
it('should use "" if value is undefined', () => { it('should use "" if value is undefined', () => {
const val = pipe.transform(void(0), mapping); var val = pipe.transform(void(0), mapping);
expect(val).toEqual(''); expect(val).toEqual('');
}); });

View File

@ -8,35 +8,40 @@
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';
export function main() { export function main() {
describe('I18nSelectPipe', () => { describe('I18nSelectPipe', () => {
const pipe: I18nSelectPipe = new I18nSelectPipe(); var pipe: I18nSelectPipe;
const mapping = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'}; var mapping = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'};
beforeEach(() => { pipe = new I18nSelectPipe(); });
it('should be marked as pure', it('should be marked as pure',
() => { expect(new PipeResolver().resolve(I18nSelectPipe).pure).toEqual(true); }); () => { expect(new PipeResolver().resolve(I18nSelectPipe).pure).toEqual(true); });
describe('transform', () => { describe('transform', () => {
it('should return the "male" text if value is "male"', () => { it('should return male text if value is male', () => {
const val = pipe.transform('male', mapping); var val = pipe.transform('male', mapping);
expect(val).toEqual('Invite him.'); expect(val).toEqual('Invite him.');
}); });
it('should return the "female" text if value is "female"', () => { it('should return female text if value is female', () => {
const val = pipe.transform('female', mapping); var val = pipe.transform('female', mapping);
expect(val).toEqual('Invite her.'); expect(val).toEqual('Invite her.');
}); });
it('should return the "other" text if value is neither "male" nor "female"', it('should return "" if value is anything other than male or female', () => {
() => { expect(pipe.transform('Anything else', mapping)).toEqual('Invite them.'); }); var val = pipe.transform('Anything else', mapping);
expect(val).toEqual('');
it('should return an empty text if value is null or undefined', () => {
expect(pipe.transform(null, mapping)).toEqual('');
expect(pipe.transform(void 0, mapping)).toEqual('');
}); });
it('should throw on bad arguments', it('should use "" if value is undefined', () => {
var val = pipe.transform(void(0), mapping);
expect(val).toEqual('');
});
it('should not support bad arguments',
() => { expect(() => pipe.transform('male', <any>'hey')).toThrowError(); }); () => { expect(() => pipe.transform('male', <any>'hey')).toThrowError(); });
}); });

View File

@ -13,10 +13,10 @@ import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('JsonPipe', () => { describe('JsonPipe', () => {
const regNewLine = '\n'; var regNewLine = '\n';
let inceptionObj: any; var inceptionObj: any;
let inceptionObjString: string; var inceptionObjString: string;
let pipe: JsonPipe; var pipe: JsonPipe;
function normalize(obj: string): string { return obj.replace(regNewLine, ''); } function normalize(obj: string): string { return obj.replace(regNewLine, ''); }
@ -39,14 +39,14 @@ export function main() {
() => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); }); () => { expect(pipe.transform(inceptionObj)).toEqual(inceptionObjString); });
it('should return JSON-formatted string even when normalized', () => { it('should return JSON-formatted string even when normalized', () => {
const dream1 = normalize(pipe.transform(inceptionObj)); var dream1 = normalize(pipe.transform(inceptionObj));
const dream2 = normalize(inceptionObjString); var dream2 = normalize(inceptionObjString);
expect(dream1).toEqual(dream2); expect(dream1).toEqual(dream2);
}); });
it('should return JSON-formatted string similar to Json.stringify', () => { it('should return JSON-formatted string similar to Json.stringify', () => {
const dream1 = normalize(pipe.transform(inceptionObj)); var dream1 = normalize(pipe.transform(inceptionObj));
const dream2 = normalize(JSON.stringify(inceptionObj, null, 2)); var dream2 = normalize(JSON.stringify(inceptionObj, null, 2));
expect(dream1).toEqual(dream2); expect(dream1).toEqual(dream2);
}); });
}); });
@ -63,8 +63,8 @@ export function main() {
}); });
it('should work with mutable objects', async(() => { it('should work with mutable objects', async(() => {
const fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1]; let mutable: number[] = [1];
fixture.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('[\n 1\n]'); expect(fixture.nativeElement).toHaveText('[\n 1\n]');

View File

@ -11,9 +11,9 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
export function main() { export function main() {
describe('LowerCasePipe', () => { describe('LowerCasePipe', () => {
let upper: string; var upper: string;
let lower: string; var lower: string;
let pipe: LowerCasePipe; var pipe: LowerCasePipe;
beforeEach(() => { beforeEach(() => {
lower = 'something'; lower = 'something';
@ -23,14 +23,14 @@ export function main() {
describe('transform', () => { describe('transform', () => {
it('should return lowercase', () => { it('should return lowercase', () => {
const val = pipe.transform(upper); var val = pipe.transform(upper);
expect(val).toEqual(lower); expect(val).toEqual(lower);
}); });
it('should lowercase when there is a new value', () => { it('should lowercase when there is a new value', () => {
const val = pipe.transform(upper); var val = pipe.transform(upper);
expect(val).toEqual(lower); expect(val).toEqual(lower);
const val2 = pipe.transform('WAT'); var val2 = pipe.transform('WAT');
expect(val2).toEqual('wat'); expect(val2).toEqual('wat');
}); });

View File

@ -13,7 +13,7 @@ import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('Number pipes', () => { describe('Number pipes', () => {
describe('DecimalPipe', () => { describe('DecimalPipe', () => {
let pipe: DecimalPipe; var pipe: DecimalPipe;
beforeEach(() => { pipe = new DecimalPipe('en-US'); }); beforeEach(() => { pipe = new DecimalPipe('en-US'); });
@ -44,7 +44,7 @@ export function main() {
}); });
describe('PercentPipe', () => { describe('PercentPipe', () => {
let pipe: PercentPipe; var pipe: PercentPipe;
beforeEach(() => { pipe = new PercentPipe('en-US'); }); beforeEach(() => { pipe = new PercentPipe('en-US'); });
@ -60,7 +60,7 @@ export function main() {
}); });
describe('CurrencyPipe', () => { describe('CurrencyPipe', () => {
let pipe: CurrencyPipe; var pipe: CurrencyPipe;
beforeEach(() => { pipe = new CurrencyPipe('en-US'); }); beforeEach(() => { pipe = new CurrencyPipe('en-US'); });

View File

@ -13,9 +13,9 @@ import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('SlicePipe', () => { describe('SlicePipe', () => {
let list: number[]; var list: number[];
let str: string; var str: string;
let pipe: SlicePipe; var pipe: SlicePipe;
beforeEach(() => { beforeEach(() => {
list = [1, 2, 3, 4, 5]; list = [1, 2, 3, 4, 5];
@ -93,8 +93,8 @@ export function main() {
}); });
it('should work with mutable arrays', async(() => { it('should work with mutable arrays', async(() => {
const fixture = TestBed.createComponent(TestComp); let fixture = TestBed.createComponent(TestComp);
const mutable: number[] = [1, 2]; let mutable: number[] = [1, 2];
fixture.componentInstance.data = mutable; fixture.componentInstance.data = mutable;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('2'); expect(fixture.nativeElement).toHaveText('2');

View File

@ -11,9 +11,9 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
export function main() { export function main() {
describe('UpperCasePipe', () => { describe('UpperCasePipe', () => {
let upper: string; var upper: string;
let lower: string; var lower: string;
let pipe: UpperCasePipe; var pipe: UpperCasePipe;
beforeEach(() => { beforeEach(() => {
lower = 'something'; lower = 'something';
@ -24,14 +24,14 @@ export function main() {
describe('transform', () => { describe('transform', () => {
it('should return uppercase', () => { it('should return uppercase', () => {
const val = pipe.transform(lower); var val = pipe.transform(lower);
expect(val).toEqual(upper); expect(val).toEqual(upper);
}); });
it('should uppercase when there is a new value', () => { it('should uppercase when there is a new value', () => {
const val = pipe.transform(lower); var val = pipe.transform(lower);
expect(val).toEqual(upper); expect(val).toEqual(upper);
const val2 = pipe.transform('wat'); var val2 = pipe.transform('wat');
expect(val2).toEqual('WAT'); expect(val2).toEqual('WAT');
}); });

View File

@ -34,8 +34,8 @@ export class SpyLocation implements Location {
path(): string { return this._history[this._historyIndex].path; } path(): string { return this._history[this._historyIndex].path; }
isCurrentPathEqualTo(path: string, query: string = ''): boolean { isCurrentPathEqualTo(path: string, query: string = ''): boolean {
const givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path; var givenPath = path.endsWith('/') ? path.substring(0, path.length - 1) : path;
const currPath = var currPath =
this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path(); this.path().endsWith('/') ? this.path().substring(0, this.path().length - 1) : this.path();
return currPath == givenPath + (query.length > 0 ? ('?' + query) : ''); return currPath == givenPath + (query.length > 0 ? ('?' + query) : '');
@ -66,12 +66,12 @@ export class SpyLocation implements Location {
this._history.push(new LocationState(path, query)); this._history.push(new LocationState(path, query));
this._historyIndex = this._history.length - 1; this._historyIndex = this._history.length - 1;
const locationState = this._history[this._historyIndex - 1]; var locationState = this._history[this._historyIndex - 1];
if (locationState.path == path && locationState.query == query) { if (locationState.path == path && locationState.query == query) {
return; return;
} }
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push(url); this.urlChanges.push(url);
this._subject.emit({'url': url, 'pop': false}); this._subject.emit({'url': url, 'pop': false});
} }
@ -79,7 +79,7 @@ export class SpyLocation implements Location {
replaceState(path: string, query: string = '') { replaceState(path: string, query: string = '') {
path = this.prepareExternalUrl(path); path = this.prepareExternalUrl(path);
const history = this._history[this._historyIndex]; var history = this._history[this._historyIndex];
if (history.path == path && history.query == query) { if (history.path == path && history.query == query) {
return; return;
} }
@ -87,7 +87,7 @@ export class SpyLocation implements Location {
history.path = path; history.path = path;
history.query = query; history.query = query;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.urlChanges.push('replace: ' + url); this.urlChanges.push('replace: ' + url);
} }

View File

@ -44,20 +44,20 @@ export class MockLocationStrategy extends LocationStrategy {
pushState(ctx: any, title: string, path: string, query: string): void { pushState(ctx: any, title: string, path: string, query: string): void {
this.internalTitle = title; this.internalTitle = title;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.internalPath = url; this.internalPath = url;
const externalUrl = this.prepareExternalUrl(url); var externalUrl = this.prepareExternalUrl(url);
this.urlChanges.push(externalUrl); this.urlChanges.push(externalUrl);
} }
replaceState(ctx: any, title: string, path: string, query: string): void { replaceState(ctx: any, title: string, path: string, query: string): void {
this.internalTitle = title; this.internalTitle = title;
const url = path + (query.length > 0 ? ('?' + query) : ''); var url = path + (query.length > 0 ? ('?' + query) : '');
this.internalPath = url; this.internalPath = url;
const externalUrl = this.prepareExternalUrl(url); var externalUrl = this.prepareExternalUrl(url);
this.urlChanges.push('replace: ' + externalUrl); this.urlChanges.push('replace: ' + externalUrl);
} }
@ -68,7 +68,7 @@ export class MockLocationStrategy extends LocationStrategy {
back(): void { back(): void {
if (this.urlChanges.length > 0) { if (this.urlChanges.length > 0) {
this.urlChanges.pop(); this.urlChanges.pop();
const nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : ''; var nextUrl = this.urlChanges.length > 0 ? this.urlChanges[this.urlChanges.length - 1] : '';
this.simulatePopState(nextUrl); this.simulatePopState(nextUrl);
} }
} }

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
*/ */
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen'; export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host';
export {Extractor} from './src/extractor'; export {Extractor} from './src/extractor';
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
export * from '@angular/tsc-wrapped'; export * from '@angular/tsc-wrapped';

View File

@ -8,7 +8,7 @@
import {ApplicationRef, NgModule} from '@angular/core'; import {ApplicationRef, NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {ServerModule} from '@angular/platform-server'; import {BrowserModule} from '@angular/platform-browser';
import {MdButtonModule} from '@angular2-material/button'; import {MdButtonModule} from '@angular2-material/button';
import {ThirdpartyModule} from '../third_party_src/module'; import {ThirdpartyModule} from '../third_party_src/module';
@ -48,7 +48,7 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
ComponentUsingThirdParty, ComponentUsingThirdParty,
], ],
imports: [ imports: [
ServerModule, BrowserModule,
FormsModule, FormsModule,
MdButtonModule, MdButtonModule,
ModuleUsingCustomElements, ModuleUsingCustomElements,

View File

@ -8,6 +8,7 @@
import {LowerCasePipe, NgIf} from '@angular/common'; import {LowerCasePipe, NgIf} from '@angular/common';
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, Input, ModuleWithProviders, NgModule, OpaqueToken, Pipe} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Directive, Inject, Injectable, Input, ModuleWithProviders, NgModule, OpaqueToken, Pipe} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@Injectable() @Injectable()
export class SomeService { export class SomeService {

View File

@ -19,9 +19,9 @@ describe('template codegen output', () => {
it('should apply the animate states to the element', (done) => { it('should apply the animate states to the element', (done) => {
const compFixture = createComponent(AnimateCmp); const compFixture = createComponent(AnimateCmp);
const debugElement = compFixture.debugElement; var debugElement = compFixture.debugElement;
const targetDebugElement = findTargetElement(<DebugElement>debugElement); var targetDebugElement = findTargetElement(<DebugElement>debugElement);
compFixture.componentInstance.setAsOpen(); compFixture.componentInstance.setAsOpen();
compFixture.detectChanges(); compFixture.detectChanges();
@ -45,9 +45,9 @@ describe('template codegen output', () => {
it('should apply the default animate state to the element', (done) => { it('should apply the default animate state to the element', (done) => {
const compFixture = createComponent(AnimateCmp); const compFixture = createComponent(AnimateCmp);
const debugElement = compFixture.debugElement; var debugElement = compFixture.debugElement;
const targetDebugElement = findTargetElement(<DebugElement>debugElement); var targetDebugElement = findTargetElement(<DebugElement>debugElement);
compFixture.componentInstance.setAsSomethingElse(); compFixture.componentInstance.setAsSomethingElse();
compFixture.detectChanges(); compFixture.detectChanges();

View File

@ -15,8 +15,8 @@ import {createComponent} from './util';
describe('content projection', () => { describe('content projection', () => {
it('should support entryComponents in components', () => { it('should support entryComponents in components', () => {
const compFixture = createComponent(CompWithEntryComponents); var compFixture = createComponent(CompWithEntryComponents);
const cf = compFixture.componentInstance.cfr.resolveComponentFactory(BasicComp); var cf = compFixture.componentInstance.cfr.resolveComponentFactory(BasicComp);
expect(cf.componentType).toBe(BasicComp); expect(cf.componentType).toBe(BasicComp);
}); });

View File

@ -13,10 +13,10 @@ import {createComponent} from './util';
describe('content projection', () => { describe('content projection', () => {
it('should support basic content projection', () => { it('should support basic content projection', () => {
const mainCompFixture = createComponent(ProjectingComp); var mainCompFixture = createComponent(ProjectingComp);
const debugElement = mainCompFixture.debugElement; var debugElement = mainCompFixture.debugElement;
const compWithProjection = debugElement.query(By.directive(CompWithNgContent)); var compWithProjection = debugElement.query(By.directive(CompWithNgContent));
expect(compWithProjection.children.length).toBe(1); expect(compWithProjection.children.length).toBe(1);
expect(compWithProjection.children[0].attributes['greeting']).toEqual('Hello world!'); expect(compWithProjection.children[0].attributes['greeting']).toEqual('Hello world!');
}); });

View File

@ -14,18 +14,18 @@ import {createComponent} from './util';
describe('child queries', () => { describe('child queries', () => {
it('should support compiling child queries', () => { it('should support compiling child queries', () => {
const childQueryCompFixture = createComponent(CompWithChildQuery); var childQueryCompFixture = createComponent(CompWithChildQuery);
const debugElement = childQueryCompFixture.debugElement; var debugElement = childQueryCompFixture.debugElement;
const compWithChildren = debugElement.query(By.directive(CompWithChildQuery)); var compWithChildren = debugElement.query(By.directive(CompWithChildQuery));
expect(childQueryCompFixture.componentInstance.child).toBeDefined(); expect(childQueryCompFixture.componentInstance.child).toBeDefined();
expect(childQueryCompFixture.componentInstance.child instanceof CompForChildQuery).toBe(true); expect(childQueryCompFixture.componentInstance.child instanceof CompForChildQuery).toBe(true);
}); });
it('should support compiling children queries', () => { it('should support compiling children queries', () => {
const childQueryCompFixture = createComponent(CompWithChildQuery); var childQueryCompFixture = createComponent(CompWithChildQuery);
const debugElement = childQueryCompFixture.debugElement; var debugElement = childQueryCompFixture.debugElement;
const compWithChildren = debugElement.query(By.directive(CompWithChildQuery)); var compWithChildren = debugElement.query(By.directive(CompWithChildQuery));
childQueryCompFixture.detectChanges(); childQueryCompFixture.detectChanges();

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js" "ng-xi18n": "./src/extract_i18n.js"
}, },
"dependencies": { "dependencies": {
"@angular/tsc-wrapped": "^0.4.0", "@angular/tsc-wrapped": "^0.3.0",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"minimist": "^1.2.0" "minimist": "^1.2.0"
}, },

View File

@ -13,21 +13,23 @@
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext} from './compiler_host'; import {PathMappedReflectorHost} from './path_mapped_reflector_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core'; import {Console} from './private_import_core';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
const nodeFs = require('fs');
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
const PREAMBLE = `/** const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular 2 template compiler. * This file is generated by the Angular 2 template compiler.
* Do not edit. * Do not edit.
* @suppress {suspiciousCode,uselessCode,missingProperties}
*/ */
/* tslint:disable */ /* tslint:disable */
@ -36,8 +38,8 @@ const PREAMBLE = `/**
export class CodeGenerator { export class CodeGenerator {
constructor( constructor(
private options: AngularCompilerOptions, private program: ts.Program, private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private compiler: compiler.AotCompiler, public host: ts.CompilerHost, private staticReflector: StaticReflector,
private ngCompilerHost: CompilerHost) {} private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
// Write codegen in a directory structure matching the sources. // Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string { private calculateEmitPath(filePath: string): string {
@ -62,11 +64,11 @@ export class CodeGenerator {
return path.join(this.options.genDir, relativePath); return path.join(this.options.genDir, relativePath);
} }
codegen(): Promise<any> { codegen(options: {transitiveModules: boolean}): Promise<any> {
return this.compiler const staticSymbols =
.compileAll(this.program.getSourceFiles().map( extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
.then(generatedModules => { return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
@ -78,14 +80,17 @@ export class CodeGenerator {
static create( static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext, compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
ngCompilerHost?: CompilerHost): CodeGenerator { resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator {
if (!ngCompilerHost) { resourceLoader = resourceLoader || {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; get: (s: string) => {
ngCompilerHost = usePathMapping ? if (!compilerHost.fileExists(s)) {
new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) : // TODO: We should really have a test for error cases like this!
new CompilerHost(program, tsCompilerHost, options, compilerHostContext); throw new Error(`Compilation failed. Resource file not found: ${s}`);
} }
return Promise.resolve(compilerHost.readFile(s));
}
};
const transFile = cliOptions.i18nFile; const transFile = cliOptions.i18nFile;
const locale = cliOptions.locale; const locale = cliOptions.locale;
let transContent: string = ''; let transContent: string = '';
@ -94,20 +99,85 @@ export class CodeGenerator {
throw new Error( throw new Error(
`The translation file (${transFile}) locale must be provided. Use the --locale option.`); `The translation file (${transFile}) locale must be provided. Use the --locale option.`);
} }
transContent = readFileSync(transFile, 'utf8'); transContent = nodeFs.readFileSync(transFile, 'utf8');
} }
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
debug: options.debug === true, const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
translations: transContent, if (!reflectorHost) {
i18nFormat: cliOptions.i18nFormat, const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
locale: cliOptions.locale, reflectorHost = usePathMapping ?
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) :
GENERATED_FILES new ReflectorHost(program, compilerHost, options, reflectorHostContext);
}
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser =
new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat);
const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
}); });
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost); const normalizer =
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
const expressionParser = new compiler.Parser(new compiler.Lexer());
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
const console = new Console();
const tmplParser = new compiler.TemplateParser(
expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, staticReflector);
// TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
new compiler.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
cliOptions.locale, cliOptions.i18nFormat);
return new CodeGenerator(
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
} }
} }
export function excludeFilePattern(options: AngularCompilerOptions): RegExp { export function extractProgramSymbols(
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
options: AngularCompilerOptions): StaticSymbol[] {
// Compare with false since the default should be true
const skipFileNames =
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
const staticSymbols: StaticSymbol[] = [];
program.getSourceFiles()
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
.forEach(sourceFile => {
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName);
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSrcPath}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath));
}
});
return staticSymbols;
} }

View File

@ -1,247 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export interface CompilerHostContext {
fileExists(fileName: string): boolean;
directoryExists(directoryName: string): boolean;
readFile(fileName: string): string;
readResource(fileName: string): Promise<string>;
assumeFileExists(fileName: string): void;
}
export class CompilerHost implements AotCompilerHost {
protected metadataCollector = new MetadataCollector();
protected context: CompilerHostContext;
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>();
constructor(
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
protected options: AngularCompilerOptions, context?: CompilerHostContext) {
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
this.context = context || new NodeCompilerHostContext(compilerHost);
const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string) {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
.resolvedModule;
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
};
/**
* We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
* generated | relative | relative | n/a
* existing file | n/a | absolute | relative(*)
*
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
fileNameToModuleName(importedFile: string, containingFile: string): string {
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}
containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);
if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
}
return this.dotRelative(containingDir, importedFile);
}
}
}
private dotRelative(from: string, to: string): string {
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;
}
/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modulse, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}
protected getSourceFile(filePath: string): ts.SourceFile {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return sf;
}
getMetadataFor(filePath: string): ModuleMetadata[] {
if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user refernced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
return;
}
if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath);
}
} else {
const sf = this.getSourceFile(filePath);
const metadata = this.metadataCollector.getMetadata(sf);
return metadata ? [metadata] : [];
}
}
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
let metadatas = this.resolverCache.get(filePath);
if (metadatas) {
return metadatas;
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadatas = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
const v1Metadata = metadatas.find(m => m['version'] === 1);
let v2Metadata = metadatas.find(m => m['version'] === 2);
if (!v2Metadata && v1Metadata) {
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
// as the only difference between the versions is whether all exports are contained in
// the metadata
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
if (v1Metadata.exports) {
v2Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const sourceText = this.context.readFile(dtsFilePath);
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v2Metadata.metadata[prop]) {
v2Metadata.metadata[prop] = exports.metadata[prop];
}
}
}
metadatas.push(v2Metadata);
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
}
export class NodeCompilerHostContext implements CompilerHostContext {
constructor(private host: ts.CompilerHost) {}
private assumedExists: {[fileName: string]: boolean} = {};
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
directoryExists(directoryName: string): boolean {
try {
return fs.statSync(directoryName).isDirectory();
} catch (e) {
return false;
}
}
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
readResource(s: string) {
if (!this.host.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(this.host.readFile(s));
}
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}

View File

@ -24,7 +24,17 @@ import {Extractor} from './extractor';
function extract( function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) { program: ts.Program, host: ts.CompilerHost) {
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host); const resourceLoader: compiler.ResourceLoader = {
get: (s: string) => {
if (!host.fileExists(s)) {
// TODO: We should really have a test for error cases like this!
throw new Error(`Compilation failed. Resource file not found: ${s}`);
}
return Promise.resolve(host.readFile(s));
}
};
const extractor =
Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader);
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract(); const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();

View File

@ -14,28 +14,97 @@
import 'reflect-metadata'; import 'reflect-metadata';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {excludeFilePattern} from './codegen'; import {extractProgramSymbols} from './codegen';
import {CompilerHost} from './compiler_host'; import {ReflectorHost} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
export class Extractor { export class Extractor {
constructor( constructor(
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost, private options: tsc.AngularCompilerOptions, private program: ts.Program,
private program: ts.Program) {} public host: ts.CompilerHost, private staticReflector: StaticReflector,
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
private metadataResolver: compiler.CompileMetadataResolver,
private directiveNormalizer: compiler.DirectiveNormalizer) {}
extract(): Promise<compiler.MessageBundle> { extract(): Promise<compiler.MessageBundle> {
return this.ngExtractor.extract(this.program.getSourceFiles().map( const programSymbols: StaticSymbol[] =
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))); extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
const files =
compiler.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
.files;
const errors: compiler.ParseError[] = [];
const filePromises: Promise<any>[] = [];
files.forEach(file => {
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta.isComponent) {
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
}
});
if (cmpPromises.length) {
const done =
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(...this.messageBundle.updateFromTemplate(
html, file.srcUrl, interpolationConfig));
});
});
filePromises.push(done);
}
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return Promise.all(filePromises).then(_ => this.messageBundle);
} }
static create( static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor { compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options); reflectorHost?: ReflectorHost): Extractor {
const {extractor: ngExtractor} = compiler.Extractor.create( const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
return new Extractor(ngExtractor, ngCompilerHost, program); const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options);
const staticReflector = new StaticReflector(reflectorHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new compiler.CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
const normalizer =
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
const resolver = new compiler.CompileMetadataResolver(
new compiler.NgModuleResolver(staticReflector),
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
elementSchemaRegistry, staticReflector);
// TODO(vicb): implicit tags & attributes
let messageBundle = new compiler.MessageBundle(htmlParser, [], {});
return new Extractor(
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
normalizer);
} }
} }

View File

@ -19,7 +19,9 @@ import {CodeGenerator} from './codegen';
function codegen( function codegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) { host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen(); return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
transitiveModules: true
});
} }
// CLI entry point // CLI entry point

View File

@ -6,35 +6,35 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext} from './compiler_host'; import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticSymbol} from './static_reflector';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/; const DTS = /\.d\.ts$/;
/** /**
* This version of the AotCompilerHost expects that the program will be compiled * This version of the reflector host expects that the program will be compiled
* and executed with a "path mapped" directory structure, where generated files * and executed with a "path mapped" directory structure, where generated files
* are in a parallel tree with the sources, and imported using a `./` relative * are in a parallel tree with the sources, and imported using a `./` relative
* import. This requires using TS `rootDirs` option and also teaching the module * import. This requires using TS `rootDirs` option and also teaching the module
* loader what to do. * loader what to do.
*/ */
export class PathMappedCompilerHost extends CompilerHost { export class PathMappedReflectorHost extends ReflectorHost {
constructor( constructor(
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions, program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
context?: CompilerHostContext) { context?: ReflectorHostContext) {
super(program, compilerHost, options, context); super(program, compilerHost, options, context);
} }
getCanonicalFileName(fileName: string): string { getCanonicalFileName(fileName: string): string {
if (!fileName) return fileName; if (!fileName) return fileName;
// NB: the rootDirs should have been sorted longest-first // NB: the rootDirs should have been sorted longest-first
for (const dir of this.options.rootDirs || []) { for (let dir of this.options.rootDirs || []) {
if (fileName.indexOf(dir) === 0) { if (fileName.indexOf(dir) === 0) {
fileName = fileName.substring(dir.length); fileName = fileName.substring(dir.length);
} }
@ -42,14 +42,7 @@ export class PathMappedCompilerHost extends CompilerHost {
return fileName; return fileName;
} }
moduleNameToFileName(m: string, containingFile: string) { protected resolve(m: string, containingFile: string) {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
}
for (const root of this.options.rootDirs || ['']) { for (const root of this.options.rootDirs || ['']) {
const rootedContainingFile = path.join(root, containingFile); const rootedContainingFile = path.join(root, containingFile);
const resolved = const resolved =
@ -58,7 +51,7 @@ export class PathMappedCompilerHost extends CompilerHost {
if (this.options.traceResolution) { if (this.options.traceResolution) {
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName); console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
} }
return this.getCanonicalFileName(resolved.resolvedFileName); return resolved.resolvedFileName;
} }
} }
} }
@ -69,7 +62,10 @@ export class PathMappedCompilerHost extends CompilerHost {
* Relativize the paths by checking candidate prefixes of the absolute path, to see if * Relativize the paths by checking candidate prefixes of the absolute path, to see if
* they are resolvable by the moduleResolution strategy from the CompilerHost. * they are resolvable by the moduleResolution strategy from the CompilerHost.
*/ */
fileNameToModuleName(importedFile: string, containingFile: string): string { getImportPath(containingFile: string, importedFile: string): string {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');
if (this.options.traceResolution) { if (this.options.traceResolution) {
console.log( console.log(
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile); 'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
@ -86,11 +82,11 @@ export class PathMappedCompilerHost extends CompilerHost {
} }
const resolvable = (candidate: string) => { const resolvable = (candidate: string) => {
const resolved = this.moduleNameToFileName(candidate, importedFile); const resolved = this.getCanonicalFileName(this.resolve(candidate, importedFile));
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, ''); return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
}; };
const importModuleName = importedFile.replace(EXT, ''); let importModuleName = importedFile.replace(EXT, '');
const parts = importModuleName.split(path.sep).filter(p => !!p); const parts = importModuleName.split(path.sep).filter(p => !!p);
let foundRelativeImport: string; let foundRelativeImport: string;
for (let index = parts.length - 1; index >= 0; index--) { for (let index = parts.length - 1; index >= 0; index--) {
@ -116,7 +112,7 @@ export class PathMappedCompilerHost extends CompilerHost {
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`); `Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
} }
getMetadataFor(filePath: string): ModuleMetadata[] { getMetadataFor(filePath: string): ModuleMetadata {
for (const root of this.options.rootDirs || []) { for (const root of this.options.rootDirs || []) {
const rootedPath = path.join(root, filePath); const rootedPath = path.join(root, filePath);
if (!this.compilerHost.fileExists(rootedPath)) { if (!this.compilerHost.fileExists(rootedPath)) {
@ -128,13 +124,16 @@ export class PathMappedCompilerHost extends CompilerHost {
if (DTS.test(rootedPath)) { if (DTS.test(rootedPath)) {
const metadataPath = rootedPath.replace(DTS, '.metadata.json'); const metadataPath = rootedPath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) { if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, rootedPath); const metadata = this.readMetadata(metadataPath);
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
} }
} else { } else {
const sf = this.getSourceFile(rootedPath); const sf = this.program.getSourceFile(rootedPath);
sf.fileName = sf.fileName; if (!sf) {
const metadata = this.metadataCollector.getMetadata(sf); throw new Error(`Source file ${rootedPath} not present in program.`);
return metadata ? [metadata] : []; }
sf.fileName = this.getCanonicalFileName(sf.fileName);
return this.metadataCollector.getMetadata(sf);
} }
} }
} }

View File

@ -16,3 +16,9 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
export type Console = typeof r._Console; export type Console = typeof r._Console;
export var Console: typeof r.Console = r.Console; export var Console: typeof r.Console = r.Console;
export var reflector: typeof r.reflector = r.reflector;
export type SetterFn = typeof r._SetterFn;
export type GetterFn = typeof r._GetterFn;
export type MethodFn = typeof r._MethodFn;

View File

@ -0,0 +1,354 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AssetUrl, ImportGenerator} from '@angular/compiler';
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
export interface ReflectorHostContext {
fileExists(fileName: string): boolean;
directoryExists(directoryName: string): boolean;
readFile(fileName: string): string;
assumeFileExists(fileName: string): void;
}
export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
protected metadataCollector = new MetadataCollector();
protected context: ReflectorHostContext;
private isGenDirChildOfRootDir: boolean;
protected basePath: string;
private genDir: string;
constructor(
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
protected options: AngularCompilerOptions, context?: ReflectorHostContext) {
// normalize the path so that it never ends with '/'.
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
this.context = context || new NodeReflectorHostContext(compilerHost);
var genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
}
angularImportLocations() {
return {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
}
// We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; }
protected resolve(m: string, containingFile: string) {
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
};
protected normalizeAssetUrl(url: string): string {
let assetUrl = AssetUrl.parse(url);
const path = assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null;
return this.getCanonicalFileName(path);
}
protected resolveAssetUrl(url: string, containingFile: string): string {
let assetUrl = this.normalizeAssetUrl(url);
if (assetUrl) {
return this.getCanonicalFileName(this.resolve(assetUrl, containingFile));
}
return url;
}
/**
* We want a moduleId that will appear in import statements in the generated code.
* These need to be in a form that system.js can load, so absolute file paths don't work.
*
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
* existing file.
*
* | genDir | node_module | rootDir
* --------------+----------+-------------+----------
* generated | relative | relative | n/a
* existing file | n/a | absolute | relative(*)
*
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
*/
getImportPath(containingFile: string, importedFile: string): string {
importedFile = this.resolveAssetUrl(importedFile, containingFile);
containingFile = this.resolveAssetUrl(containingFile, '');
// If a file does not yet exist (because we compile it later), we still need to
// assume it exists it so that the `resolve` method works!
if (!this.compilerHost.fileExists(importedFile)) {
this.context.assumeFileExists(importedFile);
}
containingFile = this.rewriteGenDirPath(containingFile);
const containingDir = path.dirname(containingFile);
// drop extension
importedFile = importedFile.replace(EXT, '');
var nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
const importModule = nodeModulesIndex === -1 ?
null :
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
const isGeneratedFile = IS_GENERATED.test(importedFile);
if (isGeneratedFile) {
// rewrite to genDir path
if (importModule) {
// it is generated, therefore we do a relative path to the factory
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
} else {
// assume that import is also in `genDir`
importedFile = this.rewriteGenDirPath(importedFile);
return this.dotRelative(containingDir, importedFile);
}
} else {
// user code import
if (importModule) {
return importModule;
} else {
if (!this.isGenDirChildOfRootDir) {
// assume that they are on top of each other.
importedFile = importedFile.replace(this.basePath, this.genDir);
}
return this.dotRelative(containingDir, importedFile);
}
}
}
private dotRelative(from: string, to: string): string {
var rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;
}
/**
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
*/
private rewriteGenDirPath(filepath: string) {
var nodeModulesIndex = filepath.indexOf(NODE_MODULES);
if (nodeModulesIndex !== -1) {
// If we are in node_modulse, transplant them into `genDir`.
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
} else {
// pretend that containing file is on top of the `genDir` to normalize the paths.
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
return filepath.replace(this.basePath, this.genDir);
}
}
findDeclaration(
module: string, symbolName: string, containingFile: string,
containingModule?: string): StaticSymbol {
if (!containingFile || !containingFile.length) {
if (module.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = path.join(this.basePath, 'index.ts');
}
try {
let assetUrl = this.normalizeAssetUrl(module);
if (assetUrl) {
module = assetUrl;
}
const filePath = this.resolve(module, containingFile);
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
return this.getStaticSymbol(module, symbolName);
}
const tc = this.program.getTypeChecker();
const sf = this.program.getSourceFile(filePath);
if (!sf || !(<any>sf).symbol) {
// The source file was not needed in the compile but we do need the values from
// the corresponding .ts files stored in the .metadata.json file. Check the file
// for exports to see if the file is exported.
return this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
let symbol = tc.getExportsOfModule((<any>sf).symbol).find(m => m.name === symbolName);
if (!symbol) {
throw new Error(`can't find symbol ${symbolName} exported from module ${filePath}`);
}
if (symbol &&
symbol.flags & ts.SymbolFlags.Alias) { // This is an alias, follow what it aliases
symbol = tc.getAliasedSymbol(symbol);
}
const declaration = symbol.getDeclarations()[0];
const declarationFile = this.getCanonicalFileName(declaration.getSourceFile().fileName);
return this.getStaticSymbol(declarationFile, symbol.getName());
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
private typeCache = new Map<string, StaticSymbol>();
private resolverCache = new Map<string, ModuleMetadata>();
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.typeCache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.typeCache.set(key, result);
}
return result;
}
getMetadataFor(filePath: string): ModuleMetadata {
if (!this.context.fileExists(filePath)) {
// If the file doesn't exists then we cannot return metadata for the file.
// This will occur if the user refernced a declared module for which no file
// exists for the module (i.e. jQuery or angularjs).
return;
}
if (DTS.test(filePath)) {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
const metadata = this.readMetadata(metadataPath);
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
}
} else {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return this.metadataCollector.getMetadata(
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return this.metadataCollector.getMetadata(sf);
}
}
readMetadata(filePath: string) {
try {
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
private getResolverMetadata(filePath: string): ModuleMetadata {
let metadata = this.resolverCache.get(filePath);
if (!metadata) {
metadata = this.getMetadataFor(filePath);
this.resolverCache.set(filePath, metadata);
}
return metadata;
}
protected resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.getCanonicalFileName(this.resolve(moduleName, filePath));
if (!resolvedModulePath) {
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
}
return resolvedModulePath;
};
let metadata = this.getResolverMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata.metadata[symbolName]) {
return this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (metadata.exports) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata.exports) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find(symbol => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
}
}
}
// Try to find the symbol via export * directives.
for (const moduleExport of metadata.exports) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) return candidateSymbol;
}
}
}
}
return null;
}
}
export class NodeReflectorHostContext implements ReflectorHostContext {
constructor(private host: ts.CompilerHost) {}
private assumedExists: {[fileName: string]: boolean} = {};
fileExists(fileName: string): boolean {
return this.assumedExists[fileName] || this.host.fileExists(fileName);
}
directoryExists(directoryName: string): boolean {
try {
return fs.statSync(directoryName).isDirectory();
} catch (e) {
return false;
}
}
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
}

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 {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core'; import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from './private_import_core';
import {StaticReflector} from './static_reflector'; import {StaticReflector} from './static_reflector';
export class StaticAndDynamicReflectionCapabilities { export class StaticAndDynamicReflectionCapabilities {

View File

@ -7,60 +7,54 @@
*/ */
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, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
const SUPPORTED_SCHEMA_VERSION = 2; import {ReflectorReader} from './private_import_core';
const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata', const SUPPORTED_SCHEMA_VERSION = 1;
diDecorators: '@angular/core/src/di/metadata',
diMetadata: '@angular/core/src/di/metadata',
diOpaqueToken: '@angular/core/src/di/opaque_token',
animationMetadata: '@angular/core/src/animation/metadata',
provider: '@angular/core/src/di/provider'
};
/** /**
* The host of the StaticReflector disconnects the implementation from TypeScript / other language * The host of the static resolver is expected to be able to provide module metadata in the form of
* services and from underlying file systems. * ModuleMetadata. Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*/ */
export interface StaticReflectorHost { export interface StaticReflectorHost {
/** /**
* Return a ModuleMetadata for the given module. * Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
* *
* @param modulePath is a string identifier for a module as an absolute path. * @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module. * @returns the metadata for the given module.
*/ */
getMetadataFor(modulePath: string): {[key: string]: any}[]; getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[];
/** /**
* Converts a module name that is used in an `import` to a file path. * Resolve a symbol from an import statement form, to the file where it is declared.
* I.e. * @param module the location imported from
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`. * @param containingFile for relative imports, the path of the file containing the import
*/ */
moduleNameToFileName(moduleName: string, containingFile: string): string; findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol;
angularImportLocations(): {
coreDecorators: string,
diDecorators: string,
diMetadata: string,
diOpaqueToken: string,
animationMetadata: string,
provider: string
};
getCanonicalFileName(fileName: string): string;
} }
/** /**
* A cache of static symbol used by the StaticReflector to return the same symbol for the * A token representing the a reference to a static type.
* same symbol values. *
* This token is unique for a filePath and name and can be used as a hash table key.
*/ */
export class StaticSymbolCache { export class StaticSymbol {
private cache = new Map<string, StaticSymbol>(); constructor(public filePath: string, public name: string, public members?: string[]) {}
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
} }
/** /**
@ -68,7 +62,6 @@ export class StaticSymbolCache {
* templates statically. * templates statically.
*/ */
export class StaticReflector implements ReflectorReader { export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>(); private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any}>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any}>();
private parameterCache = new Map<StaticSymbol, any[]>(); private parameterCache = new Map<StaticSymbol, any[]>();
@ -76,30 +69,26 @@ export class StaticReflector implements ReflectorReader {
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>(); private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol; private opaqueToken: StaticSymbol;
constructor( constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
this.initializeConversionMap();
}
importUri(typeOrFunc: StaticSymbol): string { importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
return staticSymbol ? staticSymbol.filePath : null; return staticSymbol ? staticSymbol.filePath : null;
} }
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.findDeclaration(moduleUrl, name, ''); return this.host.findDeclaration(moduleUrl, name, '');
} }
resolveEnum(enumIdentifier: any, name: string): any { resolveEnum(enumIdentifier: any, name: string): any {
const staticSymbol: StaticSymbol = enumIdentifier; const staticSymbol: StaticSymbol = enumIdentifier;
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]); return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
} }
public annotations(type: StaticSymbol): any[] { public annotations(type: StaticSymbol): any[] {
let annotations = this.annotationCache.get(type); let annotations = this.annotationCache.get(type);
if (!annotations) { if (!annotations) {
const classMetadata = this.getTypeMetadata(type); let classMetadata = this.getTypeMetadata(type);
if (classMetadata['decorators']) { if (classMetadata['decorators']) {
annotations = this.simplify(type, classMetadata['decorators']); annotations = this.simplify(type, classMetadata['decorators']);
} else { } else {
@ -113,10 +102,10 @@ export class StaticReflector implements ReflectorReader {
public propMetadata(type: StaticSymbol): {[key: string]: any} { public propMetadata(type: StaticSymbol): {[key: string]: any} {
let propMetadata = this.propertyCache.get(type); let propMetadata = this.propertyCache.get(type);
if (!propMetadata) { if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type); let classMetadata = this.getTypeMetadata(type);
const members = classMetadata ? classMetadata['members'] : {}; let members = classMetadata ? classMetadata['members'] : {};
propMetadata = mapStringMap(members, (propData, propName) => { propMetadata = mapStringMap(members, (propData, propName) => {
const prop = (<any[]>propData) let prop = (<any[]>propData)
.find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method'); .find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method');
if (prop && prop['decorators']) { if (prop && prop['decorators']) {
return this.simplify(type, prop['decorators']); return this.simplify(type, prop['decorators']);
@ -136,20 +125,21 @@ export class StaticReflector implements ReflectorReader {
try { try {
let parameters = this.parameterCache.get(type); let parameters = this.parameterCache.get(type);
if (!parameters) { if (!parameters) {
const classMetadata = this.getTypeMetadata(type); let classMetadata = this.getTypeMetadata(type);
const members = classMetadata ? classMetadata['members'] : null; let members = classMetadata ? classMetadata['members'] : null;
const ctorData = members ? members['__ctor__'] : null; let ctorData = members ? members['__ctor__'] : null;
if (ctorData) { if (ctorData) {
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor'); let ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []); let parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []); let parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = []; parameters = [];
parameterTypes.forEach((paramType, index) => { parameterTypes.forEach((paramType, index) => {
const nestedResult: any[] = []; let nestedResult: any[] = [];
if (paramType) { if (paramType) {
nestedResult.push(paramType); nestedResult.push(paramType);
} }
const decorators = parameterDecorators ? parameterDecorators[index] : null; let decorators = parameterDecorators ? parameterDecorators[index] : null;
if (decorators) { if (decorators) {
nestedResult.push(...decorators); nestedResult.push(...decorators);
} }
@ -190,145 +180,59 @@ export class StaticReflector implements ReflectorReader {
private initializeConversionMap(): void { private initializeConversionMap(): void {
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} = const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
ANGULAR_IMPORT_LOCATIONS; this.host.angularImportLocations();
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken'); this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(diDecorators, 'Injectable'), Injectable); this.host.findDeclaration(diDecorators, 'Injectable'), Injectable);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self); this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Attribute'), Attribute); this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild); this.host.findDeclaration(diDecorators, 'Optional'), Optional);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren); this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild); this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren); this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding); this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'HostListener'), HostListener); this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Directive'), Directive); this.host.findDeclaration(coreDecorators, 'Output'), Output);
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
this.registerDecoratorOrConstructor( this.registerDecoratorOrConstructor(
this.findDeclaration(coreDecorators, 'Component'), Component); this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule); this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Directive'), Directive);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'Component'), Component);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule);
// Note: Some metadata classes can be used directly with Provider.deps. // Note: Some metadata classes can be used directly with Provider.deps.
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host); this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self); this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self);
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf); this.registerDecoratorOrConstructor(
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional); this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
this.registerDecoratorOrConstructor(
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger); this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger);
this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state); this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state);
this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition); this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition);
this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style); this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style);
this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate); this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate);
this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes); this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes);
this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence); this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence);
this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group); this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group);
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
}
return resolvedModulePath;
};
const cacheKey = `${filePath}|${symbolName}`;
let staticSymbol = this.declarationCache.get(cacheKey);
if (staticSymbol) {
return staticSymbol;
}
const metadata = this.getModuleMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata['metadata'][symbolName]) {
staticSymbol = this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (!staticSymbol && metadata['exports']) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata['exports']) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find((symbol: any) => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
staticSymbol = this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) {
staticSymbol = candidateSymbol;
break;
}
}
}
}
}
}
this.declarationCache.set(cacheKey, staticSymbol);
return staticSymbol;
}
findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol {
try {
const filePath = this.host.moduleNameToFileName(module, containingFile);
let symbol: StaticSymbol;
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
symbol = this.getStaticSymbol(module, symbolName);
} else {
symbol = this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
return symbol;
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
} }
/** @internal */ /** @internal */
@ -341,10 +245,10 @@ export class StaticReflector implements ReflectorReader {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
let staticSymbol: StaticSymbol; let staticSymbol: StaticSymbol;
if (expression['module']) { if (expression['module']) {
staticSymbol = staticSymbol = _this.host.findDeclaration(
_this.findDeclaration(expression['module'], expression['name'], context.filePath); expression['module'], expression['name'], context.filePath);
} else { } else {
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']); staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
} }
return staticSymbol; return staticSymbol;
} }
@ -369,7 +273,7 @@ export class StaticReflector implements ReflectorReader {
function simplifyCall(expression: any) { function simplifyCall(expression: any) {
let callContext: {[name: string]: string}|undefined = undefined; let callContext: {[name: string]: string}|undefined = undefined;
if (expression['__symbolic'] == 'call') { if (expression['__symbolic'] == 'call') {
const target = expression['expression']; let target = expression['expression'];
let functionSymbol: StaticSymbol; let functionSymbol: StaticSymbol;
let targetFunction: any; let targetFunction: any;
if (target) { if (target) {
@ -412,7 +316,7 @@ export class StaticReflector implements ReflectorReader {
for (let i = 0; i < parameters.length; i++) { for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]); functionScope.define(parameters[i], args[i]);
} }
const oldScope = scope; let oldScope = scope;
let result: any; let result: any;
try { try {
scope = functionScope.done(); scope = functionScope.done();
@ -443,19 +347,19 @@ export class StaticReflector implements ReflectorReader {
return expression; return expression;
} }
if (expression instanceof Array) { if (expression instanceof Array) {
const result: any[] = []; let result: any[] = [];
for (const item of (<any>expression)) { for (let item of (<any>expression)) {
// Check for a spread expression // Check for a spread expression
if (item && item.__symbolic === 'spread') { if (item && item.__symbolic === 'spread') {
const spreadArray = simplify(item.expression); let spreadArray = simplify(item.expression);
if (Array.isArray(spreadArray)) { if (Array.isArray(spreadArray)) {
for (const spreadItem of spreadArray) { for (let spreadItem of spreadArray) {
result.push(spreadItem); result.push(spreadItem);
} }
continue; continue;
} }
} }
const value = simplify(item); let value = simplify(item);
if (shouldIgnore(value)) { if (shouldIgnore(value)) {
continue; continue;
} }
@ -553,7 +457,8 @@ export class StaticReflector implements ReflectorReader {
const members = selectTarget.members ? const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) : (selectTarget.members as string[]).concat(member) :
[member]; [member];
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members); return _this.host.getStaticSymbol(
selectTarget.filePath, selectTarget.name, members);
} }
} }
const member = simplify(expression['member']); const member = simplify(expression['member']);
@ -561,8 +466,8 @@ export class StaticReflector implements ReflectorReader {
return null; return null;
case 'reference': case 'reference':
if (!expression.module) { if (!expression.module) {
const name: string = expression['name']; let name: string = expression['name'];
const localValue = scope.resolve(name); let localValue = scope.resolve(name);
if (localValue != BindingScope.missing) { if (localValue != BindingScope.missing) {
return localValue; return localValue;
} }
@ -588,10 +493,10 @@ export class StaticReflector implements ReflectorReader {
// Determine if the function is a built-in conversion // Determine if the function is a built-in conversion
let target = expression['expression']; let target = expression['expression'];
if (target['module']) { if (target['module']) {
staticSymbol = staticSymbol = _this.host.findDeclaration(
_this.findDeclaration(target['module'], target['name'], context.filePath); target['module'], target['name'], context.filePath);
} else { } else {
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']); staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
} }
let converter = _this.conversionMap.get(staticSymbol); let converter = _this.conversionMap.get(staticSymbol);
if (converter) { if (converter) {
@ -611,8 +516,6 @@ export class StaticReflector implements ReflectorReader {
if (expression['line']) { if (expression['line']) {
message = message =
`${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`; `${message} (position ${expression['line']+1}:${expression['character']+1} in the original .ts file)`;
throw positionalError(
message, context.filePath, expression['line'], expression['character']);
} }
throw new Error(message); throw new Error(message);
} }
@ -626,11 +529,7 @@ export class StaticReflector implements ReflectorReader {
try { try {
return simplify(value); return simplify(value);
} catch (e) { } catch (e) {
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`; throw new Error(`${e.message}, resolving symbol ${context.name} in ${context.filePath}`);
if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column);
}
throw new Error(message);
} }
} }
@ -647,15 +546,10 @@ export class StaticReflector implements ReflectorReader {
public getModuleMetadata(module: string): {[key: string]: any} { public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module); let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) { if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module); moduleMetadata = this.host.getMetadataFor(module);
if (moduleMetadatas) { if (Array.isArray(moduleMetadata)) {
let maxVersion = -1; moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
moduleMetadatas.forEach((md) => { moduleMetadata[0];
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
} }
if (!moduleMetadata) { if (!moduleMetadata) {
moduleMetadata = moduleMetadata =
@ -701,7 +595,6 @@ function expandedMessage(error: any): string {
if (error.context && error.context.name) { if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`; return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
} }
break;
} }
return error.message; return error.message;
} }
@ -715,7 +608,7 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
if (!input) return {}; if (!input) return {};
const result: {[key: string]: any} = {}; const result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => { Object.keys(input).forEach((key) => {
const value = transform(input[key], key); let value = transform(input[key], key);
if (!shouldIgnore(value)) { if (!shouldIgnore(value)) {
result[key] = value; result[key] = value;
} }
@ -766,11 +659,3 @@ function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
function shouldIgnore(value: any): boolean { function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore'; return value && value.__symbolic == 'ignore';
} }
function positionalError(message: string, fileName: string, line: number, column: number): Error {
const result = new Error(message);
(result as any).fileName = fileName;
(result as any).line = line;
(result as any).column = column;
return result;
}

View File

@ -1,219 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
describe('CompilerHost', () => {
let context: MockAotContext;
let host: ts.CompilerHost;
let program: ts.Program;
let hostNestedGenDir: CompilerHost;
let hostSiblingGenDir: CompilerHost;
beforeEach(() => {
context = new MockAotContext('/tmp/src', clone(FILES));
host = new MockCompilerHost(context);
program = ts.createProgram(
['main.ts'], {
module: ts.ModuleKind.CommonJS,
},
host);
// Force a typecheck
const errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
hostNestedGenDir = new CompilerHost(
program, host, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
hostSiblingGenDir = new CompilerHost(
program, host, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts', ))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../../my.other');
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('../a/my.other');
});
});
describe('siblingGenDir', () => {
it('should import node_module from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts',
'/tmp/project/src/gen/my.ngfactory.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other.css');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
.toEqual('../my.other');
expect(hostSiblingGenDir.fileNameToModuleName(
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
.toEqual('./a/my.other');
});
});
it('should be able to produce an import from main @angular/core', () => {
expect(hostNestedGenDir.fileNameToModuleName(
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
.toEqual('@angular/core');
});
it('should be able to produce an import from main to a sub-directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
.toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
.toEqual('../lib/utils');
});
it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}}
]);
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
});
it('should be able to read empty metadata ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
});
it('should return undefined for missing modules', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
});
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
__symbolic: 'module',
version: 2,
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
}
]);
});
});
const dummyModule = 'export let foo: any[];';
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
},
'metadata_versions': {
'v1.d.ts': 'export declare class bar {}',
'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
const result: Directory = {};
for (const name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

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 {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host'; import {ReflectorHostContext} from '@angular/compiler-cli/src/reflector_host';
import * as ts from 'typescript'; import * as ts from 'typescript';
export type Entry = string | Directory; export type Entry = string | Directory;
export interface Directory { [name: string]: Entry; } export interface Directory { [name: string]: Entry; }
export class MockAotContext implements CompilerHostContext { export class MockContext implements ReflectorHostContext {
constructor(public currentDirectory: string, private files: Entry) {} constructor(public currentDirectory: string, private files: Entry) {}
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; } fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
@ -21,25 +21,17 @@ export class MockAotContext implements CompilerHostContext {
directoryExists(path: string): boolean { return typeof this.getEntry(path) === 'object'; } directoryExists(path: string): boolean { return typeof this.getEntry(path) === 'object'; }
readFile(fileName: string): string|undefined { readFile(fileName: string): string|undefined {
const data = this.getEntry(fileName); let data = this.getEntry(fileName);
if (typeof data === 'string') { if (typeof data === 'string') {
return data; return data;
} }
return undefined; return undefined;
} }
readResource(fileName: string): Promise<string> {
const result = this.readFile(fileName);
if (result == null) {
return Promise.reject(new Error(`Resource not found: ${fileName}`));
}
return Promise.resolve(result);
}
writeFile(fileName: string, data: string): void { writeFile(fileName: string, data: string): void {
const parts = fileName.split('/'); let parts = fileName.split('/');
const name = parts.pop(); let name = parts.pop();
const entry = this.getEntry(parts); let entry = this.getEntry(parts);
if (entry && typeof entry !== 'string') { if (entry && typeof entry !== 'string') {
entry[name] = data; entry[name] = data;
} }
@ -56,11 +48,11 @@ export class MockAotContext implements CompilerHostContext {
parts = normalize(parts); parts = normalize(parts);
let current = this.files; let current = this.files;
while (parts.length) { while (parts.length) {
const part = parts.shift(); let part = parts.shift();
if (typeof current === 'string') { if (typeof current === 'string') {
return undefined; return undefined;
} }
const next = (<Directory>current)[part]; let next = (<Directory>current)[part];
if (next === undefined) { if (next === undefined) {
return undefined; return undefined;
} }
@ -80,9 +72,9 @@ export class MockAotContext implements CompilerHostContext {
} }
function normalize(parts: string[]): string[] { function normalize(parts: string[]): string[] {
const result: string[] = []; let result: string[] = [];
while (parts.length) { while (parts.length) {
const part = parts.shift(); let part = parts.shift();
switch (part) { switch (part) {
case '.': case '.':
break; break;
@ -97,7 +89,7 @@ function normalize(parts: string[]): string[] {
} }
export class MockCompilerHost implements ts.CompilerHost { export class MockCompilerHost implements ts.CompilerHost {
constructor(private context: MockAotContext) {} constructor(private context: MockContext) {}
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); } fileExists(fileName: string): boolean { return this.context.fileExists(fileName); }
@ -110,7 +102,7 @@ export class MockCompilerHost implements ts.CompilerHost {
getSourceFile( getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget, fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile { onError?: (message: string) => void): ts.SourceFile {
const sourceText = this.context.readFile(fileName); let sourceText = this.context.readFile(fileName);
if (sourceText) { if (sourceText) {
return ts.createSourceFile(fileName, sourceText, languageVersion); return ts.createSourceFile(fileName, sourceText, languageVersion);
} else { } else {

View File

@ -0,0 +1,329 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import * as ts from 'typescript';
import {ReflectorHost} from '../src/reflector_host';
import {Directory, Entry, MockCompilerHost, MockContext} from './mocks';
describe('reflector_host', () => {
var context: MockContext;
var host: ts.CompilerHost;
var program: ts.Program;
var reflectorNestedGenDir: ReflectorHost;
var reflectorSiblingGenDir: ReflectorHost;
beforeEach(() => {
context = new MockContext('/tmp/src', clone(FILES));
host = new MockCompilerHost(context);
program = ts.createProgram(
['main.ts'], {
module: ts.ModuleKind.CommonJS,
},
host);
// Force a typecheck
let errors = program.getSemanticDiagnostics();
if (errors && errors.length) {
throw new Error('Expected no errors');
}
reflectorNestedGenDir = new ReflectorHost(
program, host, {
genDir: '/tmp/project/src/gen/',
basePath: '/tmp/project/src',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
reflectorSiblingGenDir = new ReflectorHost(
program, host, {
genDir: '/tmp/project/gen',
basePath: '/tmp/project/src/',
skipMetadataEmit: false,
strictMetadataEmit: false,
skipTemplateCodegen: false,
trace: false
},
context);
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/gen/my.ngfactory.ts',
'/tmp/project/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
.toEqual('../my.other.css');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
.toEqual('../my.other');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
.toEqual('../../my.other');
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
.toEqual('../a/my.other');
});
});
describe('nestedGenDir', () => {
it('should import node_module from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/gen/my.ngfactory.ts',
'/tmp/project/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});
it('should import factory from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
.toEqual('./my.other.ngfactory');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
.toEqual('../my.other.css');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
.toEqual('./a/my.other.css.shim');
});
it('should import application from factory', () => {
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
.toEqual('./my.other');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
.toEqual('../my.other');
expect(reflectorSiblingGenDir.getImportPath(
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
.toEqual('./a/my.other');
});
});
it('should provide the import locations for angular', () => {
let {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
reflectorNestedGenDir.angularImportLocations();
expect(coreDecorators).toEqual('@angular/core/src/metadata');
expect(diDecorators).toEqual('@angular/core/src/di/metadata');
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
expect(animationMetadata).toEqual('@angular/core/src/animation/metadata');
expect(provider).toEqual('@angular/core/src/di/provider');
});
it('should be able to produce an import from main @angular/core', () => {
expect(reflectorNestedGenDir.getImportPath(
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
.toEqual('@angular/core');
});
it('should be able to produce an import from main to a sub-directory', () => {
expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
});
it('should be able to produce an import from to a peer file', () => {
expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts'))
.toEqual('./collections');
});
it('should be able to produce an import from to a sibling directory', () => {
expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts'))
.toEqual('../lib/utils');
});
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflectorNestedGenDir.findDeclaration('@angular/router', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflectorNestedGenDir.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
let foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
let foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file', () => {
expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
});
it('should be able to read a metadata file', () => {
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
});
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts'))
.toBeUndefined();
});
it('should be able to read empty metadata ', () => {
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts'))
.toBeUndefined();
});
it('should return undefined for missing modules', () => {
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts'))
.toBeUndefined();
});
it('should be able to trace a named export', () => {
const symbol = reflectorNestedGenDir.findDeclaration(
'./reexport/reexport.d.ts', 'One', '/tmp/src/main.ts');
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = reflectorNestedGenDir.findDeclaration(
'./reexport/reexport.d.ts', 'Four', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = reflectorNestedGenDir.findDeclaration(
'./reexport/reexport.d.ts', 'Five', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol = reflectorNestedGenDir.findDeclaration(
'./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Thirty');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
});
const dummyModule = 'export let foo: any[];';
const FILES: Entry = {
'tmp': {
'src': {
'main.ts': `
import * as c from '@angular/core';
import * as r from '@angular/router';
import * as u from './lib/utils';
import * as cs from './lib/collections';
import * as u2 from './lib2/utils2';
`,
'lib': {
'utils.ts': dummyModule,
'collections.ts': dummyModule,
},
'lib2': {'utils2.ts': dummyModule},
'reexport': {
'reexport.d.ts': `
import * as c from '@angular/core';
`,
'reexport.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {},
exports: [
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
{from: './src/origin5'}, {from: './src/reexport2'}
]
}),
'src': {
'origin1.d.ts': `
export class One {}
export class Two {}
export class Three {}
`,
'origin1.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
}),
'origin5.d.ts': `
export class Five {}
`,
'origin5.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {
Five: {__symbolic: 'class'},
},
}),
'origin30.d.ts': `
export class Thirty {}
`,
'origin30.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {
Thirty: {__symbolic: 'class'},
},
}),
'originNone.d.ts': dummyModule,
'originNone.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {},
}),
'reexport2.d.ts': dummyModule,
'reexport2.metadata.json': JSON.stringify({
__symbolic: 'module',
version: 1,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
})
}
},
'node_modules': {
'@angular': {
'core.d.ts': dummyModule,
'core.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
'empty.metadata.json': '[]',
}
}
}
}
};
function clone(entry: Entry): Entry {
if (typeof entry === 'string') {
return entry;
} else {
let result: Directory = {};
for (let name in entry) {
result[name] = clone(entry[name]);
}
return result;
}
}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {HostListener, Inject, 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 {MetadataCollector} from '@angular/tsc-wrapped'; import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -17,12 +17,12 @@ import * as ts from 'typescript';
const TS_EXT = /(^.|(?!\.d)..)\.ts$/; const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
describe('StaticReflector', () => { describe('StaticReflector', () => {
const noContext = new StaticSymbol('', ''); let noContext = new StaticSymbol('', '');
let host: StaticReflectorHost; let host: StaticReflectorHost;
let reflector: StaticReflector; let reflector: StaticReflector;
beforeEach(() => { beforeEach(() => {
host = new MockStaticReflectorHost(); host = new MockReflectorHost();
reflector = new StaticReflector(host); reflector = new StaticReflector(host);
}); });
@ -31,37 +31,36 @@ describe('StaticReflector', () => {
} }
it('should get annotations for NgFor', () => { it('should get annotations for NgFor', () => {
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); let NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const annotations = reflector.annotations(NgFor); let annotations = reflector.annotations(NgFor);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; let annotation = annotations[0];
expect(annotation.selector).toEqual('[ngFor][ngForOf]'); expect(annotation.selector).toEqual('[ngFor][ngForOf]');
expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']); expect(annotation.inputs).toEqual(['ngForTrackBy', 'ngForOf', 'ngForTemplate']);
}); });
it('should get constructor for NgFor', () => { it('should get constructor for NgFor', () => {
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor'); let NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
const ViewContainerRef = reflector.findDeclaration( let ViewContainerRef =
'@angular/core/src/linker/view_container_ref', 'ViewContainerRef'); host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef');
const TemplateRef = let TemplateRef = host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef');
reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef'); let IterableDiffers = host.findDeclaration(
const IterableDiffers = reflector.findDeclaration( 'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
'@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers'); let ChangeDetectorRef = host.findDeclaration(
const ChangeDetectorRef = reflector.findDeclaration( 'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
'@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
const parameters = reflector.parameters(NgFor); let parameters = reflector.parameters(NgFor);
expect(parameters).toEqual([ expect(parameters).toEqual([
[ViewContainerRef], [TemplateRef], [IterableDiffers], [ChangeDetectorRef] [ViewContainerRef], [TemplateRef], [IterableDiffers], [ChangeDetectorRef]
]); ]);
}); });
it('should get annotations for HeroDetailComponent', () => { it('should get annotations for HeroDetailComponent', () => {
const HeroDetailComponent = let HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const annotations = reflector.annotations(HeroDetailComponent); let annotations = reflector.annotations(HeroDetailComponent);
expect(annotations.length).toEqual(1); expect(annotations.length).toEqual(1);
const annotation = annotations[0]; let annotation = annotations[0];
expect(annotation.selector).toEqual('my-hero-detail'); expect(annotation.selector).toEqual('my-hero-detail');
expect(annotation.animations).toEqual([trigger('myAnimation', [ expect(annotation.animations).toEqual([trigger('myAnimation', [
state('state1', style({'background': 'white'})), state('state1', style({'background': 'white'})),
@ -73,40 +72,41 @@ describe('StaticReflector', () => {
])]); ])]);
}); });
it('should throw an exception for unsupported metadata versions', () => { it('should throw and exception for unsupported metadata versions', () => {
expect(() => reflector.findDeclaration('src/version-error', 'e')) let e = host.findDeclaration('src/version-error', 'e');
expect(() => reflector.annotations(e))
.toThrow(new Error( .toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 2')); 'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 1'));
}); });
it('should get and empty annotation list for an unknown class', () => { it('should get and empty annotation list for an unknown class', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const annotations = reflector.annotations(UnknownClass); let annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]); expect(annotations).toEqual([]);
}); });
it('should get propMetadata for HeroDetailComponent', () => { it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent = let HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
const props = reflector.propMetadata(HeroDetailComponent); let props = reflector.propMetadata(HeroDetailComponent);
expect(props['hero']).toBeTruthy(); expect(props['hero']).toBeTruthy();
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]); expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
}); });
it('should get an empty object from propMetadata for an unknown class', () => { it('should get an empty object from propMetadata for an unknown class', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const properties = reflector.propMetadata(UnknownClass); let properties = reflector.propMetadata(UnknownClass);
expect(properties).toEqual({}); expect(properties).toEqual({});
}); });
it('should get empty parameters list for an unknown class ', () => { it('should get empty parameters list for an unknown class ', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); let UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
const parameters = reflector.parameters(UnknownClass); let parameters = reflector.parameters(UnknownClass);
expect(parameters).toEqual([]); expect(parameters).toEqual([]);
}); });
it('should provide context for errors reported by the collector', () => { it('should provide context for errors reported by the collector', () => {
const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass'); let SomeClass = host.findDeclaration('src/error-reporting', 'SomeClass');
expect(() => reflector.annotations(SomeClass)) expect(() => reflector.annotations(SomeClass))
.toThrow(new Error( .toThrow(new Error(
'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts')); 'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts'));
@ -128,7 +128,7 @@ describe('StaticReflector', () => {
}); });
it('should simplify an object to a copy of the object', () => { it('should simplify an object to a copy of the object', () => {
const expr = {a: 1, b: 2, c: 3}; let expr = {a: 1, b: 2, c: 3};
expect(simplify(noContext, expr)).toEqual(expr); expect(simplify(noContext, expr)).toEqual(expr);
}); });
@ -292,7 +292,7 @@ describe('StaticReflector', () => {
}); });
it('should simplify an object index', () => { it('should simplify an object index', () => {
const expr = {__symbolic: 'select', expression: {a: 1, b: 2, c: 3}, member: 'b'}; let expr = {__symbolic: 'select', expression: {a: 1, b: 2, c: 3}, member: 'b'};
expect(simplify(noContext, expr)).toBe(2); expect(simplify(noContext, expr)).toBe(2);
}); });
@ -307,14 +307,14 @@ describe('StaticReflector', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', ''), new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) ({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
.toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); .toEqual(host.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
}); });
it('should simplify a function reference as a static symbol', () => { it('should simplify a function reference as a static symbol', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'), new StaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []}))) ({__symbolic: 'function', parameters: ['a'], value: []})))
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction')); .toEqual(host.getStaticSymbol('/src/cases', 'myFunction'));
}); });
it('should simplify values initialized with a function call', () => { it('should simplify values initialized with a function call', () => {
@ -337,23 +337,6 @@ describe('StaticReflector', () => {
'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); 'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
}); });
it('should record data about the error in the exception', () => {
let threw = false;
try {
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts');
expect(metadata).toBeDefined();
const moduleMetadata: any = metadata[0]['metadata'];
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
} catch (e) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true;
}
expect(threw).toBe(true);
});
it('should error on indirect recursive calls', () => { it('should error on indirect recursive calls', () => {
expect( expect(
() => simplify( () => simplify(
@ -371,10 +354,10 @@ describe('StaticReflector', () => {
}); });
it('should be able to get metadata from a ts file', () => { it('should be able to get metadata from a ts file', () => {
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts'); let metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
expect(metadata).toEqual({ expect(metadata).toEqual({
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
Foo: { Foo: {
__symbolic: 'class', __symbolic: 'class',
@ -402,36 +385,29 @@ describe('StaticReflector', () => {
}); });
it('should be able to get metadata for a class containing a custom decorator', () => { it('should be able to get metadata for a class containing a custom decorator', () => {
const props = reflector.propMetadata( let props = reflector.propMetadata(
reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo')); host.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
expect(props).toEqual({foo: []}); expect(props).toEqual({foo: []});
}); });
it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts';
const dep = reflector.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward'));
expect(props).toEqual([[dep, new Inject(dep)]]);
});
it('should report an error for invalid function calls', () => { it('should report an error for invalid function calls', () => {
expect( expect(
() => reflector.annotations( () =>
reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent'))) reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
.toThrow(new Error( .toThrow(new Error(
`Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`)); `Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`));
}); });
it('should be able to get metadata for a class containing a static method call', () => { it('should be able to get metadata for a class containing a static method call', () => {
const annotations = reflector.annotations( const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent')); host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100}); expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
}); });
it('should be able to get metadata for a class containing a static field reference', () => { it('should be able to get metadata for a class containing a static field reference', () => {
const annotations = reflector.annotations( const annotations =
reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo')); reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]); expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
}); });
@ -439,7 +415,7 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with a conditional expression', it('should be able to get the metadata for a class calling a method with a conditional expression',
() => { () => {
const annotations = reflector.annotations( const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent')); host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([ expect(annotations[0].providers).toEqual([
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}] [{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
@ -449,85 +425,54 @@ describe('StaticReflector', () => {
it('should be able to get the metadata for a class calling a method with default parameters', it('should be able to get the metadata for a class calling a method with default parameters',
() => { () => {
const annotations = reflector.annotations( const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent')); host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers).toEqual([['a', true, false]]); expect(annotations[0].providers).toEqual([['a', true, false]]);
}); });
it('should be able to get metadata with a reference to a static method', () => { it('should be able to get metadata with a reference to a static method', () => {
const annotations = reflector.annotations( const annotations = reflector.annotations(
reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference')); host.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
expect(annotations.length).toBe(1); expect(annotations.length).toBe(1);
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
}); });
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
const foo1 = reflector.getStaticSymbol('main.ts', 'foo');
const foo2 = reflector.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file',
() => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); });
it('should be able to trace a named export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Thirty');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
it('should cache tracing a named export', () => {
const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough();
const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough();
reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
moduleNameToFileNameSpy.calls.reset();
getMetadataForSpy.calls.reset();
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(moduleNameToFileNameSpy.calls.count()).toBe(1);
expect(getMetadataForSpy.calls.count()).toBe(0);
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
}); });
class MockStaticReflectorHost implements StaticReflectorHost { class MockReflectorHost implements StaticReflectorHost {
private staticTypeCache = new Map<string, StaticSymbol>();
private collector = new MetadataCollector(); private collector = new MetadataCollector();
constructor() {}
angularImportLocations() {
return {
coreDecorators: 'angular2/src/core/metadata',
diDecorators: 'angular2/src/core/di/metadata',
diMetadata: 'angular2/src/core/di/metadata',
diOpaqueToken: 'angular2/src/core/di/opaque_token',
animationMetadata: 'angular2/src/core/animation/metadata',
provider: 'angular2/src/core/di/provider'
};
}
getCanonicalFileName(fileName: string): string { return fileName; }
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
var cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`;
var result = this.staticTypeCache.get(cacheKey);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.staticTypeCache.set(cacheKey, result);
}
return result;
}
// In tests, assume that symbols are not re-exported // In tests, assume that symbols are not re-exported
moduleNameToFileName(modulePath: string, containingFile?: string): string { findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); } function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string { function resolvePath(pathParts: string[]): string {
const result: string[] = []; let result: string[] = [];
pathParts.forEach((part, index) => { pathParts.forEach((part, index) => {
switch (part) { switch (part) {
case '': case '':
@ -546,9 +491,9 @@ class MockStaticReflectorHost implements StaticReflectorHost {
function pathTo(from: string, to: string): string { function pathTo(from: string, to: string): string {
let result = to; let result = to;
if (to.startsWith('.')) { if (to.startsWith('.')) {
const fromParts = splitPath(from); let fromParts = splitPath(from);
fromParts.pop(); // remove the file name. fromParts.pop(); // remove the file name.
const toParts = splitPath(to); let toParts = splitPath(to);
result = resolvePath(fromParts.concat(toParts)); result = resolvePath(fromParts.concat(toParts));
} }
return result; return result;
@ -557,34 +502,32 @@ class MockStaticReflectorHost implements StaticReflectorHost {
if (modulePath.indexOf('.') === 0) { if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath); const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts'; const tsName = baseName + '.ts';
if (this._getMetadataFor(tsName)) { if (this.getMetadataFor(tsName)) {
return tsName; return this.getStaticSymbol(tsName, symbolName);
} }
return baseName + '.d.ts'; return this.getStaticSymbol(baseName + '.d.ts', symbolName);
} }
return '/tmp/' + modulePath + '.d.ts'; return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
} }
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); } getMetadataFor(moduleId: string): any {
let data: {[key: string]: any} = {
private _getMetadataFor(moduleId: string): any { '/tmp/angular2/src/common/forms-deprecated/directives.d.ts': [{
const data: {[key: string]: any} = {
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
'__symbolic': 'module', '__symbolic': 'module',
'version': 2, 'version': 1,
'metadata': { 'metadata': {
'FORM_DIRECTIVES': [ 'FORM_DIRECTIVES': [
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'NgFor', 'name': 'NgFor',
'module': '@angular/common/src/directives/ng_for' 'module': 'angular2/src/common/directives/ng_for'
} }
] ]
} }
}], }],
'/tmp/@angular/common/src/directives/ng_for.d.ts': { '/tmp/angular2/src/common/directives/ng_for.d.ts': {
'__symbolic': 'module', '__symbolic': 'module',
'version': 2, 'version': 1,
'metadata': { 'metadata': {
'NgFor': { 'NgFor': {
'__symbolic': 'class', '__symbolic': 'class',
@ -594,7 +537,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Directive', 'name': 'Directive',
'module': '@angular/core/src/metadata' 'module': '../../core/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -611,22 +554,22 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'parameters': [ 'parameters': [
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '@angular/core/src/linker/view_container_ref', 'module': '../../core/linker/view_container_ref',
'name': 'ViewContainerRef' 'name': 'ViewContainerRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '@angular/core/src/linker/template_ref', 'module': '../../core/linker/template_ref',
'name': 'TemplateRef' 'name': 'TemplateRef'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '@angular/core/src/change_detection/differs/iterable_differs', 'module': '../../core/change_detection/differs/iterable_differs',
'name': 'IterableDiffers' 'name': 'IterableDiffers'
}, },
{ {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '@angular/core/src/change_detection/change_detector_ref', 'module': '../../core/change_detection/change_detector_ref',
'name': 'ChangeDetectorRef' 'name': 'ChangeDetectorRef'
} }
] ]
@ -636,17 +579,17 @@ class MockStaticReflectorHost implements StaticReflectorHost {
} }
} }
}, },
'/tmp/@angular/core/src/linker/view_container_ref.d.ts': '/tmp/angular2/src/core/linker/view_container_ref.d.ts':
{version: 2, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}}, {version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/linker/template_ref.d.ts': '/tmp/angular2/src/core/linker/template_ref.d.ts':
{version: 2, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}}, {version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts': '/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
{version: 2, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}}, {version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
'/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts': '/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
{version: 2, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}}, {version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
'/tmp/src/app/hero-detail.component.d.ts': { '/tmp/src/app/hero-detail.component.d.ts': {
'__symbolic': 'module', '__symbolic': 'module',
'version': 2, 'version': 1,
'metadata': { 'metadata': {
'HeroDetailComponent': { 'HeroDetailComponent': {
'__symbolic': 'class', '__symbolic': 'class',
@ -656,7 +599,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Component', 'name': 'Component',
'module': '@angular/core/src/metadata' 'module': 'angular2/src/core/metadata'
}, },
'arguments': [ 'arguments': [
{ {
@ -668,7 +611,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'trigger', 'name': 'trigger',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments': [ 'arguments': [
'myAnimation', 'myAnimation',
@ -676,7 +619,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'state', 'name': 'state',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments': [ 'arguments': [
'state1', 'state1',
@ -684,7 +627,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'style', 'name': 'style',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments': [ 'arguments': [
{ 'background':'white' } { 'background':'white' }
@ -696,7 +639,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'transition', 'name':'transition',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments': [ 'arguments': [
'* => *', '* => *',
@ -705,20 +648,20 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression':{ 'expression':{
'__symbolic':'reference', '__symbolic':'reference',
'name':'sequence', 'name':'sequence',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'group', 'name':'group',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[[{ 'arguments':[[{
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'animate', 'name':'animate',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[ 'arguments':[
'1s 0.5s', '1s 0.5s',
@ -726,13 +669,13 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'keyframes', 'name':'keyframes',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[[{ '__symbolic': 'call', 'arguments':[[{ '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[ { 'background': 'blue'} ] 'arguments':[ { 'background': 'blue'} ]
}, { }, {
@ -740,7 +683,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic':'reference', '__symbolic':'reference',
'name':'style', 'name':'style',
'module': '@angular/core/src/animation/metadata' 'module': 'angular2/src/core/animation/metadata'
}, },
'arguments':[ { 'background': 'red'} ] 'arguments':[ { 'background': 'red'} ]
}]] }]]
@ -766,7 +709,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'name': 'Input', 'name': 'Input',
'module': '@angular/core/src/metadata' 'module': 'angular2/src/core/metadata'
} }
} }
] ]
@ -780,7 +723,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
'__symbolic': 'call', '__symbolic': 'call',
'expression': { 'expression': {
'__symbolic': 'reference', '__symbolic': 'reference',
'module': '@angular/core/src/metadata', 'module': 'angular2/src/core/metadata',
'name': 'HostListener' 'name': 'HostListener'
}, },
'arguments': [ 'arguments': [
@ -797,11 +740,11 @@ class MockStaticReflectorHost implements StaticReflectorHost {
} }
} }
}, },
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {s: 's'}}, '/src/extern.d.ts': {'__symbolic': 'module', 'version': 1, metadata: {s: 's'}},
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}}, '/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/error-reporting.d.ts': { '/tmp/src/error-reporting.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
SomeClass: { SomeClass: {
__symbolic: 'class', __symbolic: 'class',
@ -811,7 +754,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
expression: { expression: {
__symbolic: 'reference', __symbolic: 'reference',
name: 'Component', name: 'Component',
module: '@angular/core/src/metadata' module: 'angular2/src/core/metadata'
}, },
arguments: [ arguments: [
{ {
@ -831,7 +774,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/error-references.d.ts': { '/tmp/src/error-references.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
Link1: { Link1: {
__symbolic: 'reference', __symbolic: 'reference',
@ -853,7 +796,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-declaration.d.ts': { '/tmp/src/function-declaration.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
one: { one: {
__symbolic: 'function', __symbolic: 'function',
@ -882,7 +825,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-reference.ts': { '/tmp/src/function-reference.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
one: { one: {
__symbolic: 'call', __symbolic: 'call',
@ -924,7 +867,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/function-recursive.d.ts': { '/tmp/src/function-recursive.d.ts': {
__symbolic: 'modules', __symbolic: 'modules',
version: 2, version: 1,
metadata: { metadata: {
recursive: { recursive: {
__symbolic: 'function', __symbolic: 'function',
@ -984,7 +927,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
}, },
'/tmp/src/spread.ts': { '/tmp/src/spread.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 2, version: 1,
metadata: { metadata: {
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5] spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
} }
@ -1012,8 +955,8 @@ class MockStaticReflectorHost implements StaticReflectorHost {
`, `,
'/tmp/src/invalid-calls.ts': ` '/tmp/src/invalid-calls.ts': `
import {someFunction} from './nvalid-calll-definitions.ts'; import {someFunction} from './nvalid-calll-definitions.ts';
import {Component} from '@angular/core/src/metadata'; import {Component} from 'angular2/src/core/metadata';
import {NgIf} from '@angular/common'; import {NgIf} from 'angular2/common';
@Component({ @Component({
selector: 'my-component', selector: 'my-component',
@ -1029,7 +972,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
export class MyOtherComponent { } export class MyOtherComponent { }
`, `,
'/tmp/src/static-method.ts': ` '/tmp/src/static-method.ts': `
import {Component} from '@angular/core/src/metadata'; import {Component} from 'angular2/src/core/metadata';
@Component({ @Component({
selector: 'stub' selector: 'stub'
@ -1047,7 +990,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-call.ts': ` '/tmp/src/static-method-call.ts': `
import {Component} from '@angular/core/src/metadata'; import {Component} from 'angular2/src/core/metadata';
import {MyModule} from './static-method'; import {MyModule} from './static-method';
@Component({ @Component({
@ -1066,7 +1009,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
export class MyDefaultsComponent { } export class MyDefaultsComponent { }
`, `,
'/tmp/src/static-field.ts': ` '/tmp/src/static-field.ts': `
import {Injectable} from '@angular/core'; import {Injectable} from 'angular2/core';
@Injectable() @Injectable()
export class MyModule { export class MyModule {
@ -1074,7 +1017,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-field-reference.ts': ` '/tmp/src/static-field-reference.ts': `
import {Component} from '@angular/core/src/metadata'; import {Component} from 'angular2/src/core/metadata';
import {MyModule} from './static-field'; import {MyModule} from './static-field';
@Component({ @Component({
@ -1088,7 +1031,7 @@ class MockStaticReflectorHost implements StaticReflectorHost {
} }
`, `,
'/tmp/src/static-method-ref.ts': ` '/tmp/src/static-method-ref.ts': `
import {Component} from '@angular/core/src/metadata'; import {Component} from 'angular2/src/core/metadata';
import {ClassWithStatics} from './static-method-def'; import {ClassWithStatics} from './static-method-def';
@Component({ @Component({
@ -1097,90 +1040,21 @@ class MockStaticReflectorHost implements StaticReflectorHost {
export class MethodReference { export class MethodReference {
} }
`, `
'/tmp/src/invalid-metadata.ts': `
import {Component} from '@angular/core/src/metadata';
@Component({
providers: [ { provider: 'a', useValue: (() => 1)() }]
})
export class InvalidMetadata {}
`,
'/tmp/src/forward-ref.ts': `
import {forwardRef} from '@angular/core';
import {Component} from '@angular/core/src/metadata';
import {Inject} from '@angular/core/src/di/metadata';
@Component({})
export class Forward {
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
}
export class Dep {
@Input f: Forward;
}
`,
'/tmp/src/reexport/reexport.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
exports: [
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
{from: './src/origin5'}, {from: './src/reexport2'}
]
},
'/tmp/src/reexport/src/origin1.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin5.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
Five: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin30.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {
Thirty: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/originNone.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
},
'/tmp/src/reexport/src/reexport2.d.ts': {
__symbolic: 'module',
version: 2,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
}
}; };
if (data[moduleId] && moduleId.match(TS_EXT)) { if (data[moduleId] && moduleId.match(TS_EXT)) {
const text = data[moduleId]; let text = data[moduleId];
if (typeof text === 'string') { if (typeof text === 'string') {
const sf = ts.createSourceFile( let sf = ts.createSourceFile(moduleId, data[moduleId], ts.ScriptTarget.ES5);
moduleId, data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true); let diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
if (diagnostics && diagnostics.length) { if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`); throw Error(`Error encountered during parse of file ${moduleId}`);
} }
return [this.collector.getMetadata(sf)]; return this.collector.getMetadata(sf);
} }
} }
const result = data[moduleId]; return data[moduleId];
if (result) {
return Array.isArray(result) ? result : [result];
} else {
return null;
}
} }
} }

View File

@ -25,16 +25,11 @@ export * from './src/template_parser/template_ast';
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser'; export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
export {CompilerConfig, RenderTypes} from './src/config'; export {CompilerConfig, RenderTypes} from './src/config';
export * from './src/compile_metadata'; export * from './src/compile_metadata';
export * from './src/aot/compiler_factory'; export * from './src/offline_compiler';
export * from './src/aot/compiler'; export {RuntimeCompiler} from './src/runtime_compiler';
export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol';
export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory';
export * from './src/url_resolver'; export * from './src/url_resolver';
export * from './src/resource_loader'; export * from './src/resource_loader';
export * from './src/compiler';
export {DirectiveResolver} from './src/directive_resolver'; export {DirectiveResolver} from './src/directive_resolver';
export {PipeResolver} from './src/pipe_resolver'; export {PipeResolver} from './src/pipe_resolver';
export {NgModuleResolver} from './src/ng_module_resolver'; export {NgModuleResolver} from './src/ng_module_resolver';
@ -57,6 +52,5 @@ export * from './src/selector';
export * from './src/style_compiler'; export * from './src/style_compiler';
export * from './src/template_parser/template_parser'; export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler'; export {ViewCompiler} from './src/view_compiler/view_compiler';
export {AnimationParser} from './src/animation/animation_parser';
// This file only reexports content of the `src` folder. Keep it that way. // This file only reexports content of the `src` folder. Keep it that way.

View File

@ -29,21 +29,19 @@ export class AnimationCompiler {
} }
} }
const _ANIMATION_FACTORY_ELEMENT_VAR = o.variable('element'); var _ANIMATION_FACTORY_ELEMENT_VAR = o.variable('element');
const _ANIMATION_DEFAULT_STATE_VAR = o.variable('defaultStateStyles'); var _ANIMATION_DEFAULT_STATE_VAR = o.variable('defaultStateStyles');
const _ANIMATION_FACTORY_VIEW_VAR = o.variable('view'); var _ANIMATION_FACTORY_VIEW_VAR = o.variable('view');
const _ANIMATION_FACTORY_VIEW_CONTEXT = _ANIMATION_FACTORY_VIEW_VAR.prop('animationContext'); var _ANIMATION_FACTORY_VIEW_CONTEXT = _ANIMATION_FACTORY_VIEW_VAR.prop('animationContext');
const _ANIMATION_FACTORY_RENDERER_VAR = _ANIMATION_FACTORY_VIEW_VAR.prop('renderer'); var _ANIMATION_FACTORY_RENDERER_VAR = _ANIMATION_FACTORY_VIEW_VAR.prop('renderer');
const _ANIMATION_CURRENT_STATE_VAR = o.variable('currentState'); var _ANIMATION_CURRENT_STATE_VAR = o.variable('currentState');
const _ANIMATION_NEXT_STATE_VAR = o.variable('nextState'); var _ANIMATION_NEXT_STATE_VAR = o.variable('nextState');
const _ANIMATION_PLAYER_VAR = o.variable('player'); var _ANIMATION_PLAYER_VAR = o.variable('player');
const _ANIMATION_TIME_VAR = o.variable('totalTime'); var _ANIMATION_TIME_VAR = o.variable('totalTime');
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles'); var _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles'); var _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles'); var _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers'); var EMPTY_MAP = o.literalMap([]);
const _EMPTY_MAP = o.literalMap([]);
const _EMPTY_ARRAY = o.literalArr([]);
class _AnimationBuilder implements AnimationAstVisitor { class _AnimationBuilder implements AnimationAstVisitor {
private _fnVarName: string; private _fnVarName: string;
@ -57,7 +55,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
} }
visitAnimationStyles(ast: AnimationStylesAst, context: _AnimationBuilderContext): o.Expression { visitAnimationStyles(ast: AnimationStylesAst, context: _AnimationBuilderContext): o.Expression {
const stylesArr: any[] = []; var stylesArr: any[] = [];
if (context.isExpectingFirstStyleStep) { if (context.isExpectingFirstStyleStep) {
stylesArr.push(_ANIMATION_START_STATE_STYLES_VAR); stylesArr.push(_ANIMATION_START_STATE_STYLES_VAR);
context.isExpectingFirstStyleStep = false; context.isExpectingFirstStyleStep = false;
@ -88,8 +86,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
return this._visitEndStateAnimation(ast, context); return this._visitEndStateAnimation(ast, context);
} }
const startingStylesExpr = ast.startingStyles.visit(this, context); var startingStylesExpr = ast.startingStyles.visit(this, context);
const keyframeExpressions = var keyframeExpressions =
ast.keyframes.map(keyframeEntry => keyframeEntry.visit(this, context)); ast.keyframes.map(keyframeEntry => keyframeEntry.visit(this, context));
return this._callAnimateMethod( return this._callAnimateMethod(
ast, startingStylesExpr, o.literalArr(keyframeExpressions), context); ast, startingStylesExpr, o.literalArr(keyframeExpressions), context);
@ -97,9 +95,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
/** @internal */ /** @internal */
_visitEndStateAnimation(ast: AnimationStepAst, context: _AnimationBuilderContext): o.Expression { _visitEndStateAnimation(ast: AnimationStepAst, context: _AnimationBuilderContext): o.Expression {
const startingStylesExpr = ast.startingStyles.visit(this, context); var startingStylesExpr = ast.startingStyles.visit(this, context);
const keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context)); var keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
const keyframesExpr = var keyframesExpr =
o.importExpr(resolveIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([ o.importExpr(resolveIdentifier(Identifiers.balanceAnimationKeyframes)).callFn([
_ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR, _ANIMATION_COLLECTED_STYLES, _ANIMATION_END_STATE_STYLES_VAR,
o.literalArr(keyframeExpressions) o.literalArr(keyframeExpressions)
@ -112,28 +110,23 @@ class _AnimationBuilder implements AnimationAstVisitor {
_callAnimateMethod( _callAnimateMethod(
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any, ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
context: _AnimationBuilderContext) { context: _AnimationBuilderContext) {
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
if (context.isExpectingFirstAnimateStep) {
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
context.isExpectingFirstAnimateStep = false;
}
context.totalTransitionTime += ast.duration + ast.delay; context.totalTransitionTime += ast.duration + ast.delay;
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [ return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration), _ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue o.literal(ast.delay), o.literal(ast.easing)
]); ]);
} }
visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext): visitAnimationSequence(ast: AnimationSequenceAst, context: _AnimationBuilderContext):
o.Expression { o.Expression {
const playerExprs = ast.steps.map(step => step.visit(this, context)); var playerExprs = ast.steps.map(step => step.visit(this, context));
return o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([ return o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer)).instantiate([
o.literalArr(playerExprs) o.literalArr(playerExprs)
]); ]);
} }
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression { visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
const playerExprs = ast.steps.map(step => step.visit(this, context)); var playerExprs = ast.steps.map(step => step.visit(this, context));
return o.importExpr(resolveIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([ return o.importExpr(resolveIdentifier(Identifiers.AnimationGroupPlayer)).instantiate([
o.literalArr(playerExprs) o.literalArr(playerExprs)
]); ]);
@ -141,7 +134,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
visitAnimationStateDeclaration( visitAnimationStateDeclaration(
ast: AnimationStateDeclarationAst, context: _AnimationBuilderContext): void { ast: AnimationStateDeclarationAst, context: _AnimationBuilderContext): void {
const flatStyles: {[key: string]: string | number} = {}; var flatStyles: {[key: string]: string | number} = {};
_getStylesArray(ast).forEach( _getStylesArray(ast).forEach(
entry => { Object.keys(entry).forEach(key => { flatStyles[key] = entry[key]; }); }); entry => { Object.keys(entry).forEach(key => { flatStyles[key] = entry[key]; }); });
context.stateMap.registerState(ast.stateName, flatStyles); context.stateMap.registerState(ast.stateName, flatStyles);
@ -149,17 +142,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
visitAnimationStateTransition( visitAnimationStateTransition(
ast: AnimationStateTransitionAst, context: _AnimationBuilderContext): any { ast: AnimationStateTransitionAst, context: _AnimationBuilderContext): any {
const steps = ast.animation.steps; var steps = ast.animation.steps;
const lastStep = steps[steps.length - 1]; var lastStep = steps[steps.length - 1];
if (_isEndStateAnimateStep(lastStep)) { if (_isEndStateAnimateStep(lastStep)) {
context.endStateAnimateStep = <AnimationStepAst>lastStep; context.endStateAnimateStep = <AnimationStepAst>lastStep;
} }
context.totalTransitionTime = 0; context.totalTransitionTime = 0;
context.isExpectingFirstStyleStep = true; context.isExpectingFirstStyleStep = true;
context.isExpectingFirstAnimateStep = true;
const stateChangePreconditions: o.Expression[] = []; var stateChangePreconditions: o.Expression[] = [];
ast.stateChanges.forEach(stateChange => { ast.stateChanges.forEach(stateChange => {
stateChangePreconditions.push( stateChangePreconditions.push(
@ -175,14 +167,14 @@ class _AnimationBuilder implements AnimationAstVisitor {
} }
}); });
const animationPlayerExpr = ast.animation.visit(this, context); var animationPlayerExpr = ast.animation.visit(this, context);
const reducedStateChangesPrecondition = stateChangePreconditions.reduce((a, b) => a.or(b)); var reducedStateChangesPrecondition = stateChangePreconditions.reduce((a, b) => a.or(b));
const precondition = var precondition =
_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR).and(reducedStateChangesPrecondition); _ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR).and(reducedStateChangesPrecondition);
const animationStmt = _ANIMATION_PLAYER_VAR.set(animationPlayerExpr).toStmt(); var animationStmt = _ANIMATION_PLAYER_VAR.set(animationPlayerExpr).toStmt();
const totalTimeStmt = _ANIMATION_TIME_VAR.set(o.literal(context.totalTransitionTime)).toStmt(); var totalTimeStmt = _ANIMATION_TIME_VAR.set(o.literal(context.totalTransitionTime)).toStmt();
return new o.IfStmt(precondition, [animationStmt, totalTimeStmt]); return new o.IfStmt(precondition, [animationStmt, totalTimeStmt]);
} }
@ -194,17 +186,18 @@ class _AnimationBuilder implements AnimationAstVisitor {
// this should always be defined even if the user overrides it // this should always be defined even if the user overrides it
context.stateMap.registerState(DEFAULT_STATE, {}); context.stateMap.registerState(DEFAULT_STATE, {});
const statements: o.Statement[] = []; var statements: o.Statement[] = [];
statements.push(_PREVIOUS_ANIMATION_PLAYERS statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod( .callMethod(
'getAnimationPlayers', 'cancelActiveAnimation',
[ [
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName), _ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE)) _ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
])) ])
.toDeclStmt()); .toStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt()); statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt()); statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
@ -228,7 +221,18 @@ class _AnimationBuilder implements AnimationAstVisitor {
_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR), _ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR),
[_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()])); [_ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt()]));
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles)); var RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context))); ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
@ -247,38 +251,17 @@ class _AnimationBuilder implements AnimationAstVisitor {
_ANIMATION_PLAYER_VAR _ANIMATION_PLAYER_VAR
.callMethod( .callMethod(
'onDone', 'onDone',
[o [o.fn(
.fn([], [],
[ [RENDER_STYLES_FN
_ANIMATION_PLAYER_VAR.callMethod('destroy', []).toStmt(),
RENDER_STYLES_FN
.callFn([ .callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR, _ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr( o.importExpr(resolveIdentifier(Identifiers.prepareFinalAnimationStyles))
resolveIdentifier(Identifiers.prepareFinalAnimationStyles))
.callFn([ .callFn([
_ANIMATION_START_STATE_STYLES_VAR, _ANIMATION_START_STATE_STYLES_VAR, _ANIMATION_END_STATE_STYLES_VAR
_ANIMATION_END_STATE_STYLES_VAR
]) ])
]) ])
.toStmt() .toStmt()])])
])])
.toStmt());
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
.callMethod('destroy', [])
.toStmt());
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt()); .toStmt());
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
@ -309,16 +292,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
} }
build(ast: AnimationAst): AnimationEntryCompileResult { build(ast: AnimationAst): AnimationEntryCompileResult {
const context = new _AnimationBuilderContext(); var context = new _AnimationBuilderContext();
const fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName); var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
const fnVariable = o.variable(this._fnVarName); var fnVariable = o.variable(this._fnVarName);
const lookupMap: any[] = []; var lookupMap: any[] = [];
Object.keys(context.stateMap.states).forEach(stateName => { Object.keys(context.stateMap.states).forEach(stateName => {
const value = context.stateMap.states[stateName]; const value = context.stateMap.states[stateName];
let variableValue = _EMPTY_MAP; var variableValue = EMPTY_MAP;
if (isPresent(value)) { if (isPresent(value)) {
const styleMap: any[] = []; let styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); }); Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
variableValue = o.literalMap(styleMap); variableValue = o.literalMap(styleMap);
} }
@ -336,7 +319,6 @@ class _AnimationBuilderContext {
stateMap = new _AnimationBuilderStateMap(); stateMap = new _AnimationBuilderStateMap();
endStateAnimateStep: AnimationStepAst = null; endStateAnimateStep: AnimationStepAst = null;
isExpectingFirstStyleStep = false; isExpectingFirstStyleStep = false;
isExpectingFirstAnimateStep = false;
totalTransitionTime = 0; totalTransitionTime = 0;
} }
@ -344,7 +326,7 @@ class _AnimationBuilderStateMap {
private _states: {[key: string]: {[prop: string]: string | number}} = {}; private _states: {[key: string]: {[prop: string]: string | number}} = {};
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 {
const existingEntry = this._states[name]; var existingEntry = this._states[name];
if (!existingEntry) { if (!existingEntry) {
this._states[name] = value; this._states[name] = value;
} }
@ -352,7 +334,7 @@ class _AnimationBuilderStateMap {
} }
function _compareToAnimationStateExpr(value: o.Expression, animationState: string): o.Expression { function _compareToAnimationStateExpr(value: o.Expression, animationState: string): o.Expression {
const emptyStateLiteral = o.literal(EMPTY_STATE); var emptyStateLiteral = o.literal(EMPTY_STATE);
switch (animationState) { switch (animationState) {
case EMPTY_STATE: case EMPTY_STATE:
return value.equals(emptyStateLiteral); return value.equals(emptyStateLiteral);
@ -369,8 +351,8 @@ function _isEndStateAnimateStep(step: AnimationAst): boolean {
// the final animation step is characterized by having only TWO // the final animation step is characterized by having only TWO
// keyframe values and it must have zero styles for both keyframes // keyframe values and it must have zero styles for both keyframes
if (step instanceof AnimationStepAst && step.duration > 0 && step.keyframes.length == 2) { if (step instanceof AnimationStepAst && step.duration > 0 && step.keyframes.length == 2) {
const styles1 = _getStylesArray(step.keyframes[0])[0]; var styles1 = _getStylesArray(step.keyframes[0])[0];
const 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 Object.keys(styles1).length === 0 && Object.keys(styles2).length === 0;
} }
return false; return false;

View File

@ -6,14 +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 {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, CompileDirectiveMetadata} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection'; import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core'; import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
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';
@ -35,10 +32,7 @@ export class AnimationEntryParseResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {} constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
} }
@Injectable()
export class AnimationParser { export class AnimationParser {
constructor(private _schema: ElementSchemaRegistry) {}
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] { parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
const errors: string[] = []; const errors: string[] = [];
const componentName = component.type.name; const componentName = component.type.name;
@ -72,14 +66,14 @@ export class AnimationParser {
} }
parseEntry(entry: CompileAnimationEntryMetadata): AnimationEntryParseResult { parseEntry(entry: CompileAnimationEntryMetadata): AnimationEntryParseResult {
const errors: AnimationParseError[] = []; var errors: AnimationParseError[] = [];
const stateStyles: {[key: string]: AnimationStylesAst} = {}; var stateStyles: {[key: string]: AnimationStylesAst} = {};
const transitions: CompileAnimationStateTransitionMetadata[] = []; var transitions: CompileAnimationStateTransitionMetadata[] = [];
const stateDeclarationAsts: AnimationStateDeclarationAst[] = []; var stateDeclarationAsts: AnimationStateDeclarationAst[] = [];
entry.definitions.forEach(def => { entry.definitions.forEach(def => {
if (def instanceof CompileAnimationStateDeclarationMetadata) { if (def instanceof CompileAnimationStateDeclarationMetadata) {
_parseAnimationDeclarationStates(def, this._schema, errors).forEach(ast => { _parseAnimationDeclarationStates(def, errors).forEach(ast => {
stateDeclarationAsts.push(ast); stateDeclarationAsts.push(ast);
stateStyles[ast.stateName] = ast.styles; stateStyles[ast.stateName] = ast.styles;
}); });
@ -88,40 +82,50 @@ export class AnimationParser {
} }
}); });
const stateTransitionAsts = transitions.map( var stateTransitionAsts =
transDef => _parseAnimationStateTransition(transDef, stateStyles, this._schema, errors)); transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
const ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts); var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
return new AnimationEntryParseResult(ast, errors); return new AnimationEntryParseResult(ast, errors);
} }
} }
function _parseAnimationDeclarationStates( function _parseAnimationDeclarationStates(
stateMetadata: CompileAnimationStateDeclarationMetadata, schema: ElementSchemaRegistry, stateMetadata: CompileAnimationStateDeclarationMetadata,
errors: AnimationParseError[]): AnimationStateDeclarationAst[] { errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
const normalizedStyles = _normalizeStyleMetadata(stateMetadata.styles, {}, schema, errors, false); var styleValues: Styles[] = [];
const defStyles = new AnimationStylesAst(normalizedStyles); stateMetadata.styles.styles.forEach(stylesEntry => {
const states = stateMetadata.stateNameExpr.split(/\s*,\s*/); // TODO (matsko): change this when we get CSS class integration support
if (typeof stylesEntry === 'object' && stylesEntry !== null) {
styleValues.push(stylesEntry as Styles);
} else {
errors.push(new AnimationParseError(
`State based animations cannot contain references to other states`));
}
});
var defStyles = new AnimationStylesAst(styleValues);
var states = stateMetadata.stateNameExpr.split(/\s*,\s*/);
return states.map(state => new AnimationStateDeclarationAst(state, defStyles)); return states.map(state => new AnimationStateDeclarationAst(state, defStyles));
} }
function _parseAnimationStateTransition( function _parseAnimationStateTransition(
transitionStateMetadata: CompileAnimationStateTransitionMetadata, transitionStateMetadata: CompileAnimationStateTransitionMetadata,
stateStyles: {[key: string]: AnimationStylesAst}, schema: ElementSchemaRegistry, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]): AnimationStateTransitionAst { errors: AnimationParseError[]): AnimationStateTransitionAst {
const styles = new StylesCollection(); var styles = new StylesCollection();
const transitionExprs: AnimationStateTransitionExpression[] = []; var transitionExprs: AnimationStateTransitionExpression[] = [];
const transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/); var transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
transitionStates.forEach( transitionStates.forEach(
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); }); expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
const entry = _normalizeAnimationEntry(transitionStateMetadata.steps); var entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
const animation = _normalizeStyleSteps(entry, stateStyles, schema, errors); var animation = _normalizeStyleSteps(entry, stateStyles, errors);
const animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors); var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
if (errors.length == 0) { if (errors.length == 0) {
_fillAnimationAstStartingKeyframes(animationAst, styles, errors); _fillAnimationAstStartingKeyframes(animationAst, styles, errors);
} }
const stepsAst: AnimationWithStepsAst = (animationAst instanceof AnimationWithStepsAst) ? var stepsAst: AnimationWithStepsAst = (animationAst instanceof AnimationWithStepsAst) ?
animationAst : animationAst :
new AnimationSequenceAst([animationAst]); new AnimationSequenceAst([animationAst]);
@ -143,22 +147,22 @@ function _parseAnimationAlias(alias: string, errors: AnimationParseError[]): str
function _parseAnimationTransitionExpr( function _parseAnimationTransitionExpr(
eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] { eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
const expressions: AnimationStateTransitionExpression[] = []; var expressions: AnimationStateTransitionExpression[] = [];
if (eventStr[0] == ':') { if (eventStr[0] == ':') {
eventStr = _parseAnimationAlias(eventStr, errors); eventStr = _parseAnimationAlias(eventStr, errors);
} }
const 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`));
return expressions; return expressions;
} }
const fromState = match[1]; var fromState = match[1];
const separator = match[2]; var separator = match[2];
const toState = match[3]; var toState = match[3];
expressions.push(new AnimationStateTransitionExpression(fromState, toState)); expressions.push(new AnimationStateTransitionExpression(fromState, toState));
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE; var isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
if (separator[0] == '<' && !isFullAnyStateExpr) { if (separator[0] == '<' && !isFullAnyStateExpr) {
expressions.push(new AnimationStateTransitionExpression(toState, fromState)); expressions.push(new AnimationStateTransitionExpression(toState, fromState));
} }
@ -172,31 +176,13 @@ function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnima
function _normalizeStyleMetadata( function _normalizeStyleMetadata(
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst}, entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
schema: ElementSchemaRegistry, errors: AnimationParseError[], errors: AnimationParseError[]): {[key: string]: string | number}[] {
permitStateReferences: boolean): {[key: string]: string | number}[] { var normalizedStyles: {[key: string]: string | number}[] = [];
const normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => { entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') { if (typeof styleEntry === 'string') {
if (permitStateReferences) {
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors)); normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
} else { } else {
errors.push(new AnimationParseError( normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
`State based animations cannot contain references to other states`));
}
} else {
const stylesObj = <Styles>styleEntry;
const normalizedStylesObj: Styles = {};
Object.keys(stylesObj).forEach(propName => {
const normalizedProp = schema.normalizeAnimationStyleProperty(propName);
const normalizedOutput =
schema.normalizeAnimationStyleValue(normalizedProp, propName, stylesObj[propName]);
const normalizationError = normalizedOutput['error'];
if (normalizationError) {
errors.push(new AnimationParseError(normalizationError));
}
normalizedStylesObj[normalizedProp] = normalizedOutput['value'];
});
normalizedStyles.push(normalizedStylesObj);
} }
}); });
return normalizedStyles; return normalizedStyles;
@ -204,8 +190,8 @@ function _normalizeStyleMetadata(
function _normalizeStyleSteps( function _normalizeStyleSteps(
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst}, entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata { errors: AnimationParseError[]): CompileAnimationMetadata {
const steps = _normalizeStyleStepEntry(entry, stateStyles, schema, errors); var steps = _normalizeStyleStepEntry(entry, stateStyles, errors);
return (entry instanceof CompileAnimationGroupMetadata) ? return (entry instanceof CompileAnimationGroupMetadata) ?
new CompileAnimationGroupMetadata(steps) : new CompileAnimationGroupMetadata(steps) :
new CompileAnimationSequenceMetadata(steps); new CompileAnimationSequenceMetadata(steps);
@ -214,8 +200,8 @@ function _normalizeStyleSteps(
function _mergeAnimationStyles( function _mergeAnimationStyles(
stylesList: any[], newItem: {[key: string]: string | number} | string) { stylesList: any[], newItem: {[key: string]: string | number} | string) {
if (typeof newItem === 'object' && newItem !== null && stylesList.length > 0) { if (typeof newItem === 'object' && newItem !== null && stylesList.length > 0) {
const lastIndex = stylesList.length - 1; var lastIndex = stylesList.length - 1;
const lastItem = stylesList[lastIndex]; var lastItem = stylesList[lastIndex];
if (typeof lastItem === 'object' && lastItem !== null) { if (typeof lastItem === 'object' && lastItem !== null) {
stylesList[lastIndex] = StringMapWrapper.merge( stylesList[lastIndex] = StringMapWrapper.merge(
<{[key: string]: string | number}>lastItem, <{[key: string]: string | number}>newItem); <{[key: string]: string | number}>lastItem, <{[key: string]: string | number}>newItem);
@ -227,16 +213,16 @@ function _mergeAnimationStyles(
function _normalizeStyleStepEntry( function _normalizeStyleStepEntry(
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst}, entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata[] { errors: AnimationParseError[]): CompileAnimationMetadata[] {
let steps: CompileAnimationMetadata[]; var steps: CompileAnimationMetadata[];
if (entry instanceof CompileAnimationWithStepsMetadata) { if (entry instanceof CompileAnimationWithStepsMetadata) {
steps = entry.steps; steps = entry.steps;
} else { } else {
return [entry]; return [entry];
} }
const newSteps: CompileAnimationMetadata[] = []; var newSteps: CompileAnimationMetadata[] = [];
let combinedStyles: Styles[]; var combinedStyles: Styles[];
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
@ -246,8 +232,7 @@ function _normalizeStyleStepEntry(
if (!isPresent(combinedStyles)) { if (!isPresent(combinedStyles)) {
combinedStyles = []; combinedStyles = [];
} }
_normalizeStyleMetadata( _normalizeStyleMetadata(<CompileAnimationStyleMetadata>step, stateStyles, errors)
<CompileAnimationStyleMetadata>step, stateStyles, schema, errors, true)
.forEach(entry => { _mergeAnimationStyles(combinedStyles, entry); }); .forEach(entry => { _mergeAnimationStyles(combinedStyles, entry); });
} else { } else {
// it is important that we create a metadata entry of the combined styles // it is important that we create a metadata entry of the combined styles
@ -262,17 +247,16 @@ function _normalizeStyleStepEntry(
if (step instanceof CompileAnimationAnimateMetadata) { if (step instanceof CompileAnimationAnimateMetadata) {
// we do not recurse into CompileAnimationAnimateMetadata since // we do not recurse into CompileAnimationAnimateMetadata since
// those style steps are not going to be squashed // those style steps are not going to be squashed
const animateStyleValue = (<CompileAnimationAnimateMetadata>step).styles; var animateStyleValue = (<CompileAnimationAnimateMetadata>step).styles;
if (animateStyleValue instanceof CompileAnimationStyleMetadata) { if (animateStyleValue instanceof CompileAnimationStyleMetadata) {
animateStyleValue.styles = animateStyleValue.styles =
_normalizeStyleMetadata(animateStyleValue, stateStyles, schema, errors, true); _normalizeStyleMetadata(animateStyleValue, stateStyles, errors);
} else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) { } else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) {
animateStyleValue.steps.forEach(step => { animateStyleValue.steps.forEach(
step.styles = _normalizeStyleMetadata(step, stateStyles, schema, errors, true); step => { step.styles = _normalizeStyleMetadata(step, stateStyles, errors); });
});
} }
} else if (step instanceof CompileAnimationWithStepsMetadata) { } else if (step instanceof CompileAnimationWithStepsMetadata) {
const innerSteps = _normalizeStyleStepEntry(step, stateStyles, schema, errors); let innerSteps = _normalizeStyleStepEntry(step, stateStyles, errors);
step = step instanceof CompileAnimationGroupMetadata ? step = step instanceof CompileAnimationGroupMetadata ?
new CompileAnimationGroupMetadata(innerSteps) : new CompileAnimationGroupMetadata(innerSteps) :
new CompileAnimationSequenceMetadata(innerSteps); new CompileAnimationSequenceMetadata(innerSteps);
@ -294,12 +278,12 @@ function _normalizeStyleStepEntry(
function _resolveStylesFromState( function _resolveStylesFromState(
stateName: string, stateStyles: {[key: string]: AnimationStylesAst}, stateName: string, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]) { errors: AnimationParseError[]) {
const styles: Styles[] = []; var styles: Styles[] = [];
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 {
const normalizedStateName = stateName.substring(1); var normalizedStateName = stateName.substring(1);
const value = stateStyles[normalizedStateName]; var value = stateStyles[normalizedStateName];
if (!isPresent(value)) { if (!isPresent(value)) {
errors.push(new AnimationParseError( errors.push(new AnimationParseError(
`Unable to apply styles due to missing a state: "${normalizedStateName}"`)); `Unable to apply styles due to missing a state: "${normalizedStateName}"`));
@ -322,8 +306,8 @@ function _parseAnimationKeyframes(
keyframeSequence: CompileAnimationKeyframesSequenceMetadata, currentTime: number, keyframeSequence: CompileAnimationKeyframesSequenceMetadata, currentTime: number,
collectedStyles: StylesCollection, stateStyles: {[key: string]: AnimationStylesAst}, collectedStyles: StylesCollection, stateStyles: {[key: string]: AnimationStylesAst},
errors: AnimationParseError[]): AnimationKeyframeAst[] { errors: AnimationParseError[]): AnimationKeyframeAst[] {
const totalEntries = keyframeSequence.steps.length; var totalEntries = keyframeSequence.steps.length;
let totalOffsets = 0; var totalOffsets = 0;
keyframeSequence.steps.forEach(step => totalOffsets += (isPresent(step.offset) ? 1 : 0)); keyframeSequence.steps.forEach(step => totalOffsets += (isPresent(step.offset) ? 1 : 0));
if (totalOffsets > 0 && totalOffsets < totalEntries) { if (totalOffsets > 0 && totalOffsets < totalEntries) {
@ -332,15 +316,15 @@ function _parseAnimationKeyframes(
totalOffsets = totalEntries; totalOffsets = totalEntries;
} }
let limit = totalEntries - 1; var limit = totalEntries - 1;
const margin = totalOffsets == 0 ? (1 / limit) : 0; var margin = totalOffsets == 0 ? (1 / limit) : 0;
const rawKeyframes: any[] /** TODO #9100 */ = []; var rawKeyframes: any[] /** TODO #9100 */ = [];
let index = 0; var index = 0;
let doSortKeyframes = false; var doSortKeyframes = false;
let lastOffset = 0; var lastOffset = 0;
keyframeSequence.steps.forEach(styleMetadata => { keyframeSequence.steps.forEach(styleMetadata => {
let offset = styleMetadata.offset; var offset = styleMetadata.offset;
const keyframeStyles: Styles = {}; var keyframeStyles: Styles = {};
styleMetadata.styles.forEach(entry => { styleMetadata.styles.forEach(entry => {
Object.keys(entry).forEach(prop => { Object.keys(entry).forEach(prop => {
if (prop != 'offset') { if (prop != 'offset') {
@ -364,23 +348,23 @@ function _parseAnimationKeyframes(
rawKeyframes.sort((a, b) => a[0] <= b[0] ? -1 : 1); rawKeyframes.sort((a, b) => a[0] <= b[0] ? -1 : 1);
} }
let firstKeyframe = rawKeyframes[0]; var firstKeyframe = rawKeyframes[0];
if (firstKeyframe[0] != _INITIAL_KEYFRAME) { if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
rawKeyframes.splice(0, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]); rawKeyframes.splice(0, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
} }
const firstKeyframeStyles = firstKeyframe[1]; var firstKeyframeStyles = firstKeyframe[1];
limit = rawKeyframes.length - 1; limit = rawKeyframes.length - 1;
let lastKeyframe = rawKeyframes[limit]; var lastKeyframe = rawKeyframes[limit];
if (lastKeyframe[0] != _TERMINAL_KEYFRAME) { if (lastKeyframe[0] != _TERMINAL_KEYFRAME) {
rawKeyframes.push(lastKeyframe = [_TERMINAL_KEYFRAME, {}]); rawKeyframes.push(lastKeyframe = [_TERMINAL_KEYFRAME, {}]);
limit++; limit++;
} }
const lastKeyframeStyles = lastKeyframe[1]; var lastKeyframeStyles = lastKeyframe[1];
for (let i = 1; i <= limit; i++) { for (let i = 1; i <= limit; i++) {
const entry = rawKeyframes[i]; let entry = rawKeyframes[i];
const styles = entry[1]; let styles = entry[1];
Object.keys(styles).forEach(prop => { Object.keys(styles).forEach(prop => {
if (!isPresent(firstKeyframeStyles[prop])) { if (!isPresent(firstKeyframeStyles[prop])) {
@ -390,8 +374,8 @@ function _parseAnimationKeyframes(
} }
for (let i = limit - 1; i >= 0; i--) { for (let i = limit - 1; i >= 0; i--) {
const entry = rawKeyframes[i]; let entry = rawKeyframes[i];
const styles = entry[1]; let styles = entry[1];
Object.keys(styles).forEach(prop => { Object.keys(styles).forEach(prop => {
if (!isPresent(lastKeyframeStyles[prop])) { if (!isPresent(lastKeyframeStyles[prop])) {
@ -407,21 +391,21 @@ function _parseAnimationKeyframes(
function _parseTransitionAnimation( function _parseTransitionAnimation(
entry: CompileAnimationMetadata, currentTime: number, collectedStyles: StylesCollection, entry: CompileAnimationMetadata, currentTime: number, collectedStyles: StylesCollection,
stateStyles: {[key: string]: AnimationStylesAst}, errors: AnimationParseError[]): AnimationAst { stateStyles: {[key: string]: AnimationStylesAst}, errors: AnimationParseError[]): AnimationAst {
let ast: any /** TODO #9100 */; var ast: any /** TODO #9100 */;
let playTime = 0; var playTime = 0;
const startingTime = currentTime; var startingTime = currentTime;
if (entry instanceof CompileAnimationWithStepsMetadata) { if (entry instanceof CompileAnimationWithStepsMetadata) {
let maxDuration = 0; var maxDuration = 0;
const steps: any[] /** TODO #9100 */ = []; var steps: any[] /** TODO #9100 */ = [];
const isGroup = entry instanceof CompileAnimationGroupMetadata; var isGroup = entry instanceof CompileAnimationGroupMetadata;
let previousStyles: any /** TODO #9100 */; var previousStyles: any /** TODO #9100 */;
entry.steps.forEach(entry => { entry.steps.forEach(entry => {
// these will get picked up by the next step... // these will get picked up by the next step...
const time = isGroup ? startingTime : currentTime; var time = isGroup ? startingTime : currentTime;
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
const map = stylesEntry as Styles; var map = stylesEntry as Styles;
Object.keys(map).forEach( Object.keys(map).forEach(
prop => { collectedStyles.insertAtTime(prop, time, map[prop]); }); prop => { collectedStyles.insertAtTime(prop, time, map[prop]); });
}); });
@ -429,26 +413,26 @@ function _parseTransitionAnimation(
return; return;
} }
const innerAst = _parseTransitionAnimation(entry, time, collectedStyles, stateStyles, errors); var innerAst = _parseTransitionAnimation(entry, time, collectedStyles, stateStyles, errors);
if (isPresent(previousStyles)) { if (isPresent(previousStyles)) {
if (entry instanceof CompileAnimationWithStepsMetadata) { if (entry instanceof CompileAnimationWithStepsMetadata) {
const startingStyles = new AnimationStylesAst(previousStyles); let startingStyles = new AnimationStylesAst(previousStyles);
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, '')); steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
} else { } else {
const innerStep = <AnimationStepAst>innerAst; var innerStep = <AnimationStepAst>innerAst;
innerStep.startingStyles.styles.push(...previousStyles); innerStep.startingStyles.styles.push(...previousStyles);
} }
previousStyles = null; previousStyles = null;
} }
const astDuration = innerAst.playTime; var astDuration = innerAst.playTime;
currentTime += astDuration; currentTime += astDuration;
playTime += astDuration; playTime += astDuration;
maxDuration = Math.max(astDuration, maxDuration); maxDuration = Math.max(astDuration, maxDuration);
steps.push(innerAst); steps.push(innerAst);
}); });
if (isPresent(previousStyles)) { if (isPresent(previousStyles)) {
const startingStyles = new AnimationStylesAst(previousStyles); let startingStyles = new AnimationStylesAst(previousStyles);
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, '')); steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
} }
if (isGroup) { if (isGroup) {
@ -459,18 +443,18 @@ function _parseTransitionAnimation(
ast = new AnimationSequenceAst(steps); ast = new AnimationSequenceAst(steps);
} }
} else if (entry instanceof CompileAnimationAnimateMetadata) { } else if (entry instanceof CompileAnimationAnimateMetadata) {
const timings = _parseTimeExpression(entry.timings, errors); var timings = _parseTimeExpression(entry.timings, errors);
const styles = entry.styles; var styles = entry.styles;
let keyframes: any /** TODO #9100 */; var keyframes: any /** TODO #9100 */;
if (styles instanceof CompileAnimationKeyframesSequenceMetadata) { if (styles instanceof CompileAnimationKeyframesSequenceMetadata) {
keyframes = keyframes =
_parseAnimationKeyframes(styles, currentTime, collectedStyles, stateStyles, errors); _parseAnimationKeyframes(styles, currentTime, collectedStyles, stateStyles, errors);
} else { } else {
const styleData = <CompileAnimationStyleMetadata>styles; let styleData = <CompileAnimationStyleMetadata>styles;
const offset = _TERMINAL_KEYFRAME; let offset = _TERMINAL_KEYFRAME;
const styleAst = new AnimationStylesAst(styleData.styles as Styles[]); let styleAst = new AnimationStylesAst(styleData.styles as Styles[]);
const keyframe = new AnimationKeyframeAst(offset, styleAst); var keyframe = new AnimationKeyframeAst(offset, styleAst);
keyframes = [keyframe]; keyframes = [keyframe];
} }
@ -499,10 +483,10 @@ function _fillAnimationAstStartingKeyframes(
ast: AnimationAst, collectedStyles: StylesCollection, errors: AnimationParseError[]): void { ast: AnimationAst, collectedStyles: StylesCollection, errors: AnimationParseError[]): void {
// steps that only contain style will not be filled // steps that only contain style will not be filled
if ((ast instanceof AnimationStepAst) && ast.keyframes.length > 0) { if ((ast instanceof AnimationStepAst) && ast.keyframes.length > 0) {
const keyframes = ast.keyframes; var keyframes = ast.keyframes;
if (keyframes.length == 1) { if (keyframes.length == 1) {
const endKeyframe = keyframes[0]; var endKeyframe = keyframes[0];
const startKeyframe = _createStartKeyframeFromEndKeyframe( var startKeyframe = _createStartKeyframeFromEndKeyframe(
endKeyframe, ast.startTime, ast.playTime, collectedStyles, errors); endKeyframe, ast.startTime, ast.playTime, collectedStyles, errors);
ast.keyframes = [startKeyframe, endKeyframe]; ast.keyframes = [startKeyframe, endKeyframe];
} }
@ -513,10 +497,10 @@ function _fillAnimationAstStartingKeyframes(
function _parseTimeExpression( function _parseTimeExpression(
exp: string | number, errors: AnimationParseError[]): _AnimationTimings { exp: string | number, errors: AnimationParseError[]): _AnimationTimings {
const regex = /^([\.\d]+)(m?s)(?:\s+([\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?/i; var regex = /^([\.\d]+)(m?s)(?:\s+([\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?/i;
let duration: number; var duration: number;
let delay: number = 0; var delay: number = 0;
let easing: string = null; var easing: string = null;
if (typeof exp === 'string') { if (typeof exp === 'string') {
const matches = exp.match(regex); const matches = exp.match(regex);
if (matches === null) { if (matches === null) {
@ -524,24 +508,24 @@ function _parseTimeExpression(
return new _AnimationTimings(0, 0, null); return new _AnimationTimings(0, 0, null);
} }
let durationMatch = parseFloat(matches[1]); var durationMatch = parseFloat(matches[1]);
const durationUnit = matches[2]; var durationUnit = matches[2];
if (durationUnit == 's') { if (durationUnit == 's') {
durationMatch *= _ONE_SECOND; durationMatch *= _ONE_SECOND;
} }
duration = Math.floor(durationMatch); duration = Math.floor(durationMatch);
const delayMatch = matches[3]; var delayMatch = matches[3];
const delayUnit = matches[4]; var delayUnit = matches[4];
if (isPresent(delayMatch)) { if (isPresent(delayMatch)) {
let delayVal: number = parseFloat(delayMatch); var delayVal: number = parseFloat(delayMatch);
if (isPresent(delayUnit) && delayUnit == 's') { if (isPresent(delayUnit) && delayUnit == 's') {
delayVal *= _ONE_SECOND; delayVal *= _ONE_SECOND;
} }
delay = Math.floor(delayVal); delay = Math.floor(delayVal);
} }
const easingVal = matches[5]; var easingVal = matches[5];
if (!isBlank(easingVal)) { if (!isBlank(easingVal)) {
easing = easingVal; easing = easingVal;
} }
@ -555,15 +539,15 @@ 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 {
const values: Styles = {}; var values: Styles = {};
const endTime = startTime + duration; var endTime = startTime + duration;
endKeyframe.styles.styles.forEach((styleData: Styles) => { endKeyframe.styles.styles.forEach((styleData: Styles) => {
Object.keys(styleData).forEach(prop => { Object.keys(styleData).forEach(prop => {
const val = styleData[prop]; const val = styleData[prop];
if (prop == 'offset') return; if (prop == 'offset') return;
const resultIndex = collectedStyles.indexOfAtOrBeforeTime(prop, startTime); var resultIndex = collectedStyles.indexOfAtOrBeforeTime(prop, startTime);
let resultEntry: any /** TODO #9100 */, nextEntry: any /** TODO #9100 */, var resultEntry: any /** TODO #9100 */, nextEntry: any /** TODO #9100 */,
value: any /** TODO #9100 */; value: any /** TODO #9100 */;
if (isPresent(resultIndex)) { if (isPresent(resultIndex)) {
resultEntry = collectedStyles.getByIndex(prop, resultIndex); resultEntry = collectedStyles.getByIndex(prop, resultIndex);

View File

@ -20,16 +20,16 @@ export class StylesCollection {
styles: {[key: string]: StylesCollectionEntry[]} = {}; styles: {[key: string]: StylesCollectionEntry[]} = {};
insertAtTime(property: string, time: number, value: string|number) { insertAtTime(property: string, time: number, value: string|number) {
const tuple = new StylesCollectionEntry(time, value); var tuple = new StylesCollectionEntry(time, value);
let entries = this.styles[property]; var entries = this.styles[property];
if (!isPresent(entries)) { if (!isPresent(entries)) {
entries = this.styles[property] = []; entries = this.styles[property] = [];
} }
// insert this at the right stop in the array // insert this at the right stop in the array
// this way we can keep it sorted // this way we can keep it sorted
let insertionIndex = 0; var insertionIndex = 0;
for (let i = entries.length - 1; i >= 0; i--) { for (var i = entries.length - 1; i >= 0; i--) {
if (entries[i].time <= time) { if (entries[i].time <= time) {
insertionIndex = i + 1; insertionIndex = i + 1;
break; break;
@ -40,7 +40,7 @@ export class StylesCollection {
} }
getByIndex(property: string, index: number): StylesCollectionEntry { getByIndex(property: string, index: number): StylesCollectionEntry {
const items = this.styles[property]; var items = this.styles[property];
if (isPresent(items)) { if (isPresent(items)) {
return index >= items.length ? null : items[index]; return index >= items.length ? null : items[index];
} }
@ -48,9 +48,9 @@ export class StylesCollection {
} }
indexOfAtOrBeforeTime(property: string, time: number): number { indexOfAtOrBeforeTime(property: string, time: number): number {
const entries = this.styles[property]; var entries = this.styles[property];
if (isPresent(entries)) { if (isPresent(entries)) {
for (let i = entries.length - 1; i >= 0; i--) { for (var i = entries.length - 1; i >= 0; i--) {
if (entries[i].time <= time) return i; if (entries[i].time <= time) return i;
} }
} }

View File

@ -1,75 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ViewEncapsulation} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import {NgModuleResolver} from '../ng_module_resolver';
import {TypeScriptEmitter} from '../output/ts_emitter';
import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler';
import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
/**
* Creates a new AotCompiler based on options and a host.
*/
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
{compiler: AotCompiler, reflector: StaticReflector} {
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(compilerHost);
StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({
genDebugInfo: options.debug === true,
defaultEncapsulation: ViewEncapsulation.Emulated,
logBindingUpdate: false,
useJit: false
});
const normalizer = new DirectiveNormalizer(
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
const expressionParser = new Parser(new Lexer());
const elementSchemaRegistry = new DomElementSchemaRegistry();
const console = new Console();
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
return {compiler, reflector: staticReflector};
}

View File

@ -1,24 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol';
/**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver {
/**
* Loads a resource (e.g. html / css)
*/
loadResource(path: string): Promise<string>;
}

View File

@ -1,20 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export function isStaticSymbol(value: any): value is StaticSymbol {
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
}
/**
* A token representing the a reference to a static type.
*
* This token is unique for a filePath and name and can be used as a hash table key.
*/
export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {}
}

View File

@ -17,7 +17,7 @@ export function assertArrayOfStrings(identifier: string, value: any) {
if (!Array.isArray(value)) { if (!Array.isArray(value)) {
throw new Error(`Expected '${identifier}' to be an array of strings.`); throw new Error(`Expected '${identifier}' to be an array of strings.`);
} }
for (let i = 0; i < value.length; i += 1) { for (var i = 0; i < value.length; i += 1) {
if (typeof value[i] !== 'string') { if (typeof value[i] !== 'string') {
throw new Error(`Expected '${identifier}' to be an array of strings.`); throw new Error(`Expected '${identifier}' to be an array of strings.`);
} }

View File

@ -8,7 +8,7 @@
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper, MapWrapper} from './facade/collection';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {LifecycleHooks} from './private_import_core'; import {LifecycleHooks} from './private_import_core';
import {CssSelector} from './selector'; import {CssSelector} from './selector';
@ -98,15 +98,6 @@ export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier
get identifier(): CompileIdentifierMetadata { return this; } get identifier(): CompileIdentifierMetadata { return this; }
} }
/**
* A CompileSummary is the data needed to use a directive / pipe / module
* in other modules / components. However, this data is not enough to compile
* the directive / module itself.
*/
export interface CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
}
export class CompileDiDependencyMetadata { export class CompileDiDependencyMetadata {
isAttribute: boolean; isAttribute: boolean;
isSelf: boolean; isSelf: boolean;
@ -273,16 +264,6 @@ export class CompileStylesheetMetadata {
} }
} }
/**
* Summary Metadata regarding compilation of a template.
*/
export interface CompileTemplateSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
animations: string[];
ngContentSelectors: string[];
encapsulation: ViewEncapsulation;
}
/** /**
* Metadata regarding compilation of a template. * Metadata regarding compilation of a template.
*/ */
@ -322,34 +303,6 @@ export class CompileTemplateMetadata {
} }
this.interpolation = interpolation; this.interpolation = interpolation;
} }
toSummary(): CompileTemplateSummary {
return {
isSummary: true,
animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation
};
}
}
export interface CompileDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
exportAs: string;
inputs: {[key: string]: string};
outputs: {[key: string]: string};
hostListeners: {[key: string]: string};
hostProperties: {[key: string]: string};
hostAttributes: {[key: string]: string};
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
entryComponents: CompileIdentifierMetadata[];
changeDetection: ChangeDetectionStrategy;
template: CompileTemplateSummary;
} }
/** /**
@ -373,12 +326,14 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[], queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[], entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata { } = {}): CompileDirectiveMetadata {
const hostListeners: {[key: string]: string} = {}; var hostListeners: {[key: string]: string} = {};
const hostProperties: {[key: string]: string} = {}; var hostProperties: {[key: string]: string} = {};
const hostAttributes: {[key: string]: string} = {}; var hostAttributes: {[key: string]: string} = {};
if (isPresent(host)) { if (isPresent(host)) {
Object.keys(host).forEach(key => { Object.keys(host).forEach(key => {
const value = host[key]; const value = host[key];
@ -392,21 +347,21 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
} }
}); });
} }
const inputsMap: {[key: string]: string} = {}; var inputsMap: {[key: string]: string} = {};
if (isPresent(inputs)) { if (isPresent(inputs)) {
inputs.forEach((bindConfig: string) => { inputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp` // canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp // if there is no `:`, use dirProp = elProp
const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
inputsMap[parts[0]] = parts[1]; inputsMap[parts[0]] = parts[1];
}); });
} }
const outputsMap: {[key: string]: string} = {}; var outputsMap: {[key: string]: string} = {};
if (isPresent(outputs)) { if (isPresent(outputs)) {
outputs.forEach((bindConfig: string) => { outputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp` // canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp // if there is no `:`, use dirProp = elProp
const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
outputsMap[parts[0]] = parts[1]; outputsMap[parts[0]] = parts[1];
}); });
} }
@ -441,7 +396,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
viewProviders: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
entryComponents: CompileIdentifierMetadata[]; // Note: Need to keep types here to prevent cycles!
entryComponents: CompileTypeMetadata[];
template: CompileTemplateMetadata; template: CompileTemplateMetadata;
@ -465,7 +421,9 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[], queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileIdentifierMetadata[], entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata, template?: CompileTemplateMetadata,
} = {}) { } = {}) {
this.type = type; this.type = type;
@ -488,27 +446,6 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileDirectiveSummary {
return {
isSummary: true,
type: this.type,
isComponent: this.isComponent,
selector: this.selector,
exportAs: this.exportAs,
inputs: this.inputs,
outputs: this.outputs,
hostListeners: this.hostListeners,
hostProperties: this.hostProperties,
hostAttributes: this.hostAttributes,
providers: this.providers,
viewProviders: this.viewProviders,
queries: this.queries,
entryComponents: this.entryComponents,
changeDetection: this.changeDetection,
template: this.template && this.template.toSummary()
};
}
} }
/** /**
@ -516,7 +453,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
*/ */
export function createHostComponentMeta(compMeta: CompileDirectiveMetadata): export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
CompileDirectiveMetadata { CompileDirectiveMetadata {
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate(); var template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({ return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({ type: new CompileTypeMetadata({
reference: Object, reference: Object,
@ -546,12 +483,6 @@ export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
}); });
} }
export interface CompilePipeSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
name: string;
pure: boolean;
}
export class CompilePipeMetadata implements CompileMetadataWithIdentifier { export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
@ -568,48 +499,24 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
this.pure = !!pure; this.pure = !!pure;
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompilePipeSummary {
return {isSummary: true, type: this.type, name: this.name, pure: this.pure};
}
} }
export interface CompileNgModuleInjectorSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
entryComponents: CompileIdentifierMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleInjectorSummary[];
exportedModules: CompileNgModuleInjectorSummary[];
}
export interface CompileNgModuleDirectiveSummary extends CompileSummary {
isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */;
type: CompileTypeMetadata;
exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[];
directiveLoaders: (() => Promise<void>)[];
}
export type CompileNgModuleSummary =
CompileNgModuleInjectorSummary & CompileNgModuleDirectiveSummary;
/** /**
* Metadata regarding compilation of a module. * Metadata regarding compilation of a module.
*/ */
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
declaredDirectives: CompileIdentifierMetadata[]; declaredDirectives: CompileDirectiveMetadata[];
exportedDirectives: CompileIdentifierMetadata[]; exportedDirectives: CompileDirectiveMetadata[];
declaredPipes: CompileIdentifierMetadata[]; declaredPipes: CompilePipeMetadata[];
exportedPipes: CompileIdentifierMetadata[]; exportedPipes: CompilePipeMetadata[];
entryComponents: CompileIdentifierMetadata[]; // Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
bootstrapComponents: CompileIdentifierMetadata[]; entryComponents: CompileTypeMetadata[];
bootstrapComponents: CompileTypeMetadata[];
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
importedModules: CompileNgModuleSummary[]; importedModules: CompileNgModuleMetadata[];
exportedModules: CompileNgModuleSummary[]; exportedModules: CompileNgModuleMetadata[];
schemas: SchemaMetadata[]; schemas: SchemaMetadata[];
id: string; id: string;
@ -622,14 +529,14 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type?: CompileTypeMetadata, type?: CompileTypeMetadata,
providers?: providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>, Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileIdentifierMetadata[], declaredDirectives?: CompileDirectiveMetadata[],
exportedDirectives?: CompileIdentifierMetadata[], exportedDirectives?: CompileDirectiveMetadata[],
declaredPipes?: CompileIdentifierMetadata[], declaredPipes?: CompilePipeMetadata[],
exportedPipes?: CompileIdentifierMetadata[], exportedPipes?: CompilePipeMetadata[],
entryComponents?: CompileIdentifierMetadata[], entryComponents?: CompileTypeMetadata[],
bootstrapComponents?: CompileIdentifierMetadata[], bootstrapComponents?: CompileTypeMetadata[],
importedModules?: CompileNgModuleSummary[], importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleSummary[], exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata, transitiveModule?: TransitiveCompileNgModuleMetadata,
schemas?: SchemaMetadata[], schemas?: SchemaMetadata[],
id?: string id?: string
@ -650,54 +557,18 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
toSummary(): CompileNgModuleSummary {
return {
isSummary: true,
type: this.type,
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
toInjectorSummary(): CompileNgModuleInjectorSummary {
return {
isSummary: true,
type: this.type,
entryComponents: this.entryComponents,
providers: this.providers,
importedModules: this.importedModules,
exportedModules: this.exportedModules
};
}
toDirectiveSummary(): CompileNgModuleDirectiveSummary {
return {
isSummary: true,
type: this.type,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules,
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
} }
export class TransitiveCompileNgModuleMetadata { export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<any>(); directivesSet = new Set<Type<any>>();
pipesSet = new Set<any>(); pipesSet = new Set<Type<any>>();
constructor( constructor(
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[], public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[], public pipes: CompilePipeMetadata[]) {
public directiveLoaders: (() => Promise<void>)[]) { directives.forEach(dir => this.directivesSet.add(dir.type.reference));
directives.forEach(dir => this.directivesSet.add(dir.reference)); pipes.forEach(pipe => this.pipesSet.add(pipe.type.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
} }
} }
@ -711,13 +582,22 @@ export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifi
} }
}); });
return Array.from(map.values()); return MapWrapper.values(map);
} }
function _normalizeArray(obj: any[]): any[] { function _normalizeArray(obj: any[]): any[] {
return obj || []; return obj || [];
} }
export function isStaticSymbol(value: any): value is StaticSymbol {
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
}
export interface StaticSymbol {
name: string;
filePath: string;
}
export class ProviderMeta { export class ProviderMeta {
token: any; token: any;
useClass: Type<any>; useClass: Type<any>;

View File

@ -8,29 +8,27 @@
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, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser'; import {CompilerConfig} from './config';
import {CompilerConfig} from '../config'; import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveResolver} from './directive_resolver';
import {DirectiveResolver} from '../directive_resolver'; import {DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {Lexer} from './expression_parser/lexer';
import {Lexer} from '../expression_parser/lexer'; import {Parser} from './expression_parser/parser';
import {Parser} from '../expression_parser/parser'; import * as i18n from './i18n/index';
import * as i18n from '../i18n/index'; import {CompileMetadataResolver} from './metadata_resolver';
import {CompileMetadataResolver} from '../metadata_resolver'; import {HtmlParser} from './ml_parser/html_parser';
import {HtmlParser} from '../ml_parser/html_parser'; import {NgModuleCompiler} from './ng_module_compiler';
import {NgModuleCompiler} from '../ng_module_compiler'; import {NgModuleResolver} from './ng_module_resolver';
import {NgModuleResolver} from '../ng_module_resolver'; import {PipeResolver} from './pipe_resolver';
import {PipeResolver} from '../pipe_resolver'; import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from './private_import_core';
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from '../private_import_core'; import {ResourceLoader} from './resource_loader';
import {ResourceLoader} from '../resource_loader'; import {RuntimeCompiler} from './runtime_compiler';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {StyleCompiler} from '../style_compiler'; import {StyleCompiler} from './style_compiler';
import {TemplateParser} from '../template_parser/template_parser'; import {TemplateParser} from './template_parser/template_parser';
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver'; import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from './url_resolver';
import {ViewCompiler} from '../view_compiler/view_compiler'; import {ViewCompiler} from './view_compiler/view_compiler';
import {JitCompiler} from './compiler';
const _NO_RESOURCE_LOADER: ResourceLoader = { const _NO_RESOURCE_LOADER: ResourceLoader = {
get(url: string): Promise<string>{ get(url: string): Promise<string>{
@ -39,7 +37,7 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
}; };
/** /**
* A set of providers that provide `JitCompiler` and its dependencies to use for * A set of providers that provide `RuntimeCompiler` and its dependencies to use for
* template compilation. * template compilation.
*/ */
export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> = [ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> = [
@ -69,20 +67,19 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
NgModuleCompiler, NgModuleCompiler,
DirectiveWrapperCompiler, DirectiveWrapperCompiler,
{provide: CompilerConfig, useValue: new CompilerConfig()}, {provide: CompilerConfig, useValue: new CompilerConfig()},
JitCompiler, RuntimeCompiler,
{provide: Compiler, useExisting: JitCompiler}, {provide: Compiler, useExisting: RuntimeCompiler},
DomElementSchemaRegistry, DomElementSchemaRegistry,
{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}, {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
UrlResolver, UrlResolver,
DirectiveResolver, DirectiveResolver,
PipeResolver, PipeResolver,
NgModuleResolver, NgModuleResolver
AnimationParser
]; ];
@Injectable() @Injectable()
export class JitCompilerFactory implements CompilerFactory { export class RuntimeCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[]; private _defaultOptions: CompilerOptions[];
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) { constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
this._defaultOptions = [<CompilerOptions>{ this._defaultOptions = [<CompilerOptions>{
@ -129,7 +126,7 @@ function _initReflector() {
*/ */
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [ export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
{provide: COMPILER_OPTIONS, useValue: {}, multi: true}, {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
{provide: CompilerFactory, useClass: JitCompilerFactory}, {provide: CompilerFactory, useClass: RuntimeCompilerFactory},
{provide: PLATFORM_INITIALIZER, useValue: _initReflector, multi: true}, {provide: PLATFORM_INITIALIZER, useValue: _initReflector, multi: true},
]); ]);
@ -143,7 +140,7 @@ function _mergeOptions(optionsArr: CompilerOptions[]): CompilerOptions {
} }
function _lastDefined<T>(args: T[]): T { function _lastDefined<T>(args: T[]): T {
for (let i = args.length - 1; i >= 0; i--) { for (var i = args.length - 1; i >= 0; i--) {
if (args[i] !== undefined) { if (args[i] !== undefined) {
return args[i]; return args[i];
} }
@ -152,7 +149,7 @@ function _lastDefined<T>(args: T[]): T {
} }
function _mergeArrays(parts: any[][]): any[] { function _mergeArrays(parts: any[][]): any[] {
const result: any[] = []; let result: any[] = [];
parts.forEach((part) => part && result.push(...part)); parts.forEach((part) => part && result.push(...part));
return result; return result;
} }

View File

@ -30,7 +30,7 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
export function createCheckBindingStmt( export function createCheckBindingStmt(
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] { throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
let condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([ var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
throwOnChangeVar, fieldExpr, evalResult.currValExpr throwOnChangeVar, fieldExpr, evalResult.currValExpr
]); ]);
if (evalResult.forceUpdate) { if (evalResult.forceUpdate) {

View File

@ -58,7 +58,7 @@ export function convertPropertyBinding(
} }
if (visitor.needsValueUnwrapper) { if (visitor.needsValueUnwrapper) {
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt(); var initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.push(initValueUnwrapperStmt); stmts.push(initValueUnwrapperStmt);
} }
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final])); stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
@ -86,14 +86,14 @@ export function convertActionBinding(
} }
const visitor = const visitor =
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true); new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true);
const actionStmts: o.Statement[] = []; let actionStmts: o.Statement[] = [];
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts); flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts); prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
const lastIndex = actionStmts.length - 1; var lastIndex = actionStmts.length - 1;
let preventDefaultVar: o.ReadVarExpr = null; var preventDefaultVar: o.ReadVarExpr = null;
if (lastIndex >= 0) { if (lastIndex >= 0) {
const lastStatement = actionStmts[lastIndex]; var lastStatement = actionStmts[lastIndex];
const returnExpr = convertStmtIntoExpression(lastStatement); var returnExpr = convertStmtIntoExpression(lastStatement);
if (returnExpr) { if (returnExpr) {
// Note: We need to cast the result of the method call to dynamic, // Note: We need to cast the result of the method call to dynamic,
// as it might be a void method! // as it might be a void method!
@ -112,7 +112,7 @@ export function convertActionBinding(
*/ */
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] { export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
const unwrapperStmts: o.Statement[] = []; const unwrapperStmts: o.Statement[] = [];
const readVars = o.findReadVarNames(stmts); var readVars = o.findReadVarNames(stmts);
if (readVars.has(VAL_UNWRAPPER_VAR.name)) { if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
unwrapperStmts.push( unwrapperStmts.push(
VAL_UNWRAPPER_VAR VAL_UNWRAPPER_VAR
@ -175,7 +175,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
private bindingId: string, private isAction: boolean) {} private bindingId: string, private isAction: boolean) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any { visitBinary(ast: cdAst.Binary, mode: _Mode): any {
let op: o.BinaryOperator; var op: o.BinaryOperator;
switch (ast.operation) { switch (ast.operation) {
case '+': case '+':
op = o.BinaryOperator.Plus; op = o.BinaryOperator.Plus;
@ -275,12 +275,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
args.push(this.visit(ast.expressions[i], _Mode.Expression)); args.push(this.visit(ast.expressions[i], _Mode.Expression));
} }
args.push(o.literal(ast.strings[ast.strings.length - 1])); args.push(o.literal(ast.strings[ast.strings.length - 1]));
return o.importExpr(resolveIdentifier(Identifiers.interpolate)).callFn(args);
return ast.expressions.length <= 9 ?
o.importExpr(resolveIdentifier(Identifiers.inlineInterpolate)).callFn(args) :
o.importExpr(resolveIdentifier(Identifiers.interpolate)).callFn([
args[0], o.literalArr(args.slice(1))
]);
} }
visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any { visitKeyedRead(ast: cdAst.KeyedRead, mode: _Mode): any {
@ -303,7 +298,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
const parts: any[] = []; let parts: any[] = [];
for (let i = 0; i < ast.keys.length; i++) { for (let i = 0; i < ast.keys.length; i++) {
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]); parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
} }
@ -330,9 +325,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} else { } else {
const args = this.visitAll(ast.args, _Mode.Expression); const args = this.visitAll(ast.args, _Mode.Expression);
let result: any = null; let result: any = null;
const receiver = this.visit(ast.receiver, _Mode.Expression); let receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); var varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (isPresent(varExpr)) {
result = varExpr.callFn(args); result = varExpr.callFn(args);
} }
@ -354,7 +349,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
return this.convertSafeAccess(ast, leftMostSafe, mode); return this.convertSafeAccess(ast, leftMostSafe, mode);
} else { } else {
let result: any = null; let result: any = null;
const receiver = this.visit(ast.receiver, _Mode.Expression); var receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
result = this._getLocal(ast.name); result = this._getLocal(ast.name);
} }
@ -366,9 +361,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); var varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (isPresent(varExpr)) {
throw new Error('Cannot assign to a reference or variable!'); throw new Error('Cannot assign to a reference or variable!');
} }
@ -580,11 +575,11 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
if (values.length === 0) { if (values.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY)); return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
} }
const proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`); var proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
const proxyParams: o.FnParam[] = []; var proxyParams: o.FnParam[] = [];
const proxyReturnEntries: o.Expression[] = []; var proxyReturnEntries: o.Expression[] = [];
for (let i = 0; i < values.length; i++) { for (var i = 0; i < values.length; i++) {
const paramName = `p${i}`; var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName)); proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName)); proxyReturnEntries.push(o.variable(paramName));
} }
@ -605,7 +600,7 @@ function createCachedLiteralMap(
const proxyParams: o.FnParam[] = []; const proxyParams: o.FnParam[] = [];
const proxyReturnEntries: [string, o.Expression][] = []; const proxyReturnEntries: [string, o.Expression][] = [];
const values: o.Expression[] = []; const values: o.Expression[] = [];
for (let i = 0; i < entries.length; i++) { for (var i = 0; i < entries.length; i++) {
const paramName = `p${i}`; const paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName)); proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]); proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);

View File

@ -40,7 +40,7 @@ export function createPureProxy(
fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr,
builder: {fields: o.ClassField[], ctorStmts: {push: (stmt: o.Statement) => void}}) { builder: {fields: o.ClassField[], ctorStmts: {push: (stmt: o.Statement) => void}}) {
builder.fields.push(new o.ClassField(pureProxyProp.name, null)); builder.fields.push(new o.ClassField(pureProxyProp.name, null));
const pureProxyId = var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null; argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (!pureProxyId) { if (!pureProxyId) {
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`); throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);

View File

@ -53,7 +53,7 @@ export function writeToRenderer(
.toStmt()); .toStmt());
break; break;
case PropertyBindingType.Style: case PropertyBindingType.Style:
let strValue: o.Expression = renderValue.callMethod('toString', []); var strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) { if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit)); strValue = strValue.plus(o.literal(boundProp.unit));
} }
@ -84,8 +84,8 @@ function sanitizedValue(
if (!securityContextExpression) { if (!securityContextExpression) {
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`); throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
} }
const ctx = view.prop('viewUtils').prop('sanitizer'); let ctx = view.prop('viewUtils').prop('sanitizer');
const args = [securityContextExpression, renderValue]; let args = [securityContextExpression, renderValue];
return ctx.callMethod('sanitize', args); return ctx.callMethod('sanitize', args);
} }

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