Compare commits

..

98 Commits

Author SHA1 Message Date
953cb50fa5 chore(release): cut v2.2.0-rc.0 2016-11-03 00:28:29 +01:00
3fffcf6645 chore: update changelog 2016-11-02 12:34:21 -07:00
d509ee078b fix(router): reset URL to the stable state when a navigation gets canceled
Closes #10321
2016-11-02 12:25:23 -07:00
8e221b826f fix(router): routerLink should not prevent default on non-link elements 2016-11-02 12:25:23 -07:00
830a780cb3 fix(router): CanDeactivate receives a wrong component
Closes #12592
2016-11-02 12:25:23 -07:00
6fda97287e fix(compiler): support multiple components in a view container 2016-11-01 14:21:40 -07:00
234c5599f1 refactor(compiler): remove unused constructor query support 2016-11-01 11:29:15 -07:00
f6710fefeb refactor(compiler): make view.disposable array null if empty 2016-11-01 11:29:15 -07:00
bda1909ede refactor(compiler): remove view.rootNodes and view.projectableNodes
They are replaced by generated visitor functions `view.visitRootNodes` / `view.visitProjectableNodes`.
2016-11-01 11:29:15 -07:00
b3e3cd3add refactor(compiler): inline view.contentChildren 2016-11-01 11:29:14 -07:00
e5fdf4c70a refactor(compiler): inline view.viewChildren in generated code 2016-11-01 11:29:14 -07:00
97471d74b6 refactor(compiler): remove unused subscriptions in view 2016-11-01 11:29:14 -07:00
1de04b23b1 fix(router): call data observers when the path changes 2016-11-01 11:28:43 -07:00
a178bc6c83 fix(compiler): dedupe NgModule declarations, …
This is important so that we don’t generate things multiple times.
2016-10-31 14:43:50 -07:00
642c1db9ef fix(compiler): Don’t throw on empty property bindings
Closes #12583
2016-10-31 14:43:50 -07:00
579deeb9c5 style(platform-browser): clean up hammer gestures 2016-10-31 14:43:05 -07:00
bad58824a0 refactor(playground): update gestures playground to use latest hammer.js 2016-10-31 14:43:04 -07:00
5494169fb4 style: make internal members accessibility explicit 2016-10-31 14:25:53 -07:00
5a3d7a62a2 style: merge imports from the same modules 2016-10-31 14:25:53 -07:00
a382d6dd20 style: add missing semicolons 2016-10-31 14:25:53 -07:00
52bf188b8f style: add missing copyright headers 2016-10-31 14:25:53 -07:00
6f412bb449 chore(lint): extend linting to all modules and tools 2016-10-31 14:24:27 -07:00
e9fd8645ed fix(core): improve error when multiple components match the same element
Closes #7067
2016-10-31 11:28:03 -07:00
a0aecac0e5 chore(lint): replace enforce-copyright-header rule with the native equivalent 2016-10-31 11:27:35 -07:00
938ed1c76d chore(lint): deduplicate tslint dependency 2016-10-31 11:27:34 -07:00
eb8288f76c chore(package): remove unused lodash and sorted-object 2016-10-31 11:26:59 -07:00
0936ceeab4 chore(npm): clean up clean-shrinkwrap script 2016-10-31 11:26:59 -07:00
e0ad413a8e style(tests): clean up testing_public_spec (#11452) 2016-10-31 11:26:38 -07:00
3045d02b9a docs(pipes): minor fix and improvements 2016-10-31 12:39:21 +01:00
e86573bac8 chore(lint): replace gulp check-task with tslint no-jasmine-focus rule
fixes #11800
2016-10-28 15:53:15 -07:00
0a94845435 chore(lint): replace duplicate-module-import rule with no-duplicate-imports 2016-10-28 15:53:15 -07:00
262bd23b84 chore(lint): add vrsource tslint rules dependency 2016-10-28 15:53:15 -07:00
7b8dae19af refactor(facade): cleanup Intl facade 2016-10-28 15:52:52 -07:00
7c16ef942e feat(core): add the find method to QueryList 2016-10-28 15:34:47 -07:00
a318b57257 refactor(core): removed extraneous interface from QueryList test 2016-10-28 15:34:47 -07:00
fe47e6b783 fix(router): rerun resolvers when url changes
Closes #12603
2016-10-28 15:17:00 -07:00
091c390032 fix(router): run navigations serialy
Closes #11754
2016-10-28 14:56:08 -07:00
e391cacdf9 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-10-28 11:17:12 -07:00
32feb8a532 refactor(compiler): generate host listeners in DirectiveWrappers
Part of #11683
2016-10-27 16:09:01 -07:00
a664aba2c9 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-10-27 12:13:39 -07:00
d520fae70e refactor(upgrade): spec cleanup 2016-10-27 12:12:55 -07:00
fa93fd672e fix(upgrade): silent bootstrap failures
fixes #12062
2016-10-27 12:12:54 -07:00
f4be2f907d docs(changelog): fix changelog title 2016-10-27 20:38:56 +02:00
2ea27a76d3 docs(changelog): update changelog with changes in v2.1.2 2016-10-27 20:30:48 +02:00
ec0acf9a1b chore(release): cut the 2.2.0-beta.0 release 2016-10-27 18:28:07 +02:00
a26dd28bdb refactor(upgrade): re-export the new static upgrade APIs on new entry
Add upgrade-static.umd.js bundles
This allows depending on it without getting a transitive dependency on compiler.

BREAKING CHANGE:

Four newly added APIs in 2.2.0-beta:
downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule
are no longer exported by @angular/upgrade.
Import these from @angular/upgrade/static instead.
2016-10-26 15:14:22 -07:00
7742ec00e7 fix: remove double exports of template_ast 2016-10-26 15:14:00 -07:00
2b5c983c13 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-26 14:56:57 -07:00
ef153649b3 fix(compiler-cli): fix types 2016-10-26 14:56:27 -07:00
d321b0ebf5 fix(selectors): use Maps instead of objects 2016-10-26 14:55:59 -07:00
b4265e0685 fix(xsrf): overwrite already set xsrf header 2016-10-26 14:55:24 -07:00
178fb79b5c refactor(compiler): move host properties into DirectiveWrapper
Part of #11683
2016-10-26 14:32:24 -07:00
5a7a58b1e0 refactor(compiler): make arguments in InlineArray optional. 2016-10-26 14:32:24 -07:00
f66ac821a2 refactor(compiler): extract createCheckBindingStmt into compiler_util
Part of #11683
2016-10-26 14:32:24 -07:00
fe299f4dfc refactor(compiler): minor cleanups 2016-10-26 14:32:24 -07:00
4cac650675 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-26 14:32:24 -07:00
cb7643ccea refactor(compiler): introduce ClassBuilder.
Part of #11683
2016-10-26 14:32:23 -07:00
faa3478514 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-26 14:32:23 -07:00
bc3f4bc816 refactor(compiler): extract BindingParser
Needed so that we can parse directive host bindings independent of templates.

Part of #11683
2016-10-26 14:32:23 -07:00
c9f58cf78c feat(router): export routerLinkActive w/ isActive property 2016-10-26 14:08:22 -07:00
6ccbfd41dd fix(router): preserve resolve data
Closes #12306
2016-10-26 13:53:00 -07:00
7d2554baa1 tests(router): add a test showing how to handle resovle errors 2016-10-26 13:52:59 -07:00
52a853e257 fix(router): change router not to deactivate aux routes when navigating from a componentless routes 2016-10-26 13:52:59 -07:00
8f2fa0f766 fix(router): disallow component routes with named outlets
Closes #11208, #11082
2016-10-26 13:52:59 -07:00
fc60fa790c fix(router): add a test to make sure canDeactivate guards are called for aux routes
Closes #11345
2016-10-26 13:52:58 -07:00
b74185369f fix(router): canDeactivate guards are not triggered for componentless routes
Closes #12375
2016-10-26 13:52:58 -07:00
7221632228 fix(CompilerCli): assert that all pipes and directives are declared by a module 2016-10-25 18:17:18 -07:00
02f1222a8d docs(common): minor corrections/improvements for NgClass (#12327) 2016-10-25 00:12:09 +02:00
c27ce7318f 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-25 00:10:03 +02:00
a838aba756 fix(compiler): walk third party modules (#12453)
fixes #11889
fixes #12428
2016-10-24 22:28:23 +02:00
bfc97ff2cd 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-21 15:17:57 -07:00
57051f01ce refactor: remove most facades (#12399) 2016-10-21 15:14:44 -07:00
e319cfefc3 docs(changelog): fix minor typo (#12429) 2016-10-21 14:27:07 -07:00
444014ad96 docs(changelog): add 2.1.1 changelog 2016-10-20 15:49:59 -07:00
867494a060 fix(compiler): don't access view local variables nor pipes in host expressions (#12396)
Fixes #12004
Closes #12071
2016-10-20 15:24:58 -07:00
69ad99dca6 chore(release): cut the 2.2.0-beta.0 release and add changelog 2016-10-20 14:36:46 -07:00
da5fc696bb fix(router): do not update primary route if only secondary outlet is given (#11797) 2016-10-20 10:59:08 -07:00
b44b6ef8f5 fix(router): module loader should start compiling modules when stubbedModules are set (#11742) 2016-10-20 10:58:53 -07:00
0f21a5823b cleanup(router): add a test verifying than NavigationEnd is not emitted after NavigationCancel 2016-10-20 10:56:12 -07:00
5ae6915600 fix(router): fix lazy loading triggered by redirects from wildcard routes
Closes #12183
2016-10-20 10:56:12 -07:00
8b9ab44eee feat(router): add support for ng1/ng2 migration (#12160) 2016-10-20 10:44:44 -07:00
b0a03fcab3 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 10:41:43 -07:00
c951822c35 refactor(compiler): don’t use the OfflineCompiler in extract_i18n 2016-10-20 10:41:43 -07:00
acda82c1ed refactor(compiler): remove private exports
All of `@angular/compiler` is private, so we can export
everything we need directly.
2016-10-20 10:41:43 -07:00
a8815d6b08 chore(ci): re-enable browserstack tests in ci 2016-10-20 10:01:51 -07:00
d6791ff0e0 feat(ngUpgrade): add support for AoT compiled upgrade applications
This commit introduces a new API to the ngUpgrade module, which is compatible
with AoT compilation. Primarily, it removes the dependency on reflection
over the Angular 2 metadata by introducing an API where this information
is explicitly defined, in the source code, in a way that is not lost through
AoT compilation.

This commit is a collaboration between @mhevery (who provided the original
design of the API); @gkalpak & @petebacondarwin (who implemented the
API and migrated the specs from the original ngUpgrade tests) and @alexeagle
(who provided input and review).

This commit is an starting point, there is still work to be done:

* add more documentation
* validate the API via internal projects
* align the ngUpgrade compilation of A1 directives closer to the real A1
  compiler
* add more unit tests
* consider support for async `templateUrl` A1 upgraded components

Closes #12239
2016-10-19 15:27:49 -07:00
a2d35641e3 chore(tslint.json): semicolon rule expects an array 2016-10-19 22:38:14 +01:00
76dd026447 refactor: remove some facades (#12335) 2016-10-19 13:42:39 -07:00
0ecd9b2df0 chore(ci): make browserstack tests optional until they are fixed 2016-10-19 10:41:00 -07:00
0e9503b500 feat(forms) range values need to be numbers instead of strings (#11792) 2016-10-19 10:12:13 -07:00
f77ab6a2d2 feat(datePipe): support narrow forms for month and weekdays (#12297)
Closes #12294
2016-10-19 10:05:13 -07:00
97bc97153b feat(forms): add ng-pending CSS class during async validation (#11243)
Closes #10336
2016-10-19 09:56:31 -07:00
445e5922ec feat(forms): make 'parent' a public property of 'AbstractControl' (#11855) 2016-10-19 09:55:50 -07:00
b9fc090143 feat(forms): Added emitEvent to AbstractControl methods (#11949)
* feat(forms): Added emitEvent to AbstractControl methods

* style(forms): unified named parameter
2016-10-19 09:54:54 -07:00
592f40aa9c feat(forms): add hasError and getError to AbstractControlDirective (#11985)
Allows cleaner expressions in template-driven forms.

Before:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.control.hasError('required')">Username is required</div>

After:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.hasError('required')">Username is required</div>

Fixes #7255
2016-10-19 09:49:02 -07:00
24facdea2d 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-19 09:39:16 -07:00
aa2d3372a5 fix(benchmarks): fix method name in targetable spec 2016-10-19 09:39:16 -07:00
bf60418fdc feat(forms): Validator.pattern accepts a RegExp (#12323) 2016-10-19 09:37:54 -07:00
316 changed files with 14790 additions and 5709 deletions

View File

@ -1,10 +1,80 @@
<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>
# [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27)
### Bug Fixes
* **compiler:** don't access view local variables nor pipes in host expressions ([#12396](https://github.com/angular/angular/issues/12396)) ([867494a](https://github.com/angular/angular/commit/867494a)), closes [#12004](https://github.com/angular/angular/issues/12004) [#12071](https://github.com/angular/angular/issues/12071)
* **compiler:** walk third party modules ([#12453](https://github.com/angular/angular/issues/12453)) ([a838aba](https://github.com/angular/angular/commit/a838aba)), closes [#11889](https://github.com/angular/angular/issues/11889) [#12428](https://github.com/angular/angular/issues/12428)
* **compiler:** remove double exports of template_ast ([7742ec0](https://github.com/angular/angular/commit/7742ec0))
* **compiler:** use Maps instead of objects in selector implementation ([d321b0e](https://github.com/angular/angular/commit/d321b0e))
* **compiler-cli:** fix types ([ef15364](https://github.com/angular/angular/commit/ef15364))
* **compiler-cli:** assert that all pipes and directives are declared by a module ([7221632](https://github.com/angular/angular/commit/7221632))
* **http:** overwrite already set xsrf header ([b4265e0](https://github.com/angular/angular/commit/b4265e0))
* **router:** add a test to make sure canDeactivate guards are called for aux routes ([fc60fa7](https://github.com/angular/angular/commit/fc60fa7)), closes [#11345](https://github.com/angular/angular/issues/11345)
* **router:** canDeactivate guards are not triggered for componentless routes ([b741853](https://github.com/angular/angular/commit/b741853)), closes [#12375](https://github.com/angular/angular/issues/12375)
* **router:** change router not to deactivate aux routes when navigating from a componentless routes ([52a853e](https://github.com/angular/angular/commit/52a853e))
* **router:** disallow component routes with named outlets ([8f2fa0f](https://github.com/angular/angular/commit/8f2fa0f)), closes [#11208](https://github.com/angular/angular/issues/11208) [#11082](https://github.com/angular/angular/issues/11082)
* **router:** preserve resolve data ([6ccbfd4](https://github.com/angular/angular/commit/6ccbfd4)), closes [#12306](https://github.com/angular/angular/issues/12306)
<a name="2.2.0-beta.1"></a>
# [2.2.0-beta.1](https://github.com/angular/angular/compare/2.2.0-beta.0...2.2.0-beta.1) (2016-10-27)
### Code Refactoring
* **upgrade:** re-export the new static upgrade APIs on new entry ([a26dd28](https://github.com/angular/angular/commit/a26dd28))
### Features
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
### BREAKING CHANGES
* upgrade: Four newly added APIs in 2.2.0-beta:
downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule are no longer exported by @angular/upgrade.
Import these from @angular/upgrade/static instead.
Note: The 2.2.0-beta.1 release also contains all the changes present in the 2.1.2 release.
# [2.1.1](https://github.com/angular/angular/compare/2.1.0...2.1.1) (2016-10-20) # [2.1.1](https://github.com/angular/angular/compare/2.1.0...2.1.1) (2016-10-20)
### Bug Fixes ### Bug Fixes
* **compiler:** generate aot code for animation trigger output events ([#12291](https://github.com/angular/angular/issues/12291)) ([6e5f8b5](https://github.com/angular/angular/commit/6e5f8b5)), closes [#11707](https://github.com/angular/angular/issues/11707) * **compiler:** generate aot code for animation trigger output events ([#12291](https://github.com/angular/angular/issues/12291)) ([6e5f8b5](https://github.com/angular/angular/commit/6e5f8b5)), closes [#11707](https://github.com/angular/angular/issues/11707)
* **compiler:** don't redeclare a var in the same scope ([#12386](https://github.com/angular/angular/issues/12386)) ([cca4a5c](https://github.com/angular/angular/commit/cca4a5c)) * **compiler:** don't redeclare a var in the same scope ([#12386](https://github.com/angular/angular/issues/12386)) ([cca4a5c](https://github.com/angular/angular/commit/cca4a5c))
* **core:** fix decorator defalut values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5)) * **core:** fix decorator default values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5))
* **core:** fix property decorators ([3993279](https://github.com/angular/angular/commit/3993279)), closes [#12224](https://github.com/angular/angular/issues/12224) * **core:** fix property decorators ([3993279](https://github.com/angular/angular/commit/3993279)), closes [#12224](https://github.com/angular/angular/issues/12224)
* **http:** make normalizeMethodName optimizer-compatible. ([#12370](https://github.com/angular/angular/issues/12370)) ([8409b65](https://github.com/angular/angular/commit/8409b65)) * **http:** make normalizeMethodName optimizer-compatible. ([#12370](https://github.com/angular/angular/issues/12370)) ([8409b65](https://github.com/angular/angular/commit/8409b65))
* **router:** correctly export filter operator in es5 ([#12286](https://github.com/angular/angular/issues/12286)) ([27d7677](https://github.com/angular/angular/commit/27d7677)) * **router:** correctly export filter operator in es5 ([#12286](https://github.com/angular/angular/issues/12286)) ([27d7677](https://github.com/angular/angular/commit/27d7677))

View File

@ -67,6 +67,7 @@ if [[ ${BUILD_ALL} == true ]]; then
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 .
ln -s ../../../../node_modules/hammerjs/hammer.js .
cd - cd -
echo "====== Copying files needed for benchmarks =====" echo "====== Copying files needed for benchmarks ====="
@ -101,7 +102,9 @@ 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_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
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
rm -rf ${DESTDIR} rm -rf ${DESTDIR}
@ -157,6 +160,18 @@ 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
) 2>&1 | grep -v "as external dependency" ) 2>&1 | grep -v "as external dependency"
fi fi

View File

@ -58,6 +58,7 @@ 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',
@ -124,38 +125,31 @@ gulp.task('public-api:update', ['build.sh'], (done) => {
.on('close', done); .on('close', done);
}); });
// Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the
// focused tests is found.
// Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded
// tests in our code base.
gulp.task('check-tests', function() {
const ddescribeIit = require('gulp-ddescribe-iit');
return gulp
.src([
'modules/**/*.spec.ts',
'modules/**/*_spec.ts',
])
.pipe(ddescribeIit({allowDisabledTests: true}));
});
// Check the coding standards and programming errors // Check the coding standards and programming errors
gulp.task('lint', ['check-tests', 'format:enforce', 'tools:build'], () => { gulp.task('lint', ['format:enforce', 'tools:build'], () => {
const tslint = require('gulp-tslint'); const tslint = require('gulp-tslint');
// Built-in rules are at // Built-in rules are at
// https://github.com/palantir/tslint#supported-rules // https://palantir.github.io/tslint/rules/
const tslintConfig = require('./tslint.json'); const tslintConfig = require('./tslint.json');
return gulp return gulp
.src([ .src([
// todo(vicb): add .js files when supported // todo(vicb): add .js files when supported
// see https://github.com/palantir/tslint/pull/1515 // see https://github.com/palantir/tslint/pull/1515
'modules/@angular/**/*.ts', './modules/**/*.ts',
'modules/benchpress/**/*.ts', './tools/**/*.ts',
'./*.ts', './*.ts',
// Ignore TypeScript mocks because it's not managed by us
'!./tools/@angular/tsc-wrapped/test/typescript.mocks.ts',
// Ignore generated files due to lack of copyright header
// todo(alfaproject): make generated files lintable
'!**/*.d.ts',
'!**/*.ngfactory.ts',
]) ])
.pipe(tslint({ .pipe(tslint({
tslint: require('tslint').default, tslint: require('tslint').default,
configuration: tslintConfig, configuration: tslintConfig,
rulesDirectory: 'dist/tools/tslint',
formatter: 'prose', formatter: 'prose',
})) }))
.pipe(tslint.report({emitError: true})); .pipe(tslint.report({emitError: true}));

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.min.js', 'node_modules/angular/angular.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',

View File

@ -8,13 +8,10 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that checks the regression slope of a specific metric. * A validator that checks the regression slope of a specific metric.
* Waits for the regression slope to be >=0. * Waits for the regression slope to be >=0.
@ -40,17 +37,17 @@ export class RegressionSlopeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
var latestSample = ListWrapper.slice( const latestSample =
completeSample, completeSample.length - this._sampleSize, completeSample.length); completeSample.slice(completeSample.length - this._sampleSize, completeSample.length);
var xValues: number[] = []; const xValues: number[] = [];
var yValues: number[] = []; const yValues: number[] = [];
for (var i = 0; i < latestSample.length; i++) { for (let i = 0; i < latestSample.length; i++) {
// For now, we only use the array index as x value. // For now, we only use the array index as x value.
// TODO(tbosch): think about whether we should use time here instead // TODO(tbosch): think about whether we should use time here instead
xValues.push(i); xValues.push(i);
yValues.push(latestSample[i].values[this._metric]); yValues.push(latestSample[i].values[this._metric]);
} }
var regressionSlope = Statistic.calculateRegressionSlope( const regressionSlope = Statistic.calculateRegressionSlope(
xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues)); xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues));
return regressionSlope >= 0 ? latestSample : null; return regressionSlope >= 0 ? latestSample : null;
} else { } else {

View File

@ -8,12 +8,9 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that waits for the sample to have a certain size. * A validator that waits for the sample to have a certain size.
*/ */
@ -28,8 +25,7 @@ export class SizeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
return ListWrapper.slice( return completeSample.slice(completeSample.length - this._sampleSize, completeSample.length);
completeSample, completeSample.length - this._sampleSize, completeSample.length);
} else { } else {
return null; return null;
} }

View File

@ -62,7 +62,7 @@ export class IOsDriverExtension extends WebDriverExtension {
var startTime = record['startTime']; var startTime = record['startTime'];
var endTime = record['endTime']; var endTime = record['endTime'];
if (type === 'FunctionCall' && (isBlank(data) || data['scriptName'] !== 'InjectedScript')) { if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) {
events.push(createStartEvent('script', startTime)); events.push(createStartEvent('script', startTime));
endEvent = createEndEvent('script', endTime); endEvent = createEndEvent('script', endTime);
} else if (type === 'Time') { } else if (type === 'Time') {

View File

@ -28,7 +28,7 @@ export function main() {
if (!descriptions) { if (!descriptions) {
descriptions = []; descriptions = [];
} }
if (isBlank(sampleId)) { if (sampleId == null) {
sampleId = 'null'; sampleId = 'null';
} }
var providers: Provider[] = [ var providers: Provider[] = [

View File

@ -9,7 +9,6 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
@ -44,17 +43,15 @@ 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'});
var 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(ListWrapper.slice(sample, 0, 2))) expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
.toEqual(ListWrapper.slice(sample, 0, 2)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 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'});
var 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(ListWrapper.slice(sample, 0, 2))) expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
.toEqual(ListWrapper.slice(sample, 0, 2)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -9,7 +9,6 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
@ -37,9 +36,8 @@ 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);
var 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(ListWrapper.slice(sample, 0, 2))) expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
.toEqual(ListWrapper.slice(sample, 0, 2)); expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -11,8 +11,6 @@ import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableD
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
* *
@ -31,11 +29,11 @@ import {isPresent} from '../facade/lang';
* *
* @description * @description
* *
* The CSS classes are updated as follow depending on the type of the expression evaluation: * The CSS classes are updated as follows, depending on the type of the expression evaluation:
* - `string` - the CSS classes listed in a string (space delimited) are added, * - `string` - the CSS classes listed in the string (space delimited) are added,
* - `Array` - the CSS classes (Array elements) are added, * - `Array` - the CSS classes declared as Array elements are added,
* - `Object` - keys are CSS class names that get added when the expression given in the value * - `Object` - keys are CSS classes that get added when the expression given in the value
* evaluates to a truthy value, otherwise class are removed. * evaluates to a truthy value, otherwise they are removed.
* *
* @stable * @stable
*/ */
@ -50,7 +48,6 @@ export class NgClass implements DoCheck {
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {} private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input('class') @Input('class')
set klass(v: string) { set klass(v: string) {
this._applyInitialClasses(true); this._applyInitialClasses(true);

View File

@ -33,21 +33,21 @@ 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 | Short Form | Long Form | Numeric | 2-digit | * | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------------|-------------------|-----------|-----------| * |-----------|:------:|--------|--------------|-------------------|-----------|-----------|
* | era | G | G (AD) | GGGG (Anno Domini)| - | - | * | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | y (2015) | yy (15) | * | year | y | - | - | - | y (2015) | yy (15) |
* | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) | * | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | d (3) | dd (03) | * | day | d | - | - | - | d (3) | dd (03) |
* | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - | * | weekday | E | E (S) | 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.

View File

@ -17,7 +17,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | lowercase` * @howToUse `expression | lowercase`
* @description * @description
* *
* Converts value into lowercase string using `String.prototype.toLowerCase()`. * Converts value into a lowercase string using `String.prototype.toLowerCase()`.
* *
* ### Example * ### Example
* *

View File

@ -16,7 +16,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | uppercase` * @howToUse `expression | uppercase`
* @description * @description
* *
* Converts value into lowercase string using `String.prototype.toUpperCase()`. * Converts value into an uppercase string using `String.prototype.toUpperCase()`.
* *
* ### Example * ### Example
* *

View File

@ -8,13 +8,12 @@
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
var date: Date; let date: Date;
var pipe: DatePipe; let pipe: DatePipe;
// 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
@ -34,7 +33,9 @@ export function main() {
describe('supports', () => { describe('supports', () => {
it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); }); it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); });
it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); }); it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); });
it('should support numeric strings', it('should support numeric strings',
() => { expect(() => pipe.transform('123456789')).not.toThrow(); }); () => { expect(() => pipe.transform('123456789')).not.toThrow(); });
@ -59,7 +60,7 @@ export function main() {
expect(pipe.transform(date, 'MMM')).toEqual('Jun'); expect(pipe.transform(date, 'MMM')).toEqual('Jun');
expect(pipe.transform(date, 'MMMM')).toEqual('June'); expect(pipe.transform(date, 'MMMM')).toEqual('June');
expect(pipe.transform(date, 'd')).toEqual('15'); expect(pipe.transform(date, 'd')).toEqual('15');
expect(pipe.transform(date, 'E')).toEqual('Mon'); expect(pipe.transform(date, 'EEE')).toEqual('Mon');
expect(pipe.transform(date, 'EEEE')).toEqual('Monday'); expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
expect(pipe.transform(date, 'h')).toEqual('9'); expect(pipe.transform(date, 'h')).toEqual('9');
@ -72,6 +73,9 @@ export function main() {
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
expect(pipe.transform(date, 'HH')).toEqual('09'); expect(pipe.transform(date, 'HH')).toEqual('09');
} }
expect(pipe.transform(date, 'E')).toEqual('M');
expect(pipe.transform(date, 'L')).toEqual('J');
expect(pipe.transform(date, 'm')).toEqual('3'); expect(pipe.transform(date, 'm')).toEqual('3');
expect(pipe.transform(date, 's')).toEqual('1'); expect(pipe.transform(date, 's')).toEqual('1');
expect(pipe.transform(date, 'mm')).toEqual('03'); expect(pipe.transform(date, 'mm')).toEqual('03');
@ -81,13 +85,13 @@ export function main() {
}); });
it('should format common multi component patterns', () => { it('should format common multi component patterns', () => {
expect(pipe.transform(date, 'E, M/d/y')).toEqual('Mon, 6/15/2015'); expect(pipe.transform(date, 'EEE, M/d/y')).toEqual('Mon, 6/15/2015');
expect(pipe.transform(date, 'E, M/d')).toEqual('Mon, 6/15'); expect(pipe.transform(date, 'EEE, M/d')).toEqual('Mon, 6/15');
expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15'); expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15');
expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015'); expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015');
expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015'); expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
expect(pipe.transform(date, 'yMEd')).toEqual('20156Mon15'); expect(pipe.transform(date, 'yMEEEd')).toEqual('20156Mon15');
expect(pipe.transform(date, 'MEd')).toEqual('6Mon15'); expect(pipe.transform(date, 'MEEEd')).toEqual('6Mon15');
expect(pipe.transform(date, 'MMMd')).toEqual('Jun15'); expect(pipe.transform(date, 'MMMd')).toEqual('Jun15');
expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015'); expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('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

View File

@ -27,7 +27,7 @@ Then you can add an import statement in the `bootstrap` allowing you to bootstra
generated code: generated code:
```typescript ```typescript
main_module.ts main.module.ts
------------- -------------
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {Component, NgModule, ApplicationRef} from '@angular/core'; import {Component, NgModule, ApplicationRef} from '@angular/core';
@ -49,7 +49,7 @@ export class MainModule {
bootstrap.ts bootstrap.ts
------------- -------------
import {MainModuleNgFactory} from './main_module.ngfactory'; import {MainModuleNgFactory} from './main.module.ngfactory';
import {platformBrowser} from '@angular/platform-browser'; import {platformBrowser} from '@angular/platform-browser';
platformBrowser().bootstrapModuleFactory(MainModuleNgFactory); platformBrowser().bootstrapModuleFactory(MainModuleNgFactory);

View File

@ -7,6 +7,7 @@
*/ */
export {CodeGenerator} from './src/codegen'; export {CodeGenerator} from './src/codegen';
export {Extractor} from './src/extractor';
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host'; export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector'; export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';

View File

@ -0,0 +1,18 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'use-third-party',
template: '<third-party-comp [thirdParty]="title"></third-party-comp>' +
'<another-third-party-comp></another-third-party-comp>',
})
export class ComponentUsingThirdParty {
title: string = 'from 3rd party';
}

View File

@ -12,6 +12,10 @@
<source>Welcome</source> <source>Welcome</source>
<target>tervetuloa</target> <target>tervetuloa</target>
</trans-unit> </trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target>other-3rdP-component</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -9,4 +9,5 @@
<translationbundle> <translationbundle>
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation> <translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation> <translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
</translationbundle> </translationbundle>

View File

@ -11,9 +11,12 @@ import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser'; 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 {MultipleComponentsMyComp, NextComp} from './a/multiple_components'; import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
import {AnimateCmp} from './animate'; import {AnimateCmp} from './animate';
import {BasicComp} from './basic'; import {BasicComp} from './basic';
import {ComponentUsingThirdParty} from './comp_using_3rdp';
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components'; import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features'; import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures'; import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
@ -22,35 +25,47 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
@NgModule({ @NgModule({
declarations: [ declarations: [
SomeDirectiveInRootModule,
SomePipeInRootModule,
AnimateCmp, AnimateCmp,
BasicComp, BasicComp,
CompConsumingEvents,
CompForChildQuery, CompForChildQuery,
CompWithEntryComponents, CompUsingPipes,
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider, CompWithAnalyzeEntryComponentsProvider,
ProjectingComp,
CompWithChildQuery, CompWithChildQuery,
CompWithDirectiveChild, CompWithDirectiveChild,
CompWithEntryComponents,
CompWithNgContent, CompWithNgContent,
CompUsingRootModuleDirectiveAndPipe,
CompWithProviders, CompWithProviders,
CompWithReferences, CompWithReferences,
CompUsingPipes, DirectiveForQuery,
CompConsumingEvents,
DirPublishingEvents, DirPublishingEvents,
MultipleComponentsMyComp, MultipleComponentsMyComp,
DirectiveForQuery,
NextComp, NextComp,
ProjectingComp,
SomeDirectiveInRootModule,
SomePipeInRootModule,
ComponentUsingThirdParty,
], ],
imports: [ imports: [
BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements, BrowserModule,
MdButtonModule FormsModule,
MdButtonModule,
ModuleUsingCustomElements,
someLibModuleWithProviders(),
ThirdpartyModule,
], ],
providers: [SomeService], providers: [SomeService],
entryComponents: [ entryComponents: [
AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider, AnimateCmp,
ProjectingComp, CompWithChildQuery, CompUsingRootModuleDirectiveAndPipe, CompWithReferences BasicComp,
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider,
CompWithChildQuery,
CompWithEntryComponents,
CompWithReferences,
ProjectingComp,
ComponentUsingThirdParty,
] ]
}) })
export class MainModule { export class MainModule {

View File

@ -43,13 +43,13 @@ describe('template codegen output', () => {
}); });
it('should be able to create the basic component', () => { it('should be able to create the basic component', () => {
var compFixture = createComponent(BasicComp); const compFixture = createComponent(BasicComp);
expect(compFixture.componentInstance).toBeTruthy(); expect(compFixture.componentInstance).toBeTruthy();
}); });
it('should support ngIf', () => { it('should support ngIf', () => {
var compFixture = createComponent(BasicComp); const compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3); expect(debugElement.children.length).toBe(3);
compFixture.componentInstance.ctxBool = true; compFixture.componentInstance.ctxBool = true;
@ -59,8 +59,8 @@ describe('template codegen output', () => {
}); });
it('should support ngFor', () => { it('should support ngFor', () => {
var compFixture = createComponent(BasicComp); const compFixture = createComponent(BasicComp);
var debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3); expect(debugElement.children.length).toBe(3);
// test NgFor // test NgFor
@ -83,11 +83,9 @@ describe('template codegen output', () => {
}); });
it('should support i18n for content tags', () => { it('should support i18n for content tags', () => {
const compFixture = createComponent(BasicComp); const containerElement = createComponent(BasicComp).nativeElement;
const debugElement = compFixture.debugElement; const pElement = containerElement.children.find((c: any) => c.name == 'p');
const containerElement = <any>debugElement.nativeElement; const pText = pElement.children.map((c: any) => c.data).join('').trim();
const pElement = <any>containerElement.children.find((c: any) => c.name == 'p');
const pText = <string>pElement.children.map((c: any) => c.data).join('').trim();
expect(pText).toBe('tervetuloa'); expect(pText).toBe('tervetuloa');
}); });
}); });

View File

@ -34,6 +34,7 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)> <!ELEMENT ex (#PCDATA)>
]> ]>
<messagebundle> <messagebundle>
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg> <msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg> <msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle> </messagebundle>
@ -43,6 +44,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template"> <file source-language="en" datatype="plaintext" original="ng2.template">
<body> <body>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html"> <trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
<source>translate me</source> <source>translate me</source>
<target/> <target/>

View File

@ -7,6 +7,7 @@
*/ */
import './init'; import './init';
import {ComponentUsingThirdParty} from '../src/comp_using_3rdp';
import {MainModule} from '../src/module'; import {MainModule} from '../src/module';
import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures'; import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures';
@ -15,9 +16,9 @@ import {createComponent, createModule} from './util';
describe('NgModule', () => { describe('NgModule', () => {
it('should support providers', () => { it('should support providers', () => {
const moduleRef = createModule(); const moduleRef = createModule();
expect(moduleRef.instance instanceof MainModule).toBe(true); expect(moduleRef.instance instanceof MainModule).toEqual(true);
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toBe(true); expect(moduleRef.injector.get(MainModule) instanceof MainModule).toEqual(true);
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true); expect(moduleRef.injector.get(SomeService) instanceof SomeService).toEqual(true);
}); });
it('should support entryComponents components', () => { it('should support entryComponents components', () => {
@ -26,7 +27,7 @@ describe('NgModule', () => {
CompUsingRootModuleDirectiveAndPipe); CompUsingRootModuleDirectiveAndPipe);
expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe); expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe);
const compRef = cf.create(moduleRef.injector); const compRef = cf.create(moduleRef.injector);
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toBe(true); expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toEqual(true);
}); });
it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components', it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components',
@ -42,12 +43,30 @@ describe('NgModule', () => {
]); ]);
}); });
describe('third-party modules', () => {
// https://github.com/angular/angular/issues/11889
it('should support third party entryComponents components', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const thirdPComps = fixture.nativeElement.children;
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component');
});
// https://github.com/angular/angular/issues/12428
it('should support third party directives', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const debugElement = fixture.debugElement;
fixture.detectChanges();
expect(debugElement.children[0].properties['title']).toEqual('from 3rd party');
});
});
it('should support module directives and pipes', () => { it('should support module directives and pipes', () => {
const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe); const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe);
compFixture.detectChanges(); compFixture.detectChanges();
const debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toBe('transformed someValue'); expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
}); });
it('should support module directives and pipes on lib modules', () => { it('should support module directives and pipes on lib modules', () => {
@ -55,10 +74,10 @@ describe('NgModule', () => {
compFixture.detectChanges(); compFixture.detectChanges();
const debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toBe('transformed someValue'); expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toBe(true); expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toEqual(true);
expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule) expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule)
.toBe(true); .toEqual(true);
}); });
}); });

View File

@ -0,0 +1,8 @@
This folder emulates consuming precompiled modules and components.
It is compiled separately from the other sources under `src`
to only generate `*.js` / `*.d.ts` / `*.metadata.json` files,
but no `*.ngfactory.ts` files.
** WARNING **
Do not import components/directives from here directly as we want to test that ngc still compiles
them when they are not imported.

View File

@ -0,0 +1,16 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'third-party-comp',
template: '<div>3rdP-component</div>',
})
export class ThirdPartyComponent {
}

View File

@ -0,0 +1,17 @@
/**
* @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 {Directive, Input} from '@angular/core';
@Directive({
selector: '[thirdParty]',
host: {'[title]': 'thirdParty'},
})
export class ThirdPartyDirective {
@Input() thirdParty: string;
}

View File

@ -0,0 +1,28 @@
/**
* @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 {NgModule} from '@angular/core';
import {ThirdPartyComponent} from './comp';
import {ThirdPartyDirective} from './directive';
import {AnotherThirdPartyModule} from './other_module';
@NgModule({
declarations: [
ThirdPartyComponent,
ThirdPartyDirective,
],
exports: [
AnotherThirdPartyModule,
ThirdPartyComponent,
ThirdPartyDirective,
],
imports: [AnotherThirdPartyModule]
})
export class ThirdpartyModule {
}

View File

@ -0,0 +1,16 @@
/**
* @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 {Component} from '@angular/core';
@Component({
selector: 'another-third-party-comp',
template: '<div i18n>other-3rdP-component</div>',
})
export class AnotherThirdpartyComponent {
}

View File

@ -0,0 +1,17 @@
/**
* @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 {NgModule} from '@angular/core';
import {AnotherThirdpartyComponent} from './other_comp';
@NgModule({
declarations: [AnotherThirdpartyComponent],
exports: [AnotherThirdpartyComponent],
})
export class AnotherThirdPartyModule {
}

View File

@ -0,0 +1,20 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": ".",
"outDir": "../node_modules/third_party"
}
}

View File

@ -0,0 +1,29 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
},
"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
]
}

View File

@ -1,29 +0,0 @@
{
"angularCompilerOptions": {
// For TypeScript 1.8, we have to lay out generated files
// in the same source directory with your code.
"genDir": ".",
"debug": true
},
"compilerOptions": {
"target": "es5",
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"rootDir": "",
"declaration": true,
"lib": ["es6", "dom"],
"baseUrl": "."
},
"files": [
"src/module",
"src/bootstrap",
"test/all_spec",
"benchmarks/src/tree/ng2/index_aot.ts",
"benchmarks/src/tree/ng2_switch/index_aot.ts",
"benchmarks/src/largetable/ng2/index_aot.ts",
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
]
}

View File

@ -11,7 +11,7 @@
* Intended to be used in a build step. * Intended to be used in a build step.
*/ */
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {Directive, NgModule, ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -35,73 +35,16 @@ const PREAMBLE = `/**
`; `;
export class CodeGeneratorModuleCollector {
constructor(
private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost,
private program: ts.Program, private options: AngularCompilerOptions) {}
getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} {
// Compare with false since the default should be true
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
GENERATED_OR_DTS_FILES :
GENERATED_FILES;
let filePaths = this.program.getSourceFiles()
.filter(sf => !skipFileNames.test(sf.fileName))
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
return {fileMetas, ngModules};
}
private readFileMetadata(absSourcePath: string): FileMetadata {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {directives: [], ngModules: [], fileUrl: absSourcePath};
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
const annotations = this.staticReflector.annotations(staticType);
annotations.forEach((annotation) => {
if (annotation instanceof NgModule) {
result.ngModules.push(staticType);
} else if (annotation instanceof Directive) {
result.directives.push(staticType);
}
});
}
return result;
}
}
export class CodeGenerator { export class CodeGenerator {
private moduleCollector: CodeGeneratorModuleCollector;
constructor( constructor(
private options: AngularCompilerOptions, private program: ts.Program, private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector, public host: ts.CompilerHost, private staticReflector: StaticReflector,
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) { private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
this.moduleCollector =
new CodeGeneratorModuleCollector(staticReflector, reflectorHost, program, options);
}
// 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 {
let root = this.options.basePath; let root = this.options.basePath;
for (let eachRootDir of this.options.rootDirs || []) { for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) { if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
} }
@ -111,31 +54,28 @@ export class CodeGenerator {
} }
// transplant the codegen path to be inside the `genDir` // transplant the codegen path to be inside the `genDir`
var relativePath: string = path.relative(root, filePath); let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) { while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`. // into `genDir`.
relativePath = relativePath.substr(3); relativePath = relativePath.substr(3);
} }
return path.join(this.options.genDir, relativePath); return path.join(this.options.genDir, relativePath);
} }
codegen(): Promise<any> { codegen(options: {transitiveModules: boolean}): Promise<any> {
const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program); const staticSymbols =
const analyzedNgModules = this.compiler.analyzeModules(ngModules); extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
return Promise.all(fileMetas.map(
(fileMeta) => return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
this.compiler generatedModules.forEach(generatedModule => {
.compile( const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules) const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
.then((generatedModules) => { this.host.writeFile(
generatedModules.forEach((generatedModule) => { emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
const sourceFile = this.program.getSourceFile(fileMeta.fileUrl); });
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); });
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
})));
} }
static create( static create(
@ -193,7 +133,9 @@ export class CodeGenerator {
// TODO(vicb): do not pass cliOptions.i18nFormat here // TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler( const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver), resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
new compiler.ViewCompiler(config), new compiler.DirectiveWrapperCompiler(config), new compiler.ViewCompiler(config, elementSchemaRegistry),
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
cliOptions.locale, cliOptions.i18nFormat); cliOptions.locale, cliOptions.i18nFormat);
@ -202,8 +144,40 @@ export class CodeGenerator {
} }
} }
export interface FileMetadata { export function extractProgramSymbols(
fileUrl: string; program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
directives: StaticSymbol[]; options: AngularCompilerOptions): StaticSymbol[] {
ngModules: 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

@ -10,27 +10,32 @@
/** /**
* Extract i18n messages from source code * Extract i18n messages from source code
*
* TODO(vicb): factorize code with the CodeGenerator
*/ */
// Must be imported first, because angular2 decorators throws on load. // Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata'; import 'reflect-metadata';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {Component, NgModule, ViewEncapsulation} from '@angular/core'; import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import {Console} from './private_import_core'; import {Extractor} from './extractor';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
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 htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); const resourceLoader: compiler.ResourceLoader = {
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser); 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();
return (bundlePromise).then(messageBundle => { return (bundlePromise).then(messageBundle => {
@ -46,6 +51,7 @@ function extract(
case 'xliff': case 'xliff':
case 'xlf': case 'xlf':
default: default:
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
ext = 'xlf'; ext = 'xlf';
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG); serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
break; break;
@ -56,139 +62,6 @@ function extract(
}); });
} }
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
export class Extractor {
constructor(
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) {}
private readFileMetadata(absSourcePath: string): FileMetadata {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
}
for (const symbol of symbols) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
const annotations = this.staticReflector.annotations(staticType);
annotations.forEach((annotation) => {
if (annotation instanceof NgModule) {
result.ngModules.push(staticType);
} else if (annotation instanceof Component) {
result.components.push(staticType);
}
});
}
return result;
}
extract(): Promise<compiler.MessageBundle> {
const filePaths =
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
const analyzedNgModules = compiler.analyzeModules(ngModules, this.metadataResolver);
const errors: compiler.ParseError[] = [];
let bundlePromise =
Promise
.all(fileMetas.map((fileMeta) => {
const url = fileMeta.fileUrl;
return Promise.all(fileMeta.components.map(compType => {
const compMeta = this.metadataResolver.getDirectiveMetadata(<any>compType);
const ngModule = analyzedNgModules.ngModuleByDirective.get(compType);
if (!ngModule) {
throw new Error(
`Cannot determine the module for component ${compMeta.type.name}!`);
}
return Promise
.all([compMeta, ...ngModule.transitiveModule.directives].map(
dirMeta =>
this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
.then((normalizedCompWithDirectives) => {
const compMeta = normalizedCompWithDirectives[0];
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, url, interpolationConfig));
});
}));
}))
.then(_ => this.messageBundle);
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return bundlePromise;
}
static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, htmlParser: compiler.I18NHtmlParser,
reflectorHostContext?: ReflectorHostContext): Extractor {
const resourceLoader: compiler.ResourceLoader = {
get: (s: string) => {
if (!compilerHost.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(compilerHost.readFile(s));
}
};
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
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(
program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer);
}
}
interface FileMetadata {
fileUrl: string;
components: StaticSymbol[];
ngModules: StaticSymbol[];
}
// Entry point // Entry point
if (require.main === module) { if (require.main === module) {
const args = require('minimist')(process.argv.slice(2)); const args = require('minimist')(process.argv.slice(2));

View File

@ -0,0 +1,110 @@
/**
* @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
*/
/**
* Extract i18n messages from source code
*/
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as tsc from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {extractProgramSymbols} from './codegen';
import {ReflectorHost} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
export class Extractor {
constructor(
private options: tsc.AngularCompilerOptions, 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> {
const programSymbols: StaticSymbol[] =
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(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
reflectorHost?: ReflectorHost): Extractor {
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
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

@ -250,6 +250,12 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
} else { } else {
const sf = this.program.getSourceFile(filePath); const sf = this.program.getSourceFile(filePath);
if (!sf) { 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.`); throw new Error(`Source file ${filePath} not present in program.`);
} }
return this.metadataCollector.getMetadata(sf); return this.metadataCollector.getMetadata(sf);

View File

@ -25,7 +25,7 @@ export interface StaticReflectorHost {
* @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}[];
/** /**
* Resolve a symbol from an import statement form, to the file where it is declared. * Resolve a symbol from an import statement form, to the file where it is declared.
@ -72,13 +72,12 @@ export class StaticReflector implements ReflectorReader {
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
importUri(typeOrFunc: StaticSymbol): string { importUri(typeOrFunc: StaticSymbol): string {
var staticSymbol = this.host.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 {
const result = this.host.findDeclaration(moduleUrl, name, ''); return this.host.findDeclaration(moduleUrl, name, '');
return result;
} }
resolveEnum(enumIdentifier: any, name: string): any { resolveEnum(enumIdentifier: any, name: string): any {
@ -238,9 +237,9 @@ export class StaticReflector implements ReflectorReader {
/** @internal */ /** @internal */
public simplify(context: StaticSymbol, value: any): any { public simplify(context: StaticSymbol, value: any): any {
let _this = this; const _this = this;
let scope = BindingScope.empty; let scope = BindingScope.empty;
let calling = new Map<StaticSymbol, boolean>(); const calling = new Map<StaticSymbol, boolean>();
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any { function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
@ -255,16 +254,15 @@ export class StaticReflector implements ReflectorReader {
} }
function resolveReferenceValue(staticSymbol: StaticSymbol): any { function resolveReferenceValue(staticSymbol: StaticSymbol): any {
let result: any = staticSymbol; const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath); const declarationValue =
let declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null; moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue; return declarationValue;
} }
function isOpaqueToken(context: StaticSymbol, value: any): boolean { function isOpaqueToken(context: StaticSymbol, value: any): boolean {
if (value && value.__symbolic === 'new' && value.expression) { if (value && value.__symbolic === 'new' && value.expression) {
let target = value.expression; const target = value.expression;
if (target.__symbolic == 'reference') { if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), _this.opaqueToken); return sameSymbol(resolveReference(context, target), _this.opaqueToken);
} }
@ -535,7 +533,7 @@ export class StaticReflector implements ReflectorReader {
} }
} }
let result = simplifyInContext(context, value, 0); const result = simplifyInContext(context, value, 0);
if (shouldIgnore(result)) { if (shouldIgnore(result)) {
return undefined; return undefined;
} }
@ -550,8 +548,7 @@ export class StaticReflector implements ReflectorReader {
if (!moduleMetadata) { if (!moduleMetadata) {
moduleMetadata = this.host.getMetadataFor(module); moduleMetadata = this.host.getMetadataFor(module);
if (Array.isArray(moduleMetadata)) { if (Array.isArray(moduleMetadata)) {
moduleMetadata = (<Array<any>>moduleMetadata) moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
.find(element => element.version === SUPPORTED_SCHEMA_VERSION) ||
moduleMetadata[0]; moduleMetadata[0];
} }
if (!moduleMetadata) { if (!moduleMetadata) {
@ -568,12 +565,8 @@ export class StaticReflector implements ReflectorReader {
} }
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
let moduleMetadata = this.getModuleMetadata(type.filePath); const moduleMetadata = this.getModuleMetadata(type.filePath);
let result = moduleMetadata['metadata'][type.name]; return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
if (!result) {
result = {__symbolic: 'class'};
}
return result;
} }
} }
@ -613,7 +606,7 @@ function produceErrorMessage(error: any): string {
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any): function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
{[key: string]: any} { {[key: string]: any} {
if (!input) return {}; if (!input) return {};
var result: {[key: string]: any} = {}; const result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => { Object.keys(input).forEach((key) => {
let value = transform(input[key], key); let value = transform(input[key], key);
if (!shouldIgnore(value)) { if (!shouldIgnore(value)) {
@ -638,8 +631,7 @@ abstract class BindingScope {
public static empty: BindingScope = {resolve: name => BindingScope.missing}; public static empty: BindingScope = {resolve: name => BindingScope.missing};
public static build(): BindingScopeBuilder { public static build(): BindingScopeBuilder {
let current = new Map<string, any>(); const current = new Map<string, any>();
let parent: BindingScope = undefined;
return { return {
define: function(name, value) { define: function(name, value) {
current.set(name, value); current.set(name, value);

View File

@ -8,7 +8,6 @@
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector'; import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ListWrapper} from '@angular/facade/src/collection';
import {MetadataCollector} from '@angular/tsc-wrapped'; import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -474,7 +473,7 @@ class MockReflectorHost implements StaticReflectorHost {
function resolvePath(pathParts: string[]): string { function resolvePath(pathParts: string[]): string {
let result: string[] = []; let result: string[] = [];
ListWrapper.forEachWithIndex(pathParts, (part, index) => { pathParts.forEach((part, index) => {
switch (part) { switch (part) {
case '': case '':
case '.': case '.':

View File

@ -34,9 +34,8 @@ 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';
export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './src/ml_parser/interpolation_config'; export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './src/ml_parser/interpolation_config';
export {ElementSchemaRegistry} from './src/schema/element_schema_registry'; export * from './src/schema/element_schema_registry';
export * from './src/i18n/index'; export * from './src/i18n/index';
export * from './src/template_parser/template_ast';
export * from './src/directive_normalizer'; export * from './src/directive_normalizer';
export * from './src/expression_parser/lexer'; export * from './src/expression_parser/lexer';
export * from './src/expression_parser/parser'; export * from './src/expression_parser/parser';

View File

@ -7,7 +7,7 @@
*/ */
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 {ListWrapper, 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';
@ -180,8 +180,7 @@ function _normalizeStyleMetadata(
var normalizedStyles: {[key: string]: string | number}[] = []; var normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => { entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') { if (typeof styleEntry === 'string') {
ListWrapper.addAll( normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
normalizedStyles, _resolveStylesFromState(<string>styleEntry, stateStyles, errors));
} else { } else {
normalizedStyles.push(<{[key: string]: string | number}>styleEntry); normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
} }
@ -346,12 +345,12 @@ function _parseAnimationKeyframes(
}); });
if (doSortKeyframes) { if (doSortKeyframes) {
ListWrapper.sort(rawKeyframes, (a, b) => a[0] <= b[0] ? -1 : 1); rawKeyframes.sort((a, b) => a[0] <= b[0] ? -1 : 1);
} }
var firstKeyframe = rawKeyframes[0]; var firstKeyframe = rawKeyframes[0];
if (firstKeyframe[0] != _INITIAL_KEYFRAME) { if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
ListWrapper.insert(rawKeyframes, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]); rawKeyframes.splice(0, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
} }
var firstKeyframeStyles = firstKeyframe[1]; var firstKeyframeStyles = firstKeyframe[1];
@ -421,7 +420,7 @@ function _parseTransitionAnimation(
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, '')); steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
} else { } else {
var innerStep = <AnimationStepAst>innerAst; var innerStep = <AnimationStepAst>innerAst;
ListWrapper.addAll(innerStep.startingStyles.styles, previousStyles); innerStep.startingStyles.styles.push(...previousStyles);
} }
previousStyles = null; previousStyles = null;
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
export class StylesCollectionEntry { export class StylesCollectionEntry {
@ -37,7 +36,7 @@ export class StylesCollection {
} }
} }
ListWrapper.insert(entries, insertionIndex, tuple); entries.splice(insertionIndex, 0, tuple);
} }
getByIndex(property: string, index: number): StylesCollectionEntry { getByIndex(property: string, index: number): StylesCollectionEntry {

View File

@ -9,7 +9,7 @@
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ListWrapper, MapWrapper} from './facade/collection'; import {ListWrapper, MapWrapper} from './facade/collection';
import {isPresent, normalizeBlank, normalizeBool} 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';
import {sanitizeIdentifier, splitAtColon} from './util'; import {sanitizeIdentifier, splitAtColon} from './util';
@ -23,7 +23,6 @@ function unimplemented(): any {
// group 2: "event" from "(event)" // group 2: "event" from "(event)"
// group 3: "@trigger" from "@trigger" // group 3: "@trigger" from "@trigger"
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/; const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
const UNDEFINED = new Object();
export abstract class CompileMetadataWithIdentifier { export abstract class CompileMetadataWithIdentifier {
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); } get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
@ -106,33 +105,27 @@ export class CompileDiDependencyMetadata {
isSkipSelf: boolean; isSkipSelf: boolean;
isOptional: boolean; isOptional: boolean;
isValue: boolean; isValue: boolean;
query: CompileQueryMetadata;
viewQuery: CompileQueryMetadata;
token: CompileTokenMetadata; token: CompileTokenMetadata;
value: any; value: any;
constructor( constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, token, value}: {
{isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery, token, isAttribute?: boolean,
value}: { isSelf?: boolean,
isAttribute?: boolean, isHost?: boolean,
isSelf?: boolean, isSkipSelf?: boolean,
isHost?: boolean, isOptional?: boolean,
isSkipSelf?: boolean, isValue?: boolean,
isOptional?: boolean, query?: CompileQueryMetadata,
isValue?: boolean, viewQuery?: CompileQueryMetadata,
query?: CompileQueryMetadata, token?: CompileTokenMetadata,
viewQuery?: CompileQueryMetadata, value?: any
token?: CompileTokenMetadata, } = {}) {
value?: any this.isAttribute = !!isAttribute;
} = {}) { this.isSelf = !!isSelf;
this.isAttribute = normalizeBool(isAttribute); this.isHost = !!isHost;
this.isSelf = normalizeBool(isSelf); this.isSkipSelf = !!isSkipSelf;
this.isHost = normalizeBool(isHost); this.isOptional = !!isOptional;
this.isSkipSelf = normalizeBool(isSkipSelf); this.isValue = !!isValue;
this.isOptional = normalizeBool(isOptional);
this.isValue = normalizeBool(isValue);
this.query = query;
this.viewQuery = viewQuery;
this.token = token; this.token = token;
this.value = value; this.value = value;
} }
@ -161,8 +154,8 @@ export class CompileProviderMetadata {
this.useValue = useValue; this.useValue = useValue;
this.useExisting = useExisting; this.useExisting = useExisting;
this.useFactory = useFactory; this.useFactory = useFactory;
this.deps = normalizeBlank(deps); this.deps = deps || null;
this.multi = normalizeBool(multi); this.multi = !!multi;
} }
} }
@ -192,7 +185,7 @@ export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) { {value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
this.value = value; this.value = value;
this.identifier = identifier; this.identifier = identifier;
this.identifierIsInstance = normalizeBool(identifierIsInstance); this.identifierIsInstance = !!identifierIsInstance;
} }
get reference(): any { get reference(): any {
@ -227,7 +220,7 @@ export class CompileTypeMetadata extends CompileIdentifierMetadata {
lifecycleHooks?: LifecycleHooks[]; lifecycleHooks?: LifecycleHooks[];
} = {}) { } = {}) {
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value}); super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
this.isHost = normalizeBool(isHost); this.isHost = !!isHost;
this.diDeps = _normalizeArray(diDeps); this.diDeps = _normalizeArray(diDeps);
this.lifecycleHooks = _normalizeArray(lifecycleHooks); this.lifecycleHooks = _normalizeArray(lifecycleHooks);
} }
@ -248,8 +241,8 @@ export class CompileQueryMetadata {
read?: CompileTokenMetadata read?: CompileTokenMetadata
} = {}) { } = {}) {
this.selectors = selectors; this.selectors = selectors;
this.descendants = normalizeBool(descendants); this.descendants = !!descendants;
this.first = normalizeBool(first); this.first = !!first;
this.propertyName = propertyName; this.propertyName = propertyName;
this.read = read; this.read = read;
} }
@ -303,9 +296,9 @@ export class CompileTemplateMetadata {
this.styles = _normalizeArray(styles); this.styles = _normalizeArray(styles);
this.styleUrls = _normalizeArray(styleUrls); this.styleUrls = _normalizeArray(styleUrls);
this.externalStylesheets = _normalizeArray(externalStylesheets); this.externalStylesheets = _normalizeArray(externalStylesheets);
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : []; this.animations = animations ? ListWrapper.flatten(animations) : [];
this.ngContentSelectors = ngContentSelectors || []; this.ngContentSelectors = ngContentSelectors || [];
if (isPresent(interpolation) && interpolation.length != 2) { if (interpolation && interpolation.length != 2) {
throw new Error(`'interpolation' should have a start and an end symbol.`); throw new Error(`'interpolation' should have a start and an end symbol.`);
} }
this.interpolation = interpolation; this.interpolation = interpolation;
@ -375,7 +368,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
return new CompileDirectiveMetadata({ return new CompileDirectiveMetadata({
type, type,
isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection, isComponent: !!isComponent, selector, exportAs, changeDetection,
inputs: inputsMap, inputs: inputsMap,
outputs: outputsMap, outputs: outputsMap,
hostListeners, hostListeners,
@ -503,13 +496,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
} = {}) { } = {}) {
this.type = type; this.type = type;
this.name = name; this.name = name;
this.pure = normalizeBool(pure); this.pure = !!pure;
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
} }
/** /**
* Metadata regarding compilation of a directive. * Metadata regarding compilation of a module.
*/ */
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
@ -569,6 +562,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
export class TransitiveCompileNgModuleMetadata { export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type<any>>(); directivesSet = new Set<Type<any>>();
pipesSet = new Set<Type<any>>(); pipesSet = new Set<Type<any>>();
constructor( constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[], public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
@ -581,11 +575,13 @@ export class TransitiveCompileNgModuleMetadata {
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]): export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
T[] { T[] {
const map = new Map<any, T>(); const map = new Map<any, T>();
items.forEach((item) => { items.forEach((item) => {
if (!map.get(item.identifier.reference)) { if (!map.get(item.identifier.reference)) {
map.set(item.identifier.reference, item); map.set(item.identifier.reference, item);
} }
}); });
return MapWrapper.values(map); return MapWrapper.values(map);
} }

View File

@ -0,0 +1,48 @@
/**
* @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 {Identifiers, resolveIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast';
import {ConvertPropertyBindingResult} from './expression_converter';
export class CheckBindingField {
constructor(public expression: o.ReadPropExpr, public bindingId: string) {}
}
export function createCheckBindingField(builder: ClassBuilder): CheckBindingField {
const bindingId = `${builder.fields.length}`;
const fieldExpr = createBindFieldExpr(bindingId);
// private is fine here as no child view will reference the cached value...
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
return new CheckBindingField(fieldExpr, bindingId);
}
export function createCheckBindingStmt(
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
throwOnChangeVar, fieldExpr, evalResult.currValExpr
]);
if (evalResult.forceUpdate) {
condition = evalResult.forceUpdate.or(condition);
}
return [
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
]))
];
}
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
}

View File

@ -10,52 +10,130 @@
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {createPureProxy} from './identifier_util';
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
export interface NameResolver { export interface NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression; callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression; getLocal(name: string): o.Expression;
createLiteralArray(values: o.Expression[]): o.Expression;
createLiteralMap(values: Array<Array<string|o.Expression>>): o.Expression;
} }
export class ExpressionWithWrappedValueInfo { export class EventHandlerVars { static event = o.variable('$event'); }
export class ConvertPropertyBindingResult {
constructor( constructor(
public expression: o.Expression, public needsValueUnwrapper: boolean, public stmts: o.Statement[], public currValExpr: o.Expression,
public temporaryCount: number) {} public forceUpdate: o.Expression) {}
} }
export function convertCdExpressionToIr( /**
nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST, * Converts the given expression AST into an executable output AST, assuming the expression is
valueUnwrapper: o.ReadVarExpr, bindingIndex: number): ExpressionWithWrappedValueInfo { * used in a property binding.
const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper, bindingIndex); */
const irAst: o.Expression = expression.visit(visitor, _Mode.Expression); export function convertPropertyBinding(
return new ExpressionWithWrappedValueInfo( builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
irAst, visitor.needsValueUnwrapper, visitor.temporaryCount); expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
if (!nameResolver) {
nameResolver = new DefaultNameResolver();
}
const visitor = new _AstToIrVisitor(
builder, nameResolver, implicitReceiver, VAL_UNWRAPPER_VAR, bindingId, false);
const outputExpr: o.Expression = expression.visit(visitor, _Mode.Expression);
if (!outputExpr) {
// e.g. an empty expression was given
return null;
}
if (visitor.temporaryCount) {
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
}
if (visitor.needsValueUnwrapper) {
var initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.push(initValueUnwrapperStmt);
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
if (visitor.needsValueUnwrapper) {
return new ConvertPropertyBindingResult(
stmts, currValExpr, VAL_UNWRAPPER_VAR.prop('hasWrappedValue'));
} else {
return new ConvertPropertyBindingResult(stmts, currValExpr, null);
}
} }
export function convertCdStatementToIr( export class ConvertActionBindingResult {
nameResolver: NameResolver, implicitReceiver: o.Expression, stmt: cdAst.AST, constructor(public stmts: o.Statement[], public preventDefault: o.ReadVarExpr) {}
bindingIndex: number): o.Statement[] {
const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, null, bindingIndex);
let statements: o.Statement[] = [];
flattenStatements(stmt.visit(visitor, _Mode.Statement), statements);
prependTemporaryDecls(visitor.temporaryCount, bindingIndex, statements);
return statements;
} }
function temporaryName(bindingIndex: number, temporaryNumber: number): string { /**
return `tmp_${bindingIndex}_${temporaryNumber}`; * Converts the given expression AST into an executable output AST, assuming the expression is
* used in an action binding (e.g. an event handler).
*/
export function convertActionBinding(
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
action: cdAst.AST, bindingId: string): ConvertActionBindingResult {
if (!nameResolver) {
nameResolver = new DefaultNameResolver();
}
const visitor =
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true);
let actionStmts: o.Statement[] = [];
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
var lastIndex = actionStmts.length - 1;
var preventDefaultVar: o.ReadVarExpr = null;
if (lastIndex >= 0) {
var lastStatement = actionStmts[lastIndex];
var returnExpr = convertStmtIntoExpression(lastStatement);
if (returnExpr) {
// Note: We need to cast the result of the method call to dynamic,
// as it might be a void method!
preventDefaultVar = createPreventDefaultVar(bindingId);
actionStmts[lastIndex] =
preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false)))
.toDeclStmt(null, [o.StmtModifier.Final]);
}
}
return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
} }
export function temporaryDeclaration(bindingIndex: number, temporaryNumber: number): o.Statement { /**
return new o.DeclareVarStmt(temporaryName(bindingIndex, temporaryNumber), o.NULL_EXPR); * Creates variables that are shared by multiple calls to `convertActionBinding` /
* `convertPropertyBinding`
*/
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
const unwrapperStmts: o.Statement[] = [];
var readVars = o.findReadVarNames(stmts);
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
unwrapperStmts.push(
VAL_UNWRAPPER_VAR
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
return unwrapperStmts;
}
function temporaryName(bindingId: string, temporaryNumber: number): string {
return `tmp_${bindingId}_${temporaryNumber}`;
}
export function temporaryDeclaration(bindingId: string, temporaryNumber: number): o.Statement {
return new o.DeclareVarStmt(temporaryName(bindingId, temporaryNumber), o.NULL_EXPR);
} }
function prependTemporaryDecls( function prependTemporaryDecls(
temporaryCount: number, bindingIndex: number, statements: o.Statement[]) { temporaryCount: number, bindingId: string, statements: o.Statement[]) {
for (let i = temporaryCount - 1; i >= 0; i--) { for (let i = temporaryCount - 1; i >= 0; i--) {
statements.unshift(temporaryDeclaration(bindingIndex, i)); statements.unshift(temporaryDeclaration(bindingId, i));
} }
} }
@ -92,8 +170,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
public temporaryCount: number = 0; public temporaryCount: number = 0;
constructor( constructor(
private _nameResolver: NameResolver, private _implicitReceiver: o.Expression, private _builder: ClassBuilder, private _nameResolver: NameResolver,
private _valueUnwrapper: o.ReadVarExpr, private bindingIndex: number) {} private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr,
private bindingId: string, private isAction: boolean) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any { visitBinary(ast: cdAst.Binary, mode: _Mode): any {
var op: o.BinaryOperator; var op: o.BinaryOperator;
@ -170,6 +249,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const input = this.visit(ast.exp, _Mode.Expression); const input = this.visit(ast.exp, _Mode.Expression);
const args = this.visitAll(ast.args, _Mode.Expression); const args = this.visitAll(ast.args, _Mode.Expression);
const value = this._nameResolver.callPipe(ast.name, input, args); const value = this._nameResolver.callPipe(ast.name, input, args);
if (!value) {
throw new Error(`Illegal state: Pipe ${ast.name} is not allowed here!`);
}
this.needsValueUnwrapper = true; this.needsValueUnwrapper = true;
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value])); return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
} }
@ -209,8 +291,10 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
return convertToStatementIfNeeded( const parts = this.visitAll(ast.expressions, mode);
mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode))); const literalArr =
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
return convertToStatementIfNeeded(mode, literalArr);
} }
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
@ -218,13 +302,22 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
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)]);
} }
return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts)); const literalMap =
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
return convertToStatementIfNeeded(mode, literalMap);
} }
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value)); return convertToStatementIfNeeded(mode, o.literal(ast.value));
} }
private _getLocal(name: string): o.Expression {
if (this.isAction && name == EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return this._nameResolver.getLocal(name);
}
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast); const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) { if (leftMostSafe) {
@ -234,7 +327,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
let result: any = null; let result: any = null;
let receiver = this.visit(ast.receiver, _Mode.Expression); let receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
var varExpr = this._nameResolver.getLocal(ast.name); var varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (isPresent(varExpr)) {
result = varExpr.callFn(args); result = varExpr.callFn(args);
} }
@ -258,7 +351,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
let result: any = null; let result: any = null;
var 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._nameResolver.getLocal(ast.name); result = this._getLocal(ast.name);
} }
if (isBlank(result)) { if (isBlank(result)) {
result = receiver.prop(ast.name); result = receiver.prop(ast.name);
@ -270,7 +363,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
let 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) {
var varExpr = this._nameResolver.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!');
} }
@ -459,12 +552,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
private allocateTemporary(): o.ReadVarExpr { private allocateTemporary(): o.ReadVarExpr {
const tempNumber = this._currentTemporary++; const tempNumber = this._currentTemporary++;
this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount); this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
return new o.ReadVarExpr(temporaryName(this.bindingIndex, tempNumber)); return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber));
} }
private releaseTemporary(temporary: o.ReadVarExpr) { private releaseTemporary(temporary: o.ReadVarExpr) {
this._currentTemporary--; this._currentTemporary--;
if (temporary.name != temporaryName(this.bindingIndex, this._currentTemporary)) { if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) {
throw new Error(`Temporary ${temporary.name} released out of order`); throw new Error(`Temporary ${temporary.name} released out of order`);
} }
} }
@ -477,3 +570,69 @@ function flattenStatements(arg: any, output: o.Statement[]) {
output.push(arg); output.push(arg);
} }
} }
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
}
var proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: o.Expression[] = [];
for (var i = 0; i < values.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName));
}
createPureProxy(
o.fn(
proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))],
new o.ArrayType(o.DYNAMIC_TYPE)),
values.length, proxyExpr, builder);
return proxyExpr.callFn(values);
}
function createCachedLiteralMap(
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
if (entries.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
}
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
const proxyParams: o.FnParam[] = [];
const proxyReturnEntries: [string, o.Expression][] = [];
const values: o.Expression[] = [];
for (var i = 0; i < entries.length; i++) {
const paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
values.push(<o.Expression>entries[i][1]);
}
createPureProxy(
o.fn(
proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))],
new o.MapType(o.DYNAMIC_TYPE)),
entries.length, proxyExpr, builder);
return proxyExpr.callFn(values);
}
class DefaultNameResolver implements NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
getLocal(name: string): o.Expression { return null; }
}
function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
return o.variable(`currVal_${bindingId}`); // fix syntax highlighting: `
}
function createPreventDefaultVar(bindingId: string): o.ReadVarExpr {
return o.variable(`pd_${bindingId}`);
}
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
if (stmt instanceof o.ExpressionStatement) {
return stmt.expr;
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
}

View File

@ -0,0 +1,60 @@
/**
* @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 {CompileTokenMetadata} from '../compile_metadata';
import {isPresent} from '../facade/lang';
import {IdentifierSpec, Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
}
export function createInlineArray(values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
}
const log2 = Math.log(values.length) / Math.log(2);
const index = Math.ceil(log2);
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
Identifiers.InlineArrayDynamic;
const identifier = resolveIdentifier(identifierSpec);
return o.importExpr(identifier).instantiate([
<o.Expression>o.literal(values.length)
].concat(values));
}
export function createPureProxy(
fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr,
builder: {fields: o.ClassField[], ctorStmts: {push: (stmt: o.Statement) => void}}) {
builder.fields.push(new o.ClassField(pureProxyProp.name, null));
var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (!pureProxyId) {
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
}
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
.toStmt());
}
export function createEnumExpression(enumType: IdentifierSpec, enumValue: any): o.Expression {
const enumName =
Object.keys(enumType.runtime).find((propName) => enumType.runtime[propName] === enumValue);
if (!enumName) {
throw new Error(`Unknown enum value ${enumValue} in ${enumType.name}`);
}
return o.importExpr(resolveEnumIdentifier(resolveIdentifier(enumType), enumName));
}

View File

@ -0,0 +1,146 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SecurityContext} from '@angular/core';
import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {createEnumExpression} from './identifier_util';
export function writeToRenderer(
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
renderValue: o.Expression, logBindingUpdate: boolean,
securityContextExpression?: o.Expression): o.Statement[] {
const updateStmts: o.Statement[] = [];
const renderer = view.prop('renderer');
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
switch (boundProp.type) {
case PropertyBindingType.Property:
if (logBindingUpdate) {
updateStmts.push(
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
}
updateStmts.push(
renderer
.callMethod(
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Attribute:
renderValue =
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
updateStmts.push(
renderer
.callMethod(
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Class:
updateStmts.push(
renderer
.callMethod(
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Style:
var strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
renderer
.callMethod(
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Animation:
throw new Error('Illegal state: Should not come here!');
}
return updateStmts;
}
function sanitizedValue(
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
securityContextExpression?: o.Expression): o.Expression {
if (boundProp.securityContext === SecurityContext.NONE) {
return renderValue; // No sanitization needed.
}
if (!boundProp.needsRuntimeSecurityContext) {
securityContextExpression =
createEnumExpression(Identifiers.SecurityContext, boundProp.securityContext);
}
if (!securityContextExpression) {
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
}
let ctx = view.prop('viewUtils').prop('sanitizer');
let args = [securityContextExpression, renderValue];
return ctx.callMethod('sanitize', args);
}
export function triggerAnimation(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
lastRenderValue: o.Expression) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
const animationName = boundProp.name;
const animationFnExpr =
componentView.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
view, renderElement,
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
]))
.toDeclStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
const registerStmts = [
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt(),
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt(),
];
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);
return {updateStmts, detachStmts};
}

View File

@ -9,24 +9,37 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser';
import {Identifiers, resolveIdentifier} from './identifiers'; import {Identifiers, resolveIdentifier} from './identifiers';
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core'; import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {BindingParser} from './template_parser/binding_parser';
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
export class DirectiveWrapperCompileResult { export class DirectiveWrapperCompileResult {
constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {} constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {}
} }
const CONTEXT_FIELD_NAME = 'context'; const CONTEXT_FIELD_NAME = 'context';
const CHANGES_FIELD_NAME = 'changes'; const CHANGES_FIELD_NAME = '_changes';
const CHANGED_FIELD_NAME = 'changed'; const CHANGED_FIELD_NAME = '_changed';
const EVENT_HANDLER_FIELD_NAME = '_eventHandler';
const CURR_VALUE_VAR = o.variable('currValue'); const CURR_VALUE_VAR = o.variable('currValue');
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange'); const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
const FORCE_UPDATE_VAR = o.variable('forceUpdate'); const FORCE_UPDATE_VAR = o.variable('forceUpdate');
const VIEW_VAR = o.variable('view'); const VIEW_VAR = o.variable('view');
const COMPONENT_VIEW_VAR = o.variable('componentView');
const RENDER_EL_VAR = o.variable('el'); const RENDER_EL_VAR = o.variable('el');
const EVENT_NAME_VAR = o.variable('eventName');
const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt(); const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt();
@ -42,62 +55,107 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
export class DirectiveWrapperCompiler { export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; } static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
constructor(private compilerConfig: CompilerConfig) {} constructor(
private compilerConfig: CompilerConfig, private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult { compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult {
const dirDepParamNames: string[] = []; const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
for (let i = 0; i < dirMeta.type.diDeps.length; i++) { reportParseErrors(hostParseResult.errors, this._console);
dirDepParamNames.push(`p${i}`);
}
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
let lifecycleHooks: GenConfig = {
genChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 ||
this.compilerConfig.logBindingUpdate,
ngOnChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
ngOnInit: dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
ngDoCheck: dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1
};
const fields: o.ClassField[] = [ const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta);
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(dirMeta.type)), Object.keys(dirMeta.inputs).forEach((inputFieldName) => {
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE), addCheckInputMethod(inputFieldName, builder);
];
const ctorStmts: o.Statement[] =
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
if (lifecycleHooks.genChanges) {
fields.push(new o.ClassField(CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE)));
ctorStmts.push(RESET_CHANGES_STMT);
}
const methods: o.ClassMethod[] = [];
Object.keys(dirMeta.inputs).forEach((inputFieldName, idx) => {
const fieldName = `_${inputFieldName}`;
// private is fine here as no child view will reference the cached value...
fields.push(new o.ClassField(fieldName, null, [o.StmtModifier.Private]));
ctorStmts.push(o.THIS_EXPR.prop(fieldName)
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
methods.push(checkInputMethod(inputFieldName, o.THIS_EXPR.prop(fieldName), lifecycleHooks));
}); });
methods.push(detectChangesInternalMethod(lifecycleHooks, this.compilerConfig.genDebugInfo)); addNgDoCheckMethod(builder);
addCheckHostMethod(hostParseResult.hostProps, builder);
addHandleEventMethod(hostParseResult.hostListeners, builder);
addSubscribeMethod(dirMeta, builder);
ctorStmts.push( const classStmt = builder.build();
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) return new DirectiveWrapperCompileResult([classStmt], classStmt.name);
.set(o.importExpr(dirMeta.type)
.instantiate(dirDepParamNames.map((paramName) => o.variable(paramName))))
.toStmt());
const ctor = new o.ClassMethod(
null, dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
ctorStmts);
const wrapperClassName = DirectiveWrapperCompiler.dirWrapperClassName(dirMeta.type);
const classStmt = new o.ClassStmt(wrapperClassName, null, fields, [], ctor, methods);
return new DirectiveWrapperCompileResult([classStmt], wrapperClassName);
} }
} }
function detectChangesInternalMethod( class DirectiveWrapperBuilder implements ClassBuilder {
lifecycleHooks: GenConfig, logBindingUpdate: boolean): o.ClassMethod { fields: o.ClassField[] = [];
getters: o.ClassGetter[] = [];
methods: o.ClassMethod[] = [];
ctorStmts: o.Statement[] = [];
detachStmts: o.Statement[] = [];
destroyStmts: o.Statement[] = [];
genChanges: boolean;
ngOnChanges: boolean;
ngOnInit: boolean;
ngDoCheck: boolean;
ngOnDestroy: boolean;
constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) {
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
this.genChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 ||
this.compilerConfig.logBindingUpdate;
this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1;
this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1;
this.ngDoCheck = dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1;
this.ngOnDestroy = dirLifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1;
if (this.ngOnDestroy) {
this.destroyStmts.push(
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnDestroy', []).toStmt());
}
}
build(): o.ClassStmt {
const dirDepParamNames: string[] = [];
for (let i = 0; i < this.dirMeta.type.diDeps.length; i++) {
dirDepParamNames.push(`p${i}`);
}
const methods = [
new o.ClassMethod(
'ngOnDetach',
[
new o.FnParam(
VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
],
this.detachStmts),
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
];
const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]),
];
const ctorStmts: o.Statement[] =
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
if (this.genChanges) {
fields.push(new o.ClassField(
CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private]));
ctorStmts.push(RESET_CHANGES_STMT);
}
ctorStmts.push(
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.set(o.importExpr(this.dirMeta.type)
.instantiate(dirDepParamNames.map((paramName) => o.variable(paramName))))
.toStmt());
return createClassStmt({
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type),
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
builders: [{fields, ctorStmts, methods}, this]
});
}
}
function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
const changedVar = o.variable('changed'); const changedVar = o.variable('changed');
const stmts: o.Statement[] = [ const stmts: o.Statement[] = [
changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(), changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(),
@ -105,14 +163,14 @@ function detectChangesInternalMethod(
]; ];
const lifecycleStmts: o.Statement[] = []; const lifecycleStmts: o.Statement[] = [];
if (lifecycleHooks.genChanges) { if (builder.genChanges) {
const onChangesStmts: o.Statement[] = []; const onChangesStmts: o.Statement[] = [];
if (lifecycleHooks.ngOnChanges) { if (builder.ngOnChanges) {
onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)]) .callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
.toStmt()); .toStmt());
} }
if (logBindingUpdate) { if (builder.compilerConfig.logBindingUpdate) {
onChangesStmts.push( onChangesStmts.push(
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges)) o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
.callFn( .callFn(
@ -123,12 +181,12 @@ function detectChangesInternalMethod(
lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts)); lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts));
} }
if (lifecycleHooks.ngOnInit) { if (builder.ngOnInit) {
lifecycleStmts.push(new o.IfStmt( lifecycleStmts.push(new o.IfStmt(
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)), VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()])); [o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
} }
if (lifecycleHooks.ngDoCheck) { if (builder.ngDoCheck) {
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt()); lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
} }
if (lifecycleStmts.length > 0) { if (lifecycleStmts.length > 0) {
@ -136,51 +194,265 @@ function detectChangesInternalMethod(
} }
stmts.push(new o.ReturnStatement(changedVar)); stmts.push(new o.ReturnStatement(changedVar));
return new o.ClassMethod( builder.methods.push(new o.ClassMethod(
'detectChangesInternal', 'ngDoCheck',
[ [
new o.FnParam( new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
], ],
stmts, o.BOOL_TYPE); stmts, o.BOOL_TYPE));
} }
function checkInputMethod( function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
input: string, fieldExpr: o.ReadPropExpr, lifecycleHooks: GenConfig): o.ClassMethod { const field = createCheckBindingField(builder);
var onChangeStatements: o.Statement[] = [ var onChangeStatements: o.Statement[] = [
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(), o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(),
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(), o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
]; ];
if (lifecycleHooks.genChanges) { if (builder.genChanges) {
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME) onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
.key(o.literal(input)) .key(o.literal(input))
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange)) .set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
.instantiate([fieldExpr, CURR_VALUE_VAR])) .instantiate([field.expression, CURR_VALUE_VAR]))
.toStmt()); .toStmt());
} }
onChangeStatements.push(fieldExpr.set(CURR_VALUE_VAR).toStmt());
var methodBody: o.Statement[] = [ var methodBody: o.Statement[] = createCheckBindingStmt(
new o.IfStmt( {currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression,
FORCE_UPDATE_VAR.or(o.importExpr(resolveIdentifier(Identifiers.checkBinding)) THROW_ON_CHANGE_VAR, onChangeStatements);
.callFn([THROW_ON_CHANGE_VAR, fieldExpr, CURR_VALUE_VAR])), builder.methods.push(new o.ClassMethod(
onChangeStatements),
];
return new o.ClassMethod(
`check_${input}`, `check_${input}`,
[ [
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE), new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
], ],
methodBody); methodBody));
} }
interface GenConfig { function addCheckHostMethod(
genChanges: boolean; hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
ngOnChanges: boolean; const stmts: o.Statement[] = [];
ngOnInit: boolean; const methodParams: o.FnParam[] = [
ngDoCheck: boolean; new o.FnParam(
} VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
];
hostProps.forEach((hostProp, hostPropIdx) => {
const field = createCheckBindingField(builder);
const evalResult = convertPropertyBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
if (!evalResult) {
return;
}
let securityContextExpr: o.ReadVarExpr;
if (hostProp.needsRuntimeSecurityContext) {
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
methodParams.push(new o.FnParam(
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
}
let checkBindingStmts: o.Statement[];
if (hostProp.isAnimation) {
const {updateStmts, detachStmts} = triggerAnimation(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
checkBindingStmts = updateStmts;
builder.detachStmts.push(...detachStmts);
} else {
checkBindingStmts = writeToRenderer(
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
builder.compilerConfig.logBindingUpdate, securityContextExpr);
}
stmts.push(...createCheckBindingStmt(
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
});
builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
}
function addHandleEventMethod(hostListeners: BoundEventAst[], builder: DirectiveWrapperBuilder) {
const resultVar = o.variable(`result`);
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
hostListeners.forEach((hostListener, eventIdx) => {
const evalResult = convertActionBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler,
`sub_${eventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) {
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
}
// TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
actionStmts.push(
new o.IfStmt(EVENT_NAME_VAR.equals(o.literal(hostListener.fullName)), trueStmts));
});
actionStmts.push(new o.ReturnStatement(resultVar));
builder.methods.push(new o.ClassMethod(
'handleEvent',
[
new o.FnParam(EVENT_NAME_VAR.name, o.STRING_TYPE),
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
],
actionStmts, o.BOOL_TYPE));
}
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
const methodParams: o.FnParam[] = [
new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)
];
const stmts: o.Statement[] = [
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME).set(o.variable(EVENT_HANDLER_FIELD_NAME)).toStmt()
];
Object.keys(dirMeta.outputs).forEach((emitterPropName, emitterIdx) => {
const eventName = dirMeta.outputs[emitterPropName];
const paramName = `emit${emitterIdx}`;
methodParams.push(new o.FnParam(paramName, o.BOOL_TYPE));
const subscriptionFieldName = `subscription${emitterIdx}`;
builder.fields.push(new o.ClassField(subscriptionFieldName, o.DYNAMIC_TYPE));
stmts.push(new o.IfStmt(o.variable(paramName), [
o.THIS_EXPR.prop(subscriptionFieldName)
.set(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.prop(emitterPropName)
.callMethod(
o.BuiltinMethod.SubscribeObservable,
[o.variable(EVENT_HANDLER_FIELD_NAME)
.callMethod(o.BuiltinMethod.Bind, [VIEW_VAR, o.literal(eventName)])]))
.toStmt()
]));
builder.destroyStmts.push(
o.THIS_EXPR.prop(subscriptionFieldName)
.and(o.THIS_EXPR.prop(subscriptionFieldName).callMethod('unsubscribe', []))
.toStmt());
});
builder.methods.push(new o.ClassMethod('subscribe', methodParams, stmts));
}
class ParseResult {
constructor(
public hostProps: BoundElementPropertyAst[], public hostListeners: BoundEventAst[],
public errors: ParseError[]) {}
}
function parseHostBindings(
dirMeta: CompileDirectiveMetadata, exprParser: Parser,
schemaRegistry: ElementSchemaRegistry): ParseResult {
const errors: ParseError[] = [];
const parser =
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
const sourceFileName = dirMeta.type.moduleUrl ?
`in Directive ${dirMeta.type.name} in ${dirMeta.type.moduleUrl}` :
`in Directive ${dirMeta.type.name}`;
const sourceFile = new ParseSourceFile('', sourceFileName);
const sourceSpan = new ParseSourceSpan(
new ParseLocation(sourceFile, null, null, null),
new ParseLocation(sourceFile, null, null, null));
const parsedHostProps = parser.createDirectiveHostPropertyAsts(dirMeta, sourceSpan);
const parsedHostListeners = parser.createDirectiveHostEventAsts(dirMeta, sourceSpan);
return new ParseResult(parsedHostProps, parsedHostListeners, errors);
}
function reportParseErrors(parseErrors: ParseError[], console: Console) {
const warnings = parseErrors.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = parseErrors.filter(error => error.level === ParseErrorLevel.FATAL);
if (warnings.length > 0) {
this._console.warn(`Directive parse warnings:\n${warnings.join('\n')}`);
}
if (errors.length > 0) {
throw new Error(`Directive parse errors:\n${errors.join('\n')}`);
}
}
export class DirectiveWrapperExpressions {
static create(dir: CompileIdentifierMetadata, depsExpr: o.Expression[]): o.Expression {
return o.importExpr(dir).instantiate(depsExpr, o.importType(dir));
}
static context(dirWrapper: o.Expression): o.ReadPropExpr {
return dirWrapper.prop(CONTEXT_FIELD_NAME);
}
static ngDoCheck(
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
throwOnChange: o.Expression): o.Expression {
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
}
static checkHost(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
if (hostProps.length) {
return [dirWrapper
.callMethod(
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
runtimeSecurityContexts))
.toStmt()];
} else {
return [];
}
}
static ngOnDetach(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderEl: o.Expression): o.Statement[] {
if (hostProps.some(prop => prop.isAnimation)) {
return [dirWrapper
.callMethod(
'ngOnDetach',
[
view,
componentView,
renderEl,
])
.toStmt()];
} else {
return [];
}
}
static ngOnDestroy(dir: CompileDirectiveMetadata, dirWrapper: o.Expression): o.Statement[] {
if (dir.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1 ||
Object.keys(dir.outputs).length > 0) {
return [dirWrapper.callMethod('ngOnDestroy', []).toStmt()];
} else {
return [];
}
}
static subscribe(
dirMeta: CompileDirectiveMetadata, hostProps: BoundElementPropertyAst[], usedEvents: string[],
dirWrapper: o.Expression, view: o.Expression, eventListener: o.Expression): o.Statement[] {
let needsSubscribe = false;
let eventFlags: o.Expression[] = [];
Object.keys(dirMeta.outputs).forEach((propName) => {
const eventName = dirMeta.outputs[propName];
const eventUsed = usedEvents.indexOf(eventName) > -1;
needsSubscribe = needsSubscribe || eventUsed;
eventFlags.push(o.literal(eventUsed));
});
hostProps.forEach((hostProp) => {
if (hostProp.isAnimation && usedEvents.length > 0) {
needsSubscribe = true;
}
});
if (needsSubscribe) {
return [
dirWrapper.callMethod('subscribe', [view, eventListener].concat(eventFlags)).toStmt()
];
} else {
return [];
}
}
static handleEvent(
hostEvents: BoundEventAst[], dirWrapper: o.Expression, eventName: o.Expression,
event: o.Expression): o.Expression {
return dirWrapper.callMethod('handleEvent', [eventName, event]);
}
}

View File

@ -60,10 +60,11 @@ export class Parser {
parseSimpleBinding( parseSimpleBinding(
input: string, location: string, input: string, location: string,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource { interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
var ast = this._parseBindingAst(input, location, interpolationConfig); const ast = this._parseBindingAst(input, location, interpolationConfig);
if (!SimpleExpressionChecker.check(ast)) { const errors = SimpleExpressionChecker.check(ast);
if (errors.length > 0) {
this._reportError( this._reportError(
'Host binding expression can only contain field access and constants', input, location); `Host binding expression cannot contain ${errors.join(' ')}`, input, location);
} }
return new ASTWithSource(ast, input, location, this.errors); return new ASTWithSource(ast, input, location, this.errors);
} }
@ -751,51 +752,51 @@ export class _ParseAST {
} }
class SimpleExpressionChecker implements AstVisitor { class SimpleExpressionChecker implements AstVisitor {
static check(ast: AST): boolean { static check(ast: AST): string[] {
var s = new SimpleExpressionChecker(); var s = new SimpleExpressionChecker();
ast.visit(s); ast.visit(s);
return s.simple; return s.errors;
} }
simple = true; errors: string[] = [];
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {} visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
visitInterpolation(ast: Interpolation, context: any) { this.simple = false; } visitInterpolation(ast: Interpolation, context: any) {}
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {} visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {}
visitPropertyRead(ast: PropertyRead, context: any) {} visitPropertyRead(ast: PropertyRead, context: any) {}
visitPropertyWrite(ast: PropertyWrite, context: any) { this.simple = false; } visitPropertyWrite(ast: PropertyWrite, context: any) {}
visitSafePropertyRead(ast: SafePropertyRead, context: any) { this.simple = false; } visitSafePropertyRead(ast: SafePropertyRead, context: any) {}
visitMethodCall(ast: MethodCall, context: any) { this.simple = false; } visitMethodCall(ast: MethodCall, context: any) {}
visitSafeMethodCall(ast: SafeMethodCall, context: any) { this.simple = false; } visitSafeMethodCall(ast: SafeMethodCall, context: any) {}
visitFunctionCall(ast: FunctionCall, context: any) { this.simple = false; } visitFunctionCall(ast: FunctionCall, context: any) {}
visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); } visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); } visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); }
visitBinary(ast: Binary, context: any) { this.simple = false; } visitBinary(ast: Binary, context: any) {}
visitPrefixNot(ast: PrefixNot, context: any) { this.simple = false; } visitPrefixNot(ast: PrefixNot, context: any) {}
visitConditional(ast: Conditional, context: any) { this.simple = false; } visitConditional(ast: Conditional, context: any) {}
visitPipe(ast: BindingPipe, context: any) { this.simple = false; } visitPipe(ast: BindingPipe, context: any) { this.errors.push('pipes'); }
visitKeyedRead(ast: KeyedRead, context: any) { this.simple = false; } visitKeyedRead(ast: KeyedRead, context: any) {}
visitKeyedWrite(ast: KeyedWrite, context: any) { this.simple = false; } visitKeyedWrite(ast: KeyedWrite, context: any) {}
visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); } visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); }
visitChain(ast: Chain, context: any) { this.simple = false; } visitChain(ast: Chain, context: any) {}
visitQuote(ast: Quote, context: any) { this.simple = false; } visitQuote(ast: Quote, context: any) {}
} }

View File

@ -10,7 +10,6 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionS
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
import {assetUrl} from './util';
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils'); var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils');
@ -163,11 +162,6 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL, moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.checkBinding runtime: view_utils.checkBinding
}; };
static flattenNestedViewRenderNodes: IdentifierSpec = {
name: 'flattenNestedViewRenderNodes',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.flattenNestedViewRenderNodes
};
static devModeEqual: static devModeEqual:
IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual}; IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual};
static interpolate: IdentifierSpec = { static interpolate: IdentifierSpec = {
@ -190,8 +184,17 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL, moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.EMPTY_MAP runtime: view_utils.EMPTY_MAP
}; };
static createRenderElement: IdentifierSpec = {
static pureProxies = [ name: 'createRenderElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.createRenderElement
};
static selectOrCreateRenderHostElement: IdentifierSpec = {
name: 'selectOrCreateRenderHostElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.selectOrCreateRenderHostElement
};
static pureProxies: IdentifierSpec[] = [
null, null,
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1}, {name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1},
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2}, {name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2},
@ -284,6 +287,42 @@ export class Identifiers {
moduleUrl: assetUrl('core', 'animation/animation_transition'), moduleUrl: assetUrl('core', 'animation/animation_transition'),
runtime: AnimationTransition runtime: AnimationTransition
}; };
// This is just the interface!
static InlineArray:
IdentifierSpec = {name: 'InlineArray', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: null};
static inlineArrays: IdentifierSpec[] = [
{name: 'InlineArray2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray2},
{name: 'InlineArray2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray2},
{name: 'InlineArray4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray4},
{name: 'InlineArray8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray8},
{name: 'InlineArray16', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray16},
];
static EMPTY_INLINE_ARRAY: IdentifierSpec = {
name: 'EMPTY_INLINE_ARRAY',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.EMPTY_INLINE_ARRAY
};
static InlineArrayDynamic: IdentifierSpec = {
name: 'InlineArrayDynamic',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.InlineArrayDynamic
};
static subscribeToRenderElement: IdentifierSpec = {
name: 'subscribeToRenderElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.subscribeToRenderElement
};
static noop:
IdentifierSpec = {name: 'noop', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.noop};
}
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `asset:@angular/lib/${pkg}/index`;
} else {
return `asset:@angular/lib/${pkg}/src/${path}`;
}
} }
export function resolveIdentifier(identifier: IdentifierSpec) { export function resolveIdentifier(identifier: IdentifierSpec) {

View File

@ -160,7 +160,7 @@ export class CompileMetadataResolver {
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta); moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
if (dirMeta.entryComponents) { if (dirMeta.entryComponents) {
entryComponentMetadata = entryComponentMetadata =
flattenArray(dirMeta.entryComponents) flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type))) .map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata); .concat(entryComponentMetadata);
} }
@ -228,7 +228,7 @@ export class CompileMetadataResolver {
const schemas: SchemaMetadata[] = []; const schemas: SchemaMetadata[] = [];
if (meta.imports) { if (meta.imports) {
flattenArray(meta.imports).forEach((importedType) => { flattenAndDedupeArray(meta.imports).forEach((importedType) => {
let importedModuleType: Type<any>; let importedModuleType: Type<any>;
if (isValidType(importedType)) { if (isValidType(importedType)) {
importedModuleType = importedType; importedModuleType = importedType;
@ -257,7 +257,7 @@ export class CompileMetadataResolver {
} }
if (meta.exports) { if (meta.exports) {
flattenArray(meta.exports).forEach((exportedType) => { flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) { if (!isValidType(exportedType)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
@ -283,7 +283,7 @@ export class CompileMetadataResolver {
const transitiveModule = const transitiveModule =
this._getTransitiveNgModuleMetadata(importedModules, exportedModules); this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) { if (meta.declarations) {
flattenArray(meta.declarations).forEach((declaredType) => { flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) { if (!isValidType(declaredType)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
@ -313,12 +313,12 @@ export class CompileMetadataResolver {
if (meta.entryComponents) { if (meta.entryComponents) {
entryComponents.push( entryComponents.push(
...flattenArray(meta.entryComponents) ...flattenAndDedupeArray(meta.entryComponents)
.map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type))));
} }
if (meta.bootstrap) { if (meta.bootstrap) {
const typeMetadata = flattenArray(meta.bootstrap).map(type => { const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => {
if (!isValidType(type)) { if (!isValidType(type)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`); `Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
@ -331,7 +331,7 @@ export class CompileMetadataResolver {
entryComponents.push(...bootstrapComponents); entryComponents.push(...bootstrapComponents);
if (meta.schemas) { if (meta.schemas) {
schemas.push(...flattenArray(meta.schemas)); schemas.push(...flattenAndDedupeArray(meta.schemas));
} }
transitiveModule.entryComponents.push(...entryComponents); transitiveModule.entryComponents.push(...entryComponents);
@ -378,15 +378,15 @@ export class CompileMetadataResolver {
} }
private _getTypeDescriptor(type: Type<any>): string { private _getTypeDescriptor(type: Type<any>): string {
if (this._directiveResolver.resolve(type, false) !== null) { if (this._directiveResolver.resolve(type, false)) {
return 'directive'; return 'directive';
} }
if (this._pipeResolver.resolve(type, false) !== null) { if (this._pipeResolver.resolve(type, false)) {
return 'pipe'; return 'pipe';
} }
if (this._ngModuleResolver.resolve(type, false) !== null) { if (this._ngModuleResolver.resolve(type, false)) {
return 'module'; return 'module';
} }
@ -506,9 +506,7 @@ export class CompileMetadataResolver {
let isSelf = false; let isSelf = false;
let isSkipSelf = false; let isSkipSelf = false;
let isOptional = false; let isOptional = false;
let query: Query = null; let token: any = null;
let viewQuery: Query = null;
var token: any = null;
if (Array.isArray(param)) { if (Array.isArray(param)) {
param.forEach((paramEntry) => { param.forEach((paramEntry) => {
if (paramEntry instanceof Host) { if (paramEntry instanceof Host) {
@ -522,12 +520,6 @@ export class CompileMetadataResolver {
} else if (paramEntry instanceof Attribute) { } else if (paramEntry instanceof Attribute) {
isAttribute = true; isAttribute = true;
token = paramEntry.attributeName; token = paramEntry.attributeName;
} else if (paramEntry instanceof Query) {
if (paramEntry.isViewQuery) {
viewQuery = paramEntry;
} else {
query = paramEntry;
}
} else if (paramEntry instanceof Inject) { } else if (paramEntry instanceof Inject) {
token = paramEntry.token; token = paramEntry.token;
} else if (isValidType(paramEntry) && isBlank(token)) { } else if (isValidType(paramEntry) && isBlank(token)) {
@ -548,8 +540,6 @@ export class CompileMetadataResolver {
isSelf, isSelf,
isSkipSelf, isSkipSelf,
isOptional, isOptional,
query: query ? this.getQueryMetadata(query, null, typeOrFunc) : null,
viewQuery: viewQuery ? this.getQueryMetadata(viewQuery, null, typeOrFunc) : null,
token: this.getTokenMetadata(token) token: this.getTokenMetadata(token)
}); });
@ -605,19 +595,20 @@ export class CompileMetadataResolver {
} else if (isValidType(provider)) { } else if (isValidType(provider)) {
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else { } else {
let providersInfo = (<string[]>providers.reduce( const providersInfo =
(soFar: string[], seenProvider: any, seenProviderIdx: number) => { (<string[]>providers.reduce(
if (seenProviderIdx < providerIdx) { (soFar: string[], seenProvider: any, seenProviderIdx: number) => {
soFar.push(`${stringify(seenProvider)}`); if (seenProviderIdx < providerIdx) {
} else if (seenProviderIdx == providerIdx) { soFar.push(`${stringify(seenProvider)}`);
soFar.push(`?${stringify(seenProvider)}?`); } else if (seenProviderIdx == providerIdx) {
} else if (seenProviderIdx == providerIdx + 1) { soFar.push(`?${stringify(seenProvider)}?`);
soFar.push('...'); } else if (seenProviderIdx == providerIdx + 1) {
} soFar.push('...');
return soFar; }
}, return soFar;
[])) },
.join(', '); []))
.join(', ');
throw new Error( throw new Error(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`); `Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
@ -735,7 +726,6 @@ function getTransitiveModules(
return targetModules; return targetModules;
} }
function flattenArray(tree: any[], out: Array<any> = []): Array<any> { function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
if (tree) { if (tree) {
for (let i = 0; i < tree.length; i++) { for (let i = 0; i < tree.length; i++) {
@ -750,6 +740,17 @@ function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
return out; return out;
} }
function dedupeArray(array: any[]): Array<any> {
if (array) {
return Array.from(new Set(array));
}
return [];
}
function flattenAndDedupeArray(tree: any[]): Array<any> {
return dedupeArray(flattenArray(tree));
}
function isValidType(value: any): boolean { function isValidType(value: any): boolean {
return cpl.isStaticSymbol(value) || (value instanceof Type); return cpl.isStaticSymbol(value) || (value instanceof Type);
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {ParseError, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseSourceSpan} from '../parse_util';
@ -231,7 +230,7 @@ class _TreeBuilder {
private _closeVoidElement(): void { private _closeVoidElement(): void {
if (this._elementStack.length > 0) { if (this._elementStack.length > 0) {
const el = ListWrapper.last(this._elementStack); const el = this._elementStack[this._elementStack.length - 1];
if (this.getTagDefinition(el.name).isVoid) { if (this.getTagDefinition(el.name).isVoid) {
this._elementStack.pop(); this._elementStack.pop();
@ -275,7 +274,7 @@ class _TreeBuilder {
private _pushElement(el: html.Element) { private _pushElement(el: html.Element) {
if (this._elementStack.length > 0) { if (this._elementStack.length > 0) {
const parentEl = ListWrapper.last(this._elementStack); const parentEl = this._elementStack[this._elementStack.length - 1];
if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) { if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
this._elementStack.pop(); this._elementStack.pop();
} }
@ -316,7 +315,7 @@ class _TreeBuilder {
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) { for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
const el = this._elementStack[stackIndex]; const el = this._elementStack[stackIndex];
if (el.name == fullName) { if (el.name == fullName) {
ListWrapper.splice(this._elementStack, stackIndex, this._elementStack.length - stackIndex); this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
return true; return true;
} }
@ -343,7 +342,7 @@ class _TreeBuilder {
} }
private _getParentElement(): html.Element { private _getParentElement(): html.Element {
return this._elementStack.length > 0 ? ListWrapper.last(this._elementStack) : null; return this._elementStack.length > 0 ? this._elementStack[this._elementStack.length - 1] : null;
} }
/** /**
@ -361,7 +360,7 @@ class _TreeBuilder {
container = this._elementStack[i]; container = this._elementStack[i];
} }
return {parent: ListWrapper.last(this._elementStack), container}; return {parent: this._elementStack[this._elementStack.length - 1], container};
} }
private _addToParent(node: html.Node) { private _addToParent(node: html.Node) {

View File

@ -9,15 +9,16 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util'; import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {LifecycleHooks} from './private_import_core'; import {LifecycleHooks} from './private_import_core';
import {NgModuleProviderAnalyzer} from './provider_analyzer'; import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast'; import {ProviderAst} from './template_parser/template_ast';
import {createDiTokenExpression} from './util';
export class ComponentFactoryDependency { export class ComponentFactoryDependency {
constructor( constructor(
@ -82,13 +83,15 @@ export class NgModuleCompiler {
} }
} }
class _InjectorBuilder { class _InjectorBuilder implements ClassBuilder {
fields: o.ClassField[] = [];
getters: o.ClassGetter[] = [];
methods: o.ClassMethod[] = [];
ctorStmts: o.Statement[] = [];
private _tokens: CompileTokenMetadata[] = []; private _tokens: CompileTokenMetadata[] = [];
private _instances = new Map<any, o.Expression>(); private _instances = new Map<any, o.Expression>();
private _fields: o.ClassField[] = [];
private _createStmts: o.Statement[] = []; private _createStmts: o.Statement[] = [];
private _destroyStmts: o.Statement[] = []; private _destroyStmts: o.Statement[] = [];
private _getters: o.ClassGetter[] = [];
constructor( constructor(
private _ngModuleMeta: CompileNgModuleMetadata, private _ngModuleMeta: CompileNgModuleMetadata,
@ -136,26 +139,23 @@ class _InjectorBuilder {
), ),
]; ];
var ctor = new o.ClassMethod( var parentArgs = [
null, o.variable(InjectorProps.parent.name),
[new o.FnParam( o.literalArr(
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))], this._entryComponentFactories.map((componentFactory) => o.importExpr(componentFactory))),
[o.SUPER_EXPR o.literalArr(this._bootstrapComponentFactories.map(
.callFn([ (componentFactory) => o.importExpr(componentFactory)))
o.variable(InjectorProps.parent.name), ];
o.literalArr(this._entryComponentFactories.map(
(componentFactory) => o.importExpr(componentFactory))),
o.literalArr(this._bootstrapComponentFactories.map(
(componentFactory) => o.importExpr(componentFactory)))
])
.toStmt()]);
var injClassName = `${this._ngModuleMeta.type.name}Injector`; var injClassName = `${this._ngModuleMeta.type.name}Injector`;
return new o.ClassStmt( return createClassStmt({
injClassName, o.importExpr( name: injClassName,
resolveIdentifier(Identifiers.NgModuleInjector), ctorParams: [new o.FnParam(
[o.importType(this._ngModuleMeta.type)]), InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
this._fields, this._getters, ctor, methods); parent: o.importExpr(
resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
parentArgs: parentArgs,
builders: [{methods}, this]
});
} }
private _getProviderValue(provider: CompileProviderMetadata): o.Expression { private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
@ -194,11 +194,11 @@ class _InjectorBuilder {
type = o.DYNAMIC_TYPE; type = o.DYNAMIC_TYPE;
} }
if (isEager) { if (isEager) {
this._fields.push(new o.ClassField(propName, type)); this.fields.push(new o.ClassField(propName, type));
this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt()); this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt());
} else { } else {
var internalField = `_${propName}`; var internalField = `_${propName}`;
this._fields.push(new o.ClassField(internalField, type)); this.fields.push(new o.ClassField(internalField, type));
// Note: Equals is important for JS so that it also checks the undefined case! // Note: Equals is important for JS so that it also checks the undefined case!
var getterStmts = [ var getterStmts = [
new o.IfStmt( new o.IfStmt(
@ -206,7 +206,7 @@ class _InjectorBuilder {
[o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]), [o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]),
new o.ReturnStatement(o.THIS_EXPR.prop(internalField)) new o.ReturnStatement(o.THIS_EXPR.prop(internalField))
]; ];
this._getters.push(new o.ClassGetter(propName, getterStmts, type)); this.getters.push(new o.ClassGetter(propName, getterStmts, type));
} }
return o.THIS_EXPR.prop(propName); return o.THIS_EXPR.prop(propName);
} }

View File

@ -13,6 +13,7 @@ import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {ListWrapper, MapWrapper} from './facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from './ng_module_compiler';
@ -23,28 +24,88 @@ import {TemplateParser} from './template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
export class SourceModule { export class SourceModule {
constructor(public moduleUrl: string, public source: string) {} constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
} }
export class NgModulesSummary { // Returns all the source files and a mapping from modules to directives
constructor( export function analyzeNgModules(
public ngModuleByDirective: Map<StaticSymbol, CompileNgModuleMetadata>, programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
public ngModules: CompileNgModuleMetadata[]) {} metadataResolver: CompileMetadataResolver): {
} ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
} {
const {
ngModules: programNgModules,
pipesAndDirectives: programPipesOrDirectives,
} = _extractModulesAndPipesOrDirectives(programStaticSymbols, metadataResolver);
export function analyzeModules( const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
const ngModuleByDirective = new Map<StaticSymbol, CompileNgModuleMetadata>(); programNgModules.forEach(modMeta => {
const modules: CompileNgModuleMetadata[] = []; if (options.transitiveModules) {
// For every input modules add the list of transitively included modules
modMeta.transitiveModule.modules.forEach(
modMeta => { moduleMetasByRef.set(modMeta.type.reference, modMeta); });
} else {
moduleMetasByRef.set(modMeta.type.reference, modMeta);
}
});
const ngModuleMetas = MapWrapper.values(moduleMetasByRef);
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModules.forEach((ngModule) => {
const ngModuleMeta = metadataResolver.getNgModuleMetadata(<any>ngModule);
modules.push(ngModuleMeta);
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => { ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
ngModuleByDirective.set(dirMeta.type.reference, ngModuleMeta); const fileUrl = dirMeta.type.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference));
ngModuleByPipeOrDirective.set(dirMeta.type.reference, ngModuleMeta);
});
ngModuleMeta.declaredPipes.forEach((pipeMeta: CompilePipeMetadata) => {
const fileUrl = pipeMeta.type.reference.filePath;
filePaths.add(fileUrl);
ngModuleByPipeOrDirective.set(pipeMeta.type.reference, ngModuleMeta);
}); });
}); });
return new NgModulesSummary(ngModuleByDirective, modules);
// Throw an error if any of the program pipe or directives is not declared by a module
const symbolsMissingModule =
programPipesOrDirectives.filter(s => !ngModuleByPipeOrDirective.has(s));
if (symbolsMissingModule.length) {
const messages = symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, ngModules});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
};
} }
export class OfflineCompiler { export class OfflineCompiler {
@ -59,19 +120,28 @@ export class OfflineCompiler {
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string) {} private _localeId: string, private _translationFormat: string) {}
analyzeModules(ngModules: StaticSymbol[]): NgModulesSummary {
return analyzeModules(ngModules, this._metadataResolver);
}
clearCache() { clearCache() {
this._directiveNormalizer.clearCache(); this._directiveNormalizer.clearCache();
this._metadataResolver.clearCache(); this._metadataResolver.clearCache();
} }
compile( compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
moduleUrl: string, ngModulesSummary: NgModulesSummary, directives: StaticSymbol[], Promise<SourceModule[]> {
ngModules: StaticSymbol[]): Promise<SourceModule[]> { const {ngModuleByPipeOrDirective, files} =
const fileSuffix = _splitTypescriptSuffix(moduleUrl)[1]; analyzeNgModules(staticSymbols, options, this._metadataResolver);
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return Promise.all(sourceModules)
.then((modules: SourceModule[][]) => ListWrapper.flatten(modules));
}
private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise<SourceModule[]> {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = []; const statements: o.Statement[] = [];
const exportedVars: string[] = []; const exportedVars: string[] = [];
const outputSourceModules: SourceModule[] = []; const outputSourceModules: SourceModule[] = [];
@ -91,9 +161,10 @@ export class OfflineCompiler {
if (!compMeta.isComponent) { if (!compMeta.isComponent) {
return Promise.resolve(null); return Promise.resolve(null);
} }
const ngModule = ngModulesSummary.ngModuleByDirective.get(dirType); const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) { if (!ngModule) {
throw new Error(`Cannot determine the module for component ${compMeta.type.name}!`); throw new Error(
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
} }
return Promise return Promise
@ -106,7 +177,8 @@ export class OfflineCompiler {
// compile styles // compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta); const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix)); outputSourceModules.push(
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
}); });
// compile components // compile components
@ -119,8 +191,9 @@ export class OfflineCompiler {
})) }))
.then(() => { .then(() => {
if (statements.length > 0) { if (statements.length > 0) {
outputSourceModules.unshift(this._codegenSourceModule( const srcModule = this._codegenSourceModule(
_ngfactoryModuleUrl(moduleUrl), statements, exportedVars)); srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
outputSourceModules.unshift(srcModule);
} }
return outputSourceModules; return outputSourceModules;
}); });
@ -209,18 +282,21 @@ export class OfflineCompiler {
return viewResult.viewFactoryVar; return viewResult.viewFactoryVar;
} }
private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule { private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
_resolveStyleStatements(stylesCompileResult, fileSuffix); _resolveStyleStatements(stylesCompileResult, fileSuffix);
return this._codegenSourceModule( return this._codegenSourceModule(
_stylesModuleUrl( fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
stylesCompileResult.statements, [stylesCompileResult.stylesVar]); stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
} }
private _codegenSourceModule( private _codegenSourceModule(
moduleUrl: string, statements: o.Statement[], exportedVars: string[]): SourceModule { fileUrl: string, moduleUrl: string, statements: o.Statement[],
exportedVars: string[]): SourceModule {
return new SourceModule( return new SourceModule(
moduleUrl, this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars)); fileUrl, moduleUrl,
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
} }
} }
@ -282,3 +358,28 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, '']; return [path, ''];
} }
// Group the symbols by types:
// - NgModules,
// - Pipes and Directives.
function _extractModulesAndPipesOrDirectives(
programStaticSymbols: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
const ngModules: CompileNgModuleMetadata[] = [];
const pipesAndDirectives: StaticSymbol[] = [];
programStaticSymbols.forEach(staticSymbol => {
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
const directive = metadataResolver.getDirectiveMetadata(staticSymbol, false);
const pipe = metadataResolver.getPipeMetadata(<any>staticSymbol, false);
if (ngModule) {
ngModules.push(ngModule);
} else if (directive) {
pipesAndDirectives.push(staticSymbol);
} else if (pipe) {
pipesAndDirectives.push(staticSymbol);
}
});
return {ngModules, pipesAndDirectives};
}

View File

@ -0,0 +1,60 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as o from './output_ast';
/**
* Create a new class stmts based on the given data.
*/
export function createClassStmt(config: {
name: string,
parent?: o.Expression,
parentArgs?: o.Expression[],
ctorParams?: o.FnParam[],
builders: ClassBuilderPart | ClassBuilderPart[], modifiers?: o.StmtModifier[]
}): o.ClassStmt {
const parentArgs = config.parentArgs || [];
const superCtorStmts = config.parent ? [o.SUPER_EXPR.callFn(parentArgs).toStmt()] : [];
const builder =
concatClassBuilderParts(Array.isArray(config.builders) ? config.builders : [config.builders]);
const ctor =
new o.ClassMethod(null, config.ctorParams || [], superCtorStmts.concat(builder.ctorStmts));
return new o.ClassStmt(
config.name, config.parent, builder.fields, builder.getters, ctor, builder.methods,
config.modifiers || []);
}
function concatClassBuilderParts(builders: ClassBuilderPart[]) {
return {
fields: [].concat(...builders.map(builder => builder.fields || [])),
methods: [].concat(...builders.map(builder => builder.methods || [])),
getters: [].concat(...builders.map(builder => builder.getters || [])),
ctorStmts: [].concat(...builders.map(builder => builder.ctorStmts || [])),
};
}
/**
* Collects data for a generated class.
*/
export interface ClassBuilderPart {
fields?: o.ClassField[];
methods?: o.ClassMethod[];
getters?: o.ClassGetter[];
ctorStmts?: o.Statement[];
}
/**
* Collects data for a generated class.
*/
export interface ClassBuilder {
fields: o.ClassField[];
methods: o.ClassMethod[];
getters: o.ClassGetter[];
ctorStmts: o.Statement[];
}

View File

@ -7,7 +7,6 @@
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import * as o from './output_ast'; import * as o from './output_ast';
@ -153,13 +152,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
if (isPresent(expr.builtin)) { if (isPresent(expr.builtin)) {
switch (expr.builtin) { switch (expr.builtin) {
case o.BuiltinMethod.ConcatArray: case o.BuiltinMethod.ConcatArray:
result = ListWrapper.concat(receiver, args[0]); result = receiver.concat(...args);
break; break;
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
result = receiver.subscribe({next: args[0]}); result = receiver.subscribe({next: args[0]});
break; break;
case o.BuiltinMethod.Bind: case o.BuiltinMethod.Bind:
result = receiver.bind(args[0]); result = receiver.bind(...args);
break; break;
default: default:
throw new Error(`Unknown builtin method ${expr.builtin}`); throw new Error(`Unknown builtin method ${expr.builtin}`);

View File

@ -35,7 +35,7 @@ export enum ParseErrorLevel {
FATAL FATAL
} }
export abstract class ParseError { export class ParseError {
constructor( constructor(
public span: ParseSourceSpan, public msg: string, public span: ParseSourceSpan, public msg: string,
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {} public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}

View File

@ -8,8 +8,8 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata';
import {ListWrapper, MapWrapper} from './facade/collection'; import {MapWrapper} from './facade/collection';
import {isBlank, isPresent, normalizeBlank} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast'; import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
@ -91,9 +91,9 @@ export class ProviderElementContext {
get transformedDirectiveAsts(): DirectiveAst[] { get transformedDirectiveAsts(): DirectiveAst[] {
var sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier); var sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier);
var sortedDirectives = ListWrapper.clone(this._directiveAsts); var sortedDirectives = this._directiveAsts.slice();
ListWrapper.sort( sortedDirectives.sort(
sortedDirectives, (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) - (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
sortedProviderTypes.indexOf(dir2.directive.type)); sortedProviderTypes.indexOf(dir2.directive.type));
return sortedDirectives; return sortedDirectives;
} }
@ -117,7 +117,7 @@ export class ProviderElementContext {
while (currentEl !== null) { while (currentEl !== null) {
queries = currentEl._contentQueries.get(token.reference); queries = currentEl._contentQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
ListWrapper.addAll(result, queries.filter((query) => query.descendants || distance <= 1)); result.push(...queries.filter((query) => query.descendants || distance <= 1));
} }
if (currentEl._directiveAsts.length > 0) { if (currentEl._directiveAsts.length > 0) {
distance++; distance++;
@ -126,7 +126,7 @@ export class ProviderElementContext {
} }
queries = this.viewContext.viewQueries.get(token.reference); queries = this.viewContext.viewQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
ListWrapper.addAll(result, queries); result.push(...queries);
} }
return result; return result;
} }
@ -194,10 +194,8 @@ export class ProviderElementContext {
eager: boolean = null): CompileDiDependencyMetadata { eager: boolean = null): CompileDiDependencyMetadata {
if (dep.isAttribute) { if (dep.isAttribute) {
var attrValue = this._attrs[dep.token.value]; var attrValue = this._attrs[dep.token.value];
return new CompileDiDependencyMetadata({isValue: true, value: normalizeBlank(attrValue)}); return new CompileDiDependencyMetadata(
} {isValue: true, value: attrValue == null ? null : attrValue});
if (isPresent(dep.query) || isPresent(dep.viewQuery)) {
return dep;
} }
if (isPresent(dep.token)) { if (isPresent(dep.token)) {
@ -489,7 +487,7 @@ function _resolveProviders(
targetProvidersByToken.set(provider.token.reference, resolvedProvider); targetProvidersByToken.set(provider.token.reference, resolvedProvider);
} else { } else {
if (!provider.multi) { if (!provider.multi) {
ListWrapper.clear(resolvedProvider.providers); resolvedProvider.providers.length = 0;
} }
resolvedProvider.providers.push(provider); resolvedProvider.providers.push(provider);
} }
@ -502,11 +500,6 @@ function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQ
if (isPresent(component.viewQueries)) { if (isPresent(component.viewQueries)) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query)); component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
} }
component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
_addQueryToTokenMap(viewQueries, dep.viewQuery);
}
});
return viewQueries; return viewQueries;
} }
@ -517,11 +510,6 @@ function _getContentQueries(directives: CompileDirectiveMetadata[]):
if (isPresent(directive.queries)) { if (isPresent(directive.queries)) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query)); directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
} }
directive.type.diDeps.forEach((dep) => {
if (isPresent(dep.query)) {
_addQueryToTokenMap(contentQueries, dep.query);
}
});
}); });
return contentQueries; return contentQueries;
} }

View File

@ -328,7 +328,12 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
* 'NONE' security context, i.e. that they are safe inert string values. Only specific well known * 'NONE' security context, i.e. that they are safe inert string values. Only specific well known
* attack vectors are assigned their appropriate context. * attack vectors are assigned their appropriate context.
*/ */
securityContext(tagName: string, propName: string): SecurityContext { securityContext(tagName: string, propName: string, isAttribute: boolean): SecurityContext {
if (isAttribute) {
// NB: For security purposes, use the mapped property name, not the attribute name.
propName = this.getMappedPropName(propName);
}
// Make sure comparisons are case insensitive, so that case differences between attribute and // Make sure comparisons are case insensitive, so that case differences between attribute and
// property names do not have a security impact. // property names do not have a security impact.
tagName = tagName.toLowerCase(); tagName = tagName.toLowerCase();
@ -366,4 +371,6 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
return {error: false}; return {error: false};
} }
} }
allKnownElementNames(): string[] { return Object.keys(this._schema); }
} }

View File

@ -6,12 +6,14 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {SchemaMetadata} from '@angular/core'; import {SchemaMetadata, SecurityContext} from '@angular/core';
export abstract class ElementSchemaRegistry { export abstract class ElementSchemaRegistry {
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean; abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
abstract hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean; abstract hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean;
abstract securityContext(tagName: string, propName: string): any; abstract securityContext(elementName: string, propName: string, isAttribute: boolean):
SecurityContext;
abstract allKnownElementNames(): string[];
abstract getMappedPropName(propName: string): string; abstract getMappedPropName(propName: string): string;
abstract getDefaultComponentElementName(): string; abstract getDefaultComponentElementName(): string;
abstract validateProperty(name: string): {error: boolean, msg?: string}; abstract validateProperty(name: string): {error: boolean, msg?: string};

View File

@ -135,12 +135,12 @@ export class SelectorMatcher {
return notMatcher; return notMatcher;
} }
private _elementMap: {[k: string]: SelectorContext[]} = {}; private _elementMap = new Map<string, SelectorContext[]>();
private _elementPartialMap: {[k: string]: SelectorMatcher} = {}; private _elementPartialMap = new Map<string, SelectorMatcher>();
private _classMap: {[k: string]: SelectorContext[]} = {}; private _classMap = new Map<string, SelectorContext[]>();
private _classPartialMap: {[k: string]: SelectorMatcher} = {}; private _classPartialMap = new Map<string, SelectorMatcher>();
private _attrValueMap: {[k: string]: {[k: string]: SelectorContext[]}} = {}; private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
private _attrValuePartialMap: {[k: string]: {[k: string]: SelectorMatcher}} = {}; private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
private _listContexts: SelectorListContext[] = []; private _listContexts: SelectorListContext[] = [];
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) { addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
@ -195,18 +195,18 @@ export class SelectorMatcher {
const value = attrs[i + 1]; const value = attrs[i + 1];
if (isTerminal) { if (isTerminal) {
const terminalMap = matcher._attrValueMap; const terminalMap = matcher._attrValueMap;
let terminalValuesMap = terminalMap[name]; let terminalValuesMap = terminalMap.get(name);
if (!terminalValuesMap) { if (!terminalValuesMap) {
terminalValuesMap = {}; terminalValuesMap = new Map<string, SelectorContext[]>();
terminalMap[name] = terminalValuesMap; terminalMap.set(name, terminalValuesMap);
} }
this._addTerminal(terminalValuesMap, value, selectable); this._addTerminal(terminalValuesMap, value, selectable);
} else { } else {
let partialMap = matcher._attrValuePartialMap; let partialMap = matcher._attrValuePartialMap;
let partialValuesMap = partialMap[name]; let partialValuesMap = partialMap.get(name);
if (!partialValuesMap) { if (!partialValuesMap) {
partialValuesMap = {}; partialValuesMap = new Map<string, SelectorMatcher>();
partialMap[name] = partialValuesMap; partialMap.set(name, partialValuesMap);
} }
matcher = this._addPartial(partialValuesMap, value); matcher = this._addPartial(partialValuesMap, value);
} }
@ -215,20 +215,20 @@ export class SelectorMatcher {
} }
private _addTerminal( private _addTerminal(
map: {[k: string]: SelectorContext[]}, name: string, selectable: SelectorContext) { map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) {
let terminalList = map[name]; let terminalList = map.get(name);
if (!terminalList) { if (!terminalList) {
terminalList = []; terminalList = [];
map[name] = terminalList; map.set(name, terminalList);
} }
terminalList.push(selectable); terminalList.push(selectable);
} }
private _addPartial(map: {[k: string]: SelectorMatcher}, name: string): SelectorMatcher { private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
let matcher = map[name]; let matcher = map.get(name);
if (!matcher) { if (!matcher) {
matcher = new SelectorMatcher(); matcher = new SelectorMatcher();
map[name] = matcher; map.set(name, matcher);
} }
return matcher; return matcher;
} }
@ -270,7 +270,7 @@ export class SelectorMatcher {
const name = attrs[i]; const name = attrs[i];
const value = attrs[i + 1]; const value = attrs[i + 1];
const terminalValuesMap = this._attrValueMap[name]; const terminalValuesMap = this._attrValueMap.get(name);
if (value) { if (value) {
result = result =
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result; this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
@ -278,7 +278,7 @@ export class SelectorMatcher {
result = result =
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result; this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
const partialValuesMap = this._attrValuePartialMap[name]; const partialValuesMap = this._attrValuePartialMap.get(name);
if (value) { if (value) {
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result; result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
} }
@ -291,14 +291,14 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchTerminal( _matchTerminal(
map: {[k: string]: SelectorContext[]}, name: string, cssSelector: CssSelector, map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (!map || typeof name !== 'string') {
return false; return false;
} }
let selectables = map[name]; let selectables = map.get(name);
const starSelectables = map['*']; const starSelectables = map.get('*');
if (starSelectables) { if (starSelectables) {
selectables = selectables.concat(starSelectables); selectables = selectables.concat(starSelectables);
} }
@ -316,13 +316,13 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchPartial( _matchPartial(
map: {[k: string]: SelectorMatcher}, name: string, cssSelector: CssSelector, map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (!map || typeof name !== 'string') {
return false; return false;
} }
const nestedSelector = map[name]; const nestedSelector = map.get(name);
if (!nestedSelector) { if (!nestedSelector) {
return false; return false;
} }

View File

@ -0,0 +1,439 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SecurityContext} from '@angular/core';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, LiteralPrimitive, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
import {mergeNsAndName} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
import {view_utils} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector} from '../selector';
import {splitAtColon, splitAtPeriod} from '../util';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType, VariableAst} from './template_ast';
const PROPERTY_PARTS_SEPARATOR = '.';
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
const STYLE_PREFIX = 'style';
const ANIMATE_PROP_PREFIX = 'animate-';
export enum BoundPropertyType {
DEFAULT,
LITERAL_ATTR,
ANIMATION
}
/**
* Represents a parsed property.
*/
export class BoundProperty {
constructor(
public name: string, public expression: ASTWithSource, public type: BoundPropertyType,
public sourceSpan: ParseSourceSpan) {}
get isLiteral() { return this.type === BoundPropertyType.LITERAL_ATTR; }
get isAnimation() { return this.type === BoundPropertyType.ANIMATION; }
}
/**
* Parses bindings in templates and in the directive host area.
*/
export class BindingParser {
pipesByName: Map<string, CompilePipeMetadata> = new Map();
constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeMetadata[],
private _targetErrors: ParseError[]) {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan):
BoundElementPropertyAst[] {
if (dirMeta.hostProperties) {
const boundProps: BoundProperty[] = [];
Object.keys(dirMeta.hostProperties).forEach(propName => {
const expression = dirMeta.hostProperties[propName];
if (typeof expression === 'string') {
this.parsePropertyBinding(propName, expression, true, sourceSpan, [], boundProps);
} else {
this._reportError(
`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
sourceSpan);
}
});
return boundProps.map((prop) => this.createElementPropertyAst(dirMeta.selector, prop));
}
}
createDirectiveHostEventAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan):
BoundEventAst[] {
if (dirMeta.hostListeners) {
const targetEventAsts: BoundEventAst[] = [];
Object.keys(dirMeta.hostListeners).forEach(propName => {
const expression = dirMeta.hostListeners[propName];
if (typeof expression === 'string') {
this.parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
} else {
this._reportError(
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
sourceSpan);
}
});
return targetEventAsts;
}
}
parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
this._checkPipes(ast, sourceSpan);
if (ast &&
(<Interpolation>ast.ast).expressions.length > view_utils.MAX_INTERPOLATION_VALUES) {
throw new Error(
`Only support at most ${view_utils.MAX_INTERPOLATION_VALUES} interpolation values!`);
}
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
parseInlineTemplateBinding(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[], targetVars: VariableAst[]) {
const bindings = this._parseTemplateBindings(value, sourceSpan);
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
} else if (isPresent(binding.expression)) {
this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
} else {
targetMatchableAttrs.push([binding.key, '']);
this.parseLiteralAttr(binding.key, null, sourceSpan, targetMatchableAttrs, targetProps);
}
}
}
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
const sourceInfo = sourceSpan.start.toString();
try {
const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (isPresent(binding.expression)) {
this._checkPipes(binding.expression, sourceSpan);
}
});
bindingsResult.warnings.forEach(
(warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); });
return bindingsResult.templateBindings;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return [];
}
}
parseLiteralAttr(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]) {
if (_isAnimationLabel(name)) {
name = name.substring(1);
if (value) {
this._reportError(
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
sourceSpan, ParseErrorLevel.FATAL);
}
this._parseAnimation(name, value, sourceSpan, targetMatchableAttrs, targetProps);
} else {
targetProps.push(new BoundProperty(
name, this._exprParser.wrapLiteralPrimitive(value, ''), BoundPropertyType.LITERAL_ATTR,
sourceSpan));
}
}
parsePropertyBinding(
name: string, expression: string, isHost: boolean, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
let isAnimationProp = false;
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
isAnimationProp = true;
name = name.substring(ANIMATE_PROP_PREFIX.length);
} else if (_isAnimationLabel(name)) {
isAnimationProp = true;
name = name.substring(1);
}
if (isAnimationProp) {
this._parseAnimation(name, expression, sourceSpan, targetMatchableAttrs, targetProps);
} else {
this._parsePropertyAst(
name, this._parseBinding(expression, isHost, sourceSpan), sourceSpan,
targetMatchableAttrs, targetProps);
}
}
parsePropertyInterpolation(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]): boolean {
const expr = this.parseInterpolation(value, sourceSpan);
if (isPresent(expr)) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true;
}
return false;
}
private _parsePropertyAst(
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
}
private _parseAnimation(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
// This will occur when a @trigger is not paired with an expression.
// For animations it is valid to not have an expression since */void
// states will be applied by angular when the element is attached/detached
const ast = this._parseBinding(expression || 'null', false, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
}
private _parseBinding(value: string, isHostBinding: boolean, sourceSpan: ParseSourceSpan):
ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = isHostBinding ?
this._exprParser.parseSimpleBinding(value, sourceInfo, this._interpolationConfig) :
this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
createElementPropertyAst(elementSelector: string, boundProp: BoundProperty):
BoundElementPropertyAst {
if (boundProp.isAnimation) {
return new BoundElementPropertyAst(
boundProp.name, PropertyBindingType.Animation, SecurityContext.NONE, false,
boundProp.expression, null, boundProp.sourceSpan);
}
let unit: string = null;
let bindingType: PropertyBindingType;
let boundPropertyName: string;
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
let securityContexts: SecurityContext[];
if (parts.length === 1) {
var partValue = parts[0];
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContexts = calcPossibleSecurityContexts(
this._schemaRegistry, elementSelector, boundPropertyName, false);
bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
} else {
if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1];
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
securityContexts = calcPossibleSecurityContexts(
this._schemaRegistry, elementSelector, boundPropertyName, true);
const nsSeparatorIdx = boundPropertyName.indexOf(':');
if (nsSeparatorIdx > -1) {
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
boundPropertyName = mergeNsAndName(ns, name);
}
bindingType = PropertyBindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Class;
securityContexts = [SecurityContext.NONE];
} else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Style;
securityContexts = [SecurityContext.STYLE];
} else {
this._reportError(`Invalid property name '${boundProp.name}'`, boundProp.sourceSpan);
bindingType = null;
securityContexts = [];
}
}
return new BoundElementPropertyAst(
boundPropertyName, bindingType, securityContexts.length === 1 ? securityContexts[0] : null,
securityContexts.length > 1, boundProp.expression, unit, boundProp.sourceSpan);
}
parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
if (_isAnimationLabel(name)) {
name = name.substr(1);
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
} else {
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
}
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetEvents: BoundEventAst[]) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
if (phase) {
switch (phase) {
case 'start':
case 'done':
const ast = this._parseAction(expression, sourceSpan);
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
break;
default:
this._reportError(
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
sourceSpan);
break;
}
} else {
this._reportError(
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
sourceSpan);
}
}
private _parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null, name]);
const ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
if (ast) {
this._reportExpressionParserErrors(ast.errors, sourceSpan);
}
if (!ast || ast.ast instanceof EmptyExpr) {
this._reportError(`Empty expressions are not allowed`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _reportError(
message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
this._targetErrors.push(new ParseError(sourceSpan, message, level));
}
private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
for (const error of errors) {
this._reportError(error.message, sourceSpan);
}
}
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((pipeName) => {
if (!this.pipesByName.has(pipeName)) {
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
}
});
}
}
/**
* @param propName the name of the property / attribute
* @param sourceSpan
* @param isAttr true when binding to an attribute
* @private
*/
private _validatePropertyOrAttributeName(
propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean): void {
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
this._schemaRegistry.validateProperty(propName);
if (report.error) {
this._reportError(report.msg, sourceSpan, ParseErrorLevel.FATAL);
}
}
}
export class PipeCollector extends RecursiveAstVisitor {
pipes = new Set<string>();
visitPipe(ast: BindingPipe, context: any): any {
this.pipes.add(ast.name);
ast.exp.visit(this);
this.visitAll(ast.args, context);
return null;
}
}
function _isAnimationLabel(name: string): boolean {
return name[0] == '@';
}
export function calcPossibleSecurityContexts(
registry: ElementSchemaRegistry, selector: string, propName: string,
isAttribute: boolean): SecurityContext[] {
const ctxs: SecurityContext[] = [];
CssSelector.parse(selector).forEach((selector) => {
const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
const notElementNames =
new Set(selector.notSelectors.filter(selector => selector.isElementSelector())
.map((selector) => selector.element));
const possibleElementNames =
elementNames.filter(elementName => !notElementNames.has(elementName));
ctxs.push(...possibleElementNames.map(
elementName => registry.securityContext(elementName, propName, isAttribute)));
});
return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
}

View File

@ -63,8 +63,8 @@ export class AttrAst implements TemplateAst {
export class BoundElementPropertyAst implements TemplateAst { export class BoundElementPropertyAst implements TemplateAst {
constructor( constructor(
public name: string, public type: PropertyBindingType, public name: string, public type: PropertyBindingType,
public securityContext: SecurityContext, public value: AST, public unit: string, public securityContext: SecurityContext, public needsRuntimeSecurityContext: boolean,
public sourceSpan: ParseSourceSpan) {} public value: AST, public unit: string, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context); return visitor.visitElementProperty(this, context);
} }
@ -76,19 +76,23 @@ export class BoundElementPropertyAst implements TemplateAst {
* `(@trigger.phase)="callback($event)"`). * `(@trigger.phase)="callback($event)"`).
*/ */
export class BoundEventAst implements TemplateAst { export class BoundEventAst implements TemplateAst {
static calcFullName(name: string, target: string, phase: string): string {
if (target) {
return `${target}:${name}`;
} else if (phase) {
return `@${name}.${phase}`;
} else {
return name;
}
}
constructor( constructor(
public name: string, public target: string, public phase: string, public handler: AST, public name: string, public target: string, public phase: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context); return visitor.visitEvent(this, context);
} }
get fullName() { get fullName() { return BoundEventAst.calcFullName(this.name, this.target, this.phase); }
if (this.target) {
return `${this.target}:${this.name}`;
} else {
return this.name;
}
}
get isAnimation(): boolean { return !!this.phase; } get isAnimation(): boolean { return !!this.phase; }
} }

View File

@ -25,8 +25,8 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector'; import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver'; import {isStyleUrlResolvable} from '../style_url_resolver';
import {splitAtColon, splitAtPeriod} from '../util';
import {BindingParser, BoundProperty} from './binding_parser';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser'; import {PreparsedElementType, preparseElement} from './template_preparser';
@ -56,17 +56,11 @@ const IDENT_BANANA_BOX_IDX = 8;
const IDENT_PROPERTY_IDX = 9; const IDENT_PROPERTY_IDX = 9;
const IDENT_EVENT_IDX = 10; const IDENT_EVENT_IDX = 10;
const ANIMATE_PROP_PREFIX = 'animate-';
const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ELEMENT = 'template';
const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*'; const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const PROPERTY_PARTS_SEPARATOR = '.';
const ATTRIBUTE_PREFIX = 'attr';
const CLASS_PREFIX = 'class';
const STYLE_PREFIX = 'style';
const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
/** /**
@ -135,11 +129,20 @@ export class TemplateParser {
const uniqPipes = removeIdentifierDuplicates(pipes); const uniqPipes = removeIdentifierDuplicates(pipes);
const providerViewContext = const providerViewContext =
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan); new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
let interpolationConfig: InterpolationConfig;
if (component.template && component.template.interpolation) {
interpolationConfig = {
start: component.template.interpolation[0],
end: component.template.interpolation[1]
};
}
const bindingParser = new BindingParser(
this._exprParser, interpolationConfig, this._schemaRegistry, uniqPipes, errors);
const parseVisitor = new TemplateParseVisitor( const parseVisitor = new TemplateParseVisitor(
providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser, providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry, schemas,
this._schemaRegistry); errors);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...parseVisitor.errors, ...providerViewContext.errors); errors.push(...providerViewContext.errors);
} else { } else {
result = []; result = [];
} }
@ -197,130 +200,18 @@ export class TemplateParser {
class TemplateParseVisitor implements html.Visitor { class TemplateParseVisitor implements html.Visitor {
selectorMatcher = new SelectorMatcher(); selectorMatcher = new SelectorMatcher();
errors: TemplateParseError[] = [];
directivesIndex = new Map<CompileDirectiveMetadata, number>(); directivesIndex = new Map<CompileDirectiveMetadata, number>();
ngContentCount: number = 0; ngContentCount: number = 0;
pipesByName: Map<string, CompilePipeMetadata> = new Map();
private _interpolationConfig: InterpolationConfig;
constructor( constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[], public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[],
pipes: CompilePipeMetadata[], private _schemas: SchemaMetadata[], private _exprParser: Parser, private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
private _schemaRegistry: ElementSchemaRegistry) { private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
const tempMeta = providerViewContext.component.template;
if (tempMeta && tempMeta.interpolation) {
this._interpolationConfig = {
start: tempMeta.interpolation[0],
end: tempMeta.interpolation[1]
};
}
directives.forEach((directive: CompileDirectiveMetadata, index: number) => { directives.forEach((directive: CompileDirectiveMetadata, index: number) => {
const selector = CssSelector.parse(directive.selector); const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index); this.directivesIndex.set(directive, index);
}); });
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
}
private _reportError(
message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
this.errors.push(new TemplateParseError(message, sourceSpan, level));
}
private _reportParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
for (const error of errors) {
this._reportError(error.message, sourceSpan);
}
}
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
if (ast) this._reportParserErrors(ast.errors, sourceSpan);
this._checkPipes(ast, sourceSpan);
if (isPresent(ast) &&
(<Interpolation>ast.ast).expressions.length > view_utils.MAX_INTERPOLATION_VALUES) {
throw new Error(
`Only support at most ${view_utils.MAX_INTERPOLATION_VALUES} interpolation values!`);
}
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
if (ast) {
this._reportParserErrors(ast.errors, sourceSpan);
}
if (!ast || ast.ast instanceof EmptyExpr) {
this._reportError(`Empty expressions are not allowed`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
if (ast) this._reportParserErrors(ast.errors, sourceSpan);
this._checkPipes(ast, sourceSpan);
return ast;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
}
}
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
const sourceInfo = sourceSpan.start.toString();
try {
const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
this._reportParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (isPresent(binding.expression)) {
this._checkPipes(binding.expression, sourceSpan);
}
});
bindingsResult.warnings.forEach(
(warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); });
return bindingsResult.templateBindings;
} catch (e) {
this._reportError(`${e}`, sourceSpan);
return [];
}
}
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) {
const collector = new PipeCollector();
ast.visit(collector);
collector.pipes.forEach((pipeName) => {
if (!this.pipesByName.has(pipeName)) {
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
}
});
}
} }
visitExpansion(expansion: html.Expansion, context: any): any { return null; } visitExpansion(expansion: html.Expansion, context: any): any { return null; }
@ -329,7 +220,7 @@ class TemplateParseVisitor implements html.Visitor {
visitText(text: html.Text, parent: ElementContext): any { visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR); const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._parseInterpolation(text.value, text.sourceSpan); const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
if (isPresent(expr)) { if (isPresent(expr)) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan); return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else { } else {
@ -361,13 +252,12 @@ class TemplateParseVisitor implements html.Visitor {
} }
const matchableAttrs: string[][] = []; const matchableAttrs: string[][] = [];
const elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = []; const elementOrDirectiveProps: BoundProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = []; const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = []; const elementVars: VariableAst[] = [];
const animationProps: BoundElementPropertyAst[] = [];
const events: BoundEventAst[] = []; const events: BoundEventAst[] = [];
const templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = []; const templateElementOrDirectiveProps: BoundProperty[] = [];
const templateMatchableAttrs: string[][] = []; const templateMatchableAttrs: string[][] = [];
const templateElementVars: VariableAst[] = []; const templateElementVars: VariableAst[] = [];
@ -378,16 +268,27 @@ class TemplateParseVisitor implements html.Visitor {
element.attrs.forEach(attr => { element.attrs.forEach(attr => {
const hasBinding = this._parseAttr( const hasBinding = this._parseAttr(
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, animationProps, events, isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars); elementOrDirectiveRefs, elementVars);
const hasTemplateBinding = this._parseInlineTemplateBinding( let templateBindingsSource: string;
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars); if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
if (hasTemplateBinding && hasInlineTemplates) { } else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
this._reportError( const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`, templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
attr.sourceSpan); }
const hasTemplateBinding = isPresent(templateBindingsSource);
if (hasTemplateBinding) {
if (hasInlineTemplates) {
this._reportError(
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`,
attr.sourceSpan);
}
hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding(
attr.name, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars);
} }
if (!hasBinding && !hasTemplateBinding) { if (!hasBinding && !hasTemplateBinding) {
@ -395,10 +296,6 @@ class TemplateParseVisitor implements html.Visitor {
attrs.push(this.visitAttribute(attr, null)); attrs.push(this.visitAttribute(attr, null));
matchableAttrs.push([attr.name, attr.value]); matchableAttrs.push([attr.name, attr.value]);
} }
if (hasTemplateBinding) {
hasInlineTemplates = true;
}
}); });
const elementCssSelector = createElementCssSelector(nodeName, matchableAttrs); const elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
@ -409,8 +306,7 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps, isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references); elementOrDirectiveRefs, element.sourceSpan, references);
const elementProps: BoundElementPropertyAst[] = const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts) this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
.concat(animationProps);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates; const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext( const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs, this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
@ -519,39 +415,9 @@ class TemplateParseVisitor implements html.Visitor {
}); });
} }
private _parseInlineTemplateBinding(
attr: html.Attribute, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean {
let templateBindingsSource: string = null;
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
}
if (isPresent(templateBindingsSource)) {
const bindings = this._parseTemplateBindings(templateBindingsSource, attr.sourceSpan);
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, attr.sourceSpan));
} else if (isPresent(binding.expression)) {
this._parsePropertyAst(
binding.key, binding.expression, attr.sourceSpan, targetMatchableAttrs, targetProps);
} else {
targetMatchableAttrs.push([binding.key, '']);
this._parseLiteralAttr(binding.key, null, attr.sourceSpan, targetProps);
}
}
return true;
}
return false;
}
private _parseAttr( private _parseAttr(
isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][], isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[], targetProps: BoundProperty[], targetEvents: BoundEventAst[],
targetAnimationProps: BoundElementPropertyAst[], targetEvents: BoundEventAst[],
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean { targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
const name = this._normalizeAttributeName(attr.name); const name = this._normalizeAttributeName(attr.name);
const value = attr.value; const value = attr.value;
@ -563,9 +429,8 @@ class TemplateParseVisitor implements html.Visitor {
if (bindParts !== null) { if (bindParts !== null) {
hasBinding = true; hasBinding = true;
if (isPresent(bindParts[KW_BIND_IDX])) { if (isPresent(bindParts[KW_BIND_IDX])) {
this._parsePropertyOrAnimation( this._bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps, bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
targetAnimationProps);
} else if (bindParts[KW_LET_IDX]) { } else if (bindParts[KW_LET_IDX]) {
if (isTemplateElement) { if (isTemplateElement) {
@ -580,48 +445,42 @@ class TemplateParseVisitor implements html.Visitor {
this._parseReference(identifier, value, srcSpan, targetRefs); this._parseReference(identifier, value, srcSpan, targetRefs);
} else if (bindParts[KW_ON_IDX]) { } else if (bindParts[KW_ON_IDX]) {
this._parseEventOrAnimationEvent( this._bindingParser.parseEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_BINDON_IDX]) { } else if (bindParts[KW_BINDON_IDX]) {
this._parsePropertyOrAnimation( this._bindingParser.parsePropertyBinding(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps, bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
targetAnimationProps);
this._parseAssignmentEvent( this._parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_AT_IDX]) { } else if (bindParts[KW_AT_IDX]) {
if (_isAnimationLabel(name) && isPresent(value) && value.length > 0) { this._bindingParser.parseLiteralAttr(
this._reportError( name, value, srcSpan, targetMatchableAttrs, targetProps);
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
srcSpan, ParseErrorLevel.FATAL);
}
this._parseAnimation(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetAnimationProps);
} else if (bindParts[IDENT_BANANA_BOX_IDX]) { } else if (bindParts[IDENT_BANANA_BOX_IDX]) {
this._parsePropertyOrAnimation( this._bindingParser.parsePropertyBinding(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetProps, bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, targetMatchableAttrs,
targetAnimationProps); targetProps);
this._parseAssignmentEvent( this._parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) { } else if (bindParts[IDENT_PROPERTY_IDX]) {
this._parsePropertyOrAnimation( this._bindingParser.parsePropertyBinding(
bindParts[IDENT_PROPERTY_IDX], value, srcSpan, targetMatchableAttrs, targetProps, bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, targetMatchableAttrs,
targetAnimationProps); targetProps);
} else if (bindParts[IDENT_EVENT_IDX]) { } else if (bindParts[IDENT_EVENT_IDX]) {
this._parseEventOrAnimationEvent( this._bindingParser.parseEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} }
} else { } else {
hasBinding = hasBinding = this._bindingParser.parsePropertyInterpolation(
this._parsePropertyInterpolation(name, value, srcSpan, targetMatchableAttrs, targetProps); name, value, srcSpan, targetMatchableAttrs, targetProps);
} }
if (!hasBinding) { if (!hasBinding) {
this._parseLiteralAttr(name, value, srcSpan, targetProps); this._bindingParser.parseLiteralAttr(name, value, srcSpan, targetMatchableAttrs, targetProps);
} }
return hasBinding; return hasBinding;
@ -650,127 +509,13 @@ class TemplateParseVisitor implements html.Visitor {
targetRefs.push(new ElementOrDirectiveRef(identifier, value, sourceSpan)); targetRefs.push(new ElementOrDirectiveRef(identifier, value, sourceSpan));
} }
private _parsePropertyOrAnimation(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[],
targetAnimationProps: BoundElementPropertyAst[]) {
const animatePropLength = ANIMATE_PROP_PREFIX.length;
var isAnimationProp = _isAnimationLabel(name);
var animationPrefixLength = 1;
if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) {
isAnimationProp = true;
animationPrefixLength = animatePropLength;
}
if (isAnimationProp) {
this._parseAnimation(
name.substr(animationPrefixLength), expression, sourceSpan, targetMatchableAttrs,
targetAnimationProps);
} else {
this._parsePropertyAst(
name, this._parseBinding(expression, sourceSpan), sourceSpan, targetMatchableAttrs,
targetProps);
}
}
private _parseAnimation(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetAnimationProps: BoundElementPropertyAst[]) {
// This will occur when a @trigger is not paired with an expression.
// For animations it is valid to not have an expression since */void
// states will be applied by angular when the element is attached/detached
if (!isPresent(expression) || expression.length == 0) {
expression = 'null';
}
const ast = this._parseBinding(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetAnimationProps.push(new BoundElementPropertyAst(
name, PropertyBindingType.Animation, SecurityContext.NONE, ast, null, sourceSpan));
}
private _parsePropertyInterpolation(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundElementOrDirectiveProperty[]): boolean {
const expr = this._parseInterpolation(value, sourceSpan);
if (isPresent(expr)) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true;
}
return false;
}
private _parsePropertyAst(
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[]) {
targetMatchableAttrs.push([name, ast.source]);
targetProps.push(new BoundElementOrDirectiveProperty(name, ast, false, sourceSpan));
}
private _parseAssignmentEvent( private _parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) { targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
this._parseEventOrAnimationEvent( this._bindingParser.parseEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents); `${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
} }
private _parseEventOrAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
if (_isAnimationLabel(name)) {
name = name.substr(1);
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
} else {
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
}
}
private _parseAnimationEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetEvents: BoundEventAst[]) {
const matches = splitAtPeriod(name, [name, '']);
const eventName = matches[0];
const phase = matches[1].toLowerCase();
if (phase) {
switch (phase) {
case 'start':
case 'done':
const ast = this._parseAction(expression, sourceSpan);
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
break;
default:
this._reportError(
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
sourceSpan);
break;
}
} else {
this._reportError(
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
sourceSpan);
}
}
private _parseEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null, name]);
const ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
}
private _parseLiteralAttr(
name: string, value: string, sourceSpan: ParseSourceSpan,
targetProps: BoundElementOrDirectiveProperty[]) {
targetProps.push(new BoundElementOrDirectiveProperty(
name, this._exprParser.wrapLiteralPrimitive(value, ''), true, sourceSpan));
}
private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector): private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector):
{directives: CompileDirectiveMetadata[], matchElement: boolean} { {directives: CompileDirectiveMetadata[], matchElement: boolean} {
// Need to sort the directives so that we get consistent results throughout, // Need to sort the directives so that we get consistent results throughout,
@ -793,7 +538,7 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectiveAsts( private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[], isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[],
props: BoundElementOrDirectiveProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[], props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>(); const matchedReferences = new Set<string>();
let component: CompileDirectiveMetadata = null; let component: CompileDirectiveMetadata = null;
@ -803,12 +548,13 @@ class TemplateParseVisitor implements html.Visitor {
if (directive.isComponent) { if (directive.isComponent) {
component = directive; component = directive;
} }
const hostProperties: BoundElementPropertyAst[] = [];
const hostEvents: BoundEventAst[] = [];
const directiveProperties: BoundDirectivePropertyAst[] = []; const directiveProperties: BoundDirectivePropertyAst[] = [];
this._createDirectiveHostPropertyAsts( const hostProperties =
elementName, directive.hostProperties, sourceSpan, hostProperties); this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
this._createDirectiveHostEventAsts(directive.hostListeners, sourceSpan, hostEvents); // Note: We need to check the host properties here as well,
// as we don't know the element name in the DirectiveWrapperCompiler yet.
this._checkPropertiesInSchema(elementName, hostProperties);
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties); this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
elementOrDirectiveRefs.forEach((elOrDirRef) => { elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) || if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
@ -839,47 +585,11 @@ class TemplateParseVisitor implements html.Visitor {
return directiveAsts; return directiveAsts;
} }
private _createDirectiveHostPropertyAsts(
elementName: string, hostProps: {[key: string]: string}, sourceSpan: ParseSourceSpan,
targetPropertyAsts: BoundElementPropertyAst[]) {
if (hostProps) {
Object.keys(hostProps).forEach(propName => {
const expression = hostProps[propName];
if (typeof expression === 'string') {
const exprAst = this._parseBinding(expression, sourceSpan);
targetPropertyAsts.push(
this._createElementPropertyAst(elementName, propName, exprAst, sourceSpan));
} else {
this._reportError(
`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
sourceSpan);
}
});
}
}
private _createDirectiveHostEventAsts(
hostListeners: {[key: string]: string}, sourceSpan: ParseSourceSpan,
targetEventAsts: BoundEventAst[]) {
if (hostListeners) {
Object.keys(hostListeners).forEach(propName => {
const expression = hostListeners[propName];
if (typeof expression === 'string') {
this._parseEventOrAnimationEvent(propName, expression, sourceSpan, [], targetEventAsts);
} else {
this._reportError(
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
sourceSpan);
}
});
}
}
private _createDirectivePropertyAsts( private _createDirectivePropertyAsts(
directiveProperties: {[key: string]: string}, boundProps: BoundElementOrDirectiveProperty[], directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (directiveProperties) { if (directiveProperties) {
const boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>(); const boundPropsByName = new Map<string, BoundProperty>();
boundProps.forEach(boundProp => { boundProps.forEach(boundProp => {
const prevValue = boundPropsByName.get(boundProp.name); const prevValue = boundPropsByName.get(boundProp.name);
if (!prevValue || prevValue.isLiteral) { if (!prevValue || prevValue.isLiteral) {
@ -902,7 +612,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
private _createElementPropertyAsts( private _createElementPropertyAsts(
elementName: string, props: BoundElementOrDirectiveProperty[], elementName: string, props: BoundProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] { directives: DirectiveAst[]): BoundElementPropertyAst[] {
const boundElementProps: BoundElementPropertyAst[] = []; const boundElementProps: BoundElementPropertyAst[] = [];
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>(); const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
@ -913,98 +623,15 @@ class TemplateParseVisitor implements html.Visitor {
}); });
}); });
props.forEach((prop: BoundElementOrDirectiveProperty) => { props.forEach((prop: BoundProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) { if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
boundElementProps.push(this._createElementPropertyAst( boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
elementName, prop.name, prop.expression, prop.sourceSpan));
} }
}); });
this._checkPropertiesInSchema(elementName, boundElementProps);
return boundElementProps; return boundElementProps;
} }
private _createElementPropertyAst(
elementName: string, name: string, ast: AST,
sourceSpan: ParseSourceSpan): BoundElementPropertyAst {
let unit: string = null;
let bindingType: PropertyBindingType;
let boundPropertyName: string;
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
let securityContext: SecurityContext;
if (parts.length === 1) {
var partValue = parts[0];
if (_isAnimationLabel(partValue)) {
boundPropertyName = partValue.substr(1);
bindingType = PropertyBindingType.Animation;
securityContext = SecurityContext.NONE;
} else {
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
bindingType = PropertyBindingType.Property;
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, false);
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) {
let errorMsg =
`Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`;
if (elementName.indexOf('-') > -1) {
errorMsg +=
`\n1. If '${elementName}' is an Angular component and it has '${boundPropertyName}' input, then verify that it is part of this module.` +
`\n2. If '${elementName}' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.\n`;
}
this._reportError(errorMsg, sourceSpan);
}
}
} else {
if (parts[0] == ATTRIBUTE_PREFIX) {
boundPropertyName = parts[1];
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, true);
// NB: For security purposes, use the mapped property name, not the attribute name.
const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName);
securityContext = this._schemaRegistry.securityContext(elementName, mapPropName);
const nsSeparatorIdx = boundPropertyName.indexOf(':');
if (nsSeparatorIdx > -1) {
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
boundPropertyName = mergeNsAndName(ns, name);
}
bindingType = PropertyBindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Class;
securityContext = SecurityContext.NONE;
} else if (parts[0] == STYLE_PREFIX) {
unit = parts.length > 2 ? parts[2] : null;
boundPropertyName = parts[1];
bindingType = PropertyBindingType.Style;
securityContext = SecurityContext.STYLE;
} else {
this._reportError(`Invalid property name '${name}'`, sourceSpan);
bindingType = null;
securityContext = null;
}
}
return new BoundElementPropertyAst(
boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan);
}
/**
* @param propName the name of the property / attribute
* @param sourceSpan
* @param isAttr true when binding to an attribute
* @private
*/
private _validatePropertyOrAttributeName(
propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean): void {
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
this._schemaRegistry.validateProperty(propName);
if (report.error) {
this._reportError(report.msg, sourceSpan, ParseErrorLevel.FATAL);
}
}
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] { private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
return directives.filter(directive => directive.directive.isComponent); return directives.filter(directive => directive.directive.isComponent);
} }
@ -1017,7 +644,11 @@ class TemplateParseVisitor implements html.Visitor {
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) { private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
const componentTypeNames = this._findComponentDirectiveNames(directives); const componentTypeNames = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 1) { if (componentTypeNames.length > 1) {
this._reportError(`More than one component: ${componentTypeNames.join(',')}`, sourceSpan); this._reportError(
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: ${componentTypeNames.join(',')}`,
sourceSpan);
} }
} }
@ -1075,6 +706,28 @@ class TemplateParseVisitor implements html.Visitor {
} }
}); });
} }
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) {
boundProps.forEach((boundProp) => {
if (boundProp.type === PropertyBindingType.Property &&
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
let errorMsg =
`Can't bind to '${boundProp.name}' since it isn't a known property of '${elementName}'.`;
if (elementName.indexOf('-') > -1) {
errorMsg +=
`\n1. If '${elementName}' is an Angular component and it has '${boundProp.name}' input, then verify that it is part of this module.` +
`\n2. If '${elementName}' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.\n`;
}
this._reportError(errorMsg, boundProp.sourceSpan);
}
});
}
private _reportError(
message: string, sourceSpan: ParseSourceSpan,
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
this._targetErrors.push(new ParseError(sourceSpan, message, level));
}
} }
class NonBindableVisitor implements html.Visitor { class NonBindableVisitor implements html.Visitor {
@ -1113,12 +766,6 @@ class NonBindableVisitor implements html.Visitor {
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; } visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; }
} }
class BoundElementOrDirectiveProperty {
constructor(
public name: string, public expression: AST, public isLiteral: boolean,
public sourceSpan: ParseSourceSpan) {}
}
class ElementOrDirectiveRef { class ElementOrDirectiveRef {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
} }
@ -1186,21 +833,6 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][
const EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null); const EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null);
const NON_BINDABLE_VISITOR = new NonBindableVisitor(); const NON_BINDABLE_VISITOR = new NonBindableVisitor();
export class PipeCollector extends RecursiveAstVisitor {
pipes: Set<string> = new Set<string>();
visitPipe(ast: BindingPipe, context: any): any {
this.pipes.add(ast.name);
ast.exp.visit(this);
this.visitAll(ast.args, context);
return null;
}
}
function _isAnimationLabel(name: string): boolean {
return name[0] == '@';
}
function _isEmptyTextNode(node: html.Node): boolean { function _isEmptyTextNode(node: html.Node): boolean {
return node instanceof html.Text && node.value.trim().length == 0; return node instanceof html.Text && node.value.trim().length == 0;
} }

