Compare commits

...

156 Commits
2.4.1 ... 2.4.9

Author SHA1 Message Date
80fe41a88e release: cut v2.4.9 2017-03-01 23:11:26 -08:00
af652a7c8b docs: add release notes for 2.4.9 2017-03-01 23:11:08 -08:00
de36f8a3b9 revert: fix(router): do not finish bootstrap until all the routes are resolved (#14327)
This reverts commit 541de26f7e because it introduced a regression.

Closes #14681, #14588
2017-02-27 14:37:33 -08:00
b658fa9ea0 fix(http): Make ResponseOptionsArgs an interface
closes #13708
2017-02-20 17:34:30 -08:00
2a123463ac fix(router): improve robustness (#14602)
sync a 4.x change from https://github.com/angular/angular/pull/14155
2017-02-20 16:59:28 -08:00
4f93ac8762 release: cut v2.4.8 2017-02-18 13:55:23 -08:00
37ec5b9c1a docs: add changelog for 2.4.8 2017-02-18 13:54:34 -08:00
612950bdb2 test: pin down @types/* dependencies in typings test (#14569)
This is needed because the latest versions are no longer compatible with typescript 1.8 which results in build errors:
https://travis-ci.org/angular/angular/jobs/202752040#L3863
2017-02-17 14:35:56 -08:00
3804ad1d23 revert: build: first pass of de-duplicating tsconfig.json content (#14369)
This reverts commit 1c112ae66e.

First failed build (one commit after the change being reverted): https://travis-ci.org/angular/angular/jobs/202414385#L2511
2017-02-17 12:15:49 -08:00
1b1f228525 revert: build: update jasmine to 2.4 (#14362)
This reverts commit d6a8b0b686.

Jasmine 2.4 requires new typings, which require typescript 2.

See CI failure https://travis-ci.org/angular/angular/jobs/202412220#L3859
2017-02-17 11:57:17 -08:00
19e9094275 docs(router): fix broken link (#14431)
Closes #14430
2017-02-16 15:13:43 -08:00
8ff3ab0e6d docs(router): fix guards API docs (#14528) 2017-02-16 15:13:30 -08:00
9e8d740a96 ci: fix getLatestLabel (#14535) 2017-02-16 15:12:32 -08:00
3a3a100b27 build: update .pullapprove (#14506)
* build: update .pullapprove

Add tbosch/vicb/chuckjaz to more projects.

* Update .pullapprove.yml
2017-02-16 15:02:42 -08:00
f0575e014c fix(compiler): REVERT allow absolute style urls (#14365)
This reverts commit 6b9aa2ca3d.
2017-02-16 14:51:01 -08:00
ea7737ee11 fix(forms): getRawValue should correctly work with nested FormGroups/Arrays (#12964)
Closed #12963

PR Close #12964
2017-02-16 14:50:49 -08:00
fadaf1e01a fix(platform-browser): should only add styles with native encapsulation in shadow DOM (#14313)
Closes #7887

PR Close #14313
2017-02-16 14:50:11 -08:00
c716532ff2 test(forms): test undefined as argument to forms (#13720)
PR Close #13720
2017-02-16 13:49:33 -08:00
1c112ae66e build: first pass of de-duplicating tsconfig.json content (#14369)
PR Close #14369
2017-02-16 13:47:10 -08:00
193a0ae4a0 fix(compiler): allow absolute style urls (#14365)
Closes #4974

PR Close #14365
2017-02-16 13:46:42 -08:00
9ceb5d1afe fix(http): REVERT: remove dots from jsonp callback name (#13219)
This reverts commit 9e5617e41e.
2017-02-16 13:45:48 -08:00
d6a8b0b686 build: update jasmine to 2.4 (#14362)
PR Close #14362
2017-02-16 13:44:47 -08:00
3e216dd4ad ci: find latest tag when deeper than the git clone depth (#14231)
Since we have a shallow clone of the repository, it might be the case that the
latest tag (which we need for publishing the build artifacts) might not be in
the current history.

This commit incrementally deepens the clone until it finds a tag (or reaches a
max depth).

PR Close #14231
2017-02-16 13:43:15 -08:00
7b0aba4655 ci: bump node version to 6.9.5 and npm to 3.10.7
this is required by @angular/cli
2017-02-16 13:42:25 -08:00
7c87c52c38 fix(upgrade): pass correct values to ngOnChanges for interpolation bindings (#14400)
Previously, the `previousValue` and `currentValue` arguments passed to the
`SimpleChange` constructor were swapped for interpolation bindings.

This commit also refactors the code, so that interpolation bindings and property
bindings share the same implementation, and fixes some broken tests (that hide
failures by allowing the `$exceptionHandler` to swallow thrown exceptions).

This is the same as #14301, but for the 2.4.x branch.
2017-02-10 12:53:26 -08:00
541de26f7e fix(router): do not finish bootstrap until all the routes are resolved (#14327)
Fixes #12162
2017-02-09 11:59:08 -08:00
74cb575219 fix(upgrade): correctly project content on downgraded components with structural directives (#14274)
Previously, downgraded component adapters were compiling and projecting the
contents of the template element instead of the link-element. This didn't make
any difference is most cases (as the elements are the same), but broke with
structural directives (e.g. `ngRepeat`), which compile the orginal (template)
element once and then create and link clones of it.

This commit fixes it by always compiling and projecting the contents of the
correct (link) element.

Fixes #14260
2017-02-09 10:07:11 -08:00
e90661aaee docs(changelog): add changelog for 2.4.7 2017-02-08 20:28:11 -08:00
73034c75bd chore(release): cut the 2.4.7 release 2017-02-08 20:26:10 -08:00
059085b943 docs(compiler): incorrect method reference (#14314)
PR Close #14314
2017-02-08 20:23:36 -08:00
8ab89153e0 docs(core): fix typo (#14299)
Replace `than` with `then`.

PR Close #14299
2017-02-08 20:23:36 -08:00
NFM
82923d8314 docs(router): fix typos (#14213)
PR Close #14213
2017-02-08 20:23:36 -08:00
6921b3d21d docs(zone): fix whitespace around backtick code
If there is no leading empty line then the markdown renderers get confused.
2017-02-08 20:23:36 -08:00
c0ef8b25a6 ci: add petebacondarwin to the angular.io pullapprove group (#14268)
PR Close #14268
2017-02-08 20:08:20 -08:00
e845f3b226 ci: add 'public-api' pullapprove group (#14268) 2017-02-08 20:08:20 -08:00
9779f397b7 test(compiler): add integration like tests to compiler unit tests (#14157)
Closes PR #14157

PR Close #14157
2017-02-08 20:08:20 -08:00
5bb47db887 fix(upgrade): allow non-element selectors for downgraded components (#14291)
This affects the dynamic version of `upgrade` and makes it more consistent with
the static version, while removing an artificial limitation.

(This commit backports the fix from 9aafdc7 to 2.4.x.)
2017-02-07 10:02:11 -08:00
343ee8a3a2 docs(changelog): add changelog for 2.4.6 2017-02-02 20:04:17 -08:00
223b5eb367 chore(release): cut the 2.4.6 release 2017-02-02 19:18:08 -08:00
7e639aac15 fix: ngModel should use rxjs/symbol/observable to detect observable (#14236)
PR closes #14236
2017-02-02 19:18:08 -08:00
83dafd3054 ci: increase git fetch depth to 150 2017-02-02 19:18:08 -08:00
e641636624 fix(common): DatePipe parses input string if it's not a valid date in browser (#13895)
Closes #12334
Closes #13874

PR Close #13895
2017-02-02 19:18:08 -08:00
c409860a9f refactor(common): remove isDate from facade (#13895) 2017-02-02 19:18:07 -08:00
0101aa31d6 ci: fix .pullapprove.yaml'sfile conditions (#14214)
According to [the docs](http://docs.pullapprove.com/groups/conditions/), the correct keywords are `include`/`exclude`, without the trailing `s`.
2017-02-02 19:18:07 -08:00
a5b4af0fdd fix(language-service): do not crash when Angular cannot be located (#14123)
Fixes #14122

PR Close #14123
2017-02-02 19:18:07 -08:00
d9420311ca docs(forms): fix FormArray description (#14094)
Closes #14075

PR Close #14094
2017-02-02 18:50:40 -08:00
774e1db87c fix(forms): Verify functions passed into async validators returns Observable or Promise (#14053) 2017-02-02 18:50:40 -08:00
109f0d16ef fix(common): introduce isObservable method (#14067)
Closes #8848

PR Close #14067
2017-02-02 18:50:40 -08:00
71567d1eee fix(common): add PopStateEvent interface (#13400)
Closes #13378

PR Close #13400
2017-02-02 18:50:40 -08:00
bb71acc172 build: fix red travis: fetch more github history (#14193) 2017-02-02 16:02:55 -08:00
e98d6f0912 ci: fix compiler-cli paths (#14177) 2017-02-02 16:02:55 -08:00
1dbebb184f ci: fix pullapprove groups and conditions (#14167)
- restrict root to be just root
- add fallback users to all groups
- fix indentation
- change order of users so that primary reviewers are first, follow by alpha-sorted secondaries, followed by fallback reviewers
2017-02-02 16:02:55 -08:00
8882b86b54 fix(common) add interface PipeTransform to Async pipe (#14049)
PR Close #14049
2017-02-02 16:02:54 -08:00
0965636735 fix(router): fix CanActivateChild guard provided in a lazy loaded module (#13989)
Closes #12275

PR Close #13989
2017-02-02 16:02:54 -08:00
4d2901d480 fix(router): fix navigation from the root component ngOnInit hook (#13932)
Closes #13795

PR Close #13932
2017-02-02 16:02:54 -08:00
a047124e1a fix(router): fix CanActivate redirect to the root on initial load (#13929)
Closes #13530

PR Close #13929
2017-02-02 16:02:54 -08:00
09e2d20e22 fix(forms): select shows blank line when nothing is selected in IE/Edge (#13903)
Closes #10010

PR Close #13903
2017-02-02 16:02:54 -08:00
e3bdf82c0d docs(developer): add description of npm-run to run locally installed npm scripts (#13765)
PR Close #13765
2017-02-02 16:02:54 -08:00
0614289608 fix(platform-browser): remove style nodes on destroy (#13744)
Closes #11746

PR Close #13744
2017-02-02 16:02:53 -08:00
7c344a4e49 refactor(platform-browser): polishing (#13744) 2017-02-02 16:02:53 -08:00
250dbc4bc8 fix(core): add bootstrapped modules into platform modules list (#13740)
Closes #12015

PR Close #13740
2017-02-02 16:02:53 -08:00
70bbdf55da fix(testing): async/fakeAsync/inject/withModule helpers should pass through context to callback functions (#13718)
Make sure that context (`this`) that is passed to functions generated by test helpers is passed through to the callback functions. Enables usage of Jasmine's variable sharing system to prevent accidental memory leaks during test runs.
2017-02-02 16:02:53 -08:00
41b8d95fa7 fix(core): ViewContainerRef.indexOf doesn't throw error when empty (#13220)
PR Close #13220
2017-02-02 16:02:53 -08:00
1eece5046d fix(http): remove dots from jsonp callback name (#13219)
PR Close #13219
2017-02-02 16:02:52 -08:00
1ef3eeecbd docs: update COMITTER.md with info about pullapprove.com 2017-02-02 16:02:52 -08:00
94500e0fad ci: configure pullapprove to cover the whole repository 2017-02-02 16:02:52 -08:00
gc
dd53606f69 docs(public_api): change description (#13583)
* doc(public_api): change description

Benchpress has been moved to angular/angular in modules/@angular/benchpress

* docs(public_api): change description

Here means 'other projects',like angular-cli, Angular Material. And as we know, benchpress project has been moved to angular/angular in modules/@angular/benchpress. It should not be 'other projects'.
2017-02-02 16:02:52 -08:00
6c8b5dda87 style(docs): update copyright years (#13736) 2017-02-02 16:02:52 -08:00
458ccc1aff refactor(core): simplify ReflectiveInjector by removing code for Dart implementation (#14126)
ReflectiveInjector previously used two strategies for resolving dependencies. These
were to support the Dart implementation, but are no longer needed. A result of this
PR is there is no longer a 20 dependency limit and the generated code is smaller.

PR Close #14126
2017-02-02 16:01:04 -08:00
07cfd8c432 docs: remove obsolete bundles/overview.md file (#14132) 2017-02-02 16:01:03 -08:00
23bd0fbfc1 docs(http): vanilla links do not need link tags (#14097) 2017-02-02 16:32:23 -06:00
3d1e536143 docs(router): remove invalid jsdoc tags (#14097)
The `@selector` tags are not valid.
Dgeni should be able to extract this information
from the directive annotation metadata.
2017-02-02 16:31:49 -06:00
c827097610 ci: add pullapprove config for angular.io 2017-02-02 16:31:49 -06:00
8d4aa82c04 fix(i18n): parse ICU messages while normalizing templates (#14153)
Fixes:
- Inject the i18n specific HtmlParser into the directive normalizer,
- Parse ICU messages while normalizing templates,
- Normalize (visit) the content of ICU messages.

🎄🎁🎅
2017-01-31 21:00:32 -08:00
14e97516cb refactor(abstract): Use abstract keyword where possible to decrease file size. (#14112)
PR Close: #14112
2017-01-27 12:52:12 -08:00
bc47a8cc74 refactor(compiler): add ability to get the context around a ParseLocation (#14113) 2017-01-27 12:55:54 -06:00
32cc6759ef fix(common): DatePipe doesn't throw for NaN (#14117)
Fixes #14103

PR Close #14117
2017-01-27 12:55:28 -06:00
d5f1419afe refactor(size): Use abstract keyword where possible to decrease file size. (#14112) 2017-01-27 12:55:20 -06:00
117fa79c7c fix(upgrade): detect async downgrade component changes (#14039)
This commit effectively reverts 7e0f02f but for `upgrade/static`
as it was an invalid fix for #6385, that created a more significant
bug, which was that changes were not always being detected.

Angular 1 digests should be run inside the ngZone to ensure
that async changes are detected.

We don't know how to fix #6385 without breaking change detection
at this stage. That issue is triggered by async operations, such as
`setTimeout`, being triggered inside scope watcher functions.

One could argue that watcher functions should be pure and not do
work such as triggering async operations. It is possible that the
original use case could be supported by moving the debounce
logic into the watch listener function, which is only called if the
watched value actually changes.

See #13812

PR Close #14039
2017-01-27 12:53:48 -06:00
777ba46837 refactor(compiler): improve error messages in aot compiler (#14017)
Previously aot compiler prints stack traces when it fails to resolve.
New behavior: aot compiler outputs the error message.
Example: https://gist.github.com/bowenni/a7fe81d916e8cd4a06b0e133436f40fb

PR Close #14017
2017-01-27 12:53:43 -06:00
f3d55068a8 fix(compiler): allow empty translations for attributes (#14085)
fixes #13897
2017-01-27 12:53:13 -06:00
7ed39ebaaf docs(changelog): add changelog for 2.4.5 2017-01-25 13:48:29 -08:00
091f0a5aaa chore(release): cut the 2.4.5 release 2017-01-25 13:48:21 -08:00
315606e02c style(compiler): run format 2017-01-25 13:21:04 -08:00
5ea373d184 docs(core): add docs for AnimationStyles and AnimationKeyframe (#14107) 2017-01-25 11:51:02 -08:00
6e36bb7b20 docs(compiler): add comment to warn about regexp changes (#14106)
ref #14082
2017-01-25 11:50:55 -08:00
3b2fb23805 fix(upgrade/static): ensure upgraded injector is initialized early enough (#14065)
This change ensures that the upgraded AngularJS injector is initialized
before the application run blocks are executed.

Closes #13811
2017-01-25 11:49:59 -08:00
bd2eecb4de fix(compiler): fix regexp to support firefox 31 (#14082)
fixes #14029
closes #13900
2017-01-25 11:44:09 -08:00
3d351a4f5f fixup: remove message.id check from this branch 2017-01-25 11:43:16 -08:00
5492fada21 fix(compiler): [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n
There are restrictions on the character set that can be used for xmb and xtb
placeholder names.

However because changing the placeholder names would change the message IDs it
is not possible to add those restrictions to the names used internally. Then we
have to map internal name to public names when generating an xmb file and back
when translating using an xtb file.

Note for implementors of `Serializer`:
- When writing a file, the implementor should take care of converting the
internal names to public names while visiting the message nodes - this is
required because the original nodes are needed to compute the message ID.
- When reading a file, the implementor does not need to take care of the mapping
back to internal names as this is handled in the `I18nToHtmlVisitor` used by the
`TranslationBundle`.

fixes b/34339636
2017-01-25 10:35:03 -08:00
fd4f9acbcf fix(core): export animation classes required for Renderer impl (#14002)
Closes #14001
2017-01-25 10:32:16 -08:00
48528a86e1 docs(common): fix a typo on the DatePipe API docs (#14060) 2017-01-25 10:32:08 -08:00
80364def27 ci: bump node and npm versions in circle.yaml to match travis 2017-01-25 10:31:50 -08:00
1803beb4d5 Fixed documentation reference to canActivate in canDeactivate (#14018)
Simple update to code sample which references canActivate: ['canDeactivateTeam'].
2017-01-25 10:31:42 -08:00
3bcba8a570 chore(docs): add missing comments (#14003)
This is a load-bearing change to avoid duplicate licenses in closure-compiled bundles.
See https://github.com/angular/tsickle/issues/332
2017-01-25 10:30:46 -08:00
84542d8ae7 docs(changelog): add changelog for 2.4.4 2017-01-18 18:35:54 -06:00
17cb3ec565 chore(release): cut the 2.4.4 release 2017-01-18 18:32:57 -06:00
015878afe6 fix(http): don't create a blob out of ArrayBuffer when type is application/octet-stream (#13992)
Closes #13973
2017-01-18 18:28:37 -06:00
2af58622c1 fix(router): enable loadChildren with function in aot (#13909)
Closes #11075
2017-01-18 18:28:02 -06:00
7ffd10541d refactor(core): remove an unused import in application_ref (#13901) 2017-01-18 18:27:52 -06:00
481b099d82 docs(CHANGELOG): added reference to closed issue in CHANGELOG for informational purposes (#13985) 2017-01-18 18:27:25 -06:00
49c4b0fa92 fix(router): routerLinkActive should not throw when not initialized (#13273)
Fixes #13270

PR Close #13273
2017-01-18 18:27:14 -06:00
b8b6b1d27a refactor(router): clean up RouterLinkActive (#13273)
PR Close #13273
2017-01-18 18:27:03 -06:00
892b5ba950 chore(tsc-wrapped): update tsickle to latest (#13471) 2017-01-18 18:26:37 -06:00
bd15110c7d feat(security): allow calc and gradient functions. (#13943)
PR Close #13943

Also includes support for # color notation in function arguments (common
in gradient functions).
2017-01-18 18:25:45 -06:00
2250082fd7 fix(upgrade): detect async downgrade component changes (#13812)
This commit effectively reverts 7e0f02f96e
as it was an invalid fix for #6385, that created a more significant
bug, which was that changes were not always being detected.

Angular 1 digests should be run inside the ngZone to ensure
that async changes are detected.

We don't know how to fix #6385 without breaking change detection
at this stage. That issue is triggered by async operations, such as
`setTimeout`, being triggered inside scope watcher functions.

One could argue that watcher functions should be pure and not do
work such as triggering async operations. It is possible that the
original use case could be supported by moving the debounce
logic into the watch listener function, which is only called if the
watched value actually changes.

Closes #10660, #12318, #12034

PR Close #13812
2017-01-18 18:21:29 -06:00
87316c52db test(upgrade): reorganise test layout (#13812) 2017-01-18 18:21:24 -06:00
606b76d9bb chore(compiler-cli): Move calculateEmitPath into CompilerHost (#13904)
This is so that it can be overriden in an environment specific CompilerHost(like within Google) to customize the output paths.

PR Close #13904
2017-01-18 18:21:09 -06:00
3d0b1b8184 fix(common): support numeric value as discrete cases for NgPlural (#13876)
PR Close #13876
2017-01-18 18:20:56 -06:00
261fd16780 fix(animations): fix internal jscompiler issue and AOT quoting (#13798)
CL #143630929
PR Close #13798
2017-01-18 18:20:47 -06:00
104cc42f6d docs(http): Spelling Fix #13867 2017-01-18 18:20:30 -06:00
a7d28044c5 docs(changelog): add changelog for 2.4.3 2017-01-11 13:38:23 -08:00
055bea2969 chore(release): cut v2.4.3 2017-01-11 13:38:16 -08:00
dad0d21b89 chore(owners): configure pullapprove.com 2017-01-11 11:35:31 -08:00
313683f6f3 fix(compiler-cli): avoid handling functions in loadChildren as lazy load routes paths
The change avoids the compiler CLI internal API from mismatching the following case as lazy loading

```
import { NonLazyLoadedModule } from './non-lazy-loaded/non-lazy-loaded.module';

export function getNonLazyLoadedModule() { return NonLazyLoadedModule; }

export const routes = [
{ path: '/some-path', loadChildren: getNonLazyLoadedModule }
];
```

The output of the check is later passed to `RouteDef.fromString()`, so, it makes sense to be only a string.

Fixes angular/angular-cli#3204
2017-01-11 11:35:23 -08:00
338be6d6a5 refactor(common): remove some facade usages 2017-01-11 11:34:03 -08:00
4b56f79328 refactor(test): <template>/<ng-container>/*-directives
- remove outer `<div>` in tests,
- use `<ng-container>` instead of `<template>` where possible,
- use *... instead of template (tag or attr) where possible.

Fixes #13816
2017-01-11 11:33:30 -08:00
d7f2a3c71b fix(i18n): translate attributes inside elements marked for translation 2017-01-10 17:15:42 -08:00
1c929ae244 docs(NgPlural): fix API docs
Fixes #13786
2017-01-10 16:51:52 -08:00
83d0ff6d13 refactor(Compiler): misc cleanup 2017-01-10 16:50:20 -08:00
d43e5dd44d chore(benchmarks): change var to let 2017-01-09 16:08:33 -08:00
61ba223c1a fix(router): throw an error when navigate to null/undefined path
Closes #10560
Fixes #13384
2017-01-09 18:55:31 -05:00
6164eb25f3 fix(compiler-cli): add support for more than 2 levels of nested lazy routes
This change adds Compiler CLI support for any level of nesting for lazy routes.

For example `{app-root}/lazy-loaded-module-1/lazy-loaded-module-2/lazy-loaded-module-3`

Where `lazy-loaded-module-3` is lazy loaded from `lazy-loaded-module-2`,
and `lazy-loaded-module-2` is lazy loaded from module `lazy-loaded-module-1`,
and `lazy-loaded-module-1` is lazy loaded from `AppModule`

Fixes angular/angular-cli#3663
2017-01-09 18:06:26 -05:00
5e9d3dba3a fix(compiler): avoid evaluating arguments to unknown decorators
Fixes #13605
2017-01-09 18:06:11 -05:00
16922655ca fix(Router): fix checking for object intersection 2017-01-09 18:06:03 -05:00
7dc12b93fe fix(Compiler): fix template binding parsing (*directive="-...")
fixes #13800
2017-01-09 16:50:14 -05:00
1c82b58185 fix(router): RouterLink mirrors input target as attribute
Closes #13837
2017-01-09 16:50:06 -05:00
d6c414c08f fix: correctly show error when karma fails to load 2017-01-09 16:49:01 -05:00
d25d1730c7 chore(tsc-wrapped): bump version number to 0.5.1 2017-01-06 12:46:07 -08:00
03b35d2e8f docs(changelog): add release notes for 2.4.2 2017-01-06 12:40:35 -08:00
722543739e chore(release): cut the 2.4.2 release 2017-01-06 12:37:50 -08:00
56b4296a09 fix(language-service): support TypeScript 2.1 (#13655)
@angular/language-service now supports using TypeScript 2.1 as the
the TypeScript host. TypeScript 2.1 is now also partially supported
in `ngc` but is not recommended as Tsickle does not yet support 2.1.
2017-01-06 11:00:12 -08:00
f1cde4339b fix(core): animations no longer silently exits if the element is not apart of the DOM (#13763) 2017-01-06 11:00:12 -08:00
b245b920a6 fix(core): animations should blend in all previously transitioned styles into next animation if interrupted (#13148) 2017-01-06 11:00:12 -08:00
f47a71689c refactor: remove unused imports 2017-01-06 11:00:11 -08:00
6be55cc214 fix(Common): allow null/undefined values for NgForTrackBy
Reverts a breaking change introduced in 2.4.1 by #13420
fixes #13641
2017-01-06 11:00:11 -08:00
504199cf5a docs(common): add an example how to bind multiple classes based on a single parameter (#13779)
Closes #13778
2017-01-06 11:00:11 -08:00
17c5fa9293 fix(forms): Validators.required properly validate arrays (#13362)
Closes #12274
2017-01-06 11:00:11 -08:00
5f49c3ed23 fix(common): do not override locale provided on bootstrap (#13654)
Closes #13607
2017-01-06 11:00:11 -08:00
ebba63057f docs(developer): add linting section and correct command to verify API changes 2017-01-06 11:00:11 -08:00
5058461af7 feat(benchmarks): add detectChanges test for ng2 tree benchmark 2017-01-06 11:00:11 -08:00
21f5f05893 fix(core): Remove reference to "Angular 2" in dev mode warning (#13751) 2017-01-06 11:00:11 -08:00
f2ee81fa7a Typo (#13698) 2017-01-06 11:00:11 -08:00
ae1029da35 docs(Http): fix and extend samples for testing/MockBackend (#13689)
Fix samples for MockBackend and MockBackend.connections that were outdated. Also extend central sample for MockBackend to ease getting started.
2017-01-06 11:00:10 -08:00
230e33f3f1 fix(compiler): don’t throw when using ANALYZE_FOR_ENTRY_COMPONENTS with user classes (#13679)
Fixed #13565
2017-01-05 21:21:33 -08:00
ec0ca01224 docs(Core): fix API docs for ContentChild and ViewChildren (#13656)
Move the documentations of the ContentChild and ViewChildren decorators
so that they appear correctly on angular.io.

Closes #13625
2017-01-05 21:21:22 -08:00
1cd73c7a79 fix(compiler): query <template> elements before their children. (#13677)
Fixes #13118
Closes #13167
2017-01-05 21:20:56 -08:00
9f6a647908 fix(router): update route snapshot before emit new values (#13558)
Closes #12912
2017-01-05 21:20:45 -08:00
29ffdfdffe fix(Compiler): allow "." in attribute selectors (#13653)
fixes #13645
2017-01-05 21:14:54 -08:00
5754ecc3e1 fix(router): fix lazy loaded module with wildcard route (#13649)
Closes #12955
2017-01-05 21:14:42 -08:00
dab15c79dd chore(tslint): update tslint to 4.x (#13603) 2017-01-05 21:14:28 -08:00
21942a88f0 fix(upgrade): fix/improve support for lifecycle hooks (#13020)
With the exception of `$onChanges()`, all lifecycle hooks in ng1 are called on
the controller, regardless if it is the binding destination or not (i.e.
regardless of the value of `bindToController`).

This change makes `upgrade` mimic that behavior when calling lifecycle hooks.

Additionally, calling the `$onInit()` hook has been moved before calling the
linking functions, which also mimics the ng1 behavior.
2017-01-05 21:14:14 -08:00
018865ee6b fix(router): routerLink support of null/undefined (#13380)
Closes #6971
2017-01-05 21:11:56 -08:00
f7234378b6 fix(common): add link to trackBy docs (#13634) 2017-01-05 21:11:34 -08:00
5f47583c94 fixed minor typo (#13626) 2017-01-05 21:06:13 -08:00
0e7f9f0bff fix(testing): improve misleading error message when don't call compileComponents (#13543)
Closes #11301
2017-01-05 21:05:39 -08:00
269 changed files with 5516 additions and 2960 deletions

2
.nvmrc
View File

@ -1 +1 @@
6.6.0 6.9.5

248
.pullapprove.yml Normal file
View File

@ -0,0 +1,248 @@
# Configuration for pullapprove.com
#
# Approval access and primary role is determined by info in the project ownership spreadsheet:
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
#
# === GitHub username to Full name map ===
#
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# chuckjaz - Chuck Jazdzewski
# gkalpak - George Kalpakas
# IgorMinar - Igor Minar
# kara - Kara Erickson
# matsko - Matias Niemelä
# mhevery - Misko Hevery
# petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# tbosch - Tobias Bosch
# vicb - Victor Berchet
# vikerman - Vikram Subramanian
version: 2
group_defaults:
required: 1
reset_on_reopened:
enabled: true
approve_by_comment:
enabled: false
groups:
root:
conditions:
files:
include:
- "*"
exclude:
- "angular.io/*"
- "integration/*"
- "modules/*"
- "tools/*"
users:
- IgorMinar
- mhevery
public-api:
conditions:
files:
include:
- "tools/public_api_guard/*"
users:
- IgorMinar
- mhevery
build-and-ci:
conditions:
files:
include:
- "*.yml"
- "*.json"
- "*.lock"
- "tools/*"
exclude:
- "tools/@angular/tsc-wrapped/*"
- "tools/public_api_guard/*"
users:
- IgorMinar #primary
- mhevery
integration:
conditions:
files:
- "integration/*"
users:
- alexeagle
- mhevery
- tbosch
- vicb
- IgorMinar #fallback
core:
conditions:
files:
- "modules/@angular/core/*"
users:
- tbosch #primary
- mhevery
- vicb
- IgorMinar #fallback
compiler/animations:
conditions:
files:
- "modules/@angular/compiler/src/animation/*"
users:
- matsko #primary
- tbosch
- IgorMinar #fallback
- mhevery #fallback
compiler/i18n:
conditions:
files:
- "modules/@angular/compiler/src/i18n/*"
users:
- vicb #primary
- tbosch
- IgorMinar #fallback
- mhevery #fallback
compiler:
conditions:
files:
- "modules/@angular/compiler/*"
users:
- tbosch #primary
- vicb
- chuckjaz
- mhevery
- IgorMinar #fallback
compiler-cli:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "modules/@angular/compiler-cli/*"
users:
- alexeagle
- chuckjaz
- tbosch
- IgorMinar #fallback
- mhevery #fallback
common:
conditions:
files:
- "modules/@angular/common/*"
users:
- pkozlowski-opensource #primary
- vicb
- IgorMinar #fallback
- mhevery #fallback
forms:
conditions:
files:
- "modules/@angular/forms/*"
users:
- kara #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
http:
conditions:
files:
- "modules/@angular/http/*"
users:
- vikerman #primary
- alxhub
- IgorMinar #fallback
- mhevery #fallback
language-service:
conditions:
files:
- "modules/@angular/language-service/*"
users:
- chuckjaz #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
router:
conditions:
files:
- "modules/@angular/router/*"
users:
- vicb #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
upgrade:
conditions:
files:
- "modules/@angular/upgrade/*"
users:
- petebacondarwin #primary
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
platform-browser:
conditions:
files:
- "modules/@angular/platform-browser/*"
users:
- tbosch #primary
- vicb #secondary
- IgorMinar #fallback
- mhevery #fallback
platform-server:
conditions:
files:
- "modules/@angular/platform-server/*"
users:
- vikerman #primary
- alxhub
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
platform-webworker:
conditions:
files:
- "modules/@angular/platform-webworker/*"
users:
- vicb #primary
- tbosch #secondary
- IgorMinar #fallback
- mhevery #fallback
benchpress:
conditions:
files:
- "modules/@angular/benchpress/*"
users:
- tbosch #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
angular.io:
conditions:
files:
- "angular.io/*"
users:
- IgorMinar
- robwormald
- petebacondarwin
- mhevery #fallback

View File

@ -1,7 +1,7 @@
language: node_js language: node_js
sudo: false sudo: false
node_js: node_js:
- '6.6.0' - '6.9.5'
addons: addons:
# firefox: "38.0" # firefox: "38.0"

View File

@ -1,3 +1,143 @@
<a name="2.4.9"></a>
## [2.4.9](https://github.com/angular/angular/compare/2.4.8...2.4.9) (2017-03-02)
### Bug Fixes
* **http:** Make ResponseOptionsArgs an interface ([b658fa9](https://github.com/angular/angular/commit/b658fa9)), closes [#13708](https://github.com/angular/angular/issues/13708)
* **router:** improve robustness ([#14602](https://github.com/angular/angular/issues/14602)) ([2a12346](https://github.com/angular/angular/commit/2a12346))
### Reverts
* fix(router): do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([de36f8a](https://github.com/angular/angular/commit/de36f8a)), closes [#14681](https://github.com/angular/angular/issues/14681) [#14588](https://github.com/angular/angular/issues/14588)
<a name="2.4.8"></a>
## [2.4.8](https://github.com/angular/angular/compare/2.4.7...2.4.8) (2017-02-18)
### Bug Fixes
* **forms:** getRawValue should correctly work with nested FormGroups/Arrays ([#12964](https://github.com/angular/angular/issues/12964)) ([ea7737e](https://github.com/angular/angular/commit/ea7737e)), closes [#12963](https://github.com/angular/angular/issues/12963)
* **http:** REVERT: remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([9ceb5d1](https://github.com/angular/angular/commit/9ceb5d1))
* **platform-browser:** should only add styles with native encapsulation in shadow DOM ([#14313](https://github.com/angular/angular/issues/14313)) ([fadaf1e](https://github.com/angular/angular/commit/fadaf1e)), closes [#7887](https://github.com/angular/angular/issues/7887)
* **router:** do not finish bootstrap until all the routes are resolved ([#14327](https://github.com/angular/angular/issues/14327)) ([541de26](https://github.com/angular/angular/commit/541de26)), closes [#12162](https://github.com/angular/angular/issues/12162)
* **upgrade:** correctly project content on downgraded components with structural directives ([#14274](https://github.com/angular/angular/issues/14274)) ([74cb575](https://github.com/angular/angular/commit/74cb575)), closes [#14260](https://github.com/angular/angular/issues/14260)
* **upgrade:** pass correct values to `ngOnChanges` for interpolation bindings ([#14400](https://github.com/angular/angular/issues/14400)) ([7c87c52](https://github.com/angular/angular/commit/7c87c52))
<a name="2.4.7"></a>
## [2.4.7](https://github.com/angular/angular/compare/2.4.6...2.4.7) (2017-02-09)
### Bug Fixes
* **upgrade:** allow non-element selectors for downgraded components ([#14291](https://github.com/angular/angular/issues/14291)) ([5bb47db](https://github.com/angular/angular/commit/5bb47db))
<a name="2.4.6"></a>
## [2.4.6](https://github.com/angular/angular/compare/2.4.5...2.4.6) (2017-02-03)
### Bug Fixes
* **common:** add PopStateEvent interface ([#13400](https://github.com/angular/angular/issues/13400)) ([71567d1](https://github.com/angular/angular/commit/71567d1)), closes [#13378](https://github.com/angular/angular/issues/13378)
* **common:** DatePipe does't throw for NaN ([#14117](https://github.com/angular/angular/issues/14117)) ([32cc675](https://github.com/angular/angular/commit/32cc675)), closes [#14103](https://github.com/angular/angular/issues/14103)
* **common:** DatePipe parses input string if it's not a valid date in browser ([#13895](https://github.com/angular/angular/issues/13895)) ([e641636](https://github.com/angular/angular/commit/e641636)), closes [#12334](https://github.com/angular/angular/issues/12334) [#13874](https://github.com/angular/angular/issues/13874)
* **common:** introduce isObservable method ([#14067](https://github.com/angular/angular/issues/14067)) ([109f0d1](https://github.com/angular/angular/commit/109f0d1)), closes [#8848](https://github.com/angular/angular/issues/8848)
* **compiler:** allow empty translations for attributes ([#14085](https://github.com/angular/angular/issues/14085)) ([f3d5506](https://github.com/angular/angular/commit/f3d5506)), closes [#13897](https://github.com/angular/angular/issues/13897)
* **core:** add bootstrapped modules into platform modules list ([#13740](https://github.com/angular/angular/issues/13740)) ([250dbc4](https://github.com/angular/angular/commit/250dbc4)), closes [#12015](https://github.com/angular/angular/issues/12015)
* **core:** ViewContainerRef.indexOf should not throw error when empty ([#13220](https://github.com/angular/angular/issues/13220)) ([41b8d95](https://github.com/angular/angular/commit/41b8d95))
* **forms:** show a blank line when nothing is selected in IE or Edge ([#13903](https://github.com/angular/angular/issues/13903)) ([09e2d20](https://github.com/angular/angular/commit/09e2d20)), closes [#10010](https://github.com/angular/angular/issues/10010)
* **forms:** verify functions passed into async validators returns Observable or Promise ([#14053](https://github.com/angular/angular/issues/14053)) ([774e1db](https://github.com/angular/angular/commit/774e1db))
* ngModel should use rxjs/symbol/observable to detect observable ([#14236](https://github.com/angular/angular/issues/14236)) ([7e639aa](https://github.com/angular/angular/commit/7e639aa))
* **http:** remove dots from jsonp callback name ([#13219](https://github.com/angular/angular/issues/13219)) ([1eece50](https://github.com/angular/angular/commit/1eece50))
* **i18n:** parse ICU messages while normalizing templates ([#14153](https://github.com/angular/angular/issues/14153)) ([8d4aa82](https://github.com/angular/angular/commit/8d4aa82))
* **language-service:** do not crash when Angular cannot be located ([#14123](https://github.com/angular/angular/issues/14123)) ([a5b4af0](https://github.com/angular/angular/commit/a5b4af0)), closes [#14122](https://github.com/angular/angular/issues/14122)
* **platform-browser:** remove style nodes on destroy ([#13744](https://github.com/angular/angular/issues/13744)) ([0614289](https://github.com/angular/angular/commit/0614289)), closes [#11746](https://github.com/angular/angular/issues/11746)
* **router:** fix CanActivate redirect to the root on initial load ([#13929](https://github.com/angular/angular/issues/13929)) ([a047124](https://github.com/angular/angular/commit/a047124)), closes [#13530](https://github.com/angular/angular/issues/13530)
* **router:** should find guard provided in a lazy loaded module ([#13989](https://github.com/angular/angular/issues/13989)) ([0965636](https://github.com/angular/angular/commit/0965636)), closes [#12275](https://github.com/angular/angular/issues/12275)
* **router:** should allow navigation from root component in ngOnInit hook ([#13932](https://github.com/angular/angular/issues/13932)) ([4d2901d](https://github.com/angular/angular/commit/4d2901d)), closes [#13795](https://github.com/angular/angular/issues/13795)
* **testing:** async/fakeAsync/inject/withModule helpers should pass through context to callback functions ([#13718](https://github.com/angular/angular/issues/13718)) ([70bbdf5](https://github.com/angular/angular/commit/70bbdf5))
* **upgrade:** detect async downgrade component changes ([#14039](https://github.com/angular/angular/issues/14039)) ([117fa79](https://github.com/angular/angular/commit/117fa79)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385)
<a name="2.4.5"></a>
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
### Bug Fixes
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
<a name="2.4.4"></a>
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
### Bug Fixes
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
<a name="2.4.3"></a>
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
### Bug Fixes
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
<a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
### Bug Fixes
* **common:** add link to trackBy docs from the error message ([#13634](https://github.com/angular/angular/issues/13634)) ([f723437](https://github.com/angular/angular/commit/f723437))
* **common:** do not override locale provided on bootstrap ([#13654](https://github.com/angular/angular/issues/13654)) ([5f49c3e](https://github.com/angular/angular/commit/5f49c3e)), closes [#13607](https://github.com/angular/angular/issues/13607)
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
* **core/testing:** improve misleading error message when don't call compileComponents ([#13543](https://github.com/angular/angular/issues/13543)) ([0e7f9f0](https://github.com/angular/angular/commit/0e7f9f0)), closes [#11301](https://github.com/angular/angular/issues/11301)
* **forms:** Validators.required properly validate arrays ([#13362](https://github.com/angular/angular/issues/13362)) ([17c5fa9](https://github.com/angular/angular/commit/17c5fa9)), closes [#12274](https://github.com/angular/angular/issues/12274)
* **language-service:** support TypeScript 2.1 ([#13655](https://github.com/angular/angular/issues/13655)) ([56b4296](https://github.com/angular/angular/commit/56b4296))
* **router:** fix lazy loaded module with wildcard route ([#13649](https://github.com/angular/angular/issues/13649)) ([5754ecc](https://github.com/angular/angular/commit/5754ecc)), closes [#12955](https://github.com/angular/angular/issues/12955)
* **router:** routerLink support of null ([#13380](https://github.com/angular/angular/issues/13380)) ([018865e](https://github.com/angular/angular/commit/018865e)), closes [#6971](https://github.com/angular/angular/issues/6971)
* **router:** update route snapshot before emit new values ([#13558](https://github.com/angular/angular/issues/13558)) ([9f6a647](https://github.com/angular/angular/commit/9f6a647)), closes [#12912](https://github.com/angular/angular/issues/12912)
* **upgrade:** fix/improve support for lifecycle hooks ([#13020](https://github.com/angular/angular/issues/13020)) ([21942a8](https://github.com/angular/angular/commit/21942a8))
<a name="2.4.1"></a> <a name="2.4.1"></a>
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21) ## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)

View File

@ -6,29 +6,16 @@ for details about how we maintain a linear commit history, and the rules for com
As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request. As a contributor, just read the instructions in [CONTRIBUTING.md](CONTRIBUTING.md) and send a pull request.
Someone with committer access will do the rest. Someone with committer access will do the rest.
## The `PR: merge` label and `presubmit-*` branches # Change approvals
We have automated the process for merging pull requests into master. Our goal is to minimize the disruption for Change approvals in our monorepo are managed via [pullapprove.com](https://about.pullapprove.com/) and are configured via the `.pullapprove.yaml` file.
Angular committers and also prevent breakages on master.
When a PR has `pr_state: LGTM` and is ready to merge, you should add the `pr_action: merge` label.
Currently (late 2015), we need to ensure that each PR will cleanly merge into the Google-internal version control,
so the caretaker reviews the changes manually.
After this review, the caretaker adds `zomg_admin: do_merge` which is restricted to admins only. # Merging
A robot running as [mary-poppins](https://github.com/mary-poppins)
is notified that the label was added by an authorized person,
and will create a new branch in the angular project, using the convention `presubmit-{username}-pr-{number}`.
(Note: if the automation fails, committers can instead push the commits to a branch following this naming scheme.) Once a change has all the approvals either the last approver or the PR author (if PR author has the project collaborator status) should mark the PR with "PR: merge" label.
This signals to the caretaker that the PR should be merged.
When a Travis build succeeds for a presubmit branch named following the convention, # Who is the Caretaker?
Travis will re-base the commits, merge to master, and close the PR automatically.
Finally, after merge `mary-poppins` removes the presubmit branch. See [this explanation](https://twitter.com/IgorMinar/status/799365744806854656).
## Administration
The list of users who can trigger a merge by adding the `zomg_admin: do_merge` label is stored in our appengine app datastore.
Edit the contents of the [CoreTeamMember Table](
https://console.developers.google.com/project/angular2-automation/datastore/query?queryType=KindQuery&namespace=&kind=CoreTeamMember)

View File

@ -71,7 +71,13 @@ particular `gulp` and `protractor` commands. If you prefer, you can drop this pa
Since global installs can become stale, and required versions can vary by project, we avoid their Since global installs can become stale, and required versions can vary by project, we avoid their
use in these instructions. use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this *Option 2*: globally installing the package `npm-run` by running `npm install -g npm-run`
(you might need to prefix this command with `sudo`). You will then be able to run locally installed
package scripts by invoking: e.g., `npm-run gulp build`
(see [npm-run project page](https://github.com/timoxley/npm-run) for more details).
*Option 3*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`. [Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Installing Bower Modules ## Installing Bower Modules
@ -133,7 +139,8 @@ If you happen to modify the public API of Angular, API golden files must be upda
$ gulp public-api:update $ gulp public-api:update
``` ```
Note: The command `./test.sh tools` fails when the API doesn't match the golden files. Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild
the project before trying to verify after an API change.
## <a name="clang-format"></a> Formatting your source code ## <a name="clang-format"></a> Formatting your source code
@ -146,6 +153,14 @@ You can automatically format your code by running:
$ gulp format $ gulp format
``` ```
## Linting/verifying your source code
You can check that your code is properly formatted and adheres to coding style by running:
``` shell
$ gulp lint
```
## Publishing your own personal snapshot build ## Publishing your own personal snapshot build
You may find that your un-merged change needs some validation from external participants. You may find that your un-merged change needs some validation from external participants.

View File

@ -1,6 +1,6 @@
The MIT License The MIT License
Copyright (c) 2014-2016 Google, Inc. http://angular.io Copyright (c) 2014-2017 Google, Inc. http://angular.io
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,10 +1,10 @@
machine: machine:
node: node:
version: 5.4.1 version: 6.9.5
dependencies: dependencies:
pre: pre:
- npm install -g npm@3.6.0 - npm install -g npm@3.10.7
test: test:
override: override:

View File

@ -19,7 +19,7 @@ One intentional omission from this list is `@angular/compiler`, which is current
Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered. Additionally only the command line usage (not direct use of APIs) of `@angular/compiler-cli` is covered.
Other projects developed by the Angular team like angular-cli, Angular Material, benchpress, will be covered by these or similar guarantees in the future as they mature. Other projects developed by the Angular team like angular-cli, Angular Material, will be covered by these or similar guarantees in the future as they mature.
Within the supported packages, we provide guarantees for: Within the supported packages, we provide guarantees for:
@ -37,4 +37,4 @@ We explicitly don't consider the following to be our public API surface:
- the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed) - the contents and API surface of the code generated by Angular's compiler (with one notable exception: the existence and name of `NgModuleFactory` instances exported from generated code is guaranteed)
Our peer dependencies (e.g. typescript, zone.js, or rxjs) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases. Our peer dependencies (e.g. TypeScript, Zone.js, or RxJS) are not considered part of our API surface, but they are included in our SemVer policies. We might update the required version of any of these dependencies in minor releases if the update doesn't cause breaking changes for Angular applications. Peer dependency updates that result in non-trivial breaking changes must be deferred to major Angular releases.

View File

@ -11,8 +11,8 @@
// THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE // THIS CHECK SHOULD BE THE FIRST THING IN THIS FILE
// This is to ensure that we catch env issues before we error while requiring other dependencies. // This is to ensure that we catch env issues before we error while requiring other dependencies.
require('./tools/check-environment')({ require('./tools/check-environment')({
requiredNpmVersion: '>=3.5.3 <4.0.0', requiredNpmVersion: '>=3.10.7 <4.0.0',
requiredNodeVersion: '>=5.4.1 <7.0.0', requiredNodeVersion: '>=6.9.5 <7.0.0',
}); });
const gulp = require('gulp'); const gulp = require('gulp');

View File

@ -9,7 +9,7 @@
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core'; import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent, stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -25,6 +25,8 @@ import {isPresent, stringify} from '../facade/lang';
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element> * <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
* *
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element> * <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
*
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
* ``` * ```
* *
* @description * @description
@ -132,7 +134,7 @@ export class NgClass implements DoCheck {
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup)); (<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else { } else {
Object.keys(rawClassVal).forEach(klass => { Object.keys(rawClassVal).forEach(klass => {
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup); if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
}); });
} }
} }

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 {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core'; import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
import {getTypeNameForDebugging} from '../facade/lang'; import {getTypeNameForDebugging} from '../facade/lang';
@ -91,8 +91,13 @@ export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any; @Input() ngForOf: any;
@Input() @Input()
set ngForTrackBy(fn: TrackByFn) { set ngForTrackBy(fn: TrackByFn) {
if (typeof fn !== 'function') { if (isDevMode() && fn != null && typeof fn !== 'function') {
throw new Error(`trackBy must be a function, but received ${JSON.stringify(fn)}`); // TODO(vicb): use a log service once there is a public one available
if (<any>console && <any>console.warn) {
console.warn(
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
}
} }
this._trackByFn = fn; this._trackByFn = fn;
} }

View File

@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
* @howToUse * @howToUse
* ``` * ```
* <some-element [ngPlural]="value"> * <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container> * <template ngPluralCase="=0">there is nothing</template>
* <ng-container *ngPluralCase="'=1'">there is one</ng-container> * <template ngPluralCase="=1">there is one</template>
* <ng-container *ngPluralCase="'few'">there are a few</ng-container> * <template ngPluralCase="few">there are a few</template>
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
* </some-element> * </some-element>
* ``` * ```
* *
@ -90,8 +89,8 @@ export class NgPlural {
* @howToUse * @howToUse
* ``` * ```
* <some-element [ngPlural]="value"> * <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">...</ng-container> * <template ngPluralCase="=0">...</template>
* <ng-container *ngPluralCase="'other'">...</ng-container> * <template ngPluralCase="other">...</template>
* </some-element> * </some-element>
*``` *```
* *
@ -104,6 +103,7 @@ export class NgPluralCase {
constructor( constructor(
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>, @Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) { viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
ngPlural.addCase(value, new SwitchView(viewContainer, template)); const isANumber: boolean = !isNaN(Number(value));
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
} }
} }

View File

@ -10,6 +10,12 @@ import {EventEmitter, Injectable} from '@angular/core';
import {LocationStrategy} from './location_strategy'; import {LocationStrategy} from './location_strategy';
/** @experimental */
export interface PopStateEvent {
pop?: boolean;
type?: string;
url?: string;
}
/** /**
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL. * @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
@ -122,7 +128,7 @@ export class Location {
* Subscribe to the platform's `popState` events. * Subscribe to the platform's `popState` events.
*/ */
subscribe( subscribe(
onNext: (value: any) => void, onThrow: (exception: any) => void = null, onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object { onReturn: () => void = null): Object {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn}); return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
} }

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core'; import {ChangeDetectorRef, OnDestroy, Pipe, PipeTransform, WrappedValue} from '@angular/core';
import {EventEmitter, Observable} from '../facade/async'; import {EventEmitter, Observable} from '../facade/async';
import {isPromise} from '../private_import_core'; import {isObservable, isPromise} from '../private_import_core';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
interface SubscriptionStrategy { interface SubscriptionStrategy {
@ -66,7 +66,7 @@ const _observableStrategy = new ObservableStrategy();
* @stable * @stable
*/ */
@Pipe({name: 'async', pure: false}) @Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy { export class AsyncPipe implements OnDestroy, PipeTransform {
private _latestValue: Object = null; private _latestValue: Object = null;
private _latestReturnedValue: Object = null; private _latestReturnedValue: Object = null;
@ -116,7 +116,7 @@ export class AsyncPipe implements OnDestroy {
return _promiseStrategy; return _promiseStrategy;
} }
if ((<any>obj).subscribe) { if (isObservable(obj)) {
return _observableStrategy; return _observableStrategy;
} }
@ -131,7 +131,7 @@ export class AsyncPipe implements OnDestroy {
this._obj = null; this._obj = null;
} }
private _updateLatestValue(async: any, value: Object) { private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) { if (async === this._obj) {
this._latestValue = value; this._latestValue = value;
this._ref.markForCheck(); this._ref.markForCheck();

View File

@ -7,13 +7,13 @@
*/ */
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {NumberWrapper} from '../facade/lang';
import {NumberWrapper, isDate} from '../facade/lang';
import {DateFormatter} from './intl'; import {DateFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
const ISO8601_DATE_REGEX =
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
// 1 2 3 4 5 6 7 8 9 10 11
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* Where: * Where:
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string * - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime). * (https://www.w3.org/TR/NOTE-datetime).
* - `format` indicates which date/time components to include. The format can be predifined as * - `format` indicates which date/time components to include. The format can be predefined as
* shown below or custom as shown in the table. * shown below or custom as shown in the table.
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`) * - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`) * - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
@ -103,7 +103,7 @@ export class DatePipe implements PipeTransform {
transform(value: any, pattern: string = 'mediumDate'): string { transform(value: any, pattern: string = 'mediumDate'): string {
let date: Date; let date: Date;
if (isBlank(value)) return null; if (isBlank(value) || value !== value) return null;
if (typeof value === 'string') { if (typeof value === 'string') {
value = value.trim(); value = value.trim();
@ -130,7 +130,12 @@ export class DatePipe implements PipeTransform {
} }
if (!isDate(date)) { if (!isDate(date)) {
throw new InvalidPipeArgumentError(DatePipe, value); let match: RegExpMatchArray;
if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
date = isoStringToDate(match);
} else {
throw new InvalidPipeArgumentError(DatePipe, value);
}
} }
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern); return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
@ -140,3 +145,31 @@ export class DatePipe implements PipeTransform {
function isBlank(obj: any): boolean { function isBlank(obj: any): boolean {
return obj == null || obj === ''; return obj == null || obj === '';
} }
function isDate(obj: any): obj is Date {
return obj instanceof Date && !isNaN(obj.valueOf());
}
function isoStringToDate(match: RegExpMatchArray): Date {
const date = new Date(0);
let tzHour = 0;
let tzMin = 0;
const dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear;
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
tzHour = toInt(match[9] + match[10]);
tzMin = toInt(match[9] + match[11]);
}
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
const h = toInt(match[4] || '0') - tzHour;
const m = toInt(match[5] || '0') - tzMin;
const s = toInt(match[6] || '0');
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
function toInt(str: string): number {
return parseInt(str, 10);
}

View File

@ -7,7 +7,6 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization'; import {NgLocalization, getPluralCategory} from '../localization';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -35,7 +34,7 @@ export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {} constructor(private _localization: NgLocalization) {}
transform(value: number, pluralMap: {[count: string]: string}): string { transform(value: number, pluralMap: {[count: string]: string}): string {
if (isBlank(value)) return ''; if (value == null) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) { if (typeof pluralMap !== 'object' || pluralMap === null) {
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap); throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);

View File

@ -8,7 +8,7 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberWrapper, isBlank, isPresent} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {NumberFormatStyle, NumberFormatter} from './intl'; import {NumberFormatStyle, NumberFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
function formatNumber( function formatNumber(
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle, pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string { digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null; if (value == null) return null;
// Convert strings to numbers // Convert strings to numbers
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value; value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
@ -41,13 +41,13 @@ function formatNumber(
if (parts === null) { if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`); throw new Error(`${digits} is not a valid digit info for number pipes`);
} }
if (isPresent(parts[1])) { // min integer digits if (parts[1] != null) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]); minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
} }
if (isPresent(parts[3])) { // min fraction digits if (parts[3] != null) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]); minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
} }
if (isPresent(parts[5])) { // max fraction digits if (parts[5] != null) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]); maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
} }
} }

View File

@ -7,7 +7,6 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
@ -58,7 +57,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@Pipe({name: 'slice', pure: false}) @Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform { export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any { transform(value: any, start: number, end?: number): any {
if (isBlank(value)) return value; if (value == null) return value;
if (!this.supports(value)) { if (!this.supports(value)) {
throw new InvalidPipeArgumentError(SlicePipe, value); throw new InvalidPipeArgumentError(SlicePipe, value);

View File

@ -9,3 +9,4 @@
import {__core_private__ as r} from '@angular/core'; import {__core_private__ as r} from '@angular/core';
export const isPromise: typeof r.isPromise = r.isPromise; export const isPromise: typeof r.isPromise = r.isPromise;
export const isObservable: typeof r.isObservable = r.isObservable;

View File

@ -7,7 +7,7 @@
*/ */
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component, ContentChild, TemplateRef} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -29,10 +29,7 @@ export function main() {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [TestComponent],
TestComponent,
ComponentUsingTestComponent,
],
imports: [CommonModule], imports: [CommonModule],
}); });
}); });
@ -77,7 +74,7 @@ export function main() {
})); }));
it('should iterate over an array of objects', async(() => { it('should iterate over an array of objects', async(() => {
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>'; const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
// INIT // INIT
@ -95,7 +92,7 @@ export function main() {
})); }));
it('should gracefully handle nulls', async(() => { it('should gracefully handle nulls', async(() => {
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>'; const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText(''); detectChangesAndExpectText('');
@ -140,12 +137,8 @@ export function main() {
})); }));
it('should repeat over nested arrays', async(() => { it('should repeat over nested arrays', async(() => {
const template = '<div>' + const template = '<div *ngFor="let item of items">' +
'<div template="ngFor let item of items">' + '<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>'; '</div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -157,10 +150,9 @@ export function main() {
})); }));
it('should repeat over nested arrays with no intermediate element', async(() => { it('should repeat over nested arrays with no intermediate element', async(() => {
const template = '<div><template ngFor let-item [ngForOf]="items">' + const template = '<div *ngFor="let item of items">' +
'<div template="ngFor let subitem of item">' + '<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
'{{subitem}}-{{item.length}};' + '</div>';
'</div></template></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [['a', 'b'], ['c']]; getComponent().items = [['a', 'b'], ['c']];
@ -170,10 +162,11 @@ export function main() {
detectChangesAndExpectText('e-1;f-2;g-2;'); detectChangesAndExpectText('e-1;f-2;g-2;');
})); }));
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => { it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
const template = const template = `<div *ngFor="let item of items; let i=index">` +
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` + `<div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`; `<div *ngIf="i % 2 == 0">even|</div>` +
`</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -189,8 +182,7 @@ export function main() {
})); }));
it('should display indices correctly', async(() => { it('should display indices correctly', async(() => {
const template = const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -202,7 +194,7 @@ export function main() {
it('should display first item correctly', async(() => { it('should display first item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>'; '<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -214,7 +206,7 @@ export function main() {
it('should display last item correctly', async(() => { it('should display last item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>'; '<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -226,7 +218,7 @@ export function main() {
it('should display even items correctly', async(() => { it('should display even items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>'; '<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -238,7 +230,7 @@ export function main() {
it('should display odd items correctly', async(() => { it('should display odd items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>'; '<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3]; getComponent().items = [0, 1, 2, 3];
@ -249,64 +241,57 @@ export function main() {
})); }));
it('should allow to use a custom template', async(() => { it('should allow to use a custom template', async(() => {
const tcTemplate = const template =
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>'; '<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}}); '<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
const cutTemplate = fixture = createTestComponent(template);
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; getComponent().items = ['a', 'b', 'c'];
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
it('should use a default template if a custom one is null', async(() => { it('should use a default template if a custom one is null', async(() => {
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items" const template =
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`; `<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}}); fixture = createTestComponent(template);
const cutTemplate = getComponent().items = ['a', 'b', 'c'];
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
it('should use a custom template when both default and a custom one are present', async(() => { it('should use a custom template when both default and a custom one are present', async(() => {
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items" const template =
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`; '<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}}); '<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
const cutTemplate = fixture = createTestComponent(template);
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; getComponent().items = ['a', 'b', 'c'];
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
describe('track by', () => { describe('track by', () => {
it('should throw if trackBy is not a function', async(() => { it('should console.warn if trackBy is not a function', async(() => {
const template = // TODO(vicb): expect a warning message when we have a proper log service
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="item?.id"></template>`; const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.value = 0;
fixture.detectChanges();
}));
getComponent().items = [{id: 1}, {id: 2}]; it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
expect(() => fixture.detectChanges()) // TODO(vicb): expect no warning message when we have a proper log service
.toThrowError(/trackBy must be a function, but received null/); const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
fixture = createTestComponent(template);
fixture.componentInstance.items = ['a', 'b', 'c'];
fixture.componentInstance.value = null;
detectChangesAndExpectText('abc');
fixture.componentInstance.value = undefined;
detectChangesAndExpectText('abc');
})); }));
it('should set the context to the component instance', async(() => { it('should set the context to the component instance', async(() => {
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`; `<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
thisArg = null; thisArg = null;
@ -316,9 +301,7 @@ export function main() {
it('should not replace tracked items', async(() => { it('should not replace tracked items', async(() => {
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index"> `<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
<p>{{items[i]}}</p>
</template>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
const buildItemList = () => { const buildItemList = () => {
@ -334,7 +317,7 @@ export function main() {
it('should update implicit local variable on view', async(() => { it('should update implicit local variable on view', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}]; getComponent().items = [{'id': 'a', 'color': 'blue'}];
@ -343,9 +326,10 @@ export function main() {
getComponent().items = [{'id': 'a', 'color': 'red'}]; getComponent().items = [{'id': 'a', 'color': 'red'}];
detectChangesAndExpectText('red'); detectChangesAndExpectText('red');
})); }));
it('should move items around and keep them updated ', async(() => { it('should move items around and keep them updated ', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}]; getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
@ -356,8 +340,7 @@ export function main() {
})); }));
it('should handle added and removed items properly when tracking by index', async(() => { it('should handle added and removed items properly when tracking by index', async(() => {
const template = const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c', 'd']; getComponent().items = ['a', 'b', 'c', 'd'];
@ -377,19 +360,14 @@ class Foo {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>; value: any;
items: any[] = [1, 2]; items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; } trackByIndex(index: number, item: any): number { return index; }
trackByContext(): void { thisArg = this; } trackByContext(): void { thisArg = this; }
} }
@Component({selector: 'outer-cmp', template: ''}) const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
class ComponentUsingTestComponent {
items: any = [1, 2];
}
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> { function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}}) return TestBed.overrideComponent(TestComponent, {set: {template: template}})

View File

@ -9,6 +9,7 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -28,131 +29,114 @@ export function main() {
}); });
it('should work in a template attribute', async(() => { it('should work in a template attribute', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
})); }));
it('should work in a template element', async(() => { it('should work on a template element', async(() => {
const template = const template = '<template [ngIf]="booleanCondition">hello2</template>';
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello2'); expect(fixture.nativeElement).toHaveText('hello2');
})); }));
it('should toggle node when condition changes', async(() => { it('should toggle node when condition changes', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
})); }));
it('should handle nested if correctly', async(() => { it('should handle nested if correctly', async(() => {
const template = const template =
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>'; '<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().nestedBooleanCondition = false; getComponent().nestedBooleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().nestedBooleanCondition = true; getComponent().nestedBooleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
})); }));
it('should update several nodes with if', async(() => { it('should update several nodes with if', async(() => {
const template = '<div>' + const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' + '<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' + '<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
'</div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
expect(getDOM().getText(fixture.nativeElement)) expect(getDOM().getText(fixture.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction'); .toEqual('helloNumberhelloStringhelloFunction');
getComponent().numberCondition = 0; getComponent().numberCondition = 0;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloString'); expect(fixture.nativeElement).toHaveText('helloString');
getComponent().numberCondition = 1; getComponent().numberCondition = 1;
getComponent().stringCondition = 'bar'; getComponent().stringCondition = 'bar';
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloNumber'); expect(fixture.nativeElement).toHaveText('helloNumber');
})); }));
it('should not add the element twice if the condition goes from true to true (JS)', it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
async(() => { const template = '<span *ngIf="numberCondition">hello</span>';
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); let els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
getDOM().addClass(els[0].nativeElement, 'marker');
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().numberCondition = 2; getComponent().numberCondition = 2;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
expect(getDOM().hasClass(els[0].nativeElement, 'marker')).toBe(true);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
})); }));
it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
getComponent().numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
.toBe(true);
}));
}); });
} }

View File

@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('switch', () => { describe('ngPlural', () => {
let fixture: ComponentFixture<any>; let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; } function getComponent(): TestComponent { return fixture.componentInstance; }
@ -33,10 +33,25 @@ export function main() {
}); });
it('should display the template according to the exact value', async(() => { it('should display the template according to the exact value', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' + '<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' + '<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul>';
fixture = createTestComponent(template);
getComponent().switchValue = 0;
detectChangesAndExpectText('you have no messages.');
getComponent().switchValue = 1;
detectChangesAndExpectText('you have one message.');
}));
it('should display the template according to the exact numeric value', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
'</ul></div>'; '</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -51,10 +66,9 @@ export function main() {
// https://github.com/angular/angular/issues/9868 // https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882 // https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => { it('should not throw when ngPluralCase contains expressions', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' + '<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -64,11 +78,10 @@ export function main() {
it('should be applicable to <ng-container> elements', async(() => { it('should be applicable to <ng-container> elements', async(() => {
const template = '<div>' + const template = '<ng-container [ngPlural]="switchValue">' +
'<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' + '<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' + '<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container></div>'; '</ng-container>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -80,11 +93,10 @@ export function main() {
})); }));
it('should display the template according to the category', async(() => { it('should display the template according to the category', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' + '<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -96,11 +108,10 @@ export function main() {
})); }));
it('should default to other when no matches are found', async(() => { it('should default to other when no matches are found', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' + '<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -109,11 +120,10 @@ export function main() {
})); }));
it('should prioritize value matches over category matches', async(() => { it('should prioritize value matches over category matches', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' + '<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);

View File

@ -29,22 +29,19 @@ export function main() {
it('should add styles specified in an object literal', async(() => { it('should add styles specified in an object literal', async(() => {
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`; const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
})); }));
it('should add and change styles specified in an object expression', async(() => { it('should add and change styles specified in an object expression', async(() => {
const template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
let expr: {[k: string]: string};
getComponent().expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
expr = getComponent().expr; let expr = getComponent().expr;
expr['max-width'] = '30%'; expr['max-width'] = '30%';
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});

View File

@ -33,11 +33,10 @@ export function main() {
describe('switch value changes', () => { describe('switch value changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a</li>' +
'<template ngSwitchCase="a"><li>when a</li></template>' + '<li *ngSwitchCase="\'b\'">when b</li>' +
'<template ngSwitchCase="b"><li>when b</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -51,11 +50,10 @@ export function main() {
}); });
it('should switch amongst when values with fallback to default', () => { it('should switch amongst when values with fallback to default', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a</li>' +
'<li template="ngSwitchCase \'a\'">when a</li>' + '<li *ngSwitchDefault>when default</li>' +
'<li template="ngSwitchDefault">when default</li>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default'); detectChangesAndExpectText('when default');
@ -71,15 +69,14 @@ export function main() {
}); });
it('should support multiple whens with the same value', () => { it('should support multiple whens with the same value', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' + '<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' + '<li *ngSwitchCase="\'b\'">when b2;</li>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');
@ -94,12 +91,11 @@ export function main() {
describe('when values changes', () => { describe('when values changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="when1">when 1;</li>' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' + '<li *ngSwitchCase="when2">when 2;</li>' +
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' + '<li *ngSwitchDefault>when default;</li>' +
'<template ngSwitchDefault><li>when default;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().when1 = 'a'; getComponent().when1 = 'a';
@ -148,11 +144,10 @@ export function main() {
}); });
it('should create the default case if there is no other case', () => { it('should create the default case if there is no other case', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');
@ -160,15 +155,14 @@ export function main() {
}); });
it('should allow defaults before cases', () => { it('should allow defaults before cases', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' + '<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' + '<li *ngSwitchCase="\'b\'">when b2;</li>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');

View File

@ -34,29 +34,22 @@ export function main() {
}); });
}); });
it('should do nothing if templateRef is null', async(() => { it('should do nothing if templateRef is `null`', async(() => {
const template = `<template [ngTemplateOutlet]="null"></template>`; const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText(''); detectChangesAndExpectText('');
})); }));
it('should insert content specified by TemplateRef', async(() => { it('should insert content specified by TemplateRef', async(() => {
const template = const template = `<template #tpl>foo</template>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo'); detectChangesAndExpectText('foo');
})); }));
it('should clear content if TemplateRef becomes null', async(() => { it('should clear content if TemplateRef becomes `null`', async(() => {
const template = const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
@ -70,7 +63,8 @@ export function main() {
it('should swap content if TemplateRef changes', async(() => { it('should swap content if TemplateRef changes', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
@ -83,70 +77,47 @@ export function main() {
detectChangesAndExpectText('bar'); detectChangesAndExpectText('bar');
})); }));
it('should display template if context is null', async(() => { it('should display template if context is `null`', async(() => {
const template = const template = `<template #tpl>foo</template>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo'); detectChangesAndExpectText('foo');
})); }));
it('should reflect initial context and changes', async(() => { it('should reflect initial context and changes', async(() => {
const template = const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('bar'); detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar'; fixture.componentInstance.context.foo = 'alter-bar';
detectChangesAndExpectText('alter-bar'); detectChangesAndExpectText('alter-bar');
})); }));
it('should reflect user defined $implicit property in the context', async(() => { it('should reflect user defined `$implicit` property in the context', async(() => {
const template = const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
fixture.detectChanges(); detectChangesAndExpectText('bra');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
detectChangesAndExpectText('bar');
})); }));
it('should reflect context re-binding', async(() => { it('should reflect context re-binding', async(() => {
const template = const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {shawshank: 'brooks'}; fixture.componentInstance.context = {shawshank: 'brooks'};
detectChangesAndExpectText('brooks'); detectChangesAndExpectText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'}; fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here'); detectChangesAndExpectText('was here');
})); }));
}); });
} }
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'}) @Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs { class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>; @ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;

View File

@ -53,11 +53,13 @@ export function main() {
it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null)); it('should return null for empty string', () => expect(pipe.transform('')).toEqual(null));
it('should return null for NaN', () => expect(pipe.transform(Number.NaN)).toEqual(null));
it('should support ISO string without time', it('should support ISO string without time',
() => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); }); () => { expect(() => pipe.transform(isoStringWithoutTime)).not.toThrow(); });
it('should not support other objects', it('should not support other objects',
() => { expect(() => pipe.transform({})).toThrowError(); }); () => expect(() => pipe.transform({})).toThrowError(/Invalid argument/));
}); });
describe('transform', () => { describe('transform', () => {
@ -188,8 +190,14 @@ export function main() {
}); });
it('should format invalid in IE ISO date',
() => expect(pipe.transform('2017-01-11T09:25:14.014-0500')).toEqual('Jan 11, 2017'));
it('should format invalid in Safari ISO date',
() => expect(pipe.transform('2017-01-20T19:00:00+0000')).toEqual('Jan 20, 2017'));
it('should remove bidi control characters', it('should remove bidi control characters',
() => { expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10); }); () => expect(pipe.transform(date, 'MM/dd/yyyy').length).toEqual(10));
}); });
}); });
} }

View File

@ -0,0 +1,23 @@
/**
* @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, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'lazy-feature-comp', template: 'lazy feature, nested!'})
export class LazyFeatureNestedComponent {
}
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyFeatureNestedComponent, pathMatch: 'full'},
])],
declarations: [LazyFeatureNestedComponent]
})
export class LazyFeatureNestedModule {
}

View File

@ -16,7 +16,10 @@ export class LazyFeatureComponent {
@NgModule({ @NgModule({
imports: [RouterModule.forChild([ imports: [RouterModule.forChild([
{path: '', component: LazyFeatureComponent, pathMatch: 'full'}, {path: '', component: LazyFeatureComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature.module#FeatureModule'} {path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
path: 'nested-feature',
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
}
])], ])],
declarations: [LazyFeatureComponent] declarations: [LazyFeatureComponent]
}) })

View File

@ -191,6 +191,8 @@ function lazyRoutesTest() {
'./lazy.module#LazyModule': 'lazy.module.ts', './lazy.module#LazyModule': 'lazy.module.ts',
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts', './feature/feature.module#FeatureModule': 'feature/feature.module.ts',
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts', './feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
'./feature.module#FeatureModule': 'feature/feature.module.ts',
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts', 'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
'./default.module': 'feature2/default.module.ts', './default.module': 'feature2/default.module.ts',
'feature/feature.module#FeatureModule': 'feature/feature.module.ts' 'feature/feature.module#FeatureModule': 'feature/feature.module.ts'

View File

@ -15,6 +15,8 @@
"declaration": true, "declaration": true,
"lib": ["es6", "dom"], "lib": ["es6", "dom"],
"baseUrl": ".", "baseUrl": ".",
"outDir": "../node_modules/third_party" "outDir": "../node_modules/third_party",
} // Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
}
} }

View File

@ -14,7 +14,9 @@
"rootDir": "", "rootDir": "",
"declaration": true, "declaration": true,
"lib": ["es6", "dom"], "lib": ["es6", "dom"],
"baseUrl": "." "baseUrl": ".",
// Prevent scanning up the directory tree for types
"typeRoots": ["node_modules/@types"]
}, },
"files": [ "files": [

View File

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

View File

@ -11,7 +11,6 @@
* 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 {ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs'; import {readFileSync} from 'fs';
import * as path from 'path'; import * as path from 'path';
@ -19,7 +18,6 @@ import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core';
const GENERATED_META_FILES = /\.json$/; const GENERATED_META_FILES = /\.json$/;
@ -38,29 +36,6 @@ export class CodeGenerator {
public host: ts.CompilerHost, private compiler: compiler.AotCompiler, public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private ngCompilerHost: CompilerHost) {} private ngCompilerHost: CompilerHost) {}
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
codegen(): Promise<any> { codegen(): Promise<any> {
return this.compiler return this.compiler
.compileAll(this.program.getSourceFiles().map( .compileAll(this.program.getSourceFiles().map(
@ -68,7 +43,7 @@ export class CodeGenerator {
.then(generatedModules => { .then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl); const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl); const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source : const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
PREAMBLE + generatedModule.source; PREAMBLE + generatedModule.source;
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);

View File

@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
return !excludeRegex.test(filePath); return !excludeRegex.test(filePath);
} }
calculateEmitPath(filePath: string): string {
// Write codegen in a directory structure matching the sources.
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
} }
export class CompilerHostContextAdapter { export class CompilerHostContextAdapter {

View File

@ -15,8 +15,6 @@
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler'; import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
// We cannot depend directly to @angular/router. // We cannot depend directly to @angular/router.
type Route = any; type Route = any;
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader'; const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
@ -63,29 +61,37 @@ export function listLazyRoutesOfModule(
const className = entryRouteDef.className; const className = entryRouteDef.className;
// List loadChildren of this single module. // List loadChildren of this single module.
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile); const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME); const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] = const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES); _extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
const routes: LazyRouteMap = {};
lazyRoutes.forEach((lazyRoute: LazyRoute) => { const allLazyRoutes = lazyRoutes.reduce(
const route: string = lazyRoute.routeDef.toString(); function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
_assertRoute(routes, lazyRoute); LazyRouteMap {
routes[route] = lazyRoute; const route: string = lazyRoute.routeDef.toString();
_assertRoute(allRoutes, lazyRoute);
allRoutes[route] = lazyRoute;
const lazyModuleSymbol = reflector.findDeclaration( // StaticReflector does not support discovering annotations like `NgModule` on default
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default'); // exports
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES); // Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
// we cannot parse its routes to see if they have loadChildren or not.
if (!lazyRoute.routeDef.className) {
return allRoutes;
}
// Populate the map using the routes we just found. const lazyModuleSymbol = reflector.findDeclaration(
subRoutes.forEach(subRoute => { lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
_assertRoute(routes, subRoute);
routes[subRoute.routeDef.toString()] = subRoute;
});
});
return routes; const subRoutes =
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
},
{});
return allLazyRoutes;
} }
@ -192,7 +198,7 @@ function _collectRoutes(
*/ */
function _collectLoadChildren(routes: Route[]): string[] { function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => { return routes.reduce((m, r) => {
if (r.loadChildren) { if (r.loadChildren && typeof r.loadChildren === 'string') {
return m.concat(r.loadChildren); return m.concat(r.loadChildren);
} else if (Array.isArray(r)) { } else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r)); return m.concat(_collectLoadChildren(r));

View File

@ -9,10 +9,10 @@
import {__core_private__ as r} from '@angular/core'; import {__core_private__ as r} from '@angular/core';
export type ReflectorReader = typeof r._ReflectorReader; export type ReflectorReader = typeof r._ReflectorReader;
export var ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader; export const ReflectorReader: typeof r.ReflectorReader = r.ReflectorReader;
export type ReflectionCapabilities = typeof r._ReflectionCapabilities; export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities; export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export type Console = typeof r._Console; export type Console = typeof r._Console;
export var Console: typeof r.Console = r.Console; export const Console: typeof r.Console = r.Console;

View File

@ -122,11 +122,9 @@ export class MockCompilerHost implements ts.CompilerHost {
return ts.getDefaultLibFileName(options); return ts.getDefaultLibFileName(options);
} }
writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); } writeFile: ts.WriteFileCallback = (fileName, text) => { this.context.writeFile(fileName, text); };
getCurrentDirectory(): string { getCurrentDirectory(): string { return this.context.currentDirectory; }
return this.context.currentDirectory;
}
getCanonicalFileName(fileName: string): string { return fileName; } getCanonicalFileName(fileName: string): string { return fileName; }

View File

@ -9,5 +9,5 @@
import {__core_private__ as r} from '@angular/core'; import {__core_private__ as r} from '@angular/core';
export type ReflectionCapabilities = typeof r._ReflectionCapabilities; export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities; export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export var reflector: typeof r.reflector = r.reflector; export const reflector: typeof r.reflector = r.reflector;

View File

@ -6,8 +6,6 @@
* 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 {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';

View File

@ -9,9 +9,10 @@
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core'; import {ReflectorReader} from '../private_import_core';
import {SyntaxError} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; import {StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = { const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata', coreDecorators: '@angular/core/src/metadata',
@ -310,6 +311,7 @@ export class StaticReflector implements ReflectorReader {
if (value && (depth != 0 || value.__symbolic != 'error')) { if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters']; const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults; const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
if (defaults && defaults.length > args.length) { if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
} }
@ -499,15 +501,15 @@ export class StaticReflector implements ReflectorReader {
return context; return context;
} }
const argExpressions: any[] = expression['arguments'] || []; const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol); let converter = self.conversionMap.get(staticSymbol);
if (converter) { if (converter) {
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
return converter(context, args); return converter(context, args);
} else { } else {
// Determine if the function is one we can simplify. // Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol); const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, args); return simplifyCall(staticSymbol, targetFunction, argExpressions);
} }
} }
break; break;
@ -537,7 +539,7 @@ export class StaticReflector implements ReflectorReader {
if (e.fileName) { if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column); throw positionalError(message, e.fileName, e.line, e.column);
} }
throw new Error(message); throw new SyntaxError(message);
} }
} }
@ -651,10 +653,6 @@ class PopulatedScope extends BindingScope {
} }
} }
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b;
}
function shouldIgnore(value: any): boolean { function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore'; return value && value.__symbolic == 'ignore';
} }

View File

@ -273,7 +273,12 @@ export class StaticSymbolResolver {
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol { getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
const filePath = this.resolveModule(module, containingFile); const filePath = this.resolveModule(module, containingFile);
if (!filePath) { if (!filePath) {
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`); this.reportError(
new Error(`Could not resolve module ${module}${containingFile ? ` relative to $ {
containingFile
} `: ''}`),
null);
return this.getStaticSymbol(`ERROR:${module}`, symbolName);
} }
return this.getStaticSymbol(filePath, symbolName); return this.getStaticSymbol(filePath, symbolName);
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver'; import {Summary, SummaryResolver} from '../summary_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol, StaticSymbolCache} from './static_symbol';

View File

@ -5,16 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver'; import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util'; import {ValueTransformer, visitValue} from '../util';
import {GeneratedFile} from './generated_file';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummarySerializerHost { export interface AotSummarySerializerHost {
/** /**
* Returns the output file path of a source file. * Returns the output file path of a source file.

View File

@ -15,10 +15,6 @@ import {LifecycleHooks, reflector} from './private_import_core';
import {CssSelector} from './selector'; import {CssSelector} from './selector';
import {splitAtColon} from './util'; import {splitAtColon} from './util';
function unimplemented(): any {
throw new Error('unimplemented');
}
// group 0: "[prop] or (event) or @trigger" // group 0: "[prop] or (event) or @trigger"
// group 1: "prop" from "[prop]" // group 1: "prop" from "[prop]"
// group 2: "event" from "(event)" // group 2: "event" from "(event)"

View File

@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder'; import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';

View File

@ -8,7 +8,7 @@
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder'; import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver = this.visit(ast.receiver, _Mode.Expression); const receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); const varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (varExpr) {
result = varExpr.callFn(args); result = varExpr.callFn(args);
} }
} }
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); const varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (varExpr) {
throw new Error('Cannot assign to a reference or variable!'); throw new Error('Cannot assign to a reference or variable!');
} }
} }

View File

@ -11,10 +11,6 @@ import {ViewEncapsulation, isDevMode} from '@angular/core';
import {CompileIdentifierMetadata} from './compile_metadata'; import {CompileIdentifierMetadata} from './compile_metadata';
import {Identifiers, createIdentifier} from './identifiers'; import {Identifiers, createIdentifier} from './identifiers';
function unimplemented(): any {
throw new Error('unimplemented');
}
export class CompilerConfig { export class CompilerConfig {
public renderTypes: RenderTypes; public renderTypes: RenderTypes;
public defaultEncapsulation: ViewEncapsulation; public defaultEncapsulation: ViewEncapsulation;
@ -52,12 +48,12 @@ export class CompilerConfig {
* to help tree shaking. * to help tree shaking.
*/ */
export abstract class RenderTypes { export abstract class RenderTypes {
get renderer(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderer(): CompileIdentifierMetadata;
get renderText(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderText(): CompileIdentifierMetadata;
get renderElement(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderElement(): CompileIdentifierMetadata;
get renderComment(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderComment(): CompileIdentifierMetadata;
get renderNode(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderNode(): CompileIdentifierMetadata;
get renderEvent(): CompileIdentifierMetadata { return unimplemented(); } abstract get renderEvent(): CompileIdentifierMetadata;
} }
export class DefaultRenderTypes implements RenderTypes { export class DefaultRenderTypes implements RenderTypes {

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
@ -102,11 +102,12 @@ export class DirectiveNormalizer {
templateAbsUrl: string): CompileTemplateMetadata { templateAbsUrl: string): CompileTemplateMetadata {
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation); const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
const rootNodesAndErrors = this._htmlParser.parse( const rootNodesAndErrors = this._htmlParser.parse(
template, stringify(prenomData.componentType), false, interpolationConfig); template, stringify(prenomData.componentType), true, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) { if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n'); const errorString = rootNodesAndErrors.errors.join('\n');
throw new SyntaxError(`Template parse errors:\n${errorString}`); throw new SyntaxError(`Template parse errors:\n${errorString}`);
} }
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({ const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles, styles: prenomData.styles,
styleUrls: prenomData.styleUrls, styleUrls: prenomData.styleUrls,
@ -228,9 +229,13 @@ class TemplatePreparseVisitor implements html.Visitor {
return null; return null;
} }
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
html.visitAll(this, ast.expression);
}
visitComment(ast: html.Comment, context: any): any { return null; } visitComment(ast: html.Comment, context: any): any { return null; }
visitAttribute(ast: html.Attribute, context: any): any { return null; } visitAttribute(ast: html.Attribute, context: any): any { return null; }
visitText(ast: html.Text, context: any): any { return null; } visitText(ast: html.Text, context: any): any { return null; }
visitExpansion(ast: html.Expansion, context: any): any { return null; }
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
} }

View File

@ -14,8 +14,6 @@ import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util'; import {splitAtColon} from './util';
/* /*
* Resolve a `Type` for {@link Directive}. * Resolve a `Type` for {@link Directive}.
* *

View File

@ -18,7 +18,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder'; import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core'; import {Console, LifecycleHooks} from './private_import_core';
import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {BindingParser} from './template_parser/binding_parser'; import {BindingParser} from './template_parser/binding_parser';
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast'; import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
@ -128,7 +128,6 @@ class DirectiveWrapperBuilder implements ClassBuilder {
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts), new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
]; ];
const fields: o.ClassField[] = [ const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]), 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(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),

View File

@ -7,7 +7,7 @@
*/ */
import * as chars from '../chars'; import * as chars from '../chars';
import {NumberWrapper, isPresent} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {CompilerInjectable} from '../injectable'; import {CompilerInjectable} from '../injectable';
export enum TokenType { export enum TokenType {
@ -120,7 +120,7 @@ function newErrorToken(index: number, message: string): Token {
return new Token(index, TokenType.Error, 0, message); return new Token(index, TokenType.Error, 0, message);
} }
export var EOF: Token = new Token(-1, TokenType.Character, 0, ''); export const EOF: Token = new Token(-1, TokenType.Character, 0, '');
class _Scanner { class _Scanner {
length: number; length: number;
@ -241,7 +241,7 @@ class _Scanner {
this.advance(); this.advance();
str += two; str += two;
} }
if (isPresent(threeCode) && this.peek == threeCode) { if (threeCode != null && this.peek == threeCode) {
this.advance(); this.advance();
str += three; str += three;
} }

View File

@ -52,7 +52,7 @@ export class Extractor {
extract(rootFiles: string[]): Promise<MessageBundle> { extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host); const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
const {ngModuleByPipeOrDirective, files, ngModules} = const {files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver); analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
return Promise return Promise
.all(ngModules.map( .all(ngModules.map(

View File

@ -53,20 +53,22 @@ enum _VisitorMode {
* @internal * @internal
*/ */
class _Visitor implements html.Visitor { class _Visitor implements html.Visitor {
private _depth: number;
// <el i18n>...</el> // <el i18n>...</el>
private _inI18nNode: boolean; private _inI18nNode: boolean;
private _depth: number;
private _inImplicitNode: boolean; private _inImplicitNode: boolean;
// <!--i18n-->...<!--/i18n--> // <!--i18n-->...<!--/i18n-->
private _inI18nBlock: boolean;
private _blockMeaningAndDesc: string; private _blockMeaningAndDesc: string;
private _blockChildren: html.Node[]; private _blockChildren: html.Node[];
private _blockStartDepth: number; private _blockStartDepth: number;
private _inI18nBlock: boolean;
// {<icu message>} // {<icu message>}
private _inIcu: boolean; private _inIcu: boolean;
// set to void 0 when not in a section
private _msgCountAtSectionStart: number; private _msgCountAtSectionStart: number;
private _errors: I18nError[]; private _errors: I18nError[];
private _mode: _VisitorMode; private _mode: _VisitorMode;
@ -208,50 +210,31 @@ class _Visitor implements html.Visitor {
this._depth++; this._depth++;
const wasInI18nNode = this._inI18nNode; const wasInI18nNode = this._inI18nNode;
const wasInImplicitNode = this._inImplicitNode; const wasInImplicitNode = this._inImplicitNode;
let childNodes: html.Node[]; let childNodes: html.Node[] = [];
let translatedChildNodes: html.Node[];
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU // Extract:
// message // - top level nodes with the (implicit) "i18n" attribute if not already in a section
// - ICU messages
const i18nAttr = _getI18nAttr(el); const i18nAttr = _getI18nAttr(el);
const i18nMeta = i18nAttr ? i18nAttr.value : '';
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu && const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
!this._isInTranslatableSection; !this._isInTranslatableSection;
const isTopLevelImplicit = !wasInImplicitNode && isImplicit; const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit; this._inImplicitNode = wasInImplicitNode || isImplicit;
if (!this._isInTranslatableSection && !this._inIcu) { if (!this._isInTranslatableSection && !this._inIcu) {
if (i18nAttr) { if (i18nAttr || isTopLevelImplicit) {
// explicit translation
this._inI18nNode = true; this._inI18nNode = true;
const message = this._addMessage(el.children, i18nAttr.value); const message = this._addMessage(el.children, i18nMeta);
childNodes = this._translateMessage(el, message); translatedChildNodes = this._translateMessage(el, message);
} else if (isTopLevelImplicit) {
// implicit translation
this._inI18nNode = true;
const message = this._addMessage(el.children);
childNodes = this._translateMessage(el, message);
} }
if (this._mode == _VisitorMode.Extract) { if (this._mode == _VisitorMode.Extract) {
const isTranslatable = i18nAttr || isTopLevelImplicit; const isTranslatable = i18nAttr || isTopLevelImplicit;
if (isTranslatable) { if (isTranslatable) this._openTranslatableSection(el);
this._openTranslatableSection(el);
}
html.visitAll(this, el.children); html.visitAll(this, el.children);
if (isTranslatable) { if (isTranslatable) this._closeTranslatableSection(el, el.children);
this._closeTranslatableSection(el, el.children);
}
}
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
} }
} else { } else {
if (i18nAttr || isTopLevelImplicit) { if (i18nAttr || isTopLevelImplicit) {
@ -263,19 +246,18 @@ class _Visitor implements html.Visitor {
// Descend into child nodes for extraction // Descend into child nodes for extraction
html.visitAll(this, el.children); html.visitAll(this, el.children);
} }
}
if (this._mode == _VisitorMode.Merge) { if (this._mode === _VisitorMode.Merge) {
// Translate attributes in ICU messages const visitNodes = translatedChildNodes || el.children;
childNodes = []; visitNodes.forEach(child => {
el.children.forEach(child => { const visited = child.visit(this, context);
const visited = child.visit(this, context); if (visited && !this._isInTranslatableSection) {
if (visited && !this._isInTranslatableSection) { // Do not add the children from translatable sections (= i18n blocks here)
// Do not add the children from translatable sections (= i18n blocks here) // They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`) childNodes = childNodes.concat(visited);
childNodes = childNodes.concat(visited); }
} });
});
}
} }
this._visitAttributesOf(el); this._visitAttributesOf(el);
@ -285,7 +267,6 @@ class _Visitor implements html.Visitor {
this._inImplicitNode = wasInImplicitNode; this._inImplicitNode = wasInImplicitNode;
if (this._mode === _VisitorMode.Merge) { if (this._mode === _VisitorMode.Merge) {
// There are no childNodes in translatable sections - those nodes will be replace anyway
const translatedAttrs = this._translateAttributes(el); const translatedAttrs = this._translateAttributes(el);
return new html.Element( return new html.Element(
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan, el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
@ -344,6 +325,7 @@ class _Visitor implements html.Visitor {
} }
// Translates the given message given the `TranslationBundle` // Translates the given message given the `TranslationBundle`
// This is used for translating elements / blocks - see `_translateAttributes` for attributes
// no-op when called in extraction mode (returns []) // no-op when called in extraction mode (returns [])
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] { private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
if (message && this._mode === _VisitorMode.Merge) { if (message && this._mode === _VisitorMode.Merge) {
@ -385,7 +367,9 @@ class _Visitor implements html.Visitor {
const message: i18n.Message = this._createI18nMessage([attr], meaning, ''); const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
const nodes = this._translations.get(message); const nodes = this._translations.get(message);
if (nodes) { if (nodes) {
if (nodes[0] instanceof html.Text) { if (nodes.length == 0) {
translatedAttributes.push(new html.Attribute(attr.name, '', attr.sourceSpan));
} else if (nodes[0] instanceof html.Text) {
const value = (nodes[0] as html.Text).value; const value = (nodes[0] as html.Text).value;
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan)); translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
} else { } else {
@ -420,7 +404,7 @@ class _Visitor implements html.Visitor {
} }
/** /**
* Marks the start of a section, see `_endSection` * Marks the start of a section, see `_closeTranslatableSection`
*/ */
private _openTranslatableSection(node: html.Node): void { private _openTranslatableSection(node: html.Node): void {
if (this._isInTranslatableSection) { if (this._isInTranslatableSection) {
@ -432,7 +416,7 @@ class _Visitor implements html.Visitor {
/** /**
* A translatable section could be: * A translatable section could be:
* - a translatable element, * - the content of translatable element,
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments * - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
*/ */
private get _isInTranslatableSection(): boolean { private get _isInTranslatableSection(): boolean {

View File

@ -111,8 +111,14 @@ export class PlaceholderRegistry {
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); } private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
private _generateUniqueName(base: string): string { private _generateUniqueName(base: string): string {
const next = this._placeHolderNameCounts[base]; const seen = this._placeHolderNameCounts.hasOwnProperty(base);
this._placeHolderNameCounts[base] = next ? next + 1 : 1; if (!seen) {
return next ? `${base}_${next}` : base; this._placeHolderNameCounts[base] = 1;
return base;
}
const id = this._placeHolderNameCounts[base];
this._placeHolderNameCounts[base] = id + 1;
return `${base}_${id}`;
} }
} }

View File

@ -8,10 +8,26 @@
import * as i18n from '../i18n_ast'; import * as i18n from '../i18n_ast';
export interface Serializer { export abstract class Serializer {
write(messages: i18n.Message[]): string; abstract write(messages: i18n.Message[]): string;
load(content: string, url: string): {[msgId: string]: i18n.Node[]}; abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
digest(message: i18n.Message): string; abstract digest(message: i18n.Message): string;
// Creates a name mapper, see `PlaceholderMapper`
// Returning `null` means that no name mapping is used.
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
}
/**
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
* back.
*
* It should be used for serialization format that put constraints on the placeholder names.
*/
export interface PlaceholderMapper {
toPublicName(internalName: string): string;
toInternalName(publicName: string): string;
} }

View File

@ -27,7 +27,7 @@ const _UNIT_TAG = 'trans-unit';
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html // http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
export class Xliff implements Serializer { export class Xliff extends Serializer {
write(messages: i18n.Message[]): string { write(messages: i18n.Message[]): string {
const visitor = new _WriteVisitor(); const visitor = new _WriteVisitor();
const visited: {[id: string]: boolean} = {}; const visited: {[id: string]: boolean} = {};

View File

@ -9,7 +9,7 @@
import {decimalDigest} from '../digest'; import {decimalDigest} from '../digest';
import * as i18n from '../i18n_ast'; import * as i18n from '../i18n_ast';
import {Serializer} from './serializer'; import {PlaceholderMapper, Serializer} from './serializer';
import * as xml from './xml_helper'; import * as xml from './xml_helper';
const _MESSAGES_TAG = 'messagebundle'; const _MESSAGES_TAG = 'messagebundle';
@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
<!ELEMENT ex (#PCDATA)>`; <!ELEMENT ex (#PCDATA)>`;
export class Xmb implements Serializer { export class Xmb extends Serializer {
write(messages: i18n.Message[]): string { write(messages: i18n.Message[]): string {
const exampleVisitor = new ExampleVisitor(); const exampleVisitor = new ExampleVisitor();
const visitor = new _Visitor(); const visitor = new _Visitor();
@ -51,6 +51,8 @@ export class Xmb implements Serializer {
if (visited[id]) return; if (visited[id]) return;
visited[id] = true; visited[id] = true;
const mapper = this.createNameMapper(message);
const attrs: {[k: string]: string} = {id}; const attrs: {[k: string]: string} = {id};
if (message.description) { if (message.description) {
@ -62,7 +64,8 @@ export class Xmb implements Serializer {
} }
rootNode.children.push( rootNode.children.push(
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes))); new xml.CR(2),
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
}); });
rootNode.children.push(new xml.CR()); rootNode.children.push(new xml.CR());
@ -82,22 +85,29 @@ export class Xmb implements Serializer {
} }
digest(message: i18n.Message): string { return digest(message); } digest(message: i18n.Message): string { return digest(message); }
createNameMapper(message: i18n.Message): PlaceholderMapper {
return new XmbPlaceholderMapper(message);
}
} }
class _Visitor implements i18n.Visitor { class _Visitor implements i18n.Visitor {
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; } visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [new xml.Text(text.value)];
}
visitContainer(container: i18n.Container, context?: any): xml.Node[] { visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
const nodes: xml.Node[] = []; const nodes: xml.Node[] = [];
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this))); container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
return nodes; return nodes;
} }
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] { visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)]; const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
Object.keys(icu.cases).forEach((c: string) => { Object.keys(icu.cases).forEach((c: string) => {
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `)); nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
}); });
nodes.push(new xml.Text(`}`)); nodes.push(new xml.Text(`}`));
@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
return nodes; return nodes;
} }
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] { visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]); const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]); let name = ctx.mapper.toPublicName(ph.startName);
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
if (ph.isVoid) { if (ph.isVoid) {
// void tags have no children nor closing tags // void tags have no children nor closing tags
return [startTagPh]; return [startTagPh];
} }
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]); const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]); name = ctx.mapper.toPublicName(ph.closeName);
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
return [startTagPh, ...this.serialize(ph.children), closeTagPh]; return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
} }
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] { visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})]; const name = ctx.mapper.toPublicName(ph.name);
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
} }
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] { visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})]; const name = ctx.mapper.toPublicName(ph.name);
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
} }
serialize(nodes: i18n.Node[]): xml.Node[] { serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
return [].concat(...nodes.map(node => node.visit(this))); return [].concat(...nodes.map(node => node.visit(this, ctx)));
} }
} }
@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
visitDeclaration(decl: xml.Declaration): void {} visitDeclaration(decl: xml.Declaration): void {}
visitDoctype(doctype: xml.Doctype): void {} visitDoctype(doctype: xml.Doctype): void {}
} }
/**
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
*
* Because such restrictions do not exist on placeholder names generated locally, the
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
* serialized and back from XTB to internal names when an XTB is loaded.
*/
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
private internalToXmb: {[k: string]: string} = {};
private xmbToNextId: {[k: string]: number} = {};
private xmbToInternal: {[k: string]: string} = {};
// create a mapping from the message
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
toPublicName(internalName: string): string {
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
null;
}
toInternalName(publicName: string): string {
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
}
visitText(text: i18n.Text, ctx?: any): any { return null; }
visitContainer(container: i18n.Container, ctx?: any): any {
container.children.forEach(child => child.visit(this));
}
visitIcu(icu: i18n.Icu, ctx?: any): any {
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
}
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
this.addPlaceholder(ph.startName);
ph.children.forEach(child => child.visit(this));
this.addPlaceholder(ph.closeName);
}
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
// XMB placeholders could only contains A-Z, 0-9 and _
private addPlaceholder(internalName: string): void {
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
return;
}
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
// Create a new XMB when it has already been used
const nextId = this.xmbToNextId[xmbName];
this.xmbToNextId[xmbName] = nextId + 1;
xmbName = `${xmbName}_${nextId}`;
} else {
this.xmbToNextId[xmbName] = 1;
}
this.internalToXmb[internalName] = xmbName;
this.xmbToInternal[xmbName] = internalName;
}
}

View File

@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
import * as i18n from '../i18n_ast'; import * as i18n from '../i18n_ast';
import {I18nError} from '../parse_util'; import {I18nError} from '../parse_util';
import {Serializer} from './serializer'; import {PlaceholderMapper, Serializer} from './serializer';
import {digest} from './xmb'; import {XmbPlaceholderMapper, digest} from './xmb';
const _TRANSLATIONS_TAG = 'translationbundle'; const _TRANSLATIONS_TAG = 'translationbundle';
const _TRANSLATION_TAG = 'translation'; const _TRANSLATION_TAG = 'translation';
const _PLACEHOLDER_TAG = 'ph'; const _PLACEHOLDER_TAG = 'ph';
export class Xtb implements Serializer { export class Xtb extends Serializer {
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); } write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
load(content: string, url: string): {[msgId: string]: i18n.Node[]} { load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
@ -43,6 +43,10 @@ export class Xtb implements Serializer {
} }
digest(message: i18n.Message): string { return digest(message); } digest(message: i18n.Message): string { return digest(message); }
createNameMapper(message: i18n.Message): PlaceholderMapper {
return new XmbPlaceholderMapper(message);
}
} }
// Extract messages as xml nodes from the xtb file // Extract messages as xml nodes from the xtb file

View File

@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
import * as i18n from './i18n_ast'; import * as i18n from './i18n_ast';
import {I18nError} from './parse_util'; import {I18nError} from './parse_util';
import {Serializer} from './serializers/serializer'; import {PlaceholderMapper, Serializer} from './serializers/serializer';
/** /**
* A container for translated messages * A container for translated messages
@ -21,16 +21,20 @@ export class TranslationBundle {
constructor( constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {}, private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
public digest: (m: i18n.Message) => string) { public digest: (m: i18n.Message) => string,
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest); public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
} }
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
static load(content: string, url: string, serializer: Serializer): TranslationBundle { static load(content: string, url: string, serializer: Serializer): TranslationBundle {
const i18nNodesByMsgId = serializer.load(content, url); const i18nNodesByMsgId = serializer.load(content, url);
const digestFn = (m: i18n.Message) => serializer.digest(m); const digestFn = (m: i18n.Message) => serializer.digest(m);
return new TranslationBundle(i18nNodesByMsgId, digestFn); const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
} }
// Returns the translation as HTML nodes from the given source message.
get(srcMsg: i18n.Message): html.Node[] { get(srcMsg: i18n.Message): html.Node[] {
const html = this._i18nToHtml.convert(srcMsg); const html = this._i18nToHtml.convert(srcMsg);
@ -46,15 +50,17 @@ export class TranslationBundle {
class I18nToHtmlVisitor implements i18n.Visitor { class I18nToHtmlVisitor implements i18n.Visitor {
private _srcMsg: i18n.Message; private _srcMsg: i18n.Message;
private _srcMsgStack: i18n.Message[] = []; private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
private _errors: I18nError[] = []; private _errors: I18nError[] = [];
private _mapper: (name: string) => string;
constructor( constructor(
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {}, private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
private _digest: (m: i18n.Message) => string) {} private _digest: (m: i18n.Message) => string,
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} { convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
this._srcMsgStack.length = 0; this._contextStack.length = 0;
this._errors.length = 0; this._errors.length = 0;
// i18n to text // i18n to text
const text = this._convertToText(srcMsg); const text = this._convertToText(srcMsg);
@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
} }
visitPlaceholder(ph: i18n.Placeholder, context?: any): string { visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
const phName = ph.name; const phName = this._mapper(ph.name);
if (this._srcMsg.placeholders.hasOwnProperty(phName)) { if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
return this._srcMsg.placeholders[phName]; return this._srcMsg.placeholders[phName];
} }
@ -105,14 +111,26 @@ class I18nToHtmlVisitor implements i18n.Visitor {
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; } visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
/**
* Convert a source message to a translated text string:
* - text nodes are replaced with their translation,
* - placeholders are replaced with their content,
* - ICU nodes are converted to ICU expressions.
*/
private _convertToText(srcMsg: i18n.Message): string { private _convertToText(srcMsg: i18n.Message): string {
const digest = this._digest(srcMsg); const digest = this._digest(srcMsg);
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) { if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
this._srcMsgStack.push(this._srcMsg); this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
this._srcMsg = srcMsg; this._srcMsg = srcMsg;
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
const nodes = this._i18nNodesByMsgId[digest]; const nodes = this._i18nNodesByMsgId[digest];
const text = nodes.map(node => node.visit(this)).join(''); const text = nodes.map(node => node.visit(this)).join('');
this._srcMsg = this._srcMsgStack.pop(); const context = this._contextStack.pop();
this._srcMsg = context.msg;
this._mapper = context.mapper;
return text; return text;
} }

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 {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol'; import {StaticSymbol} from './aot/static_symbol';
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';

View File

@ -114,7 +114,7 @@ export class JitCompiler implements Compiler {
// Note: the loadingPromise for a module only includes the loading of the exported directives // Note: the loadingPromise for a module only includes the loading of the exported directives
// of imported modules. // of imported modules.
// However, for runtime compilation, we want to transitively compile all modules, // However, for runtime compilation, we want to transitively compile all modules,
// so we also need to call loadNgModuleMetadata for all nested modules. // so we also need to call loadNgModuleDirectiveAndPipeMetadata for all nested modules.
ngModule.transitiveModule.modules.forEach((localModuleMeta) => { ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
localModuleMeta.reference, isSync)); localModuleMeta.reference, isSync));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
@ -40,6 +40,8 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);} `No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
}; };
const baseHtmlParser = new OpaqueToken('HtmlParser');
/** /**
* A set of providers that provide `JitCompiler` and its dependencies to use for * A set of providers that provide `JitCompiler` and its dependencies to use for
* template compilation. * template compilation.
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
Console, Console,
Lexer, Lexer,
Parser, Parser,
HtmlParser, {
provide: baseHtmlParser,
useClass: HtmlParser,
},
{ {
provide: i18n.I18NHtmlParser, provide: i18n.I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string, format: string) => useFactory: (parser: HtmlParser, translations: string, format: string) =>
new i18n.I18NHtmlParser(parser, translations, format), new i18n.I18NHtmlParser(parser, translations, format),
deps: [ deps: [
HtmlParser, baseHtmlParser,
[new Optional(), new Inject(TRANSLATIONS)], [new Optional(), new Inject(TRANSLATIONS)],
[new Optional(), new Inject(TRANSLATIONS_FORMAT)], [new Optional(), new Inject(TRANSLATIONS_FORMAT)],
] ]
}, },
{
provide: HtmlParser,
useExisting: i18n.I18NHtmlParser,
},
TemplateParser, TemplateParser,
DirectiveNormalizer, DirectiveNormalizer,
CompileMetadataResolver, CompileMetadataResolver,

View File

@ -294,14 +294,14 @@ export class CompileMetadataResolver {
/** /**
* Gets the metadata for the given directive. * Gets the metadata for the given directive.
* This assumes `loadNgModuleMetadata` has been called first. * This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/ */
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata { getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
const dirMeta = this._directiveCache.get(directiveType); const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) { if (!dirMeta) {
this._reportError( this._reportError(
new SyntaxError( new SyntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`), `Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType); directiveType);
} }
return dirMeta; return dirMeta;
@ -648,14 +648,14 @@ export class CompileMetadataResolver {
/** /**
* Gets the metadata for the given pipe. * Gets the metadata for the given pipe.
* This assumes `loadNgModuleMetadata` has been called first. * This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first.
*/ */
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata { getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
const pipeMeta = this._pipeCache.get(pipeType); const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) { if (!pipeMeta) {
this._reportError( this._reportError(
new SyntaxError( new SyntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`), `Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType); pipeType);
} }
return pipeMeta; return pipeMeta;

View File

@ -283,7 +283,7 @@ class _TreeBuilder {
const tagDef = this.getTagDefinition(el.name); const tagDef = this.getTagDefinition(el.name);
const {parent, container} = this._getParentElementSkippingContainers(); const {parent, container} = this._getParentElementSkippingContainers();
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) { if (parent && tagDef.requireExtraParent(parent.name)) {
const newParent = new html.Element( const newParent = new html.Element(
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan); tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
this._insertBeforeContainer(parent, container, newParent); this._insertBeforeContainer(parent, container, newParent);

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util'; import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder'; import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';

View File

@ -9,7 +9,7 @@
import {NgModule, Type} from '@angular/core'; import {NgModule, Type} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
@ -30,7 +30,7 @@ export class NgModuleResolver {
const ngModuleMeta: NgModule = const ngModuleMeta: NgModule =
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata); ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
if (isPresent(ngModuleMeta)) { if (ngModuleMeta) {
return ngModuleMeta; return ngModuleMeta;
} else { } else {
if (throwIfNotFound) { if (throwIfNotFound) {

View File

@ -68,13 +68,13 @@ export class MapType extends Type {
visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); } visitType(visitor: TypeVisitor, context: any): any { return visitor.visitMapType(this, context); }
} }
export var DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic); export const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
export var BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool); export const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
export var INT_TYPE = new BuiltinType(BuiltinTypeName.Int); export const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
export var NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number); export const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
export var STRING_TYPE = new BuiltinType(BuiltinTypeName.String); export const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
export var FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function); export const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
export var NULL_TYPE = new BuiltinType(BuiltinTypeName.Null); export const NULL_TYPE = new BuiltinType(BuiltinTypeName.Null);
export interface TypeVisitor { export interface TypeVisitor {
visitBuiltintType(type: BuiltinType, context: any): any; visitBuiltintType(type: BuiltinType, context: any): any;
@ -451,12 +451,12 @@ export interface ExpressionVisitor {
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any; visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any;
} }
export var THIS_EXPR = new ReadVarExpr(BuiltinVar.This); export const THIS_EXPR = new ReadVarExpr(BuiltinVar.This);
export var SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super); export const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super);
export var CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError); export const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError);
export var CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack); export const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack);
export var NULL_EXPR = new LiteralExpr(null, null); export const NULL_EXPR = new LiteralExpr(null, null);
export var TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE); export const TYPED_NULL_EXPR = new LiteralExpr(null, NULL_TYPE);
//// Statements //// Statements
export enum StmtModifier { export enum StmtModifier {

View File

@ -7,7 +7,6 @@
*/ */
import {CompileIdentifierMetadata} from '../compile_metadata';
import {ValueTransformer, visitValue} from '../util'; import {ValueTransformer, visitValue} from '../util';
import * as o from './output_ast'; import * as o from './output_ast';

View File

@ -48,6 +48,51 @@ export class ParseLocation {
} }
return new ParseLocation(this.file, offset, line, col); return new ParseLocation(this.file, offset, line, col);
} }
// Return the source around the location
// Up to `maxChars` or `maxLines` on each side of the location
getContext(maxChars: number, maxLines: number): {before: string, after: string} {
const content = this.file.content;
let startOffset = this.offset;
if (isPresent(startOffset)) {
if (startOffset > content.length - 1) {
startOffset = content.length - 1;
}
let endOffset = startOffset;
let ctxChars = 0;
let ctxLines = 0;
while (ctxChars < maxChars && startOffset > 0) {
startOffset--;
ctxChars++;
if (content[startOffset] == '\n') {
if (++ctxLines == maxLines) {
break;
}
}
}
ctxChars = 0;
ctxLines = 0;
while (ctxChars < maxChars && endOffset < content.length - 1) {
endOffset++;
ctxChars++;
if (content[endOffset] == '\n') {
if (++ctxLines == maxLines) {
break;
}
}
}
return {
before: content.substring(startOffset, this.offset),
after: content.substring(this.offset, endOffset + 1),
};
}
return null;
}
} }
export class ParseSourceFile { export class ParseSourceFile {
@ -74,47 +119,9 @@ export class ParseError {
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {} public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
toString(): string { toString(): string {
const source = this.span.start.file.content; const ctx = this.span.start.getContext(100, 3);
let ctxStart = this.span.start.offset; const contextStr = ctx ? ` ("${ctx.before}[ERROR ->]${ctx.after}")` : '';
let contextStr = ''; const details = this.span.details ? `, ${this.span.details}` : '';
let details = '';
if (isPresent(ctxStart)) {
if (ctxStart > source.length - 1) {
ctxStart = source.length - 1;
}
let ctxEnd = ctxStart;
let ctxLen = 0;
let ctxLines = 0;
while (ctxLen < 100 && ctxStart > 0) {
ctxStart--;
ctxLen++;
if (source[ctxStart] == '\n') {
if (++ctxLines == 3) {
break;
}
}
}
ctxLen = 0;
ctxLines = 0;
while (ctxLen < 100 && ctxEnd < source.length - 1) {
ctxEnd++;
ctxLen++;
if (source[ctxEnd] == '\n') {
if (++ctxLines == 3) {
break;
}
}
}
const context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' +
source.substring(this.span.start.offset, ctxEnd + 1);
contextStr = ` ("${context}")`;
}
if (this.span.details) {
details = `, ${this.span.details}`;
}
return `${this.msg}${contextStr}: ${this.span.start}${details}`; return `${this.msg}${contextStr}: ${this.span.start}${details}`;
} }
} }

View File

@ -9,7 +9,7 @@
import {Pipe, Type, resolveForwardRef} from '@angular/core'; import {Pipe, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
@ -38,9 +38,9 @@ export class PipeResolver {
*/ */
resolve(type: Type<any>, throwIfNotFound = true): Pipe { resolve(type: Type<any>, throwIfNotFound = true): Pipe {
const metas = this._reflector.annotations(resolveForwardRef(type)); const metas = this._reflector.annotations(resolveForwardRef(type));
if (isPresent(metas)) { if (metas) {
const annotation = ListWrapper.findLast(metas, _isPipeMetadata); const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
if (isPresent(annotation)) { if (annotation) {
return annotation; return annotation;
} }
} }

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, resolveIdentifier} 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';
@ -114,7 +114,7 @@ export class ProviderElementContext {
let queries: CompileQueryMetadata[]; let queries: CompileQueryMetadata[];
while (currentEl !== null) { while (currentEl !== null) {
queries = currentEl._contentQueries.get(tokenReference(token)); queries = currentEl._contentQueries.get(tokenReference(token));
if (isPresent(queries)) { if (queries) {
result.push(...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) {
@ -123,7 +123,7 @@ export class ProviderElementContext {
currentEl = currentEl._parent; currentEl = currentEl._parent;
} }
queries = this.viewContext.viewQueries.get(tokenReference(token)); queries = this.viewContext.viewQueries.get(tokenReference(token));
if (isPresent(queries)) { if (queries) {
result.push(...queries); result.push(...queries);
} }
return result; return result;
@ -143,7 +143,7 @@ export class ProviderElementContext {
return null; return null;
} }
let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (isPresent(transformedProviderAst)) { if (transformedProviderAst) {
return transformedProviderAst; return transformedProviderAst;
} }
if (isPresent(this._seenProviders.get(tokenReference(token)))) { if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -165,11 +165,11 @@ export class ProviderElementContext {
transformedUseExisting = null; transformedUseExisting = null;
transformedUseValue = existingDiDep.value; transformedUseValue = existingDiDep.value;
} }
} else if (isPresent(provider.useFactory)) { } else if (provider.useFactory) {
const deps = provider.deps || provider.useFactory.diDeps; const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)); deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
} else if (isPresent(provider.useClass)) { } else if (provider.useClass) {
const deps = provider.deps || provider.useClass.diDeps; const deps = provider.deps || provider.useClass.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)); deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
@ -235,7 +235,7 @@ export class ProviderElementContext {
} }
} else { } else {
// check parent elements // check parent elements
while (!result && isPresent(currElement._parent)) { while (!result && currElement._parent) {
const prevElement = currElement; const prevElement = currElement;
currElement = currElement._parent; currElement = currElement._parent;
if (prevElement._isViewRoot) { if (prevElement._isViewRoot) {
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
return null; return null;
} }
let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (isPresent(transformedProviderAst)) { if (transformedProviderAst) {
return transformedProviderAst; return transformedProviderAst;
} }
if (isPresent(this._seenProviders.get(tokenReference(token)))) { if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
transformedUseExisting = null; transformedUseExisting = null;
transformedUseValue = existingDiDep.value; transformedUseValue = existingDiDep.value;
} }
} else if (isPresent(provider.useFactory)) { } else if (provider.useFactory) {
const deps = provider.deps || provider.useFactory.diDeps; const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
} else if (isPresent(provider.useClass)) { } else if (provider.useClass) {
const deps = provider.deps || provider.useClass.diDeps; const deps = provider.deps || provider.useClass.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
@ -454,7 +454,7 @@ function _resolveProviders(
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> { function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
const viewQueries = new Map<any, CompileQueryMetadata[]>(); const viewQueries = new Map<any, CompileQueryMetadata[]>();
if (isPresent(component.viewQueries)) { if (component.viewQueries) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query)); component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
} }
return viewQueries; return viewQueries;
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
Map<any, CompileQueryMetadata[]> { Map<any, CompileQueryMetadata[]> {
const contentQueries = new Map<any, CompileQueryMetadata[]>(); const contentQueries = new Map<any, CompileQueryMetadata[]>();
directives.forEach(directive => { directives.forEach(directive => {
if (isPresent(directive.queries)) { if (directive.queries) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query)); directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
} }
}); });

View File

@ -9,12 +9,13 @@
import {getHtmlTagDefinition} from './ml_parser/html_tags'; import {getHtmlTagDefinition} from './ml_parser/html_tags';
const _SELECTOR_REGEXP = new RegExp( const _SELECTOR_REGEXP = new RegExp(
'(\\:not\\()|' + //":not(" '(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag" '([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class" '(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" // "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
'(\\))|' + // ")" '(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
'(\\s*,\\s*)', // "," '(\\))|' + // ")"
'(\\s*,\\s*)', // ","
'g'); 'g');
/** /**

View File

@ -11,7 +11,6 @@ import {SecurityContext} from '@angular/core';
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {mergeNsAndName} from '../ml_parser/tags'; import {mergeNsAndName} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
@ -111,14 +110,14 @@ export class BindingParser {
} }
parseInlineTemplateBinding( parseInlineTemplateBinding(
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) { targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan); const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i]; const binding = bindings[i];
if (binding.keyIsVar) { if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan)); targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
} else if (isPresent(binding.expression)) { } else if (binding.expression) {
this._parsePropertyAst( this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps); binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
} else { } else {
@ -136,7 +135,7 @@ export class BindingParser {
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo); const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan); this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => { bindingsResult.templateBindings.forEach((binding) => {
if (isPresent(binding.expression)) { if (binding.expression) {
this._checkPipes(binding.expression, sourceSpan); this._checkPipes(binding.expression, sourceSpan);
} }
}); });
@ -193,7 +192,7 @@ export class BindingParser {
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][], name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]): boolean { targetProps: BoundProperty[]): boolean {
const expr = this.parseInterpolation(value, sourceSpan); const expr = this.parseInterpolation(value, sourceSpan);
if (isPresent(expr)) { if (expr) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps); this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true; return true;
} }
@ -374,7 +373,7 @@ export class BindingParser {
} }
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) { private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) { if (ast) {
const collector = new PipeCollector(); const collector = new PipeCollector();
ast.visit(collector); ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => { collector.pipes.forEach((ast, pipeName) => {

View File

@ -149,7 +149,7 @@ export class TemplateParser {
return new TemplateParseResult(result, errors); return new TemplateParseResult(result, errors);
} }
if (isPresent(this.transforms)) { if (this.transforms) {
this.transforms.forEach( this.transforms.forEach(
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); }); (transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
} }
@ -218,7 +218,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._bindingParser.parseInterpolation(text.value, text.sourceSpan); const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
if (isPresent(expr)) { if (expr) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan); return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else { } else {
return new TextAst(text.value, ngContentIndex, text.sourceSpan); return new TextAst(text.value, ngContentIndex, text.sourceSpan);
@ -248,14 +248,14 @@ class TemplateParseVisitor implements html.Visitor {
return null; return null;
} }
const matchableAttrs: string[][] = []; const matchableAttrs: [string, string][] = [];
const elementOrDirectiveProps: BoundProperty[] = []; const elementOrDirectiveProps: BoundProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = []; const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = []; const elementVars: VariableAst[] = [];
const events: BoundEventAst[] = []; const events: BoundEventAst[] = [];
const templateElementOrDirectiveProps: BoundProperty[] = []; const templateElementOrDirectiveProps: BoundProperty[] = [];
const templateMatchableAttrs: string[][] = []; const templateMatchableAttrs: [string, string][] = [];
const templateElementVars: VariableAst[] = []; const templateElementVars: VariableAst[] = [];
let hasInlineTemplates = false; let hasInlineTemplates = false;
@ -268,14 +268,17 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events, isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
elementOrDirectiveRefs, elementVars); elementOrDirectiveRefs, elementVars);
let templateBindingsSource: string|undefined = undefined; let templateBindingsSource: string|undefined;
let prefixToken: string|undefined = undefined; let prefixToken: string|undefined;
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) { let normalizedName = this._normalizeAttributeName(attr.name);
if (normalizedName == TEMPLATE_ATTR) {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) { } else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
prefixToken = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
} }
const hasTemplateBinding = isPresent(templateBindingsSource); const hasTemplateBinding = isPresent(templateBindingsSource);
if (hasTemplateBinding) { if (hasTemplateBinding) {
if (hasInlineTemplates) { if (hasInlineTemplates) {
@ -285,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
hasInlineTemplates = true; hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding( this._bindingParser.parseInlineTemplateBinding(
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars); templateElementOrDirectiveProps, templateElementVars);
} }
@ -306,9 +309,11 @@ class TemplateParseVisitor implements html.Visitor {
const elementProps: BoundElementPropertyAst[] = const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts); this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
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,
references, element.sourceSpan); references, element.sourceSpan);
const children = html.visitAll( const children = html.visitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children, preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create( ElementContext.create(
@ -541,10 +546,12 @@ class TemplateParseVisitor implements html.Visitor {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>(); const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null; let component: CompileDirectiveSummary = null;
const directiveAsts = directives.map((directive) => { const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan( const sourceSpan = new ParseSourceSpan(
elementSourceSpan.start, elementSourceSpan.end, elementSourceSpan.start, elementSourceSpan.end,
`Directive ${identifierName(directive.type)}`); `Directive ${identifierName(directive.type)}`);
if (directive.isComponent) { if (directive.isComponent) {
component = directive; component = directive;
} }
@ -567,6 +574,7 @@ class TemplateParseVisitor implements html.Visitor {
return new DirectiveAst( return new DirectiveAst(
directive, directiveProperties, hostProperties, hostEvents, sourceSpan); directive, directiveProperties, hostProperties, hostEvents, sourceSpan);
}); });
elementOrDirectiveRefs.forEach((elOrDirRef) => { elementOrDirectiveRefs.forEach((elOrDirRef) => {
if (elOrDirRef.value.length > 0) { if (elOrDirRef.value.length > 0) {
if (!matchedReferences.has(elOrDirRef.name)) { if (!matchedReferences.has(elOrDirRef.name)) {
@ -581,7 +589,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan)); targetReferences.push(new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.sourceSpan));
} }
}); // fix syntax highlighting issue: ` });
return directiveAsts; return directiveAsts;
} }
@ -742,7 +750,7 @@ class NonBindableVisitor implements html.Visitor {
return null; return null;
} }
const attrNameAndValues = ast.attrs.map(attrAst => [attrAst.name, attrAst.value]); const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
const selector = createElementCssSelector(ast.name, attrNameAndValues); const selector = createElementCssSelector(ast.name, attrNameAndValues);
const ngContentIndex = parent.findNgContentIndex(selector); const ngContentIndex = parent.findNgContentIndex(selector);
const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT); const children = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
@ -811,16 +819,16 @@ class ElementContext {
} }
export function createElementCssSelector( export function createElementCssSelector(
elementName: string, matchableAttrs: string[][]): CssSelector { elementName: string, attributes: [string, string][]): CssSelector {
const cssSelector = new CssSelector(); const cssSelector = new CssSelector();
const elNameNoNs = splitNsName(elementName)[1]; const elNameNoNs = splitNsName(elementName)[1];
cssSelector.setElement(elNameNoNs); cssSelector.setElement(elNameNoNs);
for (let i = 0; i < matchableAttrs.length; i++) { for (let i = 0; i < attributes.length; i++) {
const attrName = matchableAttrs[i][0]; const attrName = attributes[i][0];
const attrNameNoNs = splitNsName(attrName)[1]; const attrNameNoNs = splitNsName(attrName)[1];
const attrValue = matchableAttrs[i][1]; const attrValue = attributes[i][1];
cssSelector.addAttribute(attrNameNoNs, attrValue); cssSelector.addAttribute(attrNameNoNs, attrValue);
if (attrName.toLowerCase() == CLASS_ATTR) { if (attrName.toLowerCase() == CLASS_ATTR) {

View File

@ -26,7 +26,7 @@ export function createOfflineCompileUrlResolver(): UrlResolver {
/** /**
* A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'. * A default provider for {@link PACKAGE_ROOT_URL} that maps to '/'.
*/ */
export var DEFAULT_PACKAGE_URL_PROVIDER = { export const DEFAULT_PACKAGE_URL_PROVIDER = {
provide: PACKAGE_ROOT_URL, provide: PACKAGE_ROOT_URL,
useValue: '/' useValue: '/'
}; };

View File

@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
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, CompileViewRootNode} from './compile_view'; import {CompileView, CompileViewRootNode} from './compile_view';
import {InjectMethodVars, ViewProperties} from './constants'; import {InjectMethodVars} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
import {getPropertyInView, injectFromViewParentInjector} from './util'; import {getPropertyInView, injectFromViewParentInjector} from './util';
@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
const propName = const propName =
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`; `_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
const instance = createProviderProperty( const instance = createProviderProperty(
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider, propName, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager, this); resolvedProvider.eager, this);
if (isDirectiveWrapper) { if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance); this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
@ -211,12 +211,7 @@ export class CompileElement extends CompileNode {
const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type))); const directiveInstance = this.instances.get(tokenReference(identifierToken(directive.type)));
directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); }); directive.queries.forEach((queryMeta) => { this._addQuery(queryMeta, directiveInstance); });
} }
const queriesWithReads: _QueryWithRead[] = [];
Array.from(this._resolvedProviders.values()).forEach((resolvedProvider) => {
const queriesForProvider = this._getQueriesFor(resolvedProvider.token);
queriesWithReads.push(
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
});
Object.keys(this.referenceTokens).forEach(varName => { Object.keys(this.referenceTokens).forEach(varName => {
const token = this.referenceTokens[varName]; const token = this.referenceTokens[varName];
let varValue: o.Expression; let varValue: o.Expression;
@ -226,27 +221,6 @@ export class CompileElement extends CompileNode {
varValue = this.renderNode; varValue = this.renderNode;
} }
this.view.locals.set(varName, varValue); this.view.locals.set(varName, varValue);
const varToken = {value: varName};
queriesWithReads.push(
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (isPresent(queryWithRead.read.identifier)) {
// query for an identifier
value = this.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = this.referenceTokens[queryWithRead.read.value];
if (isPresent(token)) {
value = this.instances.get(tokenReference(token));
} else {
value = this.elementRef;
}
}
if (isPresent(value)) {
queryWithRead.query.addValue(value, this.view);
}
}); });
} }
@ -265,12 +239,14 @@ export class CompileElement extends CompileNode {
this.view.injectorGetMethod.addStmt(createInjectInternalCondition( this.view.injectorGetMethod.addStmt(createInjectInternalCondition(
this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr)); this.nodeIndex, providerChildNodeCount, resolvedProvider, providerExpr));
}); });
}
finish() {
Array.from(this._queries.values()) Array.from(this._queries.values())
.forEach( .forEach(
queries => queries.forEach( queries => queries.forEach(
q => q => q.generateStatements(
q.afterChildren(this.view.createMethod, this.view.updateContentQueriesMethod))); this.view.createMethod, this.view.updateContentQueriesMethod)));
} }
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) { addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) {
@ -283,12 +259,11 @@ export class CompileElement extends CompileNode {
null; null;
} }
getProviderTokens(): o.Expression[] { getProviderTokens(): CompileTokenMetadata[] {
return Array.from(this._resolvedProviders.values()) return Array.from(this._resolvedProviders.values()).map(provider => provider.token);
.map((resolvedProvider) => createDiTokenExpression(resolvedProvider.token));
} }
private _getQueriesFor(token: CompileTokenMetadata): CompileQuery[] { getQueriesFor(token: CompileTokenMetadata): CompileQuery[] {
const result: CompileQuery[] = []; const result: CompileQuery[] = [];
let currentEl: CompileElement = this; let currentEl: CompileElement = this;
let distance = 0; let distance = 0;
@ -314,7 +289,7 @@ export class CompileElement extends CompileNode {
CompileQuery { CompileQuery {
const propName = const propName =
`_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`; `_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`;
const queryList = createQueryList(queryMeta, directiveInstance, propName, this.view); const queryList = createQueryList(propName, this.view);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view); const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
addQueryToTokenMap(this._queries, query); addQueryToTokenMap(this._queries, query);
return query; return query;
@ -394,8 +369,8 @@ function createInjectInternalCondition(
} }
function createProviderProperty( function createProviderProperty(
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[], propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression { compileElement: CompileElement): o.Expression {
const view = compileElement.view; const view = compileElement.view;
let resolvedProviderValueExpr: o.Expression; let resolvedProviderValueExpr: o.Expression;
let type: o.Type; let type: o.Type;
@ -426,10 +401,3 @@ function createProviderProperty(
} }
return o.THIS_EXPR.prop(propName); return o.THIS_EXPR.prop(propName);
} }
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}

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 {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';
@ -34,7 +33,7 @@ export class CompileMethod {
if (this._newState.nodeIndex !== this._currState.nodeIndex || if (this._newState.nodeIndex !== this._currState.nodeIndex ||
this._newState.sourceAst !== this._currState.sourceAst) { this._newState.sourceAst !== this._currState.sourceAst) {
const expr = this._updateDebugContext(this._newState); const expr = this._updateDebugContext(this._newState);
if (isPresent(expr)) { if (expr) {
this._bodyStatements.push(expr.toStmt()); this._bodyStatements.push(expr.toStmt());
} }
} }
@ -43,13 +42,12 @@ export class CompileMethod {
private _updateDebugContext(newState: _DebugState): o.Expression { private _updateDebugContext(newState: _DebugState): o.Expression {
this._currState = this._newState = newState; this._currState = this._newState = newState;
if (this._debugEnabled) { if (this._debugEnabled) {
const sourceLocation = const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
return o.THIS_EXPR.callMethod('debug', [ return o.THIS_EXPR.callMethod('debug', [
o.literal(newState.nodeIndex), o.literal(newState.nodeIndex),
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR, sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
]); ]);
} else { } else {
return null; return null;

View File

@ -8,7 +8,6 @@
import {CompileQueryMetadata, tokenReference} from '../compile_metadata'; import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -33,7 +32,7 @@ export class CompileQuery {
addValue(value: o.Expression, view: CompileView) { addValue(value: o.Expression, view: CompileView) {
let currentView = view; let currentView = view;
const elPath: CompileElement[] = []; const elPath: CompileElement[] = [];
while (isPresent(currentView) && currentView !== this.view) { while (currentView && currentView !== this.view) {
const parentEl = currentView.declarationElement; const parentEl = currentView.declarationElement;
elPath.unshift(parentEl); elPath.unshift(parentEl);
currentView = parentEl.view; currentView = parentEl.view;
@ -64,10 +63,10 @@ export class CompileQuery {
return !this._values.values.some(value => value instanceof ViewQueryValues); return !this._values.values.some(value => value instanceof ViewQueryValues);
} }
afterChildren(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) { generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
const values = createQueryValues(this._values); const values = createQueryValues(this._values);
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()]; const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) { if (this.ownerDirectiveExpression) {
const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList; const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
updateStmts.push( updateStmts.push(
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt()); this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
@ -110,9 +109,7 @@ function mapNestedViews(
]); ]);
} }
export function createQueryList( export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
compileView: CompileView): o.Expression {
compileView.fields.push(new o.ClassField( compileView.fields.push(new o.ClassField(
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE]))); propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
const expr = o.THIS_EXPR.prop(propertyName); const expr = o.THIS_EXPR.prop(propertyName);

View File

@ -12,7 +12,6 @@ import {EventHandlerVars, NameResolver} from '../compiler_util/expression_conver
import {createPureProxy} from '../compiler_util/identifier_util'; import {createPureProxy} from '../compiler_util/identifier_util';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} 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';
@ -119,7 +118,7 @@ export class CompileView implements NameResolver {
const directiveInstance = o.THIS_EXPR.prop('context'); const directiveInstance = o.THIS_EXPR.prop('context');
this.component.viewQueries.forEach((queryMeta, queryIndex) => { this.component.viewQueries.forEach((queryMeta, queryIndex) => {
const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`; const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`;
const queryList = createQueryList(queryMeta, directiveInstance, propName, this); const queryList = createQueryList(propName, this);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this); const query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query); addQueryToTokenMap(viewQueries, query);
}); });
@ -154,11 +153,11 @@ export class CompileView implements NameResolver {
} }
} }
afterNodes() { finish() {
Array.from(this.viewQueries.values()) Array.from(this.viewQueries.values())
.forEach( .forEach(
queries => queries.forEach( queries => queries.forEach(
q => q.afterChildren(this.createMethod, this.updateViewQueriesMethod))); q => q.generateStatements(this.createMethod, this.updateViewQueriesMethod)));
} }
} }

View File

@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {Identifiers} 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 {ViewType} from '../private_import_core';
export class ViewTypeEnum { export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression { static fromValue(value: ViewType): o.Expression {
@ -25,12 +25,6 @@ export class ViewEncapsulationEnum {
} }
} }
export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
}
}
export class ChangeDetectorStatusEnum { export class ChangeDetectorStatusEnum {
static fromValue(value: ChangeDetectorStatusEnum): o.Expression { static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectorStatus, value); return createEnumExpression(Identifiers.ChangeDetectorStatus, value);

View File

@ -15,7 +15,6 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {ViewProperties} from './constants';
import {getHandleEventMethodName} from './util'; import {getHandleEventMethodName} from './util';
export function bindOutputs( export function bindOutputs(
@ -133,4 +132,4 @@ type EventSummary = {
name: string, name: string,
target: string, target: string,
phase: string phase: string
} };

View File

@ -0,0 +1,57 @@
/**
* @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, tokenReference} from '../compile_metadata';
import * as o from '../output/output_ast';
import {CompileElement} from './compile_element';
import {CompileQuery} from './compile_query';
// Note: We can't do this when we create the CompileElements already,
// as we create embedded views before the <template> elements themselves.
export function bindQueryValues(ce: CompileElement) {
const queriesWithReads: _QueryWithRead[] = [];
ce.getProviderTokens().forEach((token) => {
const queriesForProvider = ce.getQueriesFor(token);
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
});
Object.keys(ce.referenceTokens).forEach(varName => {
const varToken = {value: varName};
queriesWithReads.push(
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
});
queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression;
if (queryWithRead.read.identifier) {
// query for an identifier
value = ce.instances.get(tokenReference(queryWithRead.read));
} else {
// query for a reference
const token = ce.referenceTokens[queryWithRead.read.value];
if (token) {
value = ce.instances.get(tokenReference(token));
} else {
value = ce.elementRef;
}
}
if (value) {
queryWithRead.query.addValue(value, ce.view);
}
});
}
class _QueryWithRead {
public read: CompileTokenMetadata;
constructor(public query: CompileQuery, match: CompileTokenMetadata) {
this.read = query.meta.read || match;
}
}

View File

@ -9,8 +9,6 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} 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';
@ -23,7 +21,7 @@ export function getPropertyInView(
} else { } else {
let viewProp: o.Expression = o.THIS_EXPR; let viewProp: o.Expression = o.THIS_EXPR;
let currView: CompileView = callingView; let currView: CompileView = callingView;
while (currView !== definedView && isPresent(currView.declarationElement.view)) { while (currView !== definedView && currView.declarationElement.view) {
currView = currView.declarationElement.view; currView = currView.declarationElement.view;
viewProp = viewProp.prop('parentView'); viewProp = viewProp.prop('parentView');
} }

View File

@ -15,6 +15,7 @@ import {CompileView} from './compile_view';
import {bindOutputs} from './event_binder'; import {bindOutputs} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, 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';
import {bindQueryValues} from './query_binder';
export function bindView( export function bindView(
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void { view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
@ -43,6 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true); const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement); bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
ast.directives.forEach((directiveAst, dirIndex) => { ast.directives.forEach((directiveAst, dirIndex) => {
@ -75,6 +77,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindQueryValues(compileElement);
bindOutputs(ast.outputs, ast.directives, compileElement, false); bindOutputs(ast.outputs, ast.directives, compileElement, false);
ast.directives.forEach((directiveAst, dirIndex) => { ast.directives.forEach((directiveAst, dirIndex) => {
const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); const directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);

View File

@ -15,7 +15,6 @@ import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier, identifierToken} from '../identifiers'; import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
import {createClassStmt} from '../output/class_builder'; 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';
@ -48,13 +47,16 @@ export function buildView(
} }
export function finishView(view: CompileView, targetStatements: o.Statement[]) { export function finishView(view: CompileView, targetStatements: o.Statement[]) {
view.afterNodes();
createViewTopLevelStmts(view, targetStatements);
view.nodes.forEach((node) => { view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.hasEmbeddedView) { if (node instanceof CompileElement) {
finishView(node.embeddedView, targetStatements); node.finish();
if (node.hasEmbeddedView) {
finishView(node.embeddedView, targetStatements);
}
} }
}); });
view.finish();
createViewTopLevelStmts(view, targetStatements);
} }
class ViewBuilderVisitor implements TemplateAstVisitor { class ViewBuilderVisitor implements TemplateAstVisitor {
@ -402,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
o.literal(view.component.template.ngContentSelectors.length), o.literal(view.component.template.ngContentSelectors.length),
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
view.styles, view.styles,
o.literalMap(view.animations.map( o.literalMap(
(entry): [string, o.Expression] => [entry.name, entry.fnExp])), view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
null, true),
])) ]))
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType)))); .toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
} }
@ -418,7 +421,9 @@ function createStaticNodeDebugInfo(node: CompileNode): o.Expression {
let componentToken: o.Expression = o.NULL_EXPR; let componentToken: o.Expression = o.NULL_EXPR;
const varTokenEntries: any[] = []; const varTokenEntries: any[] = [];
if (isPresent(compileElement)) { if (isPresent(compileElement)) {
providerTokens = compileElement.getProviderTokens(); providerTokens =
compileElement.getProviderTokens().map((token) => createDiTokenExpression(token));
if (isPresent(compileElement.component)) { if (isPresent(compileElement.component)) {
componentToken = createDiTokenExpression(identifierToken(compileElement.component.type)); componentToken = createDiTokenExpression(identifierToken(compileElement.component.type));
} }
@ -690,7 +695,6 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
view.nodes.forEach((node) => { view.nodes.forEach((node) => {
if (node instanceof CompileElement) { if (node instanceof CompileElement) {
if (node.embeddedView) { if (node.embeddedView) {
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
stmts.push(new o.IfStmt( stmts.push(new o.IfStmt(
nodeIndexVar.equals(o.literal(node.nodeIndex)), nodeIndexVar.equals(o.literal(node.nodeIndex)),
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([ [new o.ReturnStatement(node.embeddedView.classExpr.instantiate([

View File

@ -0,0 +1,2 @@
Tests in this directory are excluded from running in the browser and only running
in node.

View File

@ -0,0 +1,191 @@
/**
* @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 {AotCompiler, AotCompilerHost, createAotCompiler} from '@angular/compiler';
import {async} from '@angular/core/testing';
import * as path from 'path';
import * as ts from 'typescript';
import {ReflectionCapabilities, reflector} from './private_import_core';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, settings} from './test_util';
const DTS = /\.d\.ts$/;
// These are the files that contain the well known annotations.
const CORE_FILES = [
'@angular/core/src/metadata.ts', '@angular/core/src/di/metadata.ts',
'@angular/core/src/di/opaque_token.ts', '@angular/core/src/animation/metadata.ts',
'@angular/core/src/di/provider.ts', '@angular/core/src/linker/view.ts'
];
describe('compiler', () => {
let angularFiles: Map<string, string>;
beforeAll(() => {
const emittingHost = new EmittingCompilerHost(CORE_FILES);
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
emittingProgram.emit();
angularFiles = emittingHost.written;
});
describe('Quickstart', () => {
let host: MockCompilerHost;
let aotHost: MockAotCompilerHost;
beforeEach(() => {
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
aotHost = new MockAotCompilerHost(host);
});
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
it('should compile',
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.not.toBeUndefined();
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
.not.toBeUndefined();
})));
it('should compile using summaries',
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.not.toBeUndefined();
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
.not.toBeUndefined();
})));
});
});
function expectNoDiagnostics(program: ts.Program) {
function fileInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
}
return '';
}
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
function lineNoOf(offset: number, text: string): number {
let result = 1;
for (let i = 0; i < offset; i++) {
if (text[i] == '\n') result++;
}
return result;
}
function lineInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
const start = diagnostic.start;
let end = diagnostic.start + diagnostic.length;
const source = diagnostic.file.text;
let lineStart = start;
let lineEnd = end;
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
if (lineStart < start) lineStart++;
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
let line = source.substring(lineStart, lineEnd);
const lineIndex = line.indexOf('/n');
if (lineIndex > 0) {
line = line.substr(0, lineIndex);
end = start + lineIndex;
}
const lineNo = lineNoOf(start, source) + ': ';
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
chars(end - start, '^');
}
return '';
}
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
if (diagnostics && diagnostics.length) {
throw new Error(
'Errors from TypeScript:\n' +
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
}
}
expectNoDiagnostics(program.getOptionsDiagnostics());
expectNoDiagnostics(program.getSyntacticDiagnostics());
expectNoDiagnostics(program.getSemanticDiagnostics());
}
function isDTS(fileName: string): boolean {
return /\.d\.ts$/.test(fileName);
}
function isSource(fileName: string): boolean {
return /\.ts$/.test(fileName);
}
function isFactory(fileName: string): boolean {
return /\.ngfactory\./.test(fileName);
}
function summaryCompile(
host: MockCompilerHost, aotHost: MockAotCompilerHost,
preCompile?: (program: ts.Program) => void) {
// First compile the program to generate the summary files.
return compile(host, aotHost).then(generatedFiles => {
// Remove generated files that were not generated from a DTS file
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
// Next compile the program shrowding metadata and only treating .ts files as source.
aotHost.hideMetadata();
aotHost.tsFilesOnly();
return compile(host, aotHost);
});
}
function compile(
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
postCompile: (program: ts.Program) => void = expectNoDiagnostics) {
const program = ts.createProgram(host.scriptNames, settings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, {});
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
.then(generatedFiles => {
generatedFiles.forEach(
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
host.override(file.genFileUrl, file.source));
const newProgram = ts.createProgram(host.scriptNames, settings, host, program);
if (postCompile) postCompile(newProgram);
return generatedFiles;
});
}
const QUICKSTART = ['/quickstart/app/app.module.ts'];
const FILES: MockData = {
quickstart: {
app: {
'app.component.ts': `
import {Component} from '@angular/core/src/metadata';
@Component({
template: '<h1>Hello {{name}}</h1>'
})
export class AppComponent {
name = 'Angular';
}
`,
'app.module.ts': `
import { NgModule } from '@angular/core/src/metadata';
import { AppComponent } from './app.component';
@NgModule({
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
`
}
}
};

View File

@ -0,0 +1,13 @@
/**
* @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 {__core_private__ as r} from '@angular/core';
export type ReflectionCapabilities = typeof r._ReflectionCapabilities;
export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
export const reflector: typeof r.reflector = r.reflector;

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 {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, SyntaxError} from '@angular/compiler';
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec'; import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
@ -344,6 +344,20 @@ describe('StaticReflector', () => {
'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); 'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
}); });
it('should throw a SyntaxError without stack trace when the required resource cannot be resolved',
() => {
expect(
() => simplify(
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'AppModule'), ({
__symbolic: 'error',
message:
'Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts'
})))
.toThrowError(
SyntaxError,
'Error encountered resolving symbol values statically. Could not resolve ./does-not-exist.component relative to /tmp/src/function-reference.ts, resolving symbol AppModule in /tmp/src/function-reference.ts');
});
it('should record data about the error in the exception', () => { it('should record data about the error in the exception', () => {
let threw = false; let threw = false;
try { try {
@ -449,6 +463,40 @@ describe('StaticReflector', () => {
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
}); });
// #13605
it('should not throw on unknown decorators', () => {
const data = Object.create(DEFAULT_TEST_DATA);
const file = '/tmp/src/app.component.ts';
data[file] = `
import { Component } from '@angular/core';
export const enum TypeEnum {
type
}
export function MyValidationDecorator(p1: any, p2: any): any {
return null;
}
export function ValidationFunction(a1: any): any {
return null;
}
@Component({
selector: 'my-app',
template: "<h1>Hello {{name}}</h1>",
})
export class AppComponent {
name = 'Angular';
@MyValidationDecorator( TypeEnum.type, ValidationFunction({option: 'value'}))
myClassProp: number;
}`;
init(data);
const appComponent = reflector.getStaticSymbol(file, 'AppComponent');
expect(() => reflector.propMetadata(appComponent)).not.toThrow();
});
describe('inheritance', () => { describe('inheritance', () => {
class ClassDecorator { class ClassDecorator {
constructor(public value: any) {} constructor(public value: any) {}

View File

@ -0,0 +1,389 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost} from '@angular/compiler';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
export type MockData = string | MockDirectory;
export type MockDirectory = {
[name: string]: MockData | undefined;
};
export function isDirectory(data: MockData): data is MockDirectory {
return typeof data !== 'string';
}
const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
const angularts = /@angular\/(\w|\/|-)+\.tsx?$/;
const rxjs = /\/rxjs\//;
const tsxfile = /\.tsx$/;
export const settings: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
declaration: true,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
removeComments: false,
noImplicitAny: false,
skipLibCheck: true,
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
types: []
};
export class EmittingCompilerHost implements ts.CompilerHost {
private angularSourcePath: string|undefined;
private nodeModulesPath: string|undefined;
private writtenFiles = new Map<string, string>();
private scriptNames: string[];
private root = '/';
private collector = new MetadataCollector();
constructor(scriptNames: string[]) {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0) {
const root = moduleFilename.substr(0, distIndex);
this.nodeModulesPath = path.join(root, 'node_modules');
this.angularSourcePath = path.join(root, 'modules');
// Rewrite references to scripts with '@angular' to its corresponding location in
// the source tree.
this.scriptNames = scriptNames.map(
f => f.startsWith('@angular/') ? path.join(this.angularSourcePath, f) : f);
this.root = root;
}
}
public getWrittenFiles(): {name: string, content: string}[] {
return Array.from(this.writtenFiles).map(f => ({name: f[0], content: f[1]}));
}
public get scripts(): string[] { return this.scriptNames; }
public get written(): Map<string, string> { return this.writtenFiles; }
// ts.ModuleResolutionHost
fileExists(fileName: string): boolean { return fs.existsSync(fileName); }
readFile(fileName: string): string {
let basename = path.basename(fileName);
if (/^lib.*\.d\.ts$/.test(basename)) {
let libPath = ts.getDefaultLibFilePath(settings);
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
}
return fs.readFileSync(fileName, 'utf8');
}
directoryExists(directoryName: string): boolean {
return fs.existsSync(directoryName) && fs.statSync(directoryName).isDirectory();
}
getCurrentDirectory(): string { return this.root; }
getDirectories(dir: string): string[] {
return fs.readdirSync(dir).filter(p => {
const name = path.join(dir, p);
const stat = fs.statSync(name);
return stat && stat.isDirectory();
});
}
// ts.CompilerHost
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile {
const content = this.readFile(fileName);
if (content) {
return ts.createSourceFile(fileName, content, languageVersion);
}
}
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
this.writtenFiles.set(fileName, data);
if (sourceFiles && sourceFiles.length && DTS.test(fileName)) {
const metadataFilePath = fileName.replace(DTS, '.metadata.json');
const metadata = this.collector.getMetadata(sourceFiles[0]);
if (metadata) this.writtenFiles.set(metadataFilePath, JSON.stringify(metadata));
}
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
useCaseSensitiveFileNames(): boolean { return false; }
getNewLine(): string { return '\n'; }
}
export class MockCompilerHost implements ts.CompilerHost {
scriptNames: string[];
private angularSourcePath: string|undefined;
private nodeModulesPath: string|undefined;
private overrides = new Map<string, string>();
private writtenFiles = new Map<string, string>();
private sourceFiles = new Map<string, ts.SourceFile>();
private assumeExists = new Set<string>();
private traces: string[] = [];
constructor(scriptNames: string[], private data: MockData, private angular: Map<string, string>) {
this.scriptNames = scriptNames.slice(0);
const moduleFilename = module.filename.replace(/\\/g, '/');
let angularIndex = moduleFilename.indexOf('@angular');
let distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0) {
const root = moduleFilename.substr(0, distIndex);
this.nodeModulesPath = path.join(root, 'node_modules');
this.angularSourcePath = path.join(root, 'modules');
}
}
// Test API
override(fileName: string, content: string) {
if (content) {
this.overrides.set(fileName, content);
} else {
this.overrides.delete(fileName);
}
this.sourceFiles.delete(fileName);
}
addScript(fileName: string, content: string) {
this.overrides.set(fileName, content);
this.scriptNames.push(fileName);
this.sourceFiles.delete(fileName);
}
assumeFileExists(fileName: string) { this.assumeExists.add(fileName); }
remove(files: string[]) {
// Remove the files from the list of scripts.
const fileSet = new Set(files);
this.scriptNames = this.scriptNames.filter(f => fileSet.has(f));
// Remove files from written files
files.forEach(f => this.writtenFiles.delete(f));
}
// ts.ModuleResolutionHost
fileExists(fileName: string): boolean {
if (this.overrides.has(fileName) || this.writtenFiles.has(fileName) ||
this.assumeExists.has(fileName)) {
return true;
}
const effectiveName = this.getEffectiveName(fileName);
if (effectiveName == fileName) {
return open(fileName, this.data) != null;
} else {
if (fileName.match(rxjs)) {
return fs.existsSync(effectiveName);
}
return this.angular.has(effectiveName);
}
}
readFile(fileName: string): string { return this.getFileContent(fileName); }
trace(s: string): void { this.traces.push(s); }
getCurrentDirectory(): string { return '/'; }
getDirectories(dir: string): string[] {
const effectiveName = this.getEffectiveName(dir);
if (effectiveName === dir) {
const data = find(dir, this.data);
if (isDirectory(data)) {
return Object.keys(data).filter(k => isDirectory(data[k]));
}
return [];
} else {
return undefined;
}
}
// ts.CompilerHost
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: (message: string) => void): ts.SourceFile {
let result = this.sourceFiles.get(fileName);
if (!result) {
const content = this.getFileContent(fileName);
if (content) {
result = ts.createSourceFile(fileName, content, languageVersion);
this.sourceFiles.set(fileName, result);
}
}
return result;
}
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean) => {
this.writtenFiles.set(fileName, data);
this.sourceFiles.delete(fileName);
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
useCaseSensitiveFileNames(): boolean { return false; }
getNewLine(): string { return '\n'; }
// Private methods
private getFileContent(fileName: string): string|undefined {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
if (this.writtenFiles.has(fileName)) {
return this.writtenFiles.get(fileName);
}
let basename = path.basename(fileName);
if (/^lib.*\.d\.ts$/.test(basename)) {
let libPath = ts.getDefaultLibFilePath(settings);
return fs.readFileSync(path.join(path.dirname(libPath), basename), 'utf8');
} else {
let effectiveName = this.getEffectiveName(fileName);
if (effectiveName === fileName)
return open(fileName, this.data);
else {
if (fileName.match(rxjs)) {
if (fs.existsSync(fileName)) {
return fs.readFileSync(fileName, 'utf8');
}
}
return this.angular.get(effectiveName);
}
}
}
private getEffectiveName(name: string): string {
const node_modules = 'node_modules';
const at_angular = '/@angular';
const rxjs = '/rxjs';
if (name.startsWith('/' + node_modules)) {
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
return path.join(this.angularSourcePath, name.substr(node_modules.length + 1));
}
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
}
}
return name;
}
}
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
export class MockAotCompilerHost implements AotCompilerHost {
private metadataCollector = new MetadataCollector();
private metadataVisible: boolean = true;
private dtsAreSource: boolean = true;
constructor(private tsHost: MockCompilerHost) {}
hideMetadata() { this.metadataVisible = false; }
tsFilesOnly() { this.dtsAreSource = false; }
// StaticSymbolResolverHost
getMetadataFor(modulePath: string): {[key: string]: any}[] {
if (!this.tsHost.fileExists(modulePath)) {
return undefined;
}
if (DTS.test(modulePath)) {
if (this.metadataVisible) {
const metadataPath = modulePath.replace(DTS, '.metadata.json');
if (this.tsHost.fileExists(metadataPath)) {
let result = JSON.parse(this.tsHost.readFile(metadataPath));
return Array.isArray(result) ? result : [result];
}
}
} else {
const sf = this.tsHost.getSourceFile(modulePath, ts.ScriptTarget.Latest);
const metadata = this.metadataCollector.getMetadata(sf);
return metadata ? [metadata] : [];
}
}
moduleNameToFileName(moduleName: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) {
if (moduleName.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
}
// Any containing file gives the same result for absolute imports
containingFile = path.join('/', 'index.ts');
}
moduleName = moduleName.replace(EXT, '');
const resolved = ts.resolveModuleName(
moduleName, containingFile.replace(/\\/g, '/'),
{baseDir: '/', genDir: '/'}, this.tsHost)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
}
// AotSummaryResolverHost
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
isSourceFile(sourceFilePath: string): boolean {
return !GENERATED_FILES.test(sourceFilePath) &&
(this.dtsAreSource || !DTS.test(sourceFilePath));
}
getOutputFileName(sourceFilePath: string): string {
return sourceFilePath.replace(EXT, '') + '.d.ts';
}
// AotCompilerHost
fileNameToModuleName(importedFile: string, containingFile: string): string|null {
return importedFile.replace(EXT, '');
}
loadResource(path: string): Promise<string> {
return Promise.resolve(this.tsHost.readFile(path));
}
}
function find(fileName: string, data: MockData): MockData|undefined {
let names = fileName.split('/');
if (names.length && !names[0].length) names.shift();
let current = data;
for (let name of names) {
if (typeof current === 'string')
return undefined;
else
current = (<MockDirectory>current)[name];
if (!current) return undefined;
}
return current;
}
function open(fileName: string, data: MockData): string|undefined {
let result = find(fileName, data);
if (typeof result === 'string') {
return result;
}
return undefined;
}
function directoryExists(dirname: string, data: MockData): boolean {
let result = find(dirname, data);
return result && typeof result !== 'string';
}

View File

@ -342,6 +342,24 @@ export function main() {
const HTML = `<div>before<p i18n="m|d">foo</p><!-- comment --></div>`; const HTML = `<div>before<p i18n="m|d">foo</p><!-- comment --></div>`;
expect(fakeTranslate(HTML)).toEqual('<div>before<p>**foo**</p></div>'); expect(fakeTranslate(HTML)).toEqual('<div>before<p>**foo**</p></div>');
}); });
it('should merge empty messages', () => {
const HTML = `<div i18n>some element</div>`;
const htmlNodes: html.Node[] = parseHtml(HTML);
const messages: i18n.Message[] =
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
expect(messages.length).toEqual(1);
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
i18nMsgMap[digest(messages[0])] = [];
const translations = new TranslationBundle(i18nMsgMap, digest);
const output =
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
expect(output.errors).toEqual([]);
expect(serializeHtmlNodes(output.rootNodes).join('')).toEqual(`<div></div>`);
});
}); });
describe('blocks', () => { describe('blocks', () => {
@ -381,6 +399,25 @@ export function main() {
const HTML = `<p i18n-title="m|d" title=""></p>`; const HTML = `<p i18n-title="m|d" title=""></p>`;
expect(fakeTranslate(HTML)).toEqual('<p title=""></p>'); expect(fakeTranslate(HTML)).toEqual('<p title=""></p>');
}); });
it('should merge empty attributes', () => {
const HTML = `<div i18n-title title="some attribute">some element</div>`;
const htmlNodes: html.Node[] = parseHtml(HTML);
const messages: i18n.Message[] =
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, [], {}).messages;
expect(messages.length).toEqual(1);
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
i18nMsgMap[digest(messages[0])] = [];
const translations = new TranslationBundle(i18nMsgMap, digest);
const output =
mergeTranslations(htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, [], {});
expect(output.errors).toEqual([]);
expect(serializeHtmlNodes(output.rootNodes).join(''))
.toEqual(`<div title="">some element</div>`);
});
}); });
}); });
} }
@ -412,12 +449,11 @@ function fakeTranslate(
const translations = new TranslationBundle(i18nMsgMap, digest); const translations = new TranslationBundle(i18nMsgMap, digest);
const translatedNodes = const output = mergeTranslations(
mergeTranslations( htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs);
htmlNodes, translations, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs) expect(output.errors).toEqual([]);
.rootNodes;
return serializeHtmlNodes(translatedNodes).join(''); return serializeHtmlNodes(output.rootNodes).join('');
} }
function extract( function extract(

View File

@ -141,12 +141,22 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
<!-- /i18n --> <!-- /i18n -->
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div> <div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
<!-- make sure that ICU messages are not treated as text nodes -->
<div i18n="desc">{
response.getItemsList().length,
plural,
=0 {Found no results}
=1 {Found one result}
other {Found {{response.getItemsList().length}} results}
}</div>
` `
}) })
class I18nComponent { class I18nComponent {
count: number; count: number;
sex: string; sex: string;
sexB: string; sexB: string;
response: any = {getItemsList: (): any[] => []};
} }
class FrLocalization extends NgLocalization { class FrLocalization extends NgLocalization {
@ -182,6 +192,12 @@ const XTB = `
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph> <ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
</translation> </translation>
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation> <translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
<translation id="i18n16">avec un ID explicite</translation>
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</translation>
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex>&lt;a&gt;</ex></ph>BAR<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</ex></ph></translation>
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
</translationbundle>`; </translationbundle>`;
// unused, for reference only // unused, for reference only
@ -197,18 +213,76 @@ const XMB = `
<msg id="8670732454866344690">on translatable node</msg> <msg id="8670732454866344690">on translatable node</msg>
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg> <msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="1746565782635215"> <msg id="1746565782635215">
<ph name="ICU"/> <ph name="ICU"><ex>ICU</ex></ph>
</msg> </msg>
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg> <msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg> <msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg> <msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg> <msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
<msg id="7685649297917455806">in a translatable section</msg> <msg id="7685649297917455806">in a translatable section</msg>
<msg id="2387287228265107305"> <msg id="2387287228265107305">
<ph name="START_HEADING_LEVEL1"><ex>&lt;h1&gt;</ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex>&lt;/h1&gt;</ex></ph> <ph name="START_HEADING_LEVEL1"><ex>&lt;h1&gt;</ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex>&lt;/h1&gt;</ex></ph>
<ph name="START_TAG_DIV"><ex>&lt;div&gt;</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph> <ph name="START_TAG_DIV"><ex>&lt;div&gt;</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
<ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph> <ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg> </msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg> <msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
</messagebundle> <msg id="i18n16">with an explicit ID</msg>
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex>&lt;a&gt;</ex></ph>bar<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</ex></ph></msg>
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>;
</messagebundle>`;
const HTML = `
<div>
<h1 i18n>i18n attribute on tags</h1>
<div id="i18n-1"><p i18n>nested</p></div>
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
<div>
<p id="i18n-4" i18n-title title="on not translatable node"></p>
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
<p id="i18n-6" i18n-title title></p>
</div>
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
<div i18n id="i18n-8">
{sex, select, m {male} f {female}}
</div>
<div i18n id="i18n-8b">
{sexB, select, m {male} f {female}}
</div>
<div i18n id="i18n-9">{{ "count = " + count }}</div>
<div i18n id="i18n-10">sex = {{ sex }}</div>
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
</div>
<!-- i18n -->
<h1 id="i18n-12" >Markers in html comments</h1>
<div id="i18n-13" i18n-title title="in a translatable section"></div>
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
<!-- /i18n -->
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
<!-- make sure that ICU messages are not treated as text nodes -->
<div i18n="desc">{
response.getItemsList().length,
plural,
=0 {Found no results}
=1 {Found one result}
other {Found {{response.getItemsList().length}} results}
}</div>
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
<div i18n>{{ 'test' //i18n(ph="map name") }}</div>
`; `;

View File

@ -42,7 +42,7 @@ export function main(): void {
}); });
} }
class _TestSerializer implements Serializer { class _TestSerializer extends Serializer {
write(messages: i18n.Message[]): string { write(messages: i18n.Message[]): string {
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`) return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
.join('//'); .join('//');

View File

@ -72,6 +72,11 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target> <target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
<note priority="1" from="description">ph names</note> <note priority="1" from="description">ph names</note>
</trans-unit> </trans-unit>
<trans-unit id="empty target" datatype="html">
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
<target/>
<note priority="1" from="description">ph names</note>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>
@ -88,6 +93,7 @@ export function main(): void {
function loadAsMap(xliff: string): {[id: string]: string} { function loadAsMap(xliff: string): {[id: string]: string} {
const i18nNodesByMsgId = serializer.load(xliff, 'url'); const i18nNodesByMsgId = serializer.load(xliff, 'url');
const msgMap: {[id: string]: string} = {}; const msgMap: {[id: string]: string} = {};
Object.keys(i18nNodesByMsgId) Object.keys(i18nNodesByMsgId)
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('')); .forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
@ -109,6 +115,7 @@ export function main(): void {
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof', 'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d': 'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>', '<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
'empty target': '',
}); });
}); });

View File

@ -0,0 +1,51 @@
/**
* @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, Directive, Input} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('integration tests', () => {
let fixture: ComponentFixture<TestComponent>;
describe('directives', () => {
it('should support dotted selectors', async(() => {
@Directive({selector: '[dot.name]'})
class MyDir {
@Input('dot.name') value: string;
}
TestBed.configureTestingModule({
declarations: [
MyDir,
TestComponent,
],
});
const template = `<div [dot.name]="'foo'"></div>`;
fixture = createTestComponent(template);
fixture.detectChanges();
const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
expect(myDir.value).toEqual('foo');
}));
});
});
}
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
}
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
.createComponent(TestComponent);
}

View File

@ -44,7 +44,7 @@ const baseErrorIdentifier = {
runtime: BaseError runtime: BaseError
}; };
export var codegenExportsVars = [ export const codegenExportsVars = [
'getExpressions', 'getExpressions',
]; ];
@ -190,7 +190,7 @@ const _getExpressionsStmts: o.Statement[] = [
])) ]))
]; ];
export var codegenStmts: o.Statement[] = [ export const codegenStmts: o.Statement[] = [
new o.CommentStmt('This is a comment'), new o.CommentStmt('This is a comment'),
new o.ClassStmt( new o.ClassStmt(

View File

@ -30,14 +30,14 @@ export function main() {
it('should select by element name case sensitive', () => { it('should select by element name case sensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1); matcher.addSelectables(s1 = CssSelector.parse('someTag'), 1);
expect(matcher.match(CssSelector.parse('SOMEOTHERTAG')[0], selectableCollector)) expect(matcher.match(getSelectorFor({tag: 'SOMEOTHERTAG'}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('SOMETAG')[0], selectableCollector)).toEqual(false); expect(matcher.match(getSelectorFor({tag: 'SOMETAG'}), selectableCollector)).toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('someTag')[0], selectableCollector)).toEqual(true); expect(matcher.match(getSelectorFor({tag: 'someTag'}), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
}); });
@ -45,21 +45,22 @@ export function main() {
matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('.someClass'), 1);
matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2); matcher.addSelectables(s2 = CssSelector.parse('.someClass.class2'), 2);
expect(matcher.match(CssSelector.parse('.SOMEOTHERCLASS')[0], selectableCollector)) expect(matcher.match(getSelectorFor({classes: 'SOMEOTHERCLASS'}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('.SOMECLASS')[0], selectableCollector)).toEqual(true); expect(matcher.match(getSelectorFor({classes: 'SOMECLASS'}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('.someClass.class2')[0], selectableCollector)) expect(matcher.match(getSelectorFor({classes: 'someClass class2'}), selectableCollector))
.toEqual(true); .toEqual(true);
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"', () => { it('should not throw for class name "constructor"', () => {
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector)) expect(matcher.match(getSelectorFor({classes: 'constructor'}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
}); });
@ -68,36 +69,43 @@ export function main() {
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);
expect(matcher.match(CssSelector.parse('[SOMEOTHERATTR]')[0], selectableCollector)) expect(matcher.match(getSelectorFor({attrs: [['SOMEOTHERATTR', '']]}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR]')[0], selectableCollector)).toEqual(false); expect(matcher.match(getSelectorFor({attrs: [['SOMEATTR', '']]}), selectableCollector))
expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=someValue]')[0], selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[someAttr][someAttr2]')[0], selectableCollector)) expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'someValue']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(
matcher.match(
getSelectorFor({attrs: [['someAttr', ''], ['someAttr2', '']]}), selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]); expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset(); reset();
expect(matcher.match( expect(matcher.match(
CssSelector.parse('[someAttr=someValue][someAttr2]')[0], selectableCollector)) getSelectorFor({attrs: [['someAttr', 'someValue'], ['someAttr2', '']]}),
selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]); expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset(); reset();
expect(matcher.match( expect(matcher.match(
CssSelector.parse('[someAttr2][someAttr=someValue]')[0], selectableCollector)) getSelectorFor({attrs: [['someAttr2', ''], ['someAttr', 'someValue']]}),
selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]); expect(matched).toEqual([s1[0], 1, s2[0], 2]);
reset(); reset();
expect(matcher.match( expect(matcher.match(
CssSelector.parse('[someAttr2=someValue][someAttr]')[0], selectableCollector)) getSelectorFor({attrs: [['someAttr2', 'someValue'], ['someAttr', '']]}),
selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2]); expect(matched).toEqual([s1[0], 1, s2[0], 2]);
}); });
@ -105,11 +113,13 @@ export function main() {
it('should support "." in attribute names', () => { it('should support "." in attribute names', () => {
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false); expect(matcher.match(getSelectorFor({attrs: [['barfoo', '']]}), selectableCollector))
.toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
reset(); reset();
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true); expect(matcher.match(getSelectorFor({attrs: [['foo.bar', '']]}), selectableCollector))
.toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
}); });
@ -127,15 +137,18 @@ export function main() {
it('should select by attr name case sensitive and value case insensitive', () => { it('should select by attr name case sensitive and value case insensitive', () => {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr=someValue]'), 1);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEOTHERATTR]')[0], selectableCollector)) expect(matcher.match(
getSelectorFor({attrs: [['SOMEATTR', 'SOMEOTHERATTR']]}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[SOMEATTR=SOMEVALUE]')[0], selectableCollector)) expect(
matcher.match(getSelectorFor({attrs: [['SOMEATTR', 'SOMEVALUE']]}), selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match(CssSelector.parse('[someAttr=SOMEVALUE]')[0], selectableCollector)) expect(
matcher.match(getSelectorFor({attrs: [['someAttr', 'SOMEVALUE']]}), selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
}); });
@ -143,31 +156,38 @@ export function main() {
it('should select by element name, class name and attribute name with value', () => { it('should select by element name, class name and attribute name with value', () => {
matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1); matcher.addSelectables(s1 = CssSelector.parse('someTag.someClass[someAttr=someValue]'), 1);
expect(
matcher.match(
getSelectorFor(
{tag: 'someOtherTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
.toEqual(false);
expect(matched).toEqual([]);
expect(matcher.match( expect(matcher.match(
CssSelector.parse('someOtherTag.someOtherClass[someOtherAttr]')[0], getSelectorFor(
{tag: 'someTag', classes: 'someOtherClass', attrs: [['someOtherAttr', '']]}),
selectableCollector)) selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect( expect(matcher.match(
matcher.match( getSelectorFor(
CssSelector.parse('someTag.someOtherClass[someOtherAttr]')[0], selectableCollector)) {tag: 'someTag', classes: 'someClass', attrs: [['someOtherAttr', '']]}),
selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect(matcher.match( expect(matcher.match(
CssSelector.parse('someTag.someClass[someOtherAttr]')[0], selectableCollector)) getSelectorFor({tag: 'someTag', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
expect( expect(matcher.match(
matcher.match(CssSelector.parse('someTag.someClass[someAttr]')[0], selectableCollector)) getSelectorFor(
.toEqual(false); {tag: 'someTag', classes: 'someClass', attrs: [['someAttr', 'someValue']]}),
expect(matched).toEqual([]); selectableCollector))
expect(
matcher.match(
CssSelector.parse('someTag.someClass[someAttr=someValue]')[0], selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
}); });
@ -217,7 +237,9 @@ export function main() {
matcher.addSelectables(CssSelector.parse(':not(p)'), 4); matcher.addSelectables(CssSelector.parse(':not(p)'), 4);
matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5); matcher.addSelectables(CssSelector.parse(':not(p[someAttr])'), 5);
expect(matcher.match(CssSelector.parse('p.someClass[someAttr]')[0], selectableCollector)) expect(matcher.match(
getSelectorFor({tag: 'p', classes: 'someClass', attrs: [['someAttr', '']]}),
selectableCollector))
.toEqual(false); .toEqual(false);
expect(matched).toEqual([]); expect(matched).toEqual([]);
}); });
@ -228,32 +250,38 @@ export function main() {
matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3); matcher.addSelectables(s3 = CssSelector.parse(':not(.someClass)'), 3);
matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4); matcher.addSelectables(s4 = CssSelector.parse(':not(.someOtherClass[someAttr])'), 4);
expect(matcher.match( expect(
CssSelector.parse('p[someOtherAttr].someOtherClass')[0], selectableCollector)) matcher.match(
getSelectorFor({tag: 'p', attrs: [['someOtherAttr', '']], classes: 'someOtherClass'}),
selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]); expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
}); });
it('should match * with :not selector', () => { it('should match * with :not selector', () => {
matcher.addSelectables(CssSelector.parse(':not([a])'), 1); matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true); expect(matcher.match(getSelectorFor({tag: 'div'}), () => {})).toEqual(true);
}); });
it('should match with multiple :not selectors', () => { it('should match with multiple :not selectors', () => {
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1); matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false); expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['a', '']]}), selectableCollector))
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false); .toBe(false);
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true); expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['b', '']]}), selectableCollector))
.toBe(false);
expect(matcher.match(getSelectorFor({tag: 'div', attrs: [['c', '']]}), selectableCollector))
.toBe(true);
}); });
it('should select with one match in a list', () => { it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1); matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
expect(matcher.match(CssSelector.parse('textbox')[0], selectableCollector)).toEqual(true); expect(matcher.match(getSelectorFor({tag: 'textbox'}), selectableCollector)).toEqual(true);
expect(matched).toEqual([s1[1], 1]); expect(matched).toEqual([s1[1], 1]);
reset(); reset();
expect(matcher.match(CssSelector.parse('input[type=text]')[0], selectableCollector)) expect(matcher.match(
getSelectorFor({tag: 'input', attrs: [['type', 'text']]}), selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
}); });
@ -261,7 +289,8 @@ export function main() {
it('should not select twice with two matches in a list', () => { it('should not select twice with two matches in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1); matcher.addSelectables(s1 = CssSelector.parse('input, .someClass'), 1);
expect(matcher.match(CssSelector.parse('input.someclass')[0], selectableCollector)) expect(
matcher.match(getSelectorFor({tag: 'input', classes: 'someclass'}), selectableCollector))
.toEqual(true); .toEqual(true);
expect(matched.length).toEqual(2); expect(matched.length).toEqual(2);
expect(matched).toEqual([s1[0], 1]); expect(matched).toEqual([s1[0], 1]);
@ -404,3 +433,16 @@ export function main() {
}); });
}); });
} }
function getSelectorFor(
{tag = '', attrs = [], classes = ''}: {tag?: string, attrs?: any[], classes?: string} = {}):
CssSelector {
const selector = new CssSelector();
selector.setElement(tag);
attrs.forEach(nameValue => { selector.addAttribute(nameValue[0], nameValue[1]); });
classes.trim().split(/\s+/g).forEach(cName => { selector.addClassName(cName); });
return selector;
}

View File

@ -623,6 +623,23 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
]); ]);
}); });
it('should parse directive dotted properties', () => {
const dirA =
CompileDirectiveMetadata
.create({
selector: '[dot.name]',
type: createTypeMeta({reference: {filePath: someModuleUrl, name: 'DirA'}}),
inputs: ['localName: dot.name'],
})
.toSummary();
expect(humanizeTplAst(parse('<div [dot.name]="expr"></div>', [dirA]))).toEqual([
[ElementAst, 'div'],
[DirectiveAst, dirA],
[BoundDirectivePropertyAst, 'localName', 'expr'],
]);
});
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
const dirA = const dirA =
CompileDirectiveMetadata CompileDirectiveMetadata
@ -1244,8 +1261,15 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should parse variables via let ...', () => { it('should parse variables via let ...', () => {
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', [ const targetAst = [
]))).toEqual([[EmbeddedTemplateAst], [VariableAst, 'a', 'b'], [ElementAst, 'div']]); [EmbeddedTemplateAst],
[VariableAst, 'a', 'b'],
[ElementAst, 'div'],
];
expect(humanizeTplAst(parse('<div *ngIf="let a=b">', []))).toEqual(targetAst);
expect(humanizeTplAst(parse('<div data-*ngIf="let a=b">', []))).toEqual(targetAst);
}); });
describe('directives', () => { describe('directives', () => {
@ -1299,17 +1323,30 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should work with *... and use the attribute name as property binding name', () => { it('should work with *... and use the attribute name as property binding name', () => {
expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([ expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, ngIf], [EmbeddedTemplateAst],
[BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div'] [DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'test'],
[ElementAst, 'div'],
]);
// https://github.com/angular/angular/issues/13800
expect(humanizeTplAst(parse('<div *ngIf="-1">', [ngIf]))).toEqual([
[EmbeddedTemplateAst],
[DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', '0 - 1'],
[ElementAst, 'div'],
]); ]);
}); });
it('should work with *... and empty value', () => { it('should work with *... and empty value', () => {
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([ expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, ngIf], [EmbeddedTemplateAst],
[BoundDirectivePropertyAst, 'ngIf', 'null'], [ElementAst, 'div'] [DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'null'],
[ElementAst, 'div'],
]); ]);
}); });
}); });

View File

@ -108,9 +108,12 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
provide: COMPILER_OPTIONS, provide: COMPILER_OPTIONS,
useValue: { useValue: {
providers: [ providers: [
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver}, MockPipeResolver,
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver}, {provide: PipeResolver, useExisting: MockPipeResolver},
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver} MockDirectiveResolver,
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
MockNgModuleResolver,
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
] ]
}, },
multi: true multi: true

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