View File

@ -6,9 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileTokenMetadata} from './compile_metadata'; import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
import {isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
import * as o from './output/output_ast';
export const MODULE_SUFFIX = ''; export const MODULE_SUFFIX = '';
@ -72,25 +70,6 @@ export class ValueTransformer implements ValueVisitor {
visitOther(value: any, context: any): any { return value; } visitOther(value: any, context: any): any { return value; }
} }
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
if (path == null) {
return `asset:@angular/lib/${pkg}/index`;
} else {
return `asset:@angular/lib/${pkg}/src/${path}`;
}
}
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (isPresent(token.value)) {
return o.literal(token.value);
} else if (token.identifierIsInstance) {
return o.importExpr(token.identifier)
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
} else {
return o.importExpr(token.identifier);
}
}
export class SyncAsyncResult<T> { export class SyncAsyncResult<T> {
constructor(public syncResult: T, public asyncResult: Promise<T> = null) { constructor(public syncResult: T, public asyncResult: Promise<T> = null) {
if (!asyncResult) { if (!asyncResult) {

View File

@ -1,15 +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 {TemplateAst} from '../template_parser/template_ast';
import {CompileNode} from './compile_element';
export class CompileBinding {
constructor(public node: CompileNode, public sourceAst: TemplateAst) {}
}

View File

@ -8,18 +8,18 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {ListWrapper, MapWrapper} from '../facade/collection'; import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util'; import {convertValueToOutputAst} from '../output/value_util';
import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast'; import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {CompileView} from './compile_view'; import {CompileView, CompileViewRootNode} from './compile_view';
import {InjectMethodVars, ViewProperties} from './constants'; import {InjectMethodVars, ViewProperties} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps';
import {getPropertyInView, injectFromViewParentInjector} from './util'; import {getPropertyInView, injectFromViewParentInjector} from './util';
@ -49,9 +49,8 @@ export class CompileElement extends CompileNode {
private _queryCount = 0; private _queryCount = 0;
private _queries = new Map<any, CompileQuery[]>(); private _queries = new Map<any, CompileQuery[]>();
private _componentConstructorViewQueryLists: o.Expression[] = [];
public contentNodesByNgContentIndex: Array<o.Expression>[] = null; public contentNodesByNgContentIndex: Array<CompileViewRootNode>[] = null;
public embeddedView: CompileView; public embeddedView: CompileView;
public referenceTokens: {[key: string]: CompileTokenMetadata}; public referenceTokens: {[key: string]: CompileTokenMetadata};
@ -98,6 +97,9 @@ export class CompileElement extends CompileNode {
this.view.createMethod.addStmt(statement); this.view.createMethod.addStmt(statement);
this.appElement = o.THIS_EXPR.prop(fieldName); this.appElement = o.THIS_EXPR.prop(fieldName);
this.instances.set(resolveIdentifierToken(Identifiers.AppElement).reference, this.appElement); this.instances.set(resolveIdentifierToken(Identifiers.AppElement).reference, this.appElement);
if (this.hasViewContainer) {
this.view.viewContainerAppElements.push(this.appElement);
}
} }
private _createComponentFactoryResolver() { private _createComponentFactoryResolver() {
@ -188,8 +190,7 @@ export class CompileElement extends CompileNode {
{name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)}); {name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)});
this._targetDependencies.push( this._targetDependencies.push(
new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier)); new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier));
return o.importExpr(directiveWrapperIdentifier) return DirectiveWrapperExpressions.create(directiveWrapperIdentifier, depsExpr);
.instantiate(depsExpr, o.importType(directiveWrapperIdentifier));
} else { } else {
return o.importExpr(provider.useClass) return o.importExpr(provider.useClass)
.instantiate(depsExpr, o.importType(provider.useClass)); .instantiate(depsExpr, o.importType(provider.useClass));
@ -204,7 +205,8 @@ export class CompileElement extends CompileNode {
resolvedProvider.eager, this); resolvedProvider.eager, this);
if (isDirectiveWrapper) { if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance); this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance);
this.instances.set(resolvedProvider.token.reference, instance.prop('context')); this.instances.set(
resolvedProvider.token.reference, DirectiveWrapperExpressions.context(instance));
} else { } else {
this.instances.set(resolvedProvider.token.reference, instance); this.instances.set(resolvedProvider.token.reference, instance);
} }
@ -218,9 +220,8 @@ export class CompileElement extends CompileNode {
var queriesWithReads: _QueryWithRead[] = []; var queriesWithReads: _QueryWithRead[] = [];
MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => { MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => {
var queriesForProvider = this._getQueriesFor(resolvedProvider.token); var queriesForProvider = this._getQueriesFor(resolvedProvider.token);
ListWrapper.addAll( queriesWithReads.push(
queriesWithReads, ...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
}); });
Object.keys(this.referenceTokens).forEach(varName => { Object.keys(this.referenceTokens).forEach(varName => {
var token = this.referenceTokens[varName]; var token = this.referenceTokens[varName];
@ -232,9 +233,8 @@ export class CompileElement extends CompileNode {
} }
this.view.locals.set(varName, varValue); this.view.locals.set(varName, varValue);
var varToken = new CompileTokenMetadata({value: varName}); var varToken = new CompileTokenMetadata({value: varName});
ListWrapper.addAll( queriesWithReads.push(
queriesWithReads, ...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
}); });
queriesWithReads.forEach((queryWithRead) => { queriesWithReads.forEach((queryWithRead) => {
var value: o.Expression; var value: o.Expression;
@ -256,16 +256,9 @@ export class CompileElement extends CompileNode {
}); });
if (isPresent(this.component)) { if (isPresent(this.component)) {
var componentConstructorViewQueryList = isPresent(this.component) ?
o.literalArr(this._componentConstructorViewQueryLists) :
o.NULL_EXPR;
var compExpr = isPresent(this.getComponent()) ? this.getComponent() : o.NULL_EXPR; var compExpr = isPresent(this.getComponent()) ? this.getComponent() : o.NULL_EXPR;
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
this.appElement this.appElement.callMethod('initComponent', [compExpr, this._compViewExpr]).toStmt());
.callMethod(
'initComponent',
[compExpr, componentConstructorViewQueryList, this._compViewExpr])
.toStmt());
} }
} }
@ -292,7 +285,7 @@ export class CompileElement extends CompileNode {
this.view.createMethod, this.view.updateContentQueriesMethod))); this.view.createMethod, this.view.updateContentQueriesMethod)));
} }
addContentNode(ngContentIndex: number, nodeExpr: o.Expression) { addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr); this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr);
} }
@ -315,8 +308,7 @@ export class CompileElement extends CompileNode {
while (!currentEl.isNull()) { while (!currentEl.isNull()) {
queries = currentEl._queries.get(token.reference); queries = currentEl._queries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
ListWrapper.addAll( result.push(...queries.filter((query) => query.meta.descendants || distance <= 1));
result, queries.filter((query) => query.meta.descendants || distance <= 1));
} }
if (currentEl._directives.length > 0) { if (currentEl._directives.length > 0) {
distance++; distance++;
@ -325,7 +317,7 @@ export class CompileElement extends CompileNode {
} }
queries = this.view.componentView.viewQueries.get(token.reference); queries = this.view.componentView.viewQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
ListWrapper.addAll(result, queries); result.push(...queries);
} }
return result; return result;
} }
@ -342,20 +334,6 @@ export class CompileElement extends CompileNode {
private _getLocalDependency( private _getLocalDependency(
requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression {
var result: o.Expression = null; var result: o.Expression = null;
// constructor content query
if (!result && isPresent(dep.query)) {
result = this._addQuery(dep.query, null).queryList;
}
// constructor view query
if (!result && isPresent(dep.viewQuery)) {
result = createQueryList(
dep.viewQuery, null,
`_viewQuery_${dep.viewQuery.selectors[0].name}_${this.nodeIndex}_${this._componentConstructorViewQueryLists.length}`,
this.view);
this._componentConstructorViewQueryLists.push(result);
}
if (isPresent(dep.token)) { if (isPresent(dep.token)) {
// access builtins with special visibility // access builtins with special visibility
if (!result) { if (!result) {

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
@ -66,6 +65,8 @@ export class CompileMethod {
this._newState = new _DebugState(nodeIndex, templateAst); this._newState = new _DebugState(nodeIndex, templateAst);
} }
push(...stmts: o.Statement[]) { this.addStmts(stmts); }
addStmt(stmt: o.Statement) { addStmt(stmt: o.Statement) {
this._updateDebugContextIfNeeded(); this._updateDebugContextIfNeeded();
this._bodyStatements.push(stmt); this._bodyStatements.push(stmt);
@ -73,7 +74,7 @@ export class CompileMethod {
addStmts(stmts: o.Statement[]) { addStmts(stmts: o.Statement[]) {
this._updateDebugContextIfNeeded(); this._updateDebugContextIfNeeded();
ListWrapper.addAll(this._bodyStatements, stmts); this._bodyStatements.push(...stmts);
} }
finish(): o.Statement[] { return this._bodyStatements; } finish(): o.Statement[] { return this._bodyStatements; }

View File

@ -8,11 +8,12 @@
import {CompilePipeMetadata} from '../compile_metadata'; import {CompilePipeMetadata} from '../compile_metadata';
import {createPureProxy} from '../compiler_util/identifier_util';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {createPureProxy, getPropertyInView, injectFromViewParentInjector} from './util'; import {getPropertyInView, injectFromViewParentInjector} from './util';
export class CompilePipe { export class CompilePipe {
static call(view: CompileView, name: string, args: o.Expression[]): o.Expression { static call(view: CompileView, name: string, args: o.Expression[]): o.Expression {
@ -65,7 +66,8 @@ export class CompilePipe {
createPureProxy( createPureProxy(
pipeInstanceSeenFromPureProxy.prop('transform') pipeInstanceSeenFromPureProxy.prop('transform')
.callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]), .callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]),
args.length, purePipeProxyInstance, callingView); args.length, purePipeProxyInstance,
{fields: callingView.fields, ctorStmts: callingView.createMethod});
return o.importExpr(resolveIdentifier(Identifiers.castByValue)) return o.importExpr(resolveIdentifier(Identifiers.castByValue))
.callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')]) .callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')])
.callFn(args); .callFn(args);

View File

@ -8,33 +8,46 @@
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata';
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
import {createPureProxy} from '../compiler_util/identifier_util';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {ListWrapper, MapWrapper} from '../facade/collection'; import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
import {CompileBinding} from './compile_binding';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompilePipe} from './compile_pipe'; import {CompilePipe} from './compile_pipe';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {EventHandlerVars} from './constants'; import {getPropertyInView, getViewFactoryName} from './util';
import {NameResolver} from './expression_converter';
import {createPureProxy, getPropertyInView, getViewFactoryName} from './util'; export enum CompileViewRootNodeType {
Node,
ViewContainer,
NgContent
}
export class CompileViewRootNode {
constructor(
public type: CompileViewRootNodeType, public expr: o.Expression,
public ngContentIndex?: number) {}
}
export class CompileView implements NameResolver { export class CompileView implements NameResolver {
public viewType: ViewType; public viewType: ViewType;
public viewQueries: Map<any, CompileQuery[]>; public viewQueries: Map<any, CompileQuery[]>;
public viewChildren: o.Expression[] = [];
public nodes: CompileNode[] = []; public nodes: CompileNode[] = [];
// root nodes or AppElements for ViewContainers
public rootNodesOrAppElements: o.Expression[] = [];
public bindings: CompileBinding[] = []; public rootNodes: CompileViewRootNode[] = [];
public lastRenderNode: o.Expression = o.NULL_EXPR;
public viewContainerAppElements: o.Expression[] = [];
public classStatements: o.Statement[] = [];
public createMethod: CompileMethod; public createMethod: CompileMethod;
public animationBindingsMethod: CompileMethod; public animationBindingsMethod: CompileMethod;
public injectorGetMethod: CompileMethod; public injectorGetMethod: CompileMethod;
@ -47,12 +60,12 @@ export class CompileView implements NameResolver {
public afterViewLifecycleCallbacksMethod: CompileMethod; public afterViewLifecycleCallbacksMethod: CompileMethod;
public destroyMethod: CompileMethod; public destroyMethod: CompileMethod;
public detachMethod: CompileMethod; public detachMethod: CompileMethod;
public eventHandlerMethods: o.ClassMethod[] = []; public methods: o.ClassMethod[] = [];
public ctorStmts: o.Statement[] = [];
public fields: o.ClassField[] = []; public fields: o.ClassField[] = [];
public getters: o.ClassGetter[] = []; public getters: o.ClassGetter[] = [];
public disposables: o.Expression[] = []; public disposables: o.Expression[] = [];
public subscriptions: o.Expression[] = [];
public componentView: CompileView; public componentView: CompileView;
public purePipes = new Map<string, CompilePipe>(); public purePipes = new Map<string, CompilePipe>();
@ -102,22 +115,12 @@ export class CompileView implements NameResolver {
var viewQueries = new Map<any, CompileQuery[]>(); var viewQueries = new Map<any, CompileQuery[]>();
if (this.viewType === ViewType.COMPONENT) { if (this.viewType === ViewType.COMPONENT) {
var directiveInstance = o.THIS_EXPR.prop('context'); var directiveInstance = o.THIS_EXPR.prop('context');
ListWrapper.forEachWithIndex(this.component.viewQueries, (queryMeta, queryIndex) => { this.component.viewQueries.forEach((queryMeta, queryIndex) => {
var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`; var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`;
var queryList = createQueryList(queryMeta, directiveInstance, propName, this); var queryList = createQueryList(queryMeta, directiveInstance, propName, this);
var query = new CompileQuery(queryMeta, queryList, directiveInstance, this); var query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query); addQueryToTokenMap(viewQueries, query);
}); });
var constructorViewQueryCount = 0;
this.component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
var queryList = o.THIS_EXPR.prop('declarationAppElement')
.prop('componentConstructorViewQueries')
.key(o.literal(constructorViewQueryCount++));
var query = new CompileQuery(dep.viewQuery, queryList, null, this);
addQueryToTokenMap(viewQueries, query);
}
});
} }
this.viewQueries = viewQueries; this.viewQueries = viewQueries;
templateVariableBindings.forEach( templateVariableBindings.forEach(
@ -149,48 +152,6 @@ export class CompileView implements NameResolver {
} }
} }
createLiteralArray(values: o.Expression[]): o.Expression {
if (values.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
}
var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`);
var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: o.Expression[] = [];
for (var i = 0; i < values.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName));
}
createPureProxy(
o.fn(
proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))],
new o.ArrayType(o.DYNAMIC_TYPE)),
values.length, proxyExpr, this);
return proxyExpr.callFn(values);
}
createLiteralMap(entries: [string, o.Expression][]): o.Expression {
if (entries.length === 0) {
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
}
const proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
const proxyParams: o.FnParam[] = [];
const proxyReturnEntries: [string, o.Expression][] = [];
const values: o.Expression[] = [];
for (var i = 0; i < entries.length; i++) {
const paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
values.push(<o.Expression>entries[i][1]);
}
createPureProxy(
o.fn(
proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))],
new o.MapType(o.DYNAMIC_TYPE)),
entries.length, proxyExpr, this);
return proxyExpr.callFn(values);
}
afterNodes() { afterNodes() {
MapWrapper.values(this.viewQueries) MapWrapper.values(this.viewQueries)
.forEach( .forEach(

View File

@ -8,81 +8,32 @@
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {CompileIdentifierMetadata} from '../compile_metadata'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers'; import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType} from '../private_import_core'; import {ChangeDetectorStatus, ViewType} from '../private_import_core';
function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: string): o.Expression {
return o.importExpr(resolveEnumIdentifier(classIdentifier, name));
}
export class ViewTypeEnum { export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression { static fromValue(value: ViewType): o.Expression {
const viewType = resolveIdentifier(Identifiers.ViewType); return createEnumExpression(Identifiers.ViewType, value);
switch (value) {
case ViewType.HOST:
return _enumExpression(viewType, 'HOST');
case ViewType.COMPONENT:
return _enumExpression(viewType, 'COMPONENT');
case ViewType.EMBEDDED:
return _enumExpression(viewType, 'EMBEDDED');
default:
throw Error(`Inavlid ViewType value: ${value}`);
}
} }
} }
export class ViewEncapsulationEnum { export class ViewEncapsulationEnum {
static fromValue(value: ViewEncapsulation): o.Expression { static fromValue(value: ViewEncapsulation): o.Expression {
const viewEncapsulation = resolveIdentifier(Identifiers.ViewEncapsulation); return createEnumExpression(Identifiers.ViewEncapsulation, value);
switch (value) {
case ViewEncapsulation.Emulated:
return _enumExpression(viewEncapsulation, 'Emulated');
case ViewEncapsulation.Native:
return _enumExpression(viewEncapsulation, 'Native');
case ViewEncapsulation.None:
return _enumExpression(viewEncapsulation, 'None');
default:
throw Error(`Inavlid ViewEncapsulation value: ${value}`);
}
} }
} }
export class ChangeDetectionStrategyEnum { export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression { static fromValue(value: ChangeDetectionStrategy): o.Expression {
const changeDetectionStrategy = resolveIdentifier(Identifiers.ChangeDetectionStrategy); return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
switch (value) {
case ChangeDetectionStrategy.OnPush:
return _enumExpression(changeDetectionStrategy, 'OnPush');
case ChangeDetectionStrategy.Default:
return _enumExpression(changeDetectionStrategy, 'Default');
default:
throw Error(`Inavlid ChangeDetectionStrategy value: ${value}`);
}
} }
} }
export class ChangeDetectorStatusEnum { export class ChangeDetectorStatusEnum {
static fromValue(value: ChangeDetectorStatusEnum): o.Expression { static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
const changeDetectorStatus = resolveIdentifier(Identifiers.ChangeDetectorStatus); return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
switch (value) {
case ChangeDetectorStatus.CheckOnce:
return _enumExpression(changeDetectorStatus, 'CheckOnce');
case ChangeDetectorStatus.Checked:
return _enumExpression(changeDetectorStatus, 'Checked');
case ChangeDetectorStatus.CheckAlways:
return _enumExpression(changeDetectorStatus, 'CheckAlways');
case ChangeDetectorStatus.Detached:
return _enumExpression(changeDetectorStatus, 'Detached');
case ChangeDetectorStatus.Errored:
return _enumExpression(changeDetectorStatus, 'Errored');
case ChangeDetectorStatus.Destroyed:
return _enumExpression(changeDetectorStatus, 'Destroyed');
default:
throw Error(`Inavlid ChangeDetectorStatus value: ${value}`);
}
} }
} }
@ -94,12 +45,9 @@ export class ViewConstructorVars {
export class ViewProperties { export class ViewProperties {
static renderer = o.THIS_EXPR.prop('renderer'); static renderer = o.THIS_EXPR.prop('renderer');
static projectableNodes = o.THIS_EXPR.prop('projectableNodes');
static viewUtils = o.THIS_EXPR.prop('viewUtils'); static viewUtils = o.THIS_EXPR.prop('viewUtils');
} }
export class EventHandlerVars { static event = o.variable('$event'); }
export class InjectMethodVars { export class InjectMethodVars {
static token = o.variable('token'); static token = o.variable('token');
static requestNodeIndex = o.variable('requestNodeIndex'); static requestNodeIndex = o.variable('requestNodeIndex');
@ -110,5 +58,4 @@ export class DetectChangesVars {
static throwOnChange = o.variable(`throwOnChange`); static throwOnChange = o.variable(`throwOnChange`);
static changes = o.variable(`changes`); static changes = o.variable(`changes`);
static changed = o.variable(`changed`); static changed = o.variable(`changed`);
static valUnwrapper = o.variable(`valUnwrapper`);
} }

View File

@ -6,195 +6,134 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata} from '../compile_metadata'; import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter';
import {createInlineArray} from '../compiler_util/identifier_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {identifierToken} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileBinding} from './compile_binding';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {EventHandlerVars, ViewProperties} from './constants'; import {ViewProperties} from './constants';
import {convertCdStatementToIr} from './expression_converter'; import {getHandleEventMethodName} from './util';
export class CompileEventListener { export function bindOutputs(
private _method: CompileMethod; boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement,
private _hasComponentHostListener: boolean = false; bindToRenderer: boolean): boolean {
private _methodName: string; const usedEvents = collectEvents(boundEvents, directives);
private _eventParam: o.FnParam; if (!usedEvents.size) {
private _actionResultExprs: o.Expression[] = []; return false;
}
if (bindToRenderer) {
subscribeToRenderEvents(usedEvents, compileElement);
}
subscribeToDirectiveEvents(usedEvents, directives, compileElement);
generateHandleEventMethod(boundEvents, directives, compileElement);
return true;
}
static getOrCreate( function collectEvents(
compileElement: CompileElement, eventTarget: string, eventName: string, eventPhase: string, boundEvents: BoundEventAst[], directives: DirectiveAst[]): Map<string, EventSummary> {
targetEventListeners: CompileEventListener[]): CompileEventListener { const usedEvents = new Map<string, EventSummary>();
var listener = targetEventListeners.find( boundEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
listener => listener.eventTarget == eventTarget && listener.eventName == eventName && directives.forEach((dirAst) => {
listener.eventPhase == eventPhase); dirAst.hostEvents.forEach((event) => { usedEvents.set(event.fullName, event); });
if (!listener) { });
listener = new CompileEventListener( return usedEvents;
compileElement, eventTarget, eventName, eventPhase, targetEventListeners.length); }
targetEventListeners.push(listener);
function subscribeToRenderEvents(
usedEvents: Map<string, EventSummary>, compileElement: CompileElement) {
const eventAndTargetExprs: o.Expression[] = [];
usedEvents.forEach((event) => {
if (!event.phase) {
eventAndTargetExprs.push(o.literal(event.name), o.literal(event.target));
} }
return listener; });
} if (eventAndTargetExprs.length) {
const disposableVar = o.variable(`disposable_${compileElement.view.disposables.length}`);
get methodName() { return this._methodName; } compileElement.view.disposables.push(disposableVar);
get isAnimation() { return !!this.eventPhase; } compileElement.view.createMethod.addStmt(
disposableVar
constructor( .set(o.importExpr(resolveIdentifier(Identifiers.subscribeToRenderElement)).callFn([
public compileElement: CompileElement, public eventTarget: string, public eventName: string, o.THIS_EXPR, compileElement.renderNode, createInlineArray(eventAndTargetExprs),
public eventPhase: string, listenerIndex: number) { handleEventExpr(compileElement)
this._method = new CompileMethod(compileElement.view); ]))
this._methodName = .toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
`_handle_${sanitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
this._eventParam = new o.FnParam(
EventHandlerVars.event.name,
o.importType(this.compileElement.view.genConfig.renderTypes.renderEvent));
}
addAction(
hostEvent: BoundEventAst, directive: CompileDirectiveMetadata,
directiveInstance: o.Expression) {
if (isPresent(directive) && directive.isComponent) {
this._hasComponentHostListener = true;
}
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent);
var context = directiveInstance || this.compileElement.view.componentContext;
var actionStmts = convertCdStatementToIr(
this.compileElement.view, context, hostEvent.handler, this.compileElement.nodeIndex);
var lastIndex = actionStmts.length - 1;
if (lastIndex >= 0) {
var lastStatement = actionStmts[lastIndex];
var returnExpr = convertStmtIntoExpression(lastStatement);
var preventDefaultVar = o.variable(`pd_${this._actionResultExprs.length}`);
this._actionResultExprs.push(preventDefaultVar);
if (isPresent(returnExpr)) {
// Note: We need to cast the result of the method call to dynamic,
// as it might be a void method!
actionStmts[lastIndex] =
preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false)))
.toDeclStmt(null, [o.StmtModifier.Final]);
}
}
this._method.addStmts(actionStmts);
}
finishMethod() {
var markPathToRootStart = this._hasComponentHostListener ?
this.compileElement.appElement.prop('componentView') :
o.THIS_EXPR;
var resultExpr: o.Expression = o.literal(true);
this._actionResultExprs.forEach((expr) => { resultExpr = resultExpr.and(expr); });
var stmts =
(<o.Statement[]>[markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()])
.concat(this._method.finish())
.concat([new o.ReturnStatement(resultExpr)]);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.eventHandlerMethods.push(new o.ClassMethod(
this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private]));
}
listenToRenderer() {
var listenExpr: o.Expression;
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
if (isPresent(this.eventTarget)) {
listenExpr = ViewProperties.renderer.callMethod(
'listenGlobal', [o.literal(this.eventTarget), o.literal(this.eventName), eventListener]);
} else {
listenExpr = ViewProperties.renderer.callMethod(
'listen', [this.compileElement.renderNode, o.literal(this.eventName), eventListener]);
}
var disposable = o.variable(`disposable_${this.compileElement.view.disposables.length}`);
this.compileElement.view.disposables.push(disposable);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.createMethod.addStmt(
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
}
listenToAnimation(animationTransitionVar: o.ReadVarExpr): o.Statement {
const callbackMethod = this.eventPhase == 'start' ? 'onStart' : 'onDone';
return animationTransitionVar
.callMethod(
callbackMethod,
[o.THIS_EXPR.prop(this.methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])])
.toStmt();
}
listenToDirective(directiveInstance: o.Expression, observablePropName: string) {
var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`);
this.compileElement.view.subscriptions.push(subscription);
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
this.compileElement.view.createMethod.addStmt(
subscription
.set(directiveInstance.prop(observablePropName)
.callMethod(o.BuiltinMethod.SubscribeObservable, [eventListener]))
.toDeclStmt(null, [o.StmtModifier.Final]));
} }
} }
export function collectEventListeners( function subscribeToDirectiveEvents(
hostEvents: BoundEventAst[], dirs: DirectiveAst[], usedEvents: Map<string, EventSummary>, directives: DirectiveAst[],
compileElement: CompileElement): CompileEventListener[] { compileElement: CompileElement) {
const eventListeners: CompileEventListener[] = []; const usedEventNames = MapWrapper.keys(usedEvents);
directives.forEach((dirAst) => {
hostEvents.forEach((hostEvent) => { const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference);
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent)); compileElement.view.createMethod.addStmts(DirectiveWrapperExpressions.subscribe(
var listener = CompileEventListener.getOrCreate( dirAst.directive, dirAst.hostProperties, usedEventNames, dirWrapper, o.THIS_EXPR,
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners); handleEventExpr(compileElement)));
listener.addAction(hostEvent, null, null);
});
dirs.forEach((directiveAst) => {
var directiveInstance =
compileElement.instances.get(identifierToken(directiveAst.directive.type).reference);
directiveAst.hostEvents.forEach((hostEvent) => {
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
var listener = CompileEventListener.getOrCreate(
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
});
});
eventListeners.forEach((listener) => listener.finishMethod());
return eventListeners;
}
export function bindDirectiveOutputs(
directiveAst: DirectiveAst, directiveInstance: o.Expression,
eventListeners: CompileEventListener[]) {
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => {
const eventName = directiveAst.directive.outputs[observablePropName];
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
listener.listenToDirective(directiveInstance, observablePropName);
});
}); });
} }
export function bindRenderOutputs(eventListeners: CompileEventListener[]) { function generateHandleEventMethod(
eventListeners.forEach(listener => { boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement) {
// the animation listeners are handled within property_binder.ts to const hasComponentHostListener =
// allow them to be placed next to the animation factory statements directives.some((dirAst) => dirAst.hostEvents.some((event) => dirAst.directive.isComponent));
if (!listener.isAnimation) {
listener.listenToRenderer(); const markPathToRootStart =
hasComponentHostListener ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
const handleEventStmts = new CompileMethod(compileElement.view);
handleEventStmts.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
handleEventStmts.push(markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt());
const eventNameVar = o.variable('eventName');
const resultVar = o.variable('result');
handleEventStmts.push(resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
directives.forEach((dirAst, dirIdx) => {
const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference);
if (dirAst.hostEvents.length > 0) {
handleEventStmts.push(
resultVar
.set(DirectiveWrapperExpressions
.handleEvent(
dirAst.hostEvents, dirWrapper, eventNameVar, EventHandlerVars.event)
.and(resultVar))
.toStmt());
} }
}); });
boundEvents.forEach((renderEvent, renderEventIdx) => {
const evalResult = convertActionBinding(
compileElement.view, compileElement.view, compileElement.view.componentContext,
renderEvent.handler, `sub_${renderEventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) {
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
}
// TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
handleEventStmts.push(
new o.IfStmt(eventNameVar.equals(o.literal(renderEvent.fullName)), trueStmts));
});
handleEventStmts.push(new o.ReturnStatement(resultVar));
compileElement.view.methods.push(new o.ClassMethod(
getHandleEventMethodName(compileElement.nodeIndex),
[
new o.FnParam(eventNameVar.name, o.STRING_TYPE),
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
],
handleEventStmts.finish(), o.BOOL_TYPE));
} }
function convertStmtIntoExpression(stmt: o.Statement): o.Expression { function handleEventExpr(compileElement: CompileElement) {
if (stmt instanceof o.ExpressionStatement) { const handleEventMethodName = getHandleEventMethodName(compileElement.nodeIndex);
return stmt.expr; return o.THIS_EXPR.callMethod('eventHandler', [o.THIS_EXPR.prop(handleEventMethodName)]);
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
} }
function sanitizeEventName(name: string): string { type EventSummary = {
return name.replace(/[^a-zA-Z_]/g, '_'); name: string,
} target: string,
phase: string
}

View File

@ -7,16 +7,15 @@
*/ */
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
import {DirectiveAst, ProviderAst} from '../template_parser/template_ast'; import {DirectiveAst, ProviderAst, ProviderAstType} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars} from './constants'; import {DetectChangesVars} from './constants';
var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0)); var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0));
var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
@ -56,11 +55,24 @@ export function bindDirectiveAfterViewLifecycleCallbacks(
} }
} }
export function bindDirectiveWrapperLifecycleCallbacks(
dir: DirectiveAst, directiveWrapperIntance: o.Expression, compileElement: CompileElement) {
compileElement.view.destroyMethod.addStmts(
DirectiveWrapperExpressions.ngOnDestroy(dir.directive, directiveWrapperIntance));
compileElement.view.detachMethod.addStmts(DirectiveWrapperExpressions.ngOnDetach(
dir.hostProperties, directiveWrapperIntance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode));
}
export function bindInjectableDestroyLifecycleCallbacks( export function bindInjectableDestroyLifecycleCallbacks(
provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) { provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) {
var onDestroyMethod = compileElement.view.destroyMethod; var onDestroyMethod = compileElement.view.destroyMethod;
onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
if (provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) { if (provider.providerType !== ProviderAstType.Directive &&
provider.providerType !== ProviderAstType.Component &&
provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt()); onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt());
} }
} }

View File

@ -8,284 +8,144 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util';
import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter';
import {createEnumExpression} from '../compiler_util/identifier_util';
import {triggerAnimation, writeToRenderer} from '../compiler_util/render_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {BoundElementPropertyAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
import {camelCaseToDashCase} from '../util'; import {camelCaseToDashCase} from '../util';
import {CompileBinding} from './compile_binding';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars, ViewProperties} from './constants'; import {DetectChangesVars, ViewProperties} from './constants';
import {CompileEventListener} from './event_binder'; import {getHandleEventMethodName} from './util';
import {convertCdExpressionToIr, temporaryDeclaration} from './expression_converter';
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr { export function bindRenderText(
return o.THIS_EXPR.prop(`_expr_${exprIndex}`); boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
} const valueField = createCheckBindingField(view);
const evalResult = convertPropertyBinding(
function createCurrValueExpr(exprIndex: number): o.ReadVarExpr { view, view, view.componentContext, boundText.value, valueField.bindingId);
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: ` if (!evalResult) {
}
class EvalResult {
constructor(public forceUpdate: o.Expression) {}
}
function evalCdAst(
view: CompileView, currValExpr: o.ReadVarExpr, parsedExpression: cdAst.AST,
context: o.Expression, method: CompileMethod, bindingIndex: number): EvalResult {
var checkExpression = convertCdExpressionToIr(
view, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex);
if (!checkExpression.expression) {
// e.g. an empty expression was given
return null; return null;
} }
if (checkExpression.temporaryCount) {
for (let i = 0; i < checkExpression.temporaryCount; i++) {
method.addStmt(temporaryDeclaration(bindingIndex, i));
}
}
if (checkExpression.needsValueUnwrapper) {
var initValueUnwrapperStmt = DetectChangesVars.valUnwrapper.callMethod('reset', []).toStmt();
method.addStmt(initValueUnwrapperStmt);
}
method.addStmt(
currValExpr.set(checkExpression.expression).toDeclStmt(null, [o.StmtModifier.Final]));
if (checkExpression.needsValueUnwrapper) {
return new EvalResult(DetectChangesVars.valUnwrapper.prop('hasWrappedValue'));
} else {
return new EvalResult(null);
}
}
function bind(
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
method: CompileMethod, bindingIndex: number) {
const evalResult = evalCdAst(view, currValExpr, parsedExpression, context, method, bindingIndex);
if (!evalResult) {
return;
}
// private is fine here as no child view will reference the cached value...
view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
view.createMethod.addStmt(o.THIS_EXPR.prop(fieldExpr.name)
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
DetectChangesVars.throwOnChange, fieldExpr, currValExpr
]);
if (evalResult.forceUpdate) {
condition = evalResult.forceUpdate.or(condition);
}
method.addStmt(new o.IfStmt(
condition,
actions.concat([<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(currValExpr).toStmt()])));
}
export function bindRenderText(
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView) {
var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileNode, boundText));
var currValExpr = createCurrValueExpr(bindingIndex);
var valueField = createBindFieldExpr(bindingIndex);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText);
view.detectChangesRenderPropertiesMethod.addStmts(createCheckBindingStmt(
bind( evalResult, valueField.expression, DetectChangesVars.throwOnChange,
view, currValExpr, valueField, boundText.value, view.componentContext,
[o.THIS_EXPR.prop('renderer') [o.THIS_EXPR.prop('renderer')
.callMethod('setText', [compileNode.renderNode, currValExpr]) .callMethod('setText', [compileNode.renderNode, evalResult.currValExpr])
.toStmt()], .toStmt()]));
view.detectChangesRenderPropertiesMethod, bindingIndex);
}
function bindAndWriteToRenderer(
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement,
isHostProp: boolean, eventListeners: CompileEventListener[]) {
var view = compileElement.view;
var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => {
var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileElement, boundProp));
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
var fieldExpr = createBindFieldExpr(bindingIndex);
var currValExpr = createCurrValueExpr(bindingIndex);
var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr);
var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr);
var updateStmts: o.Statement[] = [];
var compileMethod = view.detectChangesRenderPropertiesMethod;
switch (boundProp.type) {
case PropertyBindingType.Property:
if (view.genConfig.logBindingUpdate) {
updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, renderValue));
}
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod(
'setElementProperty', [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Attribute:
renderValue =
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod(
'setElementAttribute', [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Class:
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Style:
var strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod('setElementStyle', [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Animation:
compileMethod = view.animationBindingsMethod;
const detachStmts: o.Statement[] = [];
const animationName = boundProp.name;
const targetViewExpr: o.Expression =
isHostProp ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
const animationFnExpr =
targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
o.THIS_EXPR, renderNode, oldRenderValue.equals(unitializedValue)
.conditional(emptyStateValue, oldRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
]))
.toDeclStmt());
detachStmts.push(animationTransitionVar
.set(animationFnExpr.callFn(
[o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]))
.toDeclStmt());
eventListeners.forEach(listener => {
if (listener.isAnimation && listener.eventName === animationName) {
let animationStmt = listener.listenToAnimation(animationTransitionVar);
updateStmts.push(animationStmt);
detachStmts.push(animationStmt);
}
});
view.detachMethod.addStmts(detachStmts);
break;
}
bind(
view, currValExpr, fieldExpr, boundProp.value, context, updateStmts, compileMethod,
view.bindings.length);
});
}
function sanitizedValue(
boundProp: BoundElementPropertyAst, renderValue: o.Expression): o.Expression {
let enumValue: string;
switch (boundProp.securityContext) {
case SecurityContext.NONE:
return renderValue; // No sanitization needed.
case SecurityContext.HTML:
enumValue = 'HTML';
break;
case SecurityContext.STYLE:
enumValue = 'STYLE';
break;
case SecurityContext.SCRIPT:
enumValue = 'SCRIPT';
break;
case SecurityContext.URL:
enumValue = 'URL';
break;
case SecurityContext.RESOURCE_URL:
enumValue = 'RESOURCE_URL';
break;
default:
throw new Error(`internal error, unexpected SecurityContext ${boundProp.securityContext}.`);
}
let ctx = ViewProperties.viewUtils.prop('sanitizer');
let args =
[o.importExpr(resolveIdentifier(Identifiers.SecurityContext)).prop(enumValue), renderValue];
return ctx.callMethod('sanitize', args);
} }
export function bindRenderInputs( export function bindRenderInputs(
boundProps: BoundElementPropertyAst[], compileElement: CompileElement, boundProps: BoundElementPropertyAst[], hasEvents: boolean, compileElement: CompileElement) {
eventListeners: CompileEventListener[]): void { var view = compileElement.view;
bindAndWriteToRenderer( var renderNode = compileElement.renderNode;
boundProps, compileElement.view.componentContext, compileElement, false, eventListeners); boundProps.forEach((boundProp) => {
const bindingField = createCheckBindingField(view);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
const evalResult = convertPropertyBinding(
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
if (!evalResult) {
return;
}
var checkBindingStmts: o.Statement[] = [];
var compileMethod = view.detectChangesRenderPropertiesMethod;
switch (boundProp.type) {
case PropertyBindingType.Property:
case PropertyBindingType.Attribute:
case PropertyBindingType.Class:
case PropertyBindingType.Style:
checkBindingStmts.push(...writeToRenderer(
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr,
view.genConfig.logBindingUpdate));
break;
case PropertyBindingType.Animation:
compileMethod = view.animationBindingsMethod;
const {updateStmts, detachStmts} = triggerAnimation(
o.THIS_EXPR, o.THIS_EXPR, boundProp,
(hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) :
o.importExpr(resolveIdentifier(Identifiers.noop)))
.callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]),
compileElement.renderNode, evalResult.currValExpr, bindingField.expression);
checkBindingStmts.push(...updateStmts);
view.detachMethod.addStmts(detachStmts);
break;
}
compileMethod.addStmts(createCheckBindingStmt(
evalResult, bindingField.expression, DetectChangesVars.throwOnChange, checkBindingStmts));
});
} }
export function bindDirectiveHostProps( export function bindDirectiveHostProps(
directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement, directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
eventListeners: CompileEventListener[]): void { compileElement: CompileElement, elementName: string,
bindAndWriteToRenderer( schemaRegistry: ElementSchemaRegistry): void {
directiveAst.hostProperties, directiveInstance, compileElement, true, eventListeners); // We need to provide the SecurityContext for properties that could need sanitization.
const runtimeSecurityCtxExprs =
directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext)
.map((boundProp) => {
let ctx: SecurityContext;
switch (boundProp.type) {
case PropertyBindingType.Property:
ctx = schemaRegistry.securityContext(elementName, boundProp.name, false);
break;
case PropertyBindingType.Attribute:
ctx = schemaRegistry.securityContext(elementName, boundProp.name, true);
break;
default:
throw new Error(
`Illegal state: Only property / attribute bindings can have an unknown security context! Binding ${boundProp.name}`);
}
return createEnumExpression(Identifiers.SecurityContext, ctx);
});
compileElement.view.detectChangesRenderPropertiesMethod.addStmts(
DirectiveWrapperExpressions.checkHost(
directiveAst.hostProperties, directiveWrapperInstance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode, DetectChangesVars.throwOnChange, runtimeSecurityCtxExprs));
} }
export function bindDirectiveInputs( export function bindDirectiveInputs(
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, dirIndex: number,
compileElement: CompileElement) { compileElement: CompileElement) {
var view = compileElement.view; var view = compileElement.view;
var detectChangesInInputsMethod = view.detectChangesInInputsMethod; var detectChangesInInputsMethod = view.detectChangesInInputsMethod;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
directiveAst.inputs.forEach((input) => { directiveAst.inputs.forEach((input, inputIdx) => {
var bindingIndex = view.bindings.length; // Note: We can't use `fields.length` here, as we are not adding a field!
view.bindings.push(new CompileBinding(compileElement, input)); const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
var currValExpr = createCurrValueExpr(bindingIndex); const evalResult =
const evalResult = evalCdAst( convertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
view, currValExpr, input.value, view.componentContext, detectChangesInInputsMethod,
bindingIndex);
if (!evalResult) { if (!evalResult) {
return; return;
} }
detectChangesInInputsMethod.addStmt(directiveWrapperInstance detectChangesInInputsMethod.addStmts(evalResult.stmts);
.callMethod( detectChangesInInputsMethod.addStmt(
`check_${input.directiveName}`, directiveWrapperInstance
[ .callMethod(
currValExpr, DetectChangesVars.throwOnChange, `check_${input.directiveName}`,
evalResult.forceUpdate || o.literal(false) [
]) evalResult.currValExpr, DetectChangesVars.throwOnChange,
.toStmt()); evalResult.forceUpdate || o.literal(false)
])
.toStmt());
}); });
var isOnPushComp = directiveAst.directive.isComponent && var isOnPushComp = directiveAst.directive.isComponent &&
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
let directiveDetectChangesExpr = directiveWrapperInstance.callMethod( let directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck(
'detectChangesInternal', directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode,
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]); DetectChangesVars.throwOnChange);
const directiveDetectChangesStmt = isOnPushComp ? const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView') new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', []) .callMethod('markAsCheckOnce', [])
@ -293,10 +153,3 @@ export function bindDirectiveInputs(
directiveDetectChangesExpr.toStmt(); directiveDetectChangesExpr.toStmt();
detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt); detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt);
} }
function logBindingUpdateStmt(
renderNode: o.Expression, propName: string, value: o.Expression): o.Statement {
return o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
.callFn([o.THIS_EXPR.prop('renderer'), renderNode, o.literal(propName), value])
.toStmt();
}

View File

@ -8,10 +8,10 @@
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {createDiTokenExpression} from '../util';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
@ -69,38 +69,6 @@ export function getViewFactoryName(
return `viewFactory_${component.type.name}${embeddedTemplateIndex}`; return `viewFactory_${component.type.name}${embeddedTemplateIndex}`;
} }
export function createFlatArray(expressions: o.Expression[]): o.Expression { export function getHandleEventMethodName(elementIndex: number): string {
var lastNonArrayExpressions: o.Expression[] = []; return `handleEvent_${elementIndex}`;
var result: o.Expression = o.literalArr([]); }
for (var i = 0; i < expressions.length; i++) {
var expr = expressions[i];
if (expr.type instanceof o.ArrayType) {
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
lastNonArrayExpressions = [];
}
result = result.callMethod(o.BuiltinMethod.ConcatArray, [expr]);
} else {
lastNonArrayExpressions.push(expr);
}
}
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
}
return result;
}
export function createPureProxy(
fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, view: CompileView) {
view.fields.push(new o.ClassField(pureProxyProp.name, null));
var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (!pureProxyId) {
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
}
view.createMethod.addStmt(o.THIS_EXPR.prop(pureProxyProp.name)
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
.toStmt());
}

View File

@ -6,16 +6,18 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder'; import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder'; import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void { export function bindView(
var visitor = new ViewBinderVisitor(view); view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
var visitor = new ViewBinderVisitor(view, schemaRegistry);
templateVisitAll(visitor, parsedTemplate); templateVisitAll(visitor, parsedTemplate);
view.pipes.forEach( view.pipes.forEach(
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); }); (pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
@ -24,7 +26,7 @@ export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void
class ViewBinderVisitor implements TemplateAstVisitor { class ViewBinderVisitor implements TemplateAstVisitor {
private _nodeIndex: number = 0; private _nodeIndex: number = 0;
constructor(public view: CompileView) {} constructor(public view: CompileView, private _schemaRegistry: ElementSchemaRegistry) {}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any { visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
var node = this.view.nodes[this._nodeIndex++]; var node = this.view.nodes[this._nodeIndex++];
@ -40,30 +42,29 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners: CompileEventListener[] = []; const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => { bindRenderInputs(ast.inputs, hasEvents, compileElement);
eventListeners.push(entry); ast.directives.forEach((directiveAst, dirIndex) => {
});
bindRenderInputs(ast.inputs, compileElement, eventListeners);
bindRenderOutputs(eventListeners);
ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
bindDirectiveHostProps(
bindDirectiveHostProps(directiveAst, directiveInstance, compileElement, eventListeners); directiveAst, directiveWrapperInstance, compileElement, ast.name, this._schemaRegistry);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
}); });
templateVisitAll(this, ast.children, compileElement); templateVisitAll(this, ast.children, compileElement);
// afterContent and afterView lifecycles need to be called bottom up // afterContent and afterView lifecycles need to be called bottom up
// so that children are notified before parents // so that children are notified before parents
ast.directives.forEach((directiveAst) => { ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
@ -74,24 +75,25 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement); bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst) => { ast.directives.forEach((directiveAst, dirIndex) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement); bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
}); });
bindView(compileElement.embeddedView, ast.children); bindView(compileElement.embeddedView, ast.children, this._schemaRegistry);
return null; return null;
} }

View File

@ -9,19 +9,21 @@
import {ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {ListWrapper} from '../facade/collection'; import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
import {createClassStmt} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView, CompileViewRootNode, CompileViewRootNodeType} from './compile_view';
import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps';
import {createFlatArray, getViewFactoryName} from './util'; import {getViewFactoryName} from './util';
const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
@ -37,9 +39,12 @@ export function buildView(
Array<ViewFactoryDependency|ComponentFactoryDependency|DirectiveWrapperDependency>): Array<ViewFactoryDependency|ComponentFactoryDependency|DirectiveWrapperDependency>):
number { number {
var builderVisitor = new ViewBuilderVisitor(view, targetDependencies); var builderVisitor = new ViewBuilderVisitor(view, targetDependencies);
templateVisitAll( const parentEl =
builderVisitor, template, view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent;
view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent); templateVisitAll(builderVisitor, template, parentEl);
if (view.viewType === ViewType.EMBEDDED || view.viewType === ViewType.HOST) {
view.lastRenderNode = builderVisitor.getOrCreateLastRenderNode();
}
return builderVisitor.nestedViewCount; return builderVisitor.nestedViewCount;
} }
@ -72,10 +77,16 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
if (this._isRootNode(parent)) { if (this._isRootNode(parent)) {
// store appElement as root node only for ViewContainers // store appElement as root node only for ViewContainers
if (this.view.viewType !== ViewType.COMPONENT) { if (this.view.viewType !== ViewType.COMPONENT) {
this.view.rootNodesOrAppElements.push(vcAppEl || node.renderNode); this.view.rootNodes.push(new CompileViewRootNode(
vcAppEl ? CompileViewRootNodeType.ViewContainer : CompileViewRootNodeType.Node,
vcAppEl || node.renderNode));
} }
} else if (isPresent(parent.component) && isPresent(ngContentIndex)) { } else if (isPresent(parent.component) && isPresent(ngContentIndex)) {
parent.addContentNode(ngContentIndex, vcAppEl || node.renderNode); parent.addContentNode(
ngContentIndex,
new CompileViewRootNode(
vcAppEl ? CompileViewRootNodeType.ViewContainer : CompileViewRootNodeType.Node,
vcAppEl || node.renderNode));
} }
} }
@ -96,6 +107,23 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
} }
} }
getOrCreateLastRenderNode(): o.Expression {
const view = this.view;
if (view.rootNodes.length === 0 ||
view.rootNodes[view.rootNodes.length - 1].type !== CompileViewRootNodeType.Node) {
var fieldName = `_el_${view.nodes.length}`;
view.fields.push(
new o.ClassField(fieldName, o.importType(view.genConfig.renderTypes.renderElement)));
view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName)
.set(ViewProperties.renderer.callMethod(
'createTemplateAnchor', [o.NULL_EXPR, o.NULL_EXPR]))
.toStmt());
view.rootNodes.push(
new CompileViewRootNode(CompileViewRootNodeType.Node, o.THIS_EXPR.prop(fieldName)));
}
return view.rootNodes[view.rootNodes.length - 1].expr;
}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any { visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
return this._visitText(ast, '', parent); return this._visitText(ast, '', parent);
} }
@ -128,9 +156,6 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
// have debug information for them... // have debug information for them...
this.view.createMethod.resetDebugInfo(null, ast); this.view.createMethod.resetDebugInfo(null, ast);
var parentRenderNode = this._getParentRenderNode(parent); var parentRenderNode = this._getParentRenderNode(parent);
var nodesExpression = ViewProperties.projectableNodes.key(
o.literal(ast.index),
new o.ArrayType(o.importType(this.view.genConfig.renderTypes.renderNode)));
if (parentRenderNode !== o.NULL_EXPR) { if (parentRenderNode !== o.NULL_EXPR) {
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
ViewProperties.renderer ViewProperties.renderer
@ -138,18 +163,20 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
'projectNodes', 'projectNodes',
[ [
parentRenderNode, parentRenderNode,
o.importExpr(resolveIdentifier(Identifiers.flattenNestedViewRenderNodes)) o.THIS_EXPR.callMethod('projectedNodes', [o.literal(ast.index)])
.callFn([nodesExpression])
]) ])
.toStmt()); .toStmt());
} else if (this._isRootNode(parent)) { } else if (this._isRootNode(parent)) {
if (this.view.viewType !== ViewType.COMPONENT) { if (this.view.viewType !== ViewType.COMPONENT) {
// store root nodes only for embedded/host views // store root nodes only for embedded/host views
this.view.rootNodesOrAppElements.push(nodesExpression); this.view.rootNodes.push(
new CompileViewRootNode(CompileViewRootNodeType.NgContent, null, ast.index));
} }
} else { } else {
if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) { if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) {
parent.addContentNode(ast.ngContentIndex, nodesExpression); parent.addContentNode(
ast.ngContentIndex,
new CompileViewRootNode(CompileViewRootNodeType.NgContent, null, ast.index));
} }
} }
return null; return null;
@ -157,19 +184,29 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var nodeIndex = this.view.nodes.length; var nodeIndex = this.view.nodes.length;
var createRenderNodeExpr: o.InvokeMethodExpr; var createRenderNodeExpr: o.Expression;
var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast); var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) { var directives = ast.directives.map(directiveAst => directiveAst.directive);
createRenderNodeExpr = o.THIS_EXPR.callMethod( var component = directives.find(directive => directive.isComponent);
'selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]); if (ast.name === NG_CONTAINER_TAG) {
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
} else { } else {
if (ast.name === NG_CONTAINER_TAG) { const htmlAttrs = _readHtmlAttrs(ast.attrs);
createRenderNodeExpr = ViewProperties.renderer.callMethod( const attrNameAndValues = createInlineArray(
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]); _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives).map(v => o.literal(v)));
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
createRenderNodeExpr =
o.importExpr(resolveIdentifier(Identifiers.selectOrCreateRenderHostElement)).callFn([
ViewProperties.renderer, o.literal(ast.name), attrNameAndValues, rootSelectorVar,
debugContextExpr
]);
} else { } else {
createRenderNodeExpr = ViewProperties.renderer.callMethod( createRenderNodeExpr =
'createElement', o.importExpr(resolveIdentifier(Identifiers.createRenderElement)).callFn([
[this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]); ViewProperties.renderer, this._getParentRenderNode(parent), o.literal(ast.name),
attrNameAndValues, debugContextExpr
]);
} }
} }
var fieldName = `_el_${nodeIndex}`; var fieldName = `_el_${nodeIndex}`;
@ -179,41 +216,29 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
var renderNode = o.THIS_EXPR.prop(fieldName); var renderNode = o.THIS_EXPR.prop(fieldName);
var directives = ast.directives.map(directiveAst => directiveAst.directive);
var component = directives.find(directive => directive.isComponent);
var htmlAttrs = _readHtmlAttrs(ast.attrs);
var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
for (var i = 0; i < attrNameAndValues.length; i++) {
const attrName = attrNameAndValues[i][0];
if (ast.name !== NG_CONTAINER_TAG) {
// <ng-container> are not rendered in the DOM
const attrValue = attrNameAndValues[i][1];
this.view.createMethod.addStmt(
ViewProperties.renderer
.callMethod(
'setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)])
.toStmt());
}
}
var compileElement = new CompileElement( var compileElement = new CompileElement(
parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers, parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers,
ast.hasViewContainer, false, ast.references, this.targetDependencies); ast.hasViewContainer, false, ast.references, this.targetDependencies);
this.view.nodes.push(compileElement); this.view.nodes.push(compileElement);
var compViewExpr: o.ReadVarExpr = null; var compViewExpr: o.ReadPropExpr = null;
if (isPresent(component)) { if (isPresent(component)) {
let nestedComponentIdentifier = let nestedComponentIdentifier =
new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)}); new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)});
this.targetDependencies.push( this.targetDependencies.push(
new ViewFactoryDependency(component.type, nestedComponentIdentifier)); new ViewFactoryDependency(component.type, nestedComponentIdentifier));
compViewExpr = o.variable(`compView_${nodeIndex}`); // fix highlighting: ` compViewExpr = o.THIS_EXPR.prop(`compView_${nodeIndex}`); // fix highlighting: `
this.view.fields.push(new o.ClassField(
compViewExpr.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.importType(component.type)])));
this.view.viewChildren.push(compViewExpr);
compileElement.setComponentView(compViewExpr); compileElement.setComponentView(compViewExpr);
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
compViewExpr compViewExpr
.set(o.importExpr(nestedComponentIdentifier).callFn([ .set(o.importExpr(nestedComponentIdentifier).callFn([
ViewProperties.viewUtils, compileElement.injector, compileElement.appElement ViewProperties.viewUtils, compileElement.injector, compileElement.appElement
])) ]))
.toDeclStmt()); .toStmt());
} }
compileElement.beforeChildren(); compileElement.beforeChildren();
this._addRootNodeAndProject(compileElement); this._addRootNodeAndProject(compileElement);
@ -221,18 +246,8 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1); compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1);
if (isPresent(compViewExpr)) { if (isPresent(compViewExpr)) {
var codeGenContentNodes: o.Expression;
if (this.view.component.type.isHost) {
codeGenContentNodes = ViewProperties.projectableNodes;
} else {
codeGenContentNodes = o.literalArr(
compileElement.contentNodesByNgContentIndex.map(nodes => createFlatArray(nodes)));
}
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
compViewExpr compViewExpr.callMethod('create', [compileElement.getComponent(), o.NULL_EXPR]).toStmt());
.callMethod(
'create', [compileElement.getComponent(), codeGenContentNodes, o.NULL_EXPR])
.toStmt());
} }
return null; return null;
} }
@ -329,18 +344,22 @@ function _isNgContainer(node: CompileNode, view: CompileView): boolean {
function _mergeHtmlAndDirectiveAttrs( function _mergeHtmlAndDirectiveAttrs(
declaredHtmlAttrs: {[key: string]: string}, declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveMetadata[]): string[] {
directives: CompileDirectiveMetadata[]): string[][] { const mapResult: {[key: string]: string} = {};
var result: {[key: string]: string} = {}; Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; });
Object.keys(declaredHtmlAttrs).forEach(key => { result[key] = declaredHtmlAttrs[key]; });
directives.forEach(directiveMeta => { directives.forEach(directiveMeta => {
Object.keys(directiveMeta.hostAttributes).forEach(name => { Object.keys(directiveMeta.hostAttributes).forEach(name => {
const value = directiveMeta.hostAttributes[name]; const value = directiveMeta.hostAttributes[name];
var prevValue = result[name]; const prevValue = mapResult[name];
result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; mapResult[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
}); });
}); });
return mapToKeyValueArray(result); const arrResult: string[] = [];
// Note: We need to sort to get a defined output order
// for tests and for caching generated artifacts...
Object.keys(mapResult).sort().forEach(
(attrName) => { arrResult.push(attrName, mapResult[attrName]); });
return arrResult;
} }
function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} { function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} {
@ -357,15 +376,6 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
} }
} }
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
var entryArray: string[][] = [];
Object.keys(data).forEach(name => { entryArray.push([name, data[name]]); });
// We need to sort to get a defined output order
// for tests and for caching generated artifacts...
ListWrapper.sort(entryArray);
return entryArray;
}
function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) { function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) {
var nodeDebugInfosVar: o.Expression = o.NULL_EXPR; var nodeDebugInfosVar: o.Expression = o.NULL_EXPR;
if (view.genConfig.genDebugInfo) { if (view.genConfig.genDebugInfo) {
@ -444,9 +454,6 @@ function createViewClass(
if (view.genConfig.genDebugInfo) { if (view.genConfig.genDebugInfo) {
superConstructorArgs.push(nodeDebugInfosVar); superConstructorArgs.push(nodeDebugInfosVar);
} }
var viewConstructor = new o.ClassMethod(
null, viewConstructorArgs, [o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]);
var viewMethods = [ var viewMethods = [
new o.ClassMethod( new o.ClassMethod(
'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], 'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
@ -465,17 +472,32 @@ function createViewClass(
'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)], 'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)],
generateDetectChangesMethod(view)), generateDetectChangesMethod(view)),
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()), new o.ClassMethod('destroyInternal', [], generateDestroyMethod(view)),
new o.ClassMethod('detachInternal', [], view.detachMethod.finish()) new o.ClassMethod('detachInternal', [], view.detachMethod.finish()),
].concat(view.eventHandlerMethods); generateVisitRootNodesMethod(view), generateVisitProjectableNodesMethod(view)
].filter((method) => method.body.length > 0);
var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView; var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
var viewClass = new o.ClassStmt(
view.className, o.importExpr(resolveIdentifier(superClass), [getContextType(view)]), var viewClass = createClassStmt({
view.fields, view.getters, viewConstructor, name: view.className,
viewMethods.filter((method) => method.body.length > 0)); parent: o.importExpr(resolveIdentifier(superClass), [getContextType(view)]),
parentArgs: superConstructorArgs,
ctorParams: viewConstructorArgs,
builders: [{methods: viewMethods}, view]
});
return viewClass; return viewClass;
} }
function generateDestroyMethod(view: CompileView): o.Statement[] {
const stmts: o.Statement[] = [];
view.viewContainerAppElements.forEach(
(appElement) => { stmts.push(appElement.callMethod('destroyNestedViews', []).toStmt()); });
view.viewChildren.forEach(
(viewChild) => { stmts.push(viewChild.callMethod('destroy', []).toStmt()); });
stmts.push(...view.destroyMethod.finish());
return stmts;
}
function createViewFactory( function createViewFactory(
view: CompileView, viewClass: o.ClassStmt, renderCompTypeVar: o.ReadVarExpr): o.Statement { view: CompileView, viewClass: o.ClassStmt, renderCompTypeVar: o.ReadVarExpr): o.Statement {
var viewFactoryArgs = [ var viewFactoryArgs = [
@ -549,9 +571,9 @@ function generateCreateMethod(view: CompileView): o.Statement[] {
.callMethod( .callMethod(
'init', 'init',
[ [
createFlatArray(view.rootNodesOrAppElements), view.lastRenderNode,
o.literalArr(view.nodes.map(node => node.renderNode)), o.literalArr(view.disposables), o.literalArr(view.nodes.map(node => node.renderNode)),
o.literalArr(view.subscriptions) view.disposables.length ? o.literalArr(view.disposables) : o.NULL_EXPR,
]) ])
.toStmt(), .toStmt(),
new o.ReturnStatement(resultExpr) new o.ReturnStatement(resultExpr)
@ -567,19 +589,22 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) { view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) {
return stmts; return stmts;
} }
ListWrapper.addAll(stmts, view.animationBindingsMethod.finish()); stmts.push(...view.animationBindingsMethod.finish());
ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish()); stmts.push(...view.detectChangesInInputsMethod.finish());
stmts.push( view.viewContainerAppElements.forEach((appElement) => {
o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange]) stmts.push(
.toStmt()); appElement.callMethod('detectChangesInNestedViews', [DetectChangesVars.throwOnChange])
.toStmt());
});
var afterContentStmts = view.updateContentQueriesMethod.finish().concat( var afterContentStmts = view.updateContentQueriesMethod.finish().concat(
view.afterContentLifecycleCallbacksMethod.finish()); view.afterContentLifecycleCallbacksMethod.finish());
if (afterContentStmts.length > 0) { if (afterContentStmts.length > 0) {
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts)); stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
} }
ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish()); stmts.push(...view.detectChangesRenderPropertiesMethod.finish());
stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange]) view.viewChildren.forEach((viewChild) => {
.toStmt()); stmts.push(viewChild.callMethod('detectChanges', [DetectChangesVars.throwOnChange]).toStmt());
});
var afterViewStmts = var afterViewStmts =
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish()); view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
if (afterViewStmts.length > 0) { if (afterViewStmts.length > 0) {
@ -596,12 +621,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
DetectChangesVars.changes.set(o.NULL_EXPR) DetectChangesVars.changes.set(o.NULL_EXPR)
.toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange))))); .toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange)))));
} }
if (readVars.has(DetectChangesVars.valUnwrapper.name)) { varStmts.push(...createSharedBindingVariablesIfNeeded(stmts));
varStmts.push(
DetectChangesVars.valUnwrapper
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
return varStmts.concat(stmts); return varStmts.concat(stmts);
} }
@ -631,3 +651,61 @@ function getChangeDetectionMode(view: CompileView): ChangeDetectorStatus {
} }
return mode; return mode;
} }
function generateVisitRootNodesMethod(view: CompileView): o.ClassMethod {
const cbVar = o.variable('cb');
const ctxVar = o.variable('ctx');
const stmts: o.Statement[] = generateVisitNodesStmts(view.rootNodes, cbVar, ctxVar);
return new o.ClassMethod(
'visitRootNodesInternal',
[new o.FnParam(cbVar.name, o.DYNAMIC_TYPE), new o.FnParam(ctxVar.name, o.DYNAMIC_TYPE)],
stmts);
}
function generateVisitProjectableNodesMethod(view: CompileView): o.ClassMethod {
const nodeIndexVar = o.variable('nodeIndex');
const ngContentIndexVar = o.variable('ngContentIndex');
const cbVar = o.variable('cb');
const ctxVar = o.variable('ctx');
const stmts: o.Statement[] = [];
view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.component) {
node.contentNodesByNgContentIndex.forEach((projectedNodes, ngContentIndex) => {
stmts.push(new o.IfStmt(
nodeIndexVar.equals(o.literal(node.nodeIndex))
.and(ngContentIndexVar.equals(o.literal(ngContentIndex))),
generateVisitNodesStmts(projectedNodes, cbVar, ctxVar)));
});
}
});
return new o.ClassMethod(
'visitProjectableNodesInternal',
[
new o.FnParam(nodeIndexVar.name, o.NUMBER_TYPE),
new o.FnParam(ngContentIndexVar.name, o.NUMBER_TYPE),
new o.FnParam(cbVar.name, o.DYNAMIC_TYPE), new o.FnParam(ctxVar.name, o.DYNAMIC_TYPE)
],
stmts);
}
function generateVisitNodesStmts(
nodes: CompileViewRootNode[], cb: o.Expression, ctx: o.Expression): o.Statement[] {
const stmts: o.Statement[] = [];
nodes.forEach((node) => {
switch (node.type) {
case CompileViewRootNodeType.Node:
stmts.push(cb.callFn([node.expr, ctx]).toStmt());
break;
case CompileViewRootNodeType.ViewContainer:
stmts.push(cb.callFn([node.expr.prop('nativeElement'), ctx]).toStmt());
stmts.push(node.expr.callMethod('visitNestedViewRootNodes', [cb, ctx]).toStmt());
break;
case CompileViewRootNodeType.NgContent:
stmts.push(
o.THIS_EXPR.callMethod('visitProjectedNodes', [o.literal(node.ngContentIndex), cb, ctx])
.toStmt());
break;
}
});
return stmts;
}

View File

@ -12,6 +12,7 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
@ -31,7 +32,7 @@ export class ViewCompileResult {
@Injectable() @Injectable()
export class ViewCompiler { export class ViewCompiler {
constructor(private _genConfig: CompilerConfig) {} constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {}
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
@ -47,7 +48,7 @@ export class ViewCompiler {
buildView(view, template, dependencies); buildView(view, template, dependencies);
// Need to separate binding from creation to be able to refer to // Need to separate binding from creation to be able to refer to
// variables that have been declared after usage. // variables that have been declared after usage.
bindView(view, template); bindView(view, template, this._schemaRegistry);
finishView(view, statements); finishView(view, statements);
return new ViewCompileResult(statements, view.viewFactory.name, dependencies); return new ViewCompileResult(statements, view.viewFactory.name, dependencies);

View File

@ -0,0 +1,63 @@
/**
* @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 {createInlineArray} from '../../src/compiler_util/identifier_util';
import {Identifiers, resolveIdentifier} from '../../src/identifiers';
import * as o from '../../src/output/output_ast';
export function main() {
describe('createInlineArray', () => {
function check(argCount: number, expectedIdentifier: any) {
const args = createArgs(argCount);
expect(createInlineArray(args))
.toEqual(o.importExpr(resolveIdentifier(expectedIdentifier)).instantiate([
<o.Expression>o.literal(argCount)
].concat(args)));
}
function createArgs(count: number): o.Expression[] {
const result: o.Expression[] = [];
for (var i = 0; i < count; i++) {
result.push(o.NULL_EXPR);
}
return result;
}
it('should work for arrays of length 0', () => {
expect(createInlineArray([
])).toEqual(o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY)));
});
it('should work for arrays of length 1 - 2', () => {
check(1, Identifiers.inlineArrays[0]);
check(2, Identifiers.inlineArrays[1]);
});
it('should work for arrays of length 3 - 4', () => {
for (var i = 3; i <= 4; i++) {
check(i, Identifiers.inlineArrays[2]);
}
});
it('should work for arrays of length 5 - 8', () => {
for (var i = 5; i <= 8; i++) {
check(i, Identifiers.inlineArrays[3]);
}
});
it('should work for arrays of length 9 - 16', () => {
for (var i = 9; i <= 16; i++) {
check(i, Identifiers.inlineArrays[4]);
}
});
it('should work for arrays of length > 16',
() => { check(17, Identifiers.InlineArrayDynamic); });
});
}

View File

@ -74,7 +74,10 @@ export function main() {
return; return;
} }
} }
throw Error(`Expected an error containing "${message}" to be reported`); const errMsgs = ast.errors.map(err => err.message).join('\n');
throw Error(
`Expected an error containing "${message}" to be reported, but got the errors:\n` +
errMsgs);
} }
function expectActionError(text: string, message: string) { function expectActionError(text: string, message: string) {
@ -504,16 +507,10 @@ export function main() {
validate(p); validate(p);
}); });
it('should parse a constant', () => { it('should report when encountering pipes', () => {
var p = parseSimpleBinding('[1, 2]');
expect(unparse(p)).toEqual('[1, 2]');
validate(p);
});
it('should report when the given expression is not just a field name', () => {
expectError( expectError(
validate(parseSimpleBinding('name + 1')), validate(parseSimpleBinding('a | somePipe')),
'Host binding expression can only contain field access and constants'); 'Host binding expression cannot contain pipes');
}); });
it('should report when encountering interpolation', () => { it('should report when encountering interpolation', () => {
@ -521,6 +518,10 @@ export function main() {
validate(parseSimpleBinding('{{exp}}')), validate(parseSimpleBinding('{{exp}}')),
'Got interpolation ({{}}) where expression was expected'); 'Got interpolation ({{}}) where expression was expected');
}); });
it('should report when encountering field write', () => {
expectError(validate(parseSimpleBinding('a = b')), 'Bindings cannot contain assignments');
});
}); });
describe('wrapLiteralPrimitive', () => { describe('wrapLiteralPrimitive', () => {

View File

@ -183,6 +183,22 @@ export function main() {
})); }));
}); });
it('should dedupe declarations in NgModule',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@Component({template: ''})
class MyComp {
}
@NgModule({declarations: [MyComp, MyComp]})
class MyModule {
}
const modMeta = resolver.getNgModuleMetadata(MyModule);
expect(modMeta.declaredDirectives.length).toBe(1);
expect(modMeta.declaredDirectives[0].type.reference).toBe(MyComp);
}));
}); });
} }

View File

@ -11,7 +11,7 @@
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {print} from '../../src/facade/lang'; import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/util'; import {assetUrl} from '../../src/identifiers';
function unimplemented(): any { function unimplemented(): any {
throw new Error('unimplemented'); throw new Error('unimplemented');

View File

@ -10,7 +10,7 @@
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {print} from '../../src/facade/lang'; import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/util'; import {assetUrl} from '../../src/identifiers';
import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util'; import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util';

View File

@ -7,9 +7,9 @@
*/ */
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
import {assetUrl} from '@angular/compiler/src/identifiers';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {ImportGenerator} from '@angular/compiler/src/output/path_util'; import {ImportGenerator} from '@angular/compiler/src/output/path_util';
import {assetUrl} from '@angular/compiler/src/util';
import {EventEmitter} from '@angular/core'; import {EventEmitter} from '@angular/core';
import {BaseError} from '@angular/core/src/facade/errors'; import {BaseError} from '@angular/core/src/facade/errors';
import {ViewType} from '@angular/core/src/linker/view_type'; import {ViewType} from '@angular/core/src/linker/view_type';

View File

@ -147,12 +147,12 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
}); });
it('should return security contexts for elements', () => { it('should return security contexts for elements', () => {
expect(registry.securityContext('iframe', 'srcdoc')).toBe(SecurityContext.HTML); expect(registry.securityContext('iframe', 'srcdoc', false)).toBe(SecurityContext.HTML);
expect(registry.securityContext('p', 'innerHTML')).toBe(SecurityContext.HTML); expect(registry.securityContext('p', 'innerHTML', false)).toBe(SecurityContext.HTML);
expect(registry.securityContext('a', 'href')).toBe(SecurityContext.URL); expect(registry.securityContext('a', 'href', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('a', 'style')).toBe(SecurityContext.STYLE); expect(registry.securityContext('a', 'style', false)).toBe(SecurityContext.STYLE);
expect(registry.securityContext('ins', 'cite')).toBe(SecurityContext.URL); expect(registry.securityContext('ins', 'cite', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('base', 'href')).toBe(SecurityContext.RESOURCE_URL); expect(registry.securityContext('base', 'href', false)).toBe(SecurityContext.RESOURCE_URL);
}); });
it('should detect properties on namespaced elements', () => { it('should detect properties on namespaced elements', () => {
@ -162,9 +162,14 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
}); });
it('should check security contexts case insensitive', () => { it('should check security contexts case insensitive', () => {
expect(registry.securityContext('p', 'iNnErHtMl')).toBe(SecurityContext.HTML); expect(registry.securityContext('p', 'iNnErHtMl', false)).toBe(SecurityContext.HTML);
expect(registry.securityContext('p', 'formaction')).toBe(SecurityContext.URL); expect(registry.securityContext('p', 'formaction', false)).toBe(SecurityContext.URL);
expect(registry.securityContext('p', 'formAction')).toBe(SecurityContext.URL); expect(registry.securityContext('p', 'formAction', false)).toBe(SecurityContext.URL);
});
it('should check security contexts for attributes', () => {
expect(registry.securityContext('p', 'innerHtml', true)).toBe(SecurityContext.HTML);
expect(registry.securityContext('p', 'formaction', true)).toBe(SecurityContext.URL);
}); });
describe('Angular custom elements', () => { describe('Angular custom elements', () => {

View File

@ -58,6 +58,12 @@ export function main() {
expect(matched).toEqual([s1[0], 1, s2[0], 2]); expect(matched).toEqual([s1[0], 1, s2[0], 2]);
}); });
it('should not throw for class name "constructor"', () => {
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
});
it('should select by attr name case sensitive independent of the value', () => { it('should select by attr name case sensitive independent of the value', () => {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);

View File

@ -0,0 +1,59 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SecurityContext} from '@angular/core';
import {inject} from '@angular/core/testing';
import {ElementSchemaRegistry} from '../../src/schema/element_schema_registry';
import {calcPossibleSecurityContexts} from '../../src/template_parser/binding_parser';
export function main() {
describe('BindingParser', () => {
let registry: ElementSchemaRegistry;
beforeEach(inject(
[ElementSchemaRegistry], (_registry: ElementSchemaRegistry) => { registry = _registry; }));
describe('possibleSecurityContexts', () => {
function hrefSecurityContexts(selector: string) {
return calcPossibleSecurityContexts(registry, selector, 'href', false);
}
it('should return a single security context if the selector as an element name',
() => { expect(hrefSecurityContexts('a')).toEqual([SecurityContext.URL]); });
it('should return the possible security contexts if the selector has no element name', () => {
expect(hrefSecurityContexts('[myDir]')).toEqual([
SecurityContext.NONE, SecurityContext.URL, SecurityContext.RESOURCE_URL
]);
});
it('should exclude possible elements via :not', () => {
expect(hrefSecurityContexts('[myDir]:not(link):not(base)')).toEqual([
SecurityContext.NONE, SecurityContext.URL
]);
});
it('should not exclude possible narrowed elements via :not', () => {
expect(hrefSecurityContexts('[myDir]:not(link.someClass):not(base.someClass)')).toEqual([
SecurityContext.NONE, SecurityContext.URL, SecurityContext.RESOURCE_URL
]);
});
it('should return SecurityContext.NONE if there are no possible elements',
() => { expect(hrefSecurityContexts('img:not(img)')).toEqual([SecurityContext.NONE]); });
it('should return the union of the possible security contexts if multiple selectors are specified',
() => {
expect(calcPossibleSecurityContexts(registry, 'a,link', 'href', false)).toEqual([
SecurityContext.URL, SecurityContext.RESOURCE_URL
]);
});
});
});
}

View File

@ -124,7 +124,7 @@ export function main() {
new class extends NullVisitor{ new class extends NullVisitor{
visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;} visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;}
}, },
new BoundElementPropertyAst('foo', null, null, null, 'bar', null)); new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null));
}); });
it('should visit AttrAst', () => { it('should visit AttrAst', () => {
@ -171,7 +171,7 @@ export function main() {
new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null), new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null),
new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null), new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null),
new BoundEventAst('foo', 'bar', 'goo', null, null), new BoundEventAst('foo', 'bar', 'goo', null, null),
new BoundElementPropertyAst('foo', null, null, null, 'bar', null), new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null),
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null), new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null), new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
new BoundDirectivePropertyAst('foo', 'bar', null, null) new BoundDirectivePropertyAst('foo', 'bar', null, null)
@ -1495,8 +1495,12 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}), {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors: expect(() => parse('<div>', [dirB, dirA]))
More than one component: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`); .toThrowError(
`Template parse errors:\n` +
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
}); });
it('should not allow components or element bindings nor dom events on explicit embedded templates', it('should not allow components or element bindings nor dom events on explicit embedded templates',

View File

@ -8,7 +8,7 @@
import {ResourceLoader} from '@angular/compiler'; import {ResourceLoader} from '@angular/compiler';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isBlank, normalizeBlank} from './facade/lang'; import {isBlank} from './facade/lang';
/** /**
* A mock implementation of {@link ResourceLoader} that allows outgoing requests to be mocked * A mock implementation of {@link ResourceLoader} that allows outgoing requests to be mocked
@ -20,7 +20,7 @@ export class MockResourceLoader extends ResourceLoader {
private _requests: _PendingRequest[] = []; private _requests: _PendingRequest[] = [];
get(url: string): Promise<string> { get(url: string): Promise<string> {
var request = new _PendingRequest(url); const request = new _PendingRequest(url);
this._requests.push(request); this._requests.push(request);
return request.getPromise(); return request.getPromise();
} }
@ -33,7 +33,7 @@ export class MockResourceLoader extends ResourceLoader {
* The response given will be returned if the expectation matches. * The response given will be returned if the expectation matches.
*/ */
expect(url: string, response: string) { expect(url: string, response: string) {
var expectation = new _Expectation(url, response); const expectation = new _Expectation(url, response);
this._expectations.push(expectation); this._expectations.push(expectation);
} }
@ -90,7 +90,7 @@ export class MockResourceLoader extends ResourceLoader {
if (this._definitions.has(url)) { if (this._definitions.has(url)) {
var response = this._definitions.get(url); var response = this._definitions.get(url);
request.complete(normalizeBlank(response)); request.complete(response == null ? null : response);
return; return;
} }

View File

@ -26,7 +26,9 @@ export class MockSchemaRegistry implements ElementSchemaRegistry {
return value === void 0 ? true : value; return value === void 0 ? true : value;
} }
securityContext(tagName: string, property: string): SecurityContext { allKnownElementNames(): string[] { return Object.keys(this.existingElements); }
securityContext(selector: string, property: string, isAttribute: boolean): SecurityContext {
return SecurityContext.NONE; return SecurityContext.NONE;
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper, StringMapWrapper} from '../facade/collection'; import {StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {FILL_STYLE_FLAG} from './animation_constants'; import {FILL_STYLE_FLAG} from './animation_constants';
@ -57,7 +57,7 @@ export function balanceAnimationKeyframes(
// phase 2: normalize the final keyframe // phase 2: normalize the final keyframe
var finalKeyframe = keyframes[limit]; var finalKeyframe = keyframes[limit];
ListWrapper.insert(finalKeyframe.styles.styles, 0, finalStateStyles); finalKeyframe.styles.styles.unshift(finalStateStyles);
var flatenedFinalKeyframeStyles = flattenStyles(finalKeyframe.styles.styles); var flatenedFinalKeyframeStyles = flattenStyles(finalKeyframe.styles.styles);
var extraFinalKeyframeStyles: {[key: string]: string} = {}; var extraFinalKeyframeStyles: {[key: string]: string} = {};

View File

@ -7,7 +7,6 @@
*/ */
import {Optional, Provider, SkipSelf} from '../../di'; import {Optional, Provider, SkipSelf} from '../../di';
import {ListWrapper} from '../../facade/collection';
import {getTypeNameForDebugging, isPresent} from '../../facade/lang'; import {getTypeNameForDebugging, isPresent} from '../../facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref'; import {ChangeDetectorRef} from '../change_detector_ref';
@ -51,7 +50,7 @@ export class IterableDiffers {
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers { static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (isPresent(parent)) { if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories); var copied = parent.factories.slice();
factories = factories.concat(copied); factories = factories.concat(copied);
return new IterableDiffers(factories); return new IterableDiffers(factories);
} else { } else {

View File

@ -41,7 +41,7 @@ export class KeyValueDiffers {
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers { static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
if (isPresent(parent)) { if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories); var copied = parent.factories.slice();
factories = factories.concat(copied); factories = factories.concat(copied);
return new KeyValueDiffers(factories); return new KeyValueDiffers(factories);
} else { } else {

View File

@ -7,7 +7,7 @@
*/ */
import {Injector} from '../di'; import {Injector} from '../di';
import {ListWrapper, MapWrapper, Predicate} from '../facade/collection'; import {MapWrapper, Predicate} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {RenderDebugInfo} from '../render/api'; import {RenderDebugInfo} from '../render/api';
@ -92,8 +92,7 @@ export class DebugElement extends DebugNode {
if (siblingIndex !== -1) { if (siblingIndex !== -1) {
var previousChildren = this.childNodes.slice(0, siblingIndex + 1); var previousChildren = this.childNodes.slice(0, siblingIndex + 1);
var nextChildren = this.childNodes.slice(siblingIndex + 1); var nextChildren = this.childNodes.slice(siblingIndex + 1);
this.childNodes = this.childNodes = previousChildren.concat(newChildren, nextChildren);
ListWrapper.concat(ListWrapper.concat(previousChildren, newChildren), nextChildren);
for (var i = 0; i < newChildren.length; ++i) { for (var i = 0; i < newChildren.length; ++i) {
var newChild = newChildren[i]; var newChild = newChildren[i];
if (isPresent(newChild.parent)) { if (isPresent(newChild.parent)) {

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {BaseError, WrappedError} from '../facade/errors'; import {BaseError, WrappedError} from '../facade/errors';
import {stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
import {Type} from '../type'; import {Type} from '../type';
@ -17,7 +16,7 @@ import {ReflectiveKey} from './reflective_key';
function findFirstClosedCycle(keys: any[]): any[] { function findFirstClosedCycle(keys: any[]): any[] {
var res: any[] = []; var res: any[] = [];
for (var i = 0; i < keys.length; ++i) { for (var i = 0; i < keys.length; ++i) {
if (ListWrapper.contains(res, keys[i])) { if (res.indexOf(keys[i]) > -1) {
res.push(keys[i]); res.push(keys[i]);
return res; return res;
} }
@ -28,8 +27,8 @@ function findFirstClosedCycle(keys: any[]): any[] {
function constructResolvingPath(keys: any[]): string { function constructResolvingPath(keys: any[]): string {
if (keys.length > 1) { if (keys.length > 1) {
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys)); const reversed = findFirstClosedCycle(keys.slice().reverse());
var tokenStrs = reversed.map(k => stringify(k.token)); const tokenStrs = reversed.map(k => stringify(k.token));
return ' (' + tokenStrs.join(' -> ') + ')'; return ' (' + tokenStrs.join(' -> ') + ')';
} }
@ -88,7 +87,7 @@ export class AbstractProviderError extends BaseError {
export class NoProviderError extends AbstractProviderError { export class NoProviderError extends AbstractProviderError {
constructor(injector: ReflectiveInjector, key: ReflectiveKey) { constructor(injector: ReflectiveInjector, key: ReflectiveKey) {
super(injector, key, function(keys: any[]) { super(injector, key, function(keys: any[]) {
var first = stringify(ListWrapper.first(keys).token); const first = stringify(keys[0].token);
return `No provider for ${first}!${constructResolvingPath(keys)}`; return `No provider for ${first}!${constructResolvingPath(keys)}`;
}); });
} }
@ -167,7 +166,7 @@ export class InstantiationError extends WrappedError {
} }
get message(): string { get message(): string {
var first = stringify(ListWrapper.first(this.keys).token); var first = stringify(this.keys[0].token);
return `${this.originalError.message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`; return `${this.originalError.message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`;
} }

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {unimplemented} from '../facade/errors'; import {unimplemented} from '../facade/errors';
import {Type} from '../type'; import {Type} from '../type';
@ -17,8 +16,6 @@ import {AbstractProviderError, CyclicDependencyError, InstantiationError, NoProv
import {ReflectiveKey} from './reflective_key'; import {ReflectiveKey} from './reflective_key';
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider'; import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
var __unused: Type<any>; // avoid unused import when Type union types are erased
// Threshold for the dynamic version // Threshold for the dynamic version
const _MAX_CONSTRUCTION_COUNTER = 10; const _MAX_CONSTRUCTION_COUNTER = 10;
const UNDEFINED = new Object(); const UNDEFINED = new Object();
@ -286,8 +283,7 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
constructor( constructor(
public protoStrategy: ReflectiveProtoInjectorDynamicStrategy, public protoStrategy: ReflectiveProtoInjectorDynamicStrategy,
public injector: ReflectiveInjector_) { public injector: ReflectiveInjector_) {
this.objs = new Array(protoStrategy.providers.length); this.objs = new Array(protoStrategy.providers.length).fill(UNDEFINED);
ListWrapper.fill(this.objs, UNDEFINED);
} }
resetConstructionCounter(): void { this.injector._constructionCounter = 0; } resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
@ -297,9 +293,9 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
} }
getObjByKeyId(keyId: number): any { getObjByKeyId(keyId: number): any {
var p = this.protoStrategy; const p = this.protoStrategy;
for (var i = 0; i < p.keyIds.length; i++) { for (let i = 0; i < p.keyIds.length; i++) {
if (p.keyIds[i] === keyId) { if (p.keyIds[i] === keyId) {
if (this.objs[i] === UNDEFINED) { if (this.objs[i] === UNDEFINED) {
this.objs[i] = this.injector._new(p.providers[i]); this.objs[i] = this.injector._new(p.providers[i]);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper, MapWrapper} from '../facade/collection'; import {MapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {reflector} from '../reflection/reflection'; import {reflector} from '../reflection/reflection';
import {Type} from '../type'; import {Type} from '../type';
@ -170,7 +170,7 @@ export function mergeResolvedReflectiveProviders(
var resolvedProvider: ResolvedReflectiveProvider; var resolvedProvider: ResolvedReflectiveProvider;
if (provider.multiProvider) { if (provider.multiProvider) {
resolvedProvider = new ResolvedReflectiveProvider_( resolvedProvider = new ResolvedReflectiveProvider_(
provider.key, ListWrapper.clone(provider.resolvedFactories), provider.multiProvider); provider.key, provider.resolvedFactories.slice(), provider.multiProvider);
} else { } else {
resolvedProvider = provider; resolvedProvider = provider;
} }

View File

@ -10,8 +10,10 @@ import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {unimplemented} from '../facade/errors'; import {unimplemented} from '../facade/errors';
import {Type} from '../type'; import {Type} from '../type';
import {AppElement} from './element'; import {AppElement} from './element';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {AppView} from './view';
import {ViewRef} from './view_ref'; import {ViewRef} from './view_ref';
import {ViewUtils} from './view_utils'; import {ViewUtils} from './view_utils';
@ -75,7 +77,7 @@ export class ComponentRef_<C> extends ComponentRef<C> {
get changeDetectorRef(): ChangeDetectorRef { return this._hostElement.parentView.ref; }; get changeDetectorRef(): ChangeDetectorRef { return this._hostElement.parentView.ref; };
get componentType(): Type<any> { return this._componentType; } get componentType(): Type<any> { return this._componentType; }
destroy(): void { this._hostElement.parentView.destroy(); } destroy(): void { this._hostElement.parentView.detachAndDestroy(); }
onDestroy(callback: Function): void { this.hostView.onDestroy(callback); } onDestroy(callback: Function): void { this.hostView.onDestroy(callback); }
} }
@ -104,8 +106,15 @@ export class ComponentFactory<C> {
projectableNodes = []; projectableNodes = [];
} }
// Note: Host views don't need a declarationAppElement! // Note: Host views don't need a declarationAppElement!
var hostView = this._viewFactory(vu, injector, null); var hostView: AppView<any> = this._viewFactory(vu, injector, null);
var hostElement = hostView.create(EMPTY_CONTEXT, projectableNodes, rootSelectorOrNode); hostView.visitProjectableNodesInternal =
(nodeIndex: number, ngContentIndex: number, cb: any, ctx: any) => {
const nodes = projectableNodes[ngContentIndex] || [];
for (var i = 0; i < nodes.length; i++) {
cb(nodes[i], ctx);
}
};
var hostElement = hostView.create(EMPTY_CONTEXT, rootSelectorOrNode);
return new ComponentRef_<C>(hostElement, this._componentType); return new ComponentRef_<C>(hostElement, this._componentType);
} }
} }

View File

@ -7,7 +7,6 @@
*/ */
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
@ -23,11 +22,10 @@ import {ViewType} from './view_type';
* that is needed for later instantiations. * that is needed for later instantiations.
*/ */
export class AppElement { export class AppElement {
public nestedViews: AppView<any>[] = null; public nestedViews: AppView<any>[];
public componentView: AppView<any> = null; public componentView: AppView<any>;
public component: any; public component: any;
public componentConstructorViewQueries: QueryList<any>[];
constructor( constructor(
public index: number, public parentIndex: number, public parentView: AppView<any>, public index: number, public parentIndex: number, public parentView: AppView<any>,
@ -37,16 +35,38 @@ export class AppElement {
get vcRef(): ViewContainerRef_ { return new ViewContainerRef_(this); } get vcRef(): ViewContainerRef_ { return new ViewContainerRef_(this); }
initComponent( initComponent(component: any, view: AppView<any>) {
component: any, componentConstructorViewQueries: QueryList<any>[], view: AppView<any>) {
this.component = component; this.component = component;
this.componentConstructorViewQueries = componentConstructorViewQueries;
this.componentView = view; this.componentView = view;
} }
get parentInjector(): Injector { return this.parentView.injector(this.parentIndex); } get parentInjector(): Injector { return this.parentView.injector(this.parentIndex); }
get injector(): Injector { return this.parentView.injector(this.index); } get injector(): Injector { return this.parentView.injector(this.index); }
detectChangesInNestedViews(throwOnChange: boolean): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].detectChanges(throwOnChange);
}
}
}
destroyNestedViews(): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].destroy();
}
}
}
visitNestedViewRootNodes<C>(cb: (node: any, ctx: C) => void, c: C): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].visitRootNodesInternal(cb, c);
}
}
}
mapNestedViews(nestedViewClass: any, callback: Function): any[] { mapNestedViews(nestedViewClass: any, callback: Function): any[] {
var result: any[] /** TODO #9100 */ = []; var result: any[] /** TODO #9100 */ = [];
if (isPresent(this.nestedViews)) { if (isPresent(this.nestedViews)) {
@ -69,8 +89,8 @@ export class AppElement {
nestedViews = []; nestedViews = [];
this.nestedViews = nestedViews; this.nestedViews = nestedViews;
} }
ListWrapper.removeAt(nestedViews, previousIndex); nestedViews.splice(previousIndex, 1);
ListWrapper.insert(nestedViews, currentIndex, view); nestedViews.splice(currentIndex, 0, view);
var refRenderNode: any /** TODO #9100 */; var refRenderNode: any /** TODO #9100 */;
if (currentIndex > 0) { if (currentIndex > 0) {
var prevView = nestedViews[currentIndex - 1]; var prevView = nestedViews[currentIndex - 1];
@ -93,7 +113,7 @@ export class AppElement {
nestedViews = []; nestedViews = [];
this.nestedViews = nestedViews; this.nestedViews = nestedViews;
} }
ListWrapper.insert(nestedViews, viewIndex, view); nestedViews.splice(viewIndex, 0, view);
var refRenderNode: any /** TODO #9100 */; var refRenderNode: any /** TODO #9100 */;
if (viewIndex > 0) { if (viewIndex > 0) {
var prevView = nestedViews[viewIndex - 1]; var prevView = nestedViews[viewIndex - 1];
@ -108,7 +128,7 @@ export class AppElement {
} }
detachView(viewIndex: number): AppView<any> { detachView(viewIndex: number): AppView<any> {
var view = ListWrapper.removeAt(this.nestedViews, viewIndex); const view = this.nestedViews.splice(viewIndex, 1)[0];
if (view.type === ViewType.COMPONENT) { if (view.type === ViewType.COMPONENT) {
throw new Error(`Component views can't be moved!`); throw new Error(`Component views can't be moved!`);
} }

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