Compare commits

...

101 Commits

Author SHA1 Message Date
6bacd32fbd release: cut the v7.0.0-beta.0 release 2018-08-02 11:42:10 -07:00
183757daa2 fix(core): fix tests for uninitialized @Output error (#19116)
PR Close #19116
2018-08-02 09:37:21 -07:00
adf510f986 fix(core): throw error message when @Output not initialized (#19116)
Closes #3664

PR Close #19116
2018-08-02 09:37:21 -07:00
74bce18190 Revert "docs: refactor http module import for style guide app.module (#25001)" (#25263)
This reverts commit 88da8f3d52.

PR Close #25263
2018-08-02 09:20:12 -07:00
3ba5220839 refactor(forms): ngForm element selector has been deprecated in favor of ng-form (#23721)
This has been deprecated to keep selector consistent with other core Angular selectors.  As element selectors are in kebab-case.

 Now deprecated:
 ```
 <ngForm #myForm="ngForm">
 ```

 After:
 ```
 <ng-form #myForm="ngForm">
 ```

You can also choose to supress this warnings by providing a config for `FormsModule` during import:

```ts
imports: [
 FormsModule.withConfig({warnOnDeprecatedNgFormSelector: 'never'});
]

Closes: #23678

PR Close #23721
2018-08-02 08:34:43 -07:00
5982425436 test(common): TokenExtractor should extend HttpXsrfTokenExtractor in xsrf spec (#24649)
PR Close #24649
2018-08-02 08:34:15 -07:00
140248ade0 test(common): remove unused import in xsrf spec (#24649)
PR Close #24649
2018-08-02 08:34:14 -07:00
e60737f63c docs(docs-infra): adds note according to Symlink problem (#24714)
docs: adds note according to Symlink problem

Closes #24709
docs(docs-infra): adds section "Developing on Windows"


Merge remote-tracking branch 'origin/aioREADME' into aioREADME


docs(docs-infra): adds information about admin rights


docs(docs-infra): adds hint


docs(docs-infra): Change to link


PR Close #24714
2018-08-02 08:33:24 -07:00
7b89711402 docs(elements): add section about custom element typings in elements guide (#25219)
PR Close #25219
2018-08-02 08:32:59 -07:00
f1223628a6 docs(elements): add link to full example in elements guide (#25219)
PR Close #25219
2018-08-02 08:32:59 -07:00
1b4269ad85 docs(elements): remove unnecessary whitespace in elements guide (#25219)
PR Close #25219
2018-08-02 08:32:59 -07:00
a224df43af feat(docs-infra): support sending Google Analytics events (#25042)
PR Close #25042
2018-08-01 17:04:19 -07:00
d46a961509 docs(ivy): API doc tweaks (#25258)
PR Close #25258
2018-08-01 16:19:54 -07:00
20b453008f docs: update reactiveconf 2018 in events (#24739)
PR Close #24739
2018-08-01 16:15:18 -07:00
06a1974a48 docs: Update the link to the Jasmine docs (#25175)
Solves #24462.

Also update the http part of the link to to https.

PR Close #25175
2018-08-01 16:12:43 -07:00
3d7f555044 ci: update pullapprove groups and add docs (#25257)
With this update to permissions the docs team can easily identify the technical reviewer for a particular doc, which should streamline the reviews.

I also added Jennifer into all groups that contain docs, so that she can approve changes that contain only editorial changes.

Closes #21692

PR Close #25257
2018-08-01 15:53:41 -07:00
06af7943a4 test(upgrade): run tests against AngularJS v1.7.x as well (#25231)
PR Close #25231
2018-08-01 14:10:21 -07:00
fa70a2a650 build(bazel): entry point file couldn't be resolved [ts-api-guardian] (#25052)
* When using `ts-api-guardian` on Windows, the input file can't be found due to wrong normalized path delimiters.

PR Close #25052
2018-08-01 13:29:27 -07:00
bde4402675 build: update hello_world__closure to google-closure-compiler 20180716.0.0 (#25236)
PR Close #25236
2018-08-01 13:23:35 -07:00
7d6b258778 build: revert yarn.lock rxjs version to 6.0.0 (#25236)
PR Close #25236
2018-08-01 13:23:35 -07:00
5342aeaafd docs(aio): update Kendo UI description in resource.json (#24845)
PR Close #24845
2018-08-01 10:59:16 -07:00
1dd2eaa7d2 docs: fix typos and missing word in tutorial (#20764)
PR Close #20764
2018-08-01 10:56:31 -07:00
af07ffc2ad docs(core): remove experimental tag (#24032)
PR Close #24032
2018-08-01 10:56:07 -07:00
2b6e1f0f4b docs(core): remove experimental tag (#24032)
Remove experimental note on APP_INITIALIZER.

PR Close #24032
2018-08-01 10:56:06 -07:00
7a4fb44f8d docs(aio): add Kevin Yang to GDE resources (#24791)
Add files via upload
PR Close #24791
2018-08-01 10:55:41 -07:00
88da8f3d52 docs: refactor http module import for style guide app.module (#25001)
PR Close #25001
2018-08-01 10:55:17 -07:00
01e6dab544 fix(compiler-cli): correct realPath to realpath. (#25023)
The optional property on `ts.CompilerHost` is called `realpath` (lower
case), not `realPath` (lower camel case).

It is not clear to me what the impact of this is, but the author's
intent was clearly to override `realpath`.

PR Close #25023
2018-08-01 10:54:51 -07:00
a9ecf4b929 docs: refactor lazy loading modules example (#25071)
PR Close #25071
2018-08-01 10:54:00 -07:00
166ddaadca docs(router): clarify scroll position wording (#25077)
PR Close #25077
2018-08-01 10:53:35 -07:00
1e5327872d docs(core): replace ReflectiveInjector example with Static Injector example (#25162)
PR Close #25162
2018-08-01 10:52:32 -07:00
367841d237 docs: replace ReflectiveInjector samples with Injector samples (#25162)
PR Close #25162
2018-08-01 10:52:32 -07:00
d5b73832bf refactor(animations): do not use short parameter names (#25198)
PR Close #25198
2018-08-01 10:51:58 -07:00
7075c418f9 docs(changelog): remove reverted feature entry (#25206)
PR Close #25206
2018-08-01 10:51:28 -07:00
466e026f6f docs(changelog): remove duplicate entries (#25206)
PR Close #25206
2018-08-01 10:51:28 -07:00
4976a58780 docs: update to account for CLI changes (#25223)
This should help clarify the use of providedIn and correct the documentation where it was showing the use of a now depreciated CLI command flag.

I am openly looking for feedback on this change to figure out the best wording.

PR Close #25223
2018-08-01 10:51:05 -07:00
bafe1a0d2a build(bazel): fix typo in protractor test target definition (#25235)
PR Close #25235
2018-08-01 10:50:43 -07:00
c8a4fb1faf fix(ivy): walk declaration views in listener (#25228)
PR Close #25228
2018-07-31 16:35:20 -07:00
64516da6b0 feat(ivy): support inheriting input/output from bare base class (#25094)
PR Close #25094
2018-07-31 16:25:11 -07:00
6e2a1877ab refactor(core): remove withBody from public testing API (#25171)
PR Close #25171
2018-07-31 15:09:32 -07:00
aafd502bcb fix(ivy): default to ngDevMode = true (#25208)
Before the `ngDevMode` had to be set explicitly or it would throw
an exception at runtime. This changes it so that if `ngDevModu` is
`undefined` than we default to `ngDevMode = true`. In other words
unless the developer has explicitly asked to make a prodution build
by setting `ngDevMode = false` as compilation constant, the default
is `ngDevMode = true`.

This also fixes a minor bug where the setup code would read
`global['ngDevMode']` but all other code would read `global.ngDevMode`.
This would cause issues with closure compiler since the
reading of the `ngDevMode` must be consistent.

PR Close #25208
2018-07-31 14:19:19 -07:00
4cb1074850 docs(aio): add short description for entryComponents (#21360)
PR Close #21360
2018-07-31 13:18:36 -07:00
76d8eb021c build: make postinstall script compatible with Windows (#25232)
PR Close #25232
2018-07-31 13:17:54 -07:00
3f6fc00d73 docs(forms): fix incorrect variables naming in the comments (#25150)
PR Close #25150
2018-07-31 11:42:15 -07:00
5254d3447d build(bazel): update to rules_nodejs 0.11.2 and latest rules_typescript (#25169)
PR Close #25169
2018-07-31 11:41:50 -07:00
4ee9db959a docs(docs-infra): fix topnav layout for smaller screens (#25181)
PR Close #25181
2018-07-31 11:41:22 -07:00
3f20a2fb5a docs: fix link to "Override component providers" (#24967)
Closes #24966

PR Close #24967
2018-07-30 21:53:21 -07:00
e3834b7001 feat(ivy): support change detection on the root view (#25085)
PR Close #25085
2018-07-30 21:50:54 -07:00
36648293a8 refactor(ivy): misc (#25174)
PR Close #25174
2018-07-30 16:59:48 -07:00
cd89eb8404 feat(ivy): implement the getters of ViewContainerRef (#25174)
BREAKING CHANGE: ViewContainerRef.parentInjector is deprecated without replacement

PR Close #25174
2018-07-30 16:59:48 -07:00
e99d860393 feat(compiler): add "original" placeholder value on extracted XMB (#25079)
Update XMB placeholders(<ph>) to include the original value on top of an
example. Placeholders can by definition have one example(<ex>) tag and a
text node. The text node is used by TC as the "original" value from the
placeholder, while the example should represent a dummy value.
For example: <ph name="PET"><ex>Gopher</ex>{{ petName }}</ph>.
This change makes sure that we have the original text, but it *DOES NOT*
make sure that the example is correct. The example has the same wrong
behavior of showing the interpolation text rather than a useful
example.

No breaking changes, but tools that depend on the previous behavior and
don't consider the full XMB definition may fail to parse the XMB.
Fixes b/72565847

PR Close #25079
2018-07-30 16:49:00 -07:00
24789e9ad9 docs(aio): add StrongBrew to the trainer list (#24891)
PR Close #24891
2018-07-30 16:48:17 -07:00
f94f9640d0 ci: correctly encode quoted params passed as params to curl
Previously the auth token could have been split into three separate args in bash which resulted
in two bogus requests being sent out for each curl call. These requests had to time out before
the real request was made, but without the token.

I couldn't find a better way to quickly fix this without adding some duplication.
2018-07-30 16:46:11 -07:00
4d5167ec83 docs: update bootstrapping and entry component guide to use httpclient (#25178)
PR Close #25178
2018-07-30 16:00:19 -07:00
efc6684cd3 docs: fix typo in dependency injection guide (#24972)
PR Close #24972
2018-07-30 15:56:35 -07:00
2ef777b0b2 fix(ivy): convert context code into a tree-shakable instruction (#24943)
PR Close #24943
2018-07-30 15:54:11 -07:00
fe14f180a6 fix(compiler): update compiler to flatten nested template fns (#24943)
PR Close #24943
2018-07-30 15:54:11 -07:00
87419097da fix(ivy): flatten template fns for nested views (#24943)
PR Close #24943
2018-07-30 15:54:11 -07:00
9a6d26e05b docs: refactor pipe example to use the HttpClient (#22741)
PR Close #22741
2018-07-30 14:34:32 -07:00
6a797d5401 refactor(ivy): element and ElementStart retuns void (#25173)
use `loadElement` to load an element when needed in specs

PR Close #25173
2018-07-27 17:22:18 -07:00
89e8b6fc0e refactor(ivy): update specs to make use of the element() instruction (#25173)
PR Close #25173
2018-07-27 17:22:18 -07:00
f82b6b2ed7 build(bazel): fix typos in comments (#25172)
PR Close #25172
2018-07-27 17:20:58 -07:00
a87d44c187 refactor(ivy): do not deep import from ngtsc into ngcc (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
43d0e3dd72 feat(ivy): implement initial ngcc package transformer (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
5b32aa4486 feat(ivy): implement esm2015 and esm5 ngcc file renderers (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
844d510d3f feat(ivy): implement ngcc Analyzer (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
2f70e90493 feat(ivy): implement esm2015 and esm5 file parsers (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
45cf5b5dad feat(ivy): implement esm2015 and esm5 reflection hosts (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
4ad2f11919 test(ivy): implement ngcc specific version of makeProgram (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
d7aa20d912 feat(ivy): ngcc project skeleton (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
a673494412 build: add dependencies to be used by ngcc (#24897)
PR Close #24897
2018-07-27 17:15:31 -07:00
07e6de5788 test(ivy): allow makeProgram to be more configurable (#24897)
This supports use cases needed by ngcc, where the compilation
needs to be configured for JavaScript differently to normal TypeScript.

PR Close #24897
2018-07-27 17:15:31 -07:00
6f1685ab98 fix(ivy): allow FunctionExpression to indicate a method declaration (#24897)
In some code formats (e.g. ES5) methods can actually be function
expressions. For example:

```js
function MyClass() {}
// this static method is declared as a function expression
MyClass.staticMethod = function() { ... };
```

PR Close #24897
2018-07-27 17:15:31 -07:00
67588ec606 refactor(ivy): allow ImportManager to have configurable prefix (#24897)
The ngcc compiler will want to specify its own prefix when rendering
definitions.

PR Close #24897
2018-07-27 17:15:31 -07:00
ee2c050521 fix(ivy): make ngtsc ClassMember node and declaration optional (#24897)
Not all code formats have associated nodes and declarations for class members.

PR Close #24897
2018-07-27 17:15:30 -07:00
185b932138 refactor(ivy): TypeScriptReflectionHost.isClass cannot be a type discriminator (#24897)
The `ReflectionHost` interface that is being implemented only expects a
return value of `boolean`.

Moreover, if you want to extend this class to support non-TS code formats,
e.g. ES5, the result of this call returning true does not mean that the `node`
is a `ClassDeclaration`. It could be a `VariableDeclaration`.

PR Close #24897
2018-07-27 17:15:30 -07:00
5e98421d33 style(ivy): remove underscore from TypeScriptReflectionHost._getDeclarationOfSymbol (#24897)
The linter complains that non-private members must be marked
with `@internal` if they start with an underscore.

PR Close #24897
2018-07-27 17:15:30 -07:00
8e65891985 build(ivy): fix ci failures (#25166)
PR Close #25166
2018-07-27 18:47:13 -04:00
7f59170f77 refactor(ivy): use element() where applicable in di_spec (#25166)
For future ref
Search `elementStart\(([^)]+)\);\s*\n\s*elementEnd\(\);`
Replace `element($1)`

PR Close #25166
2018-07-27 18:47:13 -04:00
9ea112473b refactor(ivy): use bit operations in node injector (#25166)
PR Close #25166
2018-07-27 18:47:13 -04:00
16f0ac38b8 refactor(ivy): simplify node injector imports (#25166)
PR Close #25166
2018-07-27 18:47:13 -04:00
15df853622 fix(ivy): walk the node injector tree and then the module injector tree (#25166)
- `directiveInjector()` is used to inject anything in the directive / component
/ pipe factories so adding `InjectionToken<T>` as a supported token type.
- `getOrCreateInjectable()` should search first in the node injector tree and
then in the module injector tree (was either or before the PR).

PR Close #25166
2018-07-27 18:47:13 -04:00
d3c0915598 docs(ivy): clarify injector API docs (#25166)
PR Close #25166
2018-07-27 18:47:13 -04:00
ce98634dfd build(compiler-cli): update tsickle dependency to support TypeScript 2.9 (#25152)
The original range (`^0.30.0`) does not match `0.32.1`, which enables support for TypeScript 2.9.

Close #25141

PR Close #25152
2018-07-27 11:25:28 -07:00
342678486d test: fix typings for DoneFn (#25163)
This also fixes CI tests, which were accidentally broken in #24663.

PR Close #25163
2018-07-27 11:13:32 -07:00
e8d4211d5c feat(docs-infra): allow notification bar to show arbitrary content (#25020)
This change generalises the notification bar rendering to allow
more complex content to be displayed.

Now you must provide the full HTML of the notification message
when using `<aio-notification>`.

Also you can control whether clicking the content triggers the
notification to close or not.

This will support the new notification specified in "Other Items : 3" of
[#24140](https://github.com/angular/angular/issues/24140#issuecomment-397480410)

PR Close #25020
2018-07-27 09:29:40 -07:00
6a4d66d432 style(docs-infra): remove unnecessary call to console.log() (#25020)
PR Close #25020
2018-07-27 09:29:39 -07:00
a3cf61b7cf docs: refactor feature modules example (#25069)
PR Close #25069
2018-07-27 09:28:12 -07:00
a1b185b723 docs: Change unnecessary step in ToH-Tutorial (#25059)
PR Close #25059
2018-07-27 09:25:59 -07:00
601064e41d build(bazel): add comment about angular bazel rules API re-export from /index.bzl (#24663)
PR Close #24663
2018-07-26 17:02:21 -07:00
e265ccd82c build(bazel): add comment for patch-types work-around (#24663)
PR Close #24663
2018-07-26 17:02:21 -07:00
dd44f63c73 build(bazel): show bazel progress in CircleCI to prevent 10m timeout with no output (#24663)
PR Close #24663
2018-07-26 17:02:21 -07:00
1d051c5841 build(bazel): use bazel managed node_modules for downstream angular from source build support (#24663)
PR Close #24663
2018-07-26 17:02:21 -07:00
323faf954b docs(router): Removed unneeded trailing text. (#24894)
PR Close #24894
2018-07-26 17:01:02 -07:00
3169edd77a fix(ivy): don't crash in listLazyRoutes() (#25080)
This commit replaces the "not implemented" error when calling
listLazyRoutes() with an empty result, which will allow testing
in the CLI before listLazyRoutes() is implemented.

PR Close #25080
2018-07-26 16:38:10 -07:00
8de304c15a fix(ivy): wait for preanalyze promises in loadNgStructureAsync() (#25080)
loadNgStructureAsync() for ngtsc has a bug where it returns a
Promise<Promise[]> instead of awaiting the entire array of Promises.

This commit uses Promise.all() to await the whole set.

PR Close #25080
2018-07-26 16:38:09 -07:00
0d1d5898e3 fix(bazel): allow compile_strategy to be (privately) imported (#25080)
compile_strategy() is used to decide whether to build Angular code
using ngc (legacy) or ngtsc (local). In order for g3 BUILD rules
to switch properly and allow testing of Ivy in g3, they need to
import this function.

This commit removes the _ prefix which allows the function to be
imported.

PR Close #25080
2018-07-26 16:38:09 -07:00
6fe865b080 fix(ivy): don't use a custom ts.CompilerHost for ngtsc (#25080)
ngtsc used to have a custom ts.CompilerHost which delegated to the plain
ts.CompilerHost. There's no need for this wrapper class and it causes
issues with CLI integration, so delete it.

PR Close #25080
2018-07-26 16:38:09 -07:00
e0c0c44d99 fix(ivy): allow relative imports of .d.ts files (#25080)
ngtsc used to assume that all .d.ts dependencies (that is, third party
packages) were imported via an absolute module path. It turns out this
assumption isn't valid; some build tools allow relative imports of
other compilation units.

In the absolute case, ngtsc assumes (and still does) that all referenced
types are available through the entrypoint from which an @NgModule was
imported. This commit adds support for relative imports, in which case
ngtsc will use relative path resolution to determine the imports.

PR Close #25080
2018-07-26 16:38:09 -07:00
13a0d527f6 fix(ivy): correctly write cross-file references (#25080)
There is a bug in the existing handling for cross-file references.
Suppose there are two files, module.ts and component.ts.

component.ts declares two components, one of which uses the other.
In the Ivy model, this means the component will get a directives:
reference to the other in its defineComponent call.

That reference is generated by looking at the declared components
of the module (in module.ts). However, the way ngtsc tracks this
reference, it ends up comparing the identifier of the component
in module.ts with the component.ts file, detecting they're not in
the same file, and generating a relative import.

This commit changes ngtsc to track all identifiers of a reference,
including the one by which it is declared. This allows toExpression()
to correctly decide that a local reference is okay in component.ts.

PR Close #25080
2018-07-26 16:38:09 -07:00
ed7aa1c3e5 fix(ivy): force new imports for .d.ts files (#25080)
When ngtsc encounters a reference to a type (for example, a Component
type listed in an NgModule declarations array), it traces the import
of that type and attempts to determine the best way to refer to it.

In the event the type is defined in the same file where a reference
is being generated, the identifier of the type is used. If the type
was imported, ngtsc has a choice. It can use the identifier from the
original import, or it can write a new import to the module where the
type came from.

ngtsc has a bug currently when it elects to rely on the user's import.
When writing a .d.ts file, the user's import may have been elided as
the type was not referred to from the type side of the program. Thus,
in .d.ts files ngtsc must always assume the import may not exist, and
generate a new one.

In .js output the import is guaranteed to still exist, so it's
preferable for ngtsc to continue using the existing import if one is
available.

This commit changes how @angular/compiler writes type definitions, and
allows it to use a different expression to write a type definition than
is used to write the value. This allows ngtsc to specify that types in
type definitions should always be imported. A corresponding change to
the staticallyResolve() Reference system allows the choice of which
type of import to use when generating an Expression from a Reference.

PR Close #25080
2018-07-26 16:38:09 -07:00
f902b5ec59 feat(ivy): resolve forwardRef() for queries (#25080)
@ContentChild[ren] and @ViewChild[ren] can contain a forwardRef() to a
type. This commit allows ngtsc to unwrap the forward reference and
deal with the node inside.

It includes two modes of support for forward reference resolution -
a foreign function resolver which understands deeply nested forward
references in expressions that are being statically evaluated, and
an unwrapForwardRef() function which deals only with top-level nodes.

Both will be useful in the future, but for now only unwrapForwardRef()
is used.

PR Close #25080
2018-07-26 16:38:09 -07:00
222 changed files with 11995 additions and 3070 deletions

View File

@ -3,7 +3,10 @@
# See remote cache documentation in /docs/BAZEL.md
# Don't be spammy in the logs
build --noshow_progress
# TODO(gmagolan): Hide progress again once build performance improves
# Presently, CircleCI can timeout during bazel test ... with the following
# error: Too long with no output (exceeded 10m0s)
# build --noshow_progress
# Don't run manual tests
test --test_tag_filters=-manual

View File

@ -8,6 +8,7 @@
# alexeagle - Alex Eagle
# alxhub - Alex Rickabaugh
# andrewseguin - Andrew Seguin
# benlesh - Ben Lesh
# brandonroberts - Brandon Roberts
# brocco - Mike Brocchi
# filipesilva - Filipe Silva
@ -15,7 +16,7 @@
# hansl - Hans Larsen
# IgorMinar - Igor Minar
# jasonaden - Jason Aden
# kapunahelewong - Kapunahele Wong
# jenniferfell - Jennifer Fell
# kara - Kara Erickson
# kyliau - Keen Yee Liau
# matsko - Matias Niemelä
@ -91,6 +92,7 @@ groups:
- "*.bzl"
- "packages/bazel/*"
- "tools/bazel.rc"
- "/docs/BAZEL.md"
users:
- alexeagle #primary
- kyliau
@ -130,42 +132,113 @@ groups:
conditions:
files:
- "packages/core/*"
- "aio/content/guide/bootstrapping.md"
- "aio/content/examples/bootstrapping/*"
- "aio/content/guide/attribute-directives.md"
- "aio/content/examples/attribute-directives/*"
- "aio/content/images/guide/attribute-directives/*"
- "aio/content/guide/structural-directives.md"
- "aio/content/examples/structural-directives/*"
- "aio/content/images/guide/structural-directives/*"
- "aio/content/guide/dynamic-component-loader.md"
- "aio/content/examples/dynamic-component-loader/*"
- "aio/content/images/guide/dynamic-component-loader/*"
- "aio/content/guide/template-syntax.md"
- "aio/content/examples/template-syntax/*"
- "aio/content/images/guide/template-syntax/*"
- "aio/content/guide/dependency-injection.md"
- "aio/content/examples/dependency-injection/*"
- "aio/content/images/guide/dependency-injection/*"
- "aio/content/guide/dependency-injection-in-action.md"
- "aio/content/examples/dependency-injection-in-action/*"
- "aio/content/images/guide/dependency-injection-in-action/*"
- "aio/content/guide/hierarchical-dependency-injection.md"
- "aio/content/examples/hierarchical-dependency-injection/*"
- "aio/content/guide/singleton-services.md"
- "aio/content/guide/dependency-injection-pattern.md"
- "aio/content/guide/providers.md"
- "aio/content/examples/providers/*"
- "aio/content/guide/component-interaction.md"
- "aio/content/examples/component-interaction/*"
- "aio/content/images/guide/component-interaction/*"
- "aio/content/guide/component-styles.md"
- "aio/content/examples/component-styles/*"
- "aio/content/guide/lifecycle-hooks.md"
- "aio/content/examples/lifecycle-hooks/*"
- "aio/content/images/guide/lifecycle-hooks/*"
- "aio/content/examples/ngcontainer/*"
- "aio/content/images/guide/ngcontainer/*"
- "aio/content/guide/pipes.md"
- "aio/content/examples/pipes/*"
- "aio/content/images/guide/pipes/*"
- "aio/content/guide/entry-components.md"
- "aio/content/guide/set-document-title.md"
- "aio/content/examples/set-document-title/*"
- "aio/content/images/guide/set-document-title/*"
- "aio/content/guide/ngmodules.md"
- "aio/content/examples/ngmodules/*"
- "aio/content/examples/ngmodule/*"
- "aio/content/images/guide/ngmodule/*"
- "aio/content/guide/ngmodule-faq.md"
- "aio/content/examples/ngmodule-faq/*"
- "aio/content/guide/module-types.md"
- "aio/content/guide/sharing-ngmodules.md"
- "aio/content/guide/frequent-ngmodules.md"
- "aio/content/images/guide/frequent-ngmodules/*"
- "aio/content/guide/ngmodule-api.md"
- "aio/content/guide/ngmodule-vs-jsmodule.md"
- "aio/content/guide/feature-modules.md"
- "aio/content/examples/feature-modules/*"
- "aio/content/images/guide/feature-modules/*"
- "aio/content/guide/lazy-loading-ngmodules.md"
- "aio/content/examples/lazy-loading-ngmodules/*"
- "aio/content/images/guide/lazy-loading-ngmodules"
users:
- mhevery #primary
- jasonaden
- kara
- vicb
- IgorMinar #fallback
- IgorMinar
- jenniferfell #docs only
animations:
conditions:
files:
- "packages/animations/*"
- "packages/platform-browser/animations/*"
- "aio/content/guide/animations.md"
- "aio/content/examples/animations/*"
- "aio/content/images/guide/animations/*"
users:
- matsko #primary
- mhevery #fallback
- IgorMinar #fallback
- jenniferfell #docs only
compiler/i18n:
conditions:
files:
- "packages/compiler/src/i18n/*"
- "aio/content/guide/i18n.md"
- "aio/content/examples/i18n/*"
users:
- vicb #primary
- alxhub
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
compiler:
conditions:
files:
- "packages/compiler/*"
- "aio/content/guide/aot-compiler.md"
users:
- alxhub #primary
- vicb
- mhevery
- IgorMinar #fallback
- jenniferfell #docs only
compiler-cli/ngtools:
conditions:
@ -174,7 +247,6 @@ groups:
users:
- hansl
- filipesilva #fallback
- brocco #fallback
- IgorMinar #fallback
compiler-cli:
@ -210,56 +282,97 @@ groups:
files:
- "packages/forms/*"
- "aio/content/guide/forms.md"
- "aio/content/guide/form-validation.md"
- "aio/content/guide/reactive-forms.md"
- "aio/content/examples/forms/*"
- "aio/content/images/guide/forms/*"
- "aio/content/guide/form-validation.md"
- "aio/content/examples/form-validation/*"
- "aio/content/images/guide/form-validation/*"
- "aio/content/guide/dynamic-form.md"
- "aio/content/examples/dynamic-form/*"
- "aio/content/images/guide/dynamic-form/*"
- "aio/content/guide/reactive-forms.md"
- "aio/content/examples/reactive-forms/*"
- "aio/content/images/guide/reactive-forms/*"
users:
- kara #primary
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
http:
conditions:
files:
- "packages/common/http/*"
- "packages/http/*"
- "aio/content/guide/http.md"
- "aio/content/examples/http/*"
- "aio/content/images/guide/http/*"
users:
- alxhub #primary
- IgorMinar
- mhevery #fallback
- jenniferfell #docs only
language-service:
conditions:
files:
- "packages/language-service/*"
- "aio/content/guide/language-service.md"
- "aio/content/images/guide/language-service/*"
users:
- kyliau #primary
# needs secondary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
router:
conditions:
files:
- "packages/router/*"
- "aio/content/guide/router.md"
- "aio/content/examples/router/*"
- "aio/content/images/guide/router/*"
users:
- jasonaden #primary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
testing:
conditions:
files:
- "*/testing/*"
- "aio/content/guide/testing.md"
- "aio/content/examples/testing/*"
- "aio/content/images/guide/testing/*"
users:
- vikerman
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
upgrade:
conditions:
files:
- "packages/upgrade/*"
- "aio/content/guide/upgrade.md"
- "aio/content/examples/upgrade-module/*"
- "aio/content/images/guide/upgrade/*"
- "aio/content/examples/upgrade-phonecat-1-typescript/*"
- "aio/content/examples/upgrade-phonecat-2-hybrid/*"
- "aio/content/examples/upgrade-phonecat-3-final/*"
- "aio/content/guide/upgrade-performance.md"
- "aio/content/guide/ajs-quick-reference.md"
- "aio/content/examples/ajs-quick-reference/*"
users:
- petebacondarwin #primary
- gkalpak
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
platform-browser:
conditions:
@ -275,12 +388,15 @@ groups:
conditions:
files:
- "packages/platform-server/*"
- "aio/content/guide/universal.md"
- "aio/content/examples/universal/*"
users:
- vikerman #primary
- alxhub #secondary
- vicb
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
platform-webworker:
conditions:
@ -296,22 +412,34 @@ groups:
conditions:
files:
- "packages/service-worker/*"
- "aio/content/guide/service-worker-getting-started.md"
- "aio/content/examples/service-worker-getting-started/*"
- "aio/content/guide/service-worker-communications.md"
- "aio/content/guide/service-worker-config.md"
- "aio/content/guide/service-worker-devops.md"
- "aio/content/guide/service-worker-intro.md"
- "aio/content/images/guide/service-worker/*"
users:
- alxhub #primary
- gkalpak
- IgorMinar #fallback
- gkalpak #primary
- alxhub
- IgorMinar
- mhevery #fallback
- jenniferfell #docs only
elements:
conditions:
files:
- "packages/elements/*"
- "aio/content/examples/elements/*"
- "aio/content/images/guide/elements/*"
- "aio/content/guide/elements.md"
users:
- andrewseguin #primary
- gkalpak
- robwormald
- IgorMinar #fallback
- mhevery #fallback
- jenniferfell #docs only
benchpress:
conditions:
@ -323,7 +451,7 @@ groups:
- IgorMinar #fallback
- mhevery #fallback
angular.io:
docs-infra:
conditions:
files:
include:
@ -336,7 +464,7 @@ groups:
- gkalpak
- mhevery #fallback
angular.io-guide-and-tutorial:
docs/guide-and-tutorial:
conditions:
files:
include:
@ -346,19 +474,20 @@ groups:
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
- kapunahelewong
- stephenfluin
- jenniferfell
- brandonroberts
- petebacondarwin
- gkalpak
- IgorMinar
- brandonroberts
- mhevery #fallback
angular.io-marketing:
docs/marketing:
conditions:
files:
include:
- "aio/content/marketing/*"
- "aio/content/images/marketing/*"
- "aio/content/navigation.json"
- "aio/content/license.md"
users:
@ -368,3 +497,43 @@ groups:
- IgorMinar
- robwormald
- mhevery #fallback
docs/observables:
conditions:
files:
- "aio/content/examples/observables/*"
- "aio/content/images/guide/observables/*"
- "aio/content/guide/observables.md"
- "aio/content/guide/comparing-observables.md"
- "aio/content/examples/observables-in-angular/*"
- "aio/content/images/guide/observables-in-angular/*"
- "aio/content/guide/observables-in-angular.md"
- "aio/content/examples/practical-observable-usage/*"
- "aio/content/guide/practical-observable-usage.md"
- "aio/content/examples/rx-library/*"
- "aio/content/guide/rx-library.md"
users:
- jasonaden
- benlesh
- IgorMinar
- mhevery
- jenniferfell #docs only
docs/packaging:
conditions:
files:
- "aio/content/guide/npm-packages.md"
- "aio/content/guide/browser-support.md"
- "aio/content/guide/typescript-configuration.md"
- "aio/content/guide/setup-systemjs-anatomy.md"
- "aio/content/examples/setup/*"
- "aio/content/guide/setup.md"
- "aio/content/guide/deployment.md"
- "aio/content/guide/releases.md"
- "aio/content/guide/updating.md"
users:
- IgorMinar #primary
- alexeagle
- hansl
- mhevery #fallback
- jenniferfell #docs only

View File

@ -15,220 +15,19 @@ alias(
actual = "@nodejs//:yarn",
)
node_modules_filegroup(
alias(
name = "node_modules",
packages = [
"adm-zip",
"ajv",
"angular",
"angular-1.5",
"angular-mocks",
"angular-mocks-1.5",
"anymatch",
"arr-diff",
"arr-flatten",
"arr-union",
"array-unique",
"asn1",
"assert-plus",
"assign-symbols",
"async-each",
"asynckit",
"atob",
"aws-sign2",
"aws4",
"balanced-match",
"base",
"base64-js",
"binary-extensions",
"blocking-proxy",
"brace-expansion",
"braces",
"bytebuffer",
"cache-base",
"caseless",
"chokidar",
"class-utils",
"co",
"collection-visit",
"combined-stream",
"component-emitter",
"concat-map",
"copy-descriptor",
"core-util-is",
"debug",
"decode-uri-component",
"define-property",
"delayed-stream",
"domino",
"expand-brackets",
"expand-range",
"extend",
"extend-shallow",
"extglob",
"extsprintf",
"fast-deep-equal",
"fast-json-stable-stringify",
"filename-regex",
"fill-range",
"for-in",
"for-own",
"forever-agent",
"form-data",
"fragment-cache",
"fs.realpath",
"get-value",
"glob",
"glob-base",
"glob-parent",
"graceful-fs",
"hammerjs",
"har-schema",
"har-validator",
"has-value",
"has-values",
"http-signature",
"https-proxy-agent",
"inflight",
"inherits",
"is-accessor-descriptor",
"is-binary-path",
"is-buffer",
"is-data-descriptor",
"is-descriptor",
"is-dotfile",
"is-equal-shallow",
"is-extendable",
"is-extglob",
"is-glob",
"is-number",
"is-plain-object",
"is-posix-bracket",
"is-primitive",
"is-typedarray",
"is-windows",
"isarray",
"isobject",
"isstream",
"jasmine",
"jasmine-core",
"jasminewd2",
"json-schema",
"json-schema-traverse",
"json-stable-stringify",
"json-stringify-safe",
"jsprim",
"kind-of",
"long",
"lru-cache",
"map-cache",
"map-visit",
"math-random",
"micromatch",
"mime-db",
"mime-types",
"minimatch",
"minimist",
"mixin-deep",
"nanomatch",
"normalize-path",
"oauth-sign",
"object.omit",
"object.pick",
"object-copy",
"object-visit",
"once",
"optimist",
"options",
"os-tmpdir",
"parse-glob",
"pascalcase",
"path-dirname",
"path-is-absolute",
"performance-now",
"posix-character-classes",
"preserve",
"process-nextick-args",
"protobufjs",
"protractor",
"qs",
"randomatic",
"readable-stream",
"readdirp",
"reflect-metadata",
"regex-cache",
"regex-not",
"remove-trailing-separator",
"repeat-element",
"repeat-string",
"request",
"ret",
"rimraf",
"safe-buffer",
"safe-regex",
"sax",
"semver",
"set-immediate-shim",
"set-value",
"shelljs",
"sigmund",
"snapdragon",
"snapdragon-node",
"snapdragon-util",
"source-map",
"source-map-resolve",
"source-map-support",
"source-map-url",
"split-string",
"sshpk",
"static-extend",
"stringstream",
"tmp",
"to-object-path",
"to-regex",
"to-regex-range",
"tough-cookie",
"tsickle",
"tslib",
"tsutils",
"tunnel-agent",
"typescript",
"union-value",
"unset-value",
"upath",
"uri-js",
"urix",
"use",
"util-deprecate",
"uuid",
"verror",
"webdriver-js-extender",
"webdriver-manager",
"wordwrap",
"wrappy",
"xhr2",
"xml2js",
"xmlbuilder",
"zone.js",
"@angular-devkit/core",
"@angular-devkit/schematics",
"@types",
"@webcomponents/custom-elements",
],
patterns = [
"node_modules/protractor/**",
"node_modules/@schematics/angular/**",
],
actual = "@angular_deps//:node_modules",
)
filegroup(
name = "web_test_bootstrap_scripts",
# do not sort
srcs = [
"//:node_modules/reflect-metadata/Reflect.js",
"//:node_modules/zone.js/dist/zone.js",
"//:node_modules/zone.js/dist/zone-testing.js",
"//:node_modules/zone.js/dist/task-tracking.js",
"@angular_deps//:node_modules/reflect-metadata/Reflect.js",
"@angular_deps//:node_modules/zone.js/dist/zone.js",
"@angular_deps//:node_modules/zone.js/dist/zone-testing.js",
"@angular_deps//:node_modules/zone.js/dist/task-tracking.js",
"//:test-events.js",
],
)
@ -236,9 +35,11 @@ filegroup(
filegroup(
name = "angularjs_scripts",
srcs = [
"//:node_modules/angular-1.5/angular.js",
"//:node_modules/angular-mocks-1.5/angular-mocks.js",
"//:node_modules/angular-mocks/angular-mocks.js",
"//:node_modules/angular/angular.js",
"@angular_deps//:node_modules/angular-1.5/angular.js",
"@angular_deps//:node_modules/angular-1.6/angular.js",
"@angular_deps//:node_modules/angular-mocks-1.5/angular-mocks.js",
"@angular_deps//:node_modules/angular-mocks-1.6/angular-mocks.js",
"@angular_deps//:node_modules/angular-mocks/angular-mocks.js",
"@angular_deps//:node_modules/angular/angular.js",
],
)

View File

@ -1,3 +1,26 @@
<a name="7.0.0-beta.0"></a>
# [7.0.0-beta.0](https://github.com/angular/angular/compare/6.1.0...7.0.0-beta.0) (2018-08-02)
### Bug Fixes
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
### Features
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
### BREAKING CHANGES
* **ivy:** ViewContainerRef.parentInjector is deprecated without replacement
<a name="6.1.0"></a>
# [6.1.0](https://github.com/angular/angular/compare/6.0.0-rc.5...6.1.0) (2018-07-25)
@ -16,26 +39,19 @@
* **common:** format fractional seconds ([#24844](https://github.com/angular/angular/issues/24844)) ([0b4d85e](https://github.com/angular/angular/commit/0b4d85e)), closes [#24831](https://github.com/angular/angular/issues/24831)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([a527c69](https://github.com/angular/angular/commit/a527c69)), closes [#24384](https://github.com/angular/angular/issues/24384)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([ff84c5c](https://github.com/angular/angular/commit/ff84c5c)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([dcabb05](https://github.com/angular/angular/commit/dcabb05)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f))
* **compiler:** i18n_extractor now outputs the correct source file name ([#24885](https://github.com/angular/angular/issues/24885)) ([c8ad965](https://github.com/angular/angular/commit/c8ad965)), closes [#24884](https://github.com/angular/angular/issues/24884)
* **compiler:** fix a few non-tree-shakeable code patterns ([#24677](https://github.com/angular/angular/issues/24677)) ([50d4a4f](https://github.com/angular/angular/commit/50d4a4f))
* **compiler:** support `.` in import statements. ([#20634](https://github.com/angular/angular/issues/20634)) ([d8f7b29](https://github.com/angular/angular/commit/d8f7b29)), closes [#20363](https://github.com/angular/angular/issues/20363)
* **compiler:** avoid a crash in ngc-wrapped. ([#23468](https://github.com/angular/angular/issues/23468)) ([e1c4930](https://github.com/angular/angular/commit/e1c4930))
* **compiler:** generate constant array for i18n attributes ([#23837](https://github.com/angular/angular/issues/23837)) ([cfde36d](https://github.com/angular/angular/commit/cfde36d))
* **compiler:** generate core-compliant hostBindings property ([#24087](https://github.com/angular/angular/issues/24087)) ([01b5acd](https://github.com/angular/angular/commit/01b5acd)), closes [#24013](https://github.com/angular/angular/issues/24013)
* **compiler:** handle undefined annotation metadata ([#23349](https://github.com/angular/angular/issues/23349)) ([ca776c5](https://github.com/angular/angular/commit/ca776c5))
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3))
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([0d5f2d3](https://github.com/angular/angular/commit/0d5f2d3))
* **compiler-cli:** don't rely on incompatible TS method ([#23550](https://github.com/angular/angular/issues/23550)) ([b1f040f](https://github.com/angular/angular/commit/b1f040f))
* **core:** stop reusing provider definitions across NgModuleRef instances ([#25022](https://github.com/angular/angular/issues/25022)) ([6b859da](https://github.com/angular/angular/commit/6b859da)), closes [#25018](https://github.com/angular/angular/issues/25018)
* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **core:** mark NgModule as not the root if APP_ROOT is set to false ([#24814](https://github.com/angular/angular/issues/24814)) ([1089261](https://github.com/angular/angular/commit/1089261))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([0922228](https://github.com/angular/angular/commit/0922228)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **core:** Injector correctly honors the @Self flag ([#24520](https://github.com/angular/angular/issues/24520)) ([ccbda9d](https://github.com/angular/angular/commit/ccbda9d))
* **core:** avoid eager providers re-initialization ([#23559](https://github.com/angular/angular/issues/23559)) ([0c6dc45](https://github.com/angular/angular/commit/0c6dc45))
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([fc03427](https://github.com/angular/angular/commit/fc03427)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
@ -44,10 +60,6 @@
* **elements:** prevent closure renaming of platform properties ([#23843](https://github.com/angular/angular/issues/23843)) ([d4b8b24](https://github.com/angular/angular/commit/d4b8b24))
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([9367e91](https://github.com/angular/angular/commit/9367e91)), closes [#17195](https://github.com/angular/angular/issues/17195)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([6881404](https://github.com/angular/angular/commit/6881404)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea))
* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d))
* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc))
* **platform-browser:** add missing deps for HammerGesturesPlugin ([#24682](https://github.com/angular/angular/issues/24682)) ([13d60ea](https://github.com/angular/angular/commit/13d60ea))
* **platform-browser:** mark Meta and Title services as tree shakable providers ([#24815](https://github.com/angular/angular/issues/24815)) ([197387d](https://github.com/angular/angular/commit/197387d))
* **platform-browser:** workaround wrong import path generated by ngc for DOCUMENT ([#24830](https://github.com/angular/angular/issues/24830)) ([7d27ecc](https://github.com/angular/angular/commit/7d27ecc))
@ -57,14 +69,12 @@
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([c73196e](https://github.com/angular/angular/commit/c73196e)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
* **router:** Fix _lastPathIndex in deeply nested empty paths ([#22394](https://github.com/angular/angular/issues/22394)) ([968f153](https://github.com/angular/angular/commit/968f153))
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([86d254d](https://github.com/angular/angular/commit/86d254d)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([5731d07](https://github.com/angular/angular/commit/5731d07)), closes [#10981](https://github.com/angular/angular/issues/10981)
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([89f64e5](https://github.com/angular/angular/commit/89f64e5)), closes [#22617](https://github.com/angular/angular/issues/22617)
* **router:** cache route handle if found ([#22475](https://github.com/angular/angular/issues/22475)) ([4cfa571](https://github.com/angular/angular/commit/4cfa571)), closes [#22474](https://github.com/angular/angular/issues/22474)
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([553a680](https://github.com/angular/angular/commit/553a680))
* **service-worker:** don't include sourceMappingURL in ngsw-worker ([#24877](https://github.com/angular/angular/issues/24877)) ([8620373](https://github.com/angular/angular/commit/8620373)), closes [#23596](https://github.com/angular/angular/issues/23596)
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9))
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([52d43a9](https://github.com/angular/angular/commit/52d43a9))
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([3ed2d75](https://github.com/angular/angular/commit/3ed2d75)), closes [#24095](https://github.com/angular/angular/issues/24095)
* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([fb59b2d](https://github.com/angular/angular/commit/fb59b2d)), closes [#23196](https://github.com/angular/angular/issues/23196)
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0bdd30e](https://github.com/angular/angular/commit/0bdd30e))
@ -82,9 +92,6 @@
* **core:** expose a Compiler API for accessing module ids from NgModule types ([#24258](https://github.com/angular/angular/issues/24258)) ([bd02b27](https://github.com/angular/angular/commit/bd02b27))
* **core:** KeyValueDiffer#diff allows null values ([#24319](https://github.com/angular/angular/issues/24319)) ([52ce9d5](https://github.com/angular/angular/commit/52ce9d5))
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977))
* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6))
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([3553977](https://github.com/angular/angular/commit/3553977))
* **core:** add support for using async/await with Jasmine ([#24637](https://github.com/angular/angular/issues/24637)) ([71100e6](https://github.com/angular/angular/commit/71100e6))
(https://github.com/angular/angular/commit/328971f)), closes [#24616](https://github.com/angular/angular/issues/24616)
* **platform-browser:** add HammerJS lazy-loader symbols to public API ([#23943](https://github.com/angular/angular/issues/23943)) ([26fbf1d](https://github.com/angular/angular/commit/26fbf1d))
* **platform-browser:** allow lazy-loading HammerJS ([#23906](https://github.com/angular/angular/issues/23906)) ([313bdce](https://github.com/angular/angular/commit/313bdce))
@ -247,7 +254,6 @@ To learn about the release highlights and our new CLI-powered update workflow fo
* **animations:** only use the WA-polyfill alongside AnimationBuilder ([#22143](https://github.com/angular/angular/issues/22143)) ([b2f366b](https://github.com/angular/angular/commit/b2f366b)), closes [#17496](https://github.com/angular/angular/issues/17496)
* **animations:** expose `element` and `params` within transition matchers ([#22693](https://github.com/angular/angular/issues/22693)) ([58b94e6](https://github.com/angular/angular/commit/58b94e6))
* **common:** better error message when non-template element used in NgIf ([#22274](https://github.com/angular/angular/issues/22274)) ([67cf11d](https://github.com/angular/angular/commit/67cf11d)), closes [#16410](https://github.com/angular/angular/issues/16410)
* **common:** better error message when non-template element used in NgIf ([#22274](https://github.com/angular/angular/issues/22274)) ([67cf11d](https://github.com/angular/angular/commit/67cf11d)), closes [#16410](https://github.com/angular/angular/issues/16410)
* **common:** export functions to format numbers, percents, currencies & dates ([#22423](https://github.com/angular/angular/issues/22423)) ([4180912](https://github.com/angular/angular/commit/4180912)), closes [#20536](https://github.com/angular/angular/issues/20536)
* **compiler:** lower @NgModule ids if needed ([#23031](https://github.com/angular/angular/issues/23031)) ([bd024c0](https://github.com/angular/angular/commit/bd024c0))
* **compiler:** implement "enableIvy" compiler option ([#21427](https://github.com/angular/angular/issues/21427)) ([64d16de](https://github.com/angular/angular/commit/64d16de))
@ -287,7 +293,6 @@ To learn about the release highlights and our new CLI-powered update workflow fo
* **animations:** report correct totalTime value even during noOp animations ([#22225](https://github.com/angular/angular/issues/22225)) ([e1bf067](https://github.com/angular/angular/commit/e1bf067))
* **animations:** avoid animation insertions during router back/refresh ([#21977](https://github.com/angular/angular/issues/21977)) ([f88fba0](https://github.com/angular/angular/commit/f88fba0)), closes [#19712](https://github.com/angular/angular/issues/19712)
* **animations:** treat numeric state name values as strings ([#22923](https://github.com/angular/angular/issues/22923)) ([e5e1b0d](https://github.com/angular/angular/commit/e5e1b0d))
* **animations:** report correct totalTime value even during noOp animations ([#22225](https://github.com/angular/angular/issues/22225)) ([e1bf067](https://github.com/angular/angular/commit/e1bf067))
* **animations:** fix increment/decrement aliases example ([#18323](https://github.com/angular/angular/issues/18323)) ([d2aa8ac](https://github.com/angular/angular/commit/d2aa8ac))
* **common:** NgClass should properly take className changes into account ([#21937](https://github.com/angular/angular/issues/21937)) ([4a42669](https://github.com/angular/angular/commit/4a42669)), closes [#21932](https://github.com/angular/angular/issues/21932)
* **common:** fix the titlecase pipe ([#22600](https://github.com/angular/angular/issues/22600)) ([7966744](https://github.com/angular/angular/commit/7966744))

View File

@ -6,9 +6,16 @@ workspace(name = "angular")
http_archive(
name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
strip_prefix = "rules_nodejs-0.10.1",
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.2.zip"],
strip_prefix = "rules_nodejs-0.11.2",
sha256 = "c00d5381adeefb56e0ef959a7b168cae628535dab933cfad1c2cd1870cd7c9de",
)
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
strip_prefix = "bazel-skylib-0.3.1",
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
)
http_archive(
@ -20,9 +27,9 @@ http_archive(
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.3.zip",
strip_prefix = "rules_typescript-0.15.3",
sha256 = "a2b26ac3fc13036011196063db1bf7f1eae81334449201dc28087ebfa3708c99",
url = "https://github.com/bazelbuild/rules_typescript/archive/1d9a4b0087f307e31af91e2b221a6447288994c6.zip",
strip_prefix = "rules_typescript-1d9a4b0087f307e31af91e2b221a6447288994c6",
sha256 = "e17ac3f33d5d3cd2a0c385c4fd28b814d0ad46c6c67ccaef97160be99d7a24eb",
)
http_archive(
@ -71,6 +78,22 @@ http_archive(
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
)
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
# Point to the integration test workspace just so that Bazel doesn't descend into it
# when expanding the //... pattern
local_repository(
name = "bazel_integration_test",
path = "integration/bazel",
)
#
# Load and install our dependencies downloaded above.
#
@ -100,26 +123,10 @@ load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
ts_setup_workspace()
load("//packages/bazel/src:ng_setup_workspace.bzl", "ng_setup_workspace")
load("@angular//:index.bzl", "ng_setup_workspace")
ng_setup_workspace()
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
# Point to the integration test workspace just so that Bazel doesn't descend into it
# when expanding the //... pattern
local_repository(
name = "bazel_integration_test",
path = "integration/bazel",
)
#
# Ask Bazel to manage these toolchain dependencies for us.
# Bazel will run `yarn install` when one of these toolchains is requested during

View File

@ -43,6 +43,17 @@ Here are the most important tasks you might need to use:
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
## Developing on Windows
The `packages/` directory may contain Linux-specific symlinks, which are not recognized by Windows.
These unresolved links cause the docs generation process to fail because it cannot locate certain files.
> Hint: The following steps require administration rights or [Windows Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) enabled!
To fix this problem, run `scripts/windows/create-symlinks.sh`. This command creates temporary files where the symlinks used to be. Make sure not to commit those files with your documentation changes.
When you are done making and testing your documentation changes, you can restore the original symlinks and delete the temporary files by running `scripts/windows/remove-symlinks.sh`.
It's necessary to remove the temporary files, because otherwise they're displayed as local changes in your git working copy and certain operations are blocked.
## Using ServiceWorker locally
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which

View File

@ -1,26 +1,38 @@
import { ReflectiveInjector } from '@angular/core';
import { Injector } from '@angular/core';
import { Car, Engine, Tires } from './car';
import { Logger } from '../logger.service';
// #docregion injector
export function useInjector() {
let injector: ReflectiveInjector;
let injector: Injector;
// #enddocregion injector
/*
// #docregion injector-no-new
// Cannot instantiate an ReflectiveInjector like this!
let injector = new ReflectiveInjector([Car, Engine, Tires]);
// Cannot instantiate an Injector like this!
let injector = new Injector([
{ provide: Car, deps: [Engine, Tires] },
{ provide: Engine, deps: [] },
{ provide: Tires, deps: [] }
]);
// #enddocregion injector-no-new
*/
// #docregion injector, injector-create-and-call
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]);
injector = Injector.create({
providers: [
{ provide: Car, deps: [Engine, Tires] },
{ provide: Engine, deps: [] },
{ provide: Tires, deps: [] }
]
});
// #docregion injector-call
let car = injector.get(Car);
// #enddocregion injector-call, injector-create-and-call
car.description = 'Injector';
injector = ReflectiveInjector.resolveAndCreate([Logger]);
injector = Injector.create({
providers: [{ provide: Logger, deps: [] }]
});
let logger = injector.get(Logger);
logger.log('Injector car.drive() said: ' + car.drive());
return car;

View File

@ -0,0 +1,9 @@
{
"description": "Angular Elements",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1].*"
],
"tags":["cookbook"]
}

View File

@ -1,15 +1,14 @@
// #docplaster
// #docregion app-module
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
// import the feature module here so you can add it to the imports array below
import { CustomerDashboardModule } from './customer-dashboard/customer-dashboard.module';
@NgModule({
declarations: [
AppComponent
@ -17,7 +16,7 @@ import { CustomerDashboardModule } from './customer-dashboard/customer-dashboard
imports: [
BrowserModule,
FormsModule,
HttpModule,
HttpClientModule,
CustomerDashboardModule // add the feature module here
],
providers: [],

View File

@ -1,9 +1,9 @@
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
@ -13,7 +13,7 @@ import { AppComponent } from './app.component';
imports: [
BrowserModule,
FormsModule,
HttpModule,
HttpClientModule,
AppRoutingModule
],
providers: [],

View File

@ -1,26 +1,21 @@
// #docregion
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import {
FlyingHeroesComponent,
FlyingHeroesImpureComponent
} from './flying-heroes.component';
import { ExponentialStrengthPipe } from './exponential-strength.pipe';
import { FetchJsonPipe } from './fetch-json.pipe';
import { FlyingHeroesComponent, FlyingHeroesImpureComponent } from './flying-heroes.component';
import { FlyingHeroesImpurePipe, FlyingHeroesPipe } from './flying-heroes.pipe';
import { HeroAsyncMessageComponent } from './hero-async-message.component';
import { HeroBirthdayComponent } from './hero-birthday1.component';
import { HeroBirthday2Component } from './hero-birthday2.component';
import { HeroListComponent } from './hero-list.component';
import { PowerBoosterComponent } from './power-booster.component';
import { PowerBoostCalculatorComponent } from './power-boost-calculator.component';
import {
FlyingHeroesPipe,
FlyingHeroesImpurePipe
} from './flying-heroes.pipe';
import { FetchJsonPipe } from './fetch-json.pipe';
import { ExponentialStrengthPipe } from './exponential-strength.pipe';
import { PowerBoosterComponent } from './power-booster.component';
@NgModule({
imports: [
@ -43,6 +38,6 @@ import { ExponentialStrengthPipe } from './exponential-strength.pipe';
FetchJsonPipe,
ExponentialStrengthPipe
],
bootstrap: [ AppComponent ]
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -1,13 +1,14 @@
// #docregion
import { Pipe, PipeTransform } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';
// #docregion pipe-metadata
@Pipe({
name: 'fetch',
pure: false
})
// #enddocregion pipe-metadata
export class FetchJsonPipe implements PipeTransform {
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';
@ -17,7 +18,7 @@ export class FetchJsonPipe implements PipeTransform {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe( result => this.cachedData = result );
this.http.get(url).subscribe(result => this.cachedData = result);
}
return this.cachedData;

View File

@ -19,7 +19,7 @@ If you use the CLI to generate an app, the default `AppModule` is as follows:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@ -31,7 +31,7 @@ import { AppComponent } from './app.component';
imports: [
BrowserModule,
FormsModule,
HttpModule
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
@ -138,7 +138,7 @@ It tells Angular about other NgModules that this particular module needs to func
This list of modules are those that export components, directives, or pipes
that the component templates in this module reference. In this case, the component is
`AppComponent`, which references components, directives, or pipes in `BrowserModule`,
`FormsModule`, or `HttpModule`.
`FormsModule`, or `HttpClientModule`.
A component template can reference another component, directive,
or pipe when the referenced class is declared in this module or
the class was imported from another module.

View File

@ -44,6 +44,9 @@ is available to <code>declarations</code> of this module.</p>
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
</td>
</tr><tr>
<td><code><b>entryComponents:</b> [SomeComponent, OtherComponent]</code></td>
<td><p>List of components not referenced in any reachable template, for example dynamically created from code.</p></td>
</tr><tr>
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
</td>

View File

@ -692,7 +692,7 @@ If the factory function needs access to other DI tokens, it can use the inject f
const TOKEN =
new InjectionToken('tree-shakeable token',
{ providedIn: 'root', factory: () =>
new AppConfig(inject(Parameter1), inject(Paremeter2)), });
new AppConfig(inject(Parameter1), inject(Parameter2)), });
</code-example>
{@a optional}

View File

@ -3,25 +3,25 @@
_Angular elements_ are Angular components packaged as _custom elements_, a web standard for defining new HTML elements in a framework-agnostic way.
[Custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) are a Web Platform feature currently supported by Chrome, Opera, and Safari, and available in other browsers through polyfills (see [Browser Support](#browser-support)).
A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code.
A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code.
The browser maintains a `CustomElementRegistry` of defined custom elements (also called Web Components), which maps an instantiable JavaScript class to an HTML tag.
The `@angular/elements` package exports a `createCustomElement()` API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.
The `@angular/elements` package exports a `createCustomElement()` API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.
Transforming a component to a custom element makes all of the required Angular infrastructure available to the browser.
Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding native HTML equivalents.
Transforming a component to a custom element makes all of the required Angular infrastructure available to the browser.
Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding native HTML equivalents.
<div class="alert is-helpful">
We are working on custom elements that can be used by web apps built on other frameworks.
A minimal, self-contained version of the Angular framework will be injected as a service to support the component's change-detection and data-binding functionality.
We are working on custom elements that can be used by web apps built on other frameworks.
A minimal, self-contained version of the Angular framework will be injected as a service to support the component's change-detection and data-binding functionality.
For more about the direction of development, check out this [video presentation](https://www.youtube.com/watch?v=Z1gLFPLVJjY&t=4s).
</div>
## Using custom elements
Custom elements bootstrap themselves - they start automatically when they are added to the DOM, and are automatically destroyed when removed from the DOM. Once a custom element is added to the DOM for any page, it looks and behaves like any other HTML element, and does not require any special knowledge of Angular terms or usage conventions.
Custom elements bootstrap themselves - they start automatically when they are added to the DOM, and are automatically destroyed when removed from the DOM. Once a custom element is added to the DOM for any page, it looks and behaves like any other HTML element, and does not require any special knowledge of Angular terms or usage conventions.
- <b>Easy dynamic content in an Angular app</b>
@ -33,14 +33,14 @@ Custom elements bootstrap themselves - they start automatically when they are ad
### How it works
Use the `createCustomElement()` function to convert a component into a class that can be registered with the browser as a custom element.
After you register your configured class with the browser's custom-element registry, you can use the new element just like a built-in HTML element in content that you add directly into the DOM:
Use the `createCustomElement()` function to convert a component into a class that can be registered with the browser as a custom element.
After you register your configured class with the browser's custom-element registry, you can use the new element just like a built-in HTML element in content that you add directly into the DOM:
```
<my-popup message="Use Angular!"></my-popup>
```
When your custom element is placed on a page, the browser creates an instance of the registered class and adds it to the DOM. The content is provided by the component's template, which uses Angular template syntax, and is rendered using the component and DOM data. Input properties in the component correspond to input attributes for the element.
When your custom element is placed on a page, the browser creates an instance of the registered class and adds it to the DOM. The content is provided by the component's template, which uses Angular template syntax, and is rendered using the component and DOM data. Input properties in the component correspond to input attributes for the element.
<figure>
@ -52,25 +52,25 @@ When your custom element is placed on a page, the browser creates an instance of
## Transforming components to custom elements
Angular provides the `createCustomElement()` function for converting an Angular component,
together with its dependencies, to a custom element. The function collects the component's
observable properties, along with the Angular functionality the browser needs to
create and destroy instances, and to detect and respond to changes.
Angular provides the `createCustomElement()` function for converting an Angular component,
together with its dependencies, to a custom element. The function collects the component's
observable properties, along with the Angular functionality the browser needs to
create and destroy instances, and to detect and respond to changes.
The conversion process implements the `NgElementConstructor` interface, and creates a
constructor class that is configured to produce a self-bootstrapping instance of your component.
The conversion process implements the `NgElementConstructor` interface, and creates a
constructor class that is configured to produce a self-bootstrapping instance of your component.
Use a JavaScript function, `customElements.define()`, to register the configured constructor
and its associated custom-element tag with the browser's `CustomElementRegistry`.
Use a JavaScript function, `customElements.define()`, to register the configured constructor
and its associated custom-element tag with the browser's `CustomElementRegistry`.
When the browser encounters the tag for the registered element, it uses the constructor to create a custom-element instance.
<figure>
<img src="generated/images/guide/elements/createElement.png" alt="Transform a component to a custom element" class="left">
<img src="generated/images/guide/elements/createElement.png" alt="Transform a component to a custom element" class="left">
</figure>
### Mapping
### Mapping
A custom element _hosts_ an Angular component, providing a bridge between the data and logic defined in the component and standard DOM APIs. Component properties and logic maps directly into HTML attributes and the browser's event system.
@ -80,13 +80,13 @@ A custom element _hosts_ an Angular component, providing a bridge between the da
For more information, see Web Component documentation for [Creating custom events](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events#Creating_custom_events).
{@a browser-support}
## Browser support for custom elements
The recently-developed [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) Web Platform feature is currently supported natively in a number of browsers. Support is pending or planned in other browsers.
The recently-developed [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) Web Platform feature is currently supported natively in a number of browsers. Support is pending or planned in other browsers.
<table>
<tr>
@ -111,7 +111,7 @@ The recently-developed [custom elements](https://developer.mozilla.org/en-US/doc
</tr>
<tr>
<td>Edge</td>
<td>Working on an implementation. <br>
<td>Working on an implementation. <br>
</td>
</tr>
@ -120,7 +120,7 @@ The recently-developed [custom elements](https://developer.mozilla.org/en-US/doc
In browsers that support Custom Elements natively, the specification requires developers use ES2015 classes to define Custom Elements - developers can opt-in to this by setting the `target: "es2015"` property in their project's `tsconfig.json`. As Custom Element and ES2015 support may not be available in all browsers, developers can instead choose to use a polyfill to support older browsers and ES5 code.
Use the [Angular CLI](https://cli.angular.io/) to automatically set up your project with the correct polyfill: `ng add @angular/elements --name=*your_project_name*`.
- For more information about polyfills, see [polyfill documentation](https://www.webcomponents.org/polyfills).
- For more information about polyfills, see [polyfill documentation](https://www.webcomponents.org/polyfills).
- For more information about Angular browser support, see [Browser Support](guide/browser-support).
@ -131,12 +131,12 @@ Previously, when you wanted to add a component to an app at runtime, you had to
Using an Angular custom element makes the process much simpler and more transparent, by providing all of the infrastructure and framework automatically&mdash;all you have to do is define the kind of event handling you want. (You do still have to exclude the component from compilation, if you are not going to use it in your app.)
The Popup Service example app defines a component that you can either load dynamically or convert to a custom element.
The Popup Service example app (shown below) defines a component that you can either load dynamically or convert to a custom element.
- `popup.component.ts` defines a simple pop-up element that displays an input message, with some animation and styling.
- `popup.component.ts` defines a simple pop-up element that displays an input message, with some animation and styling.
- `popup.service.ts` creates an injectable service that provides two different ways to invoke the PopupComponent; as a dynamic component, or as a custom element. Notice how much more setup is required for the dynamic-loading method.
- `app.module.ts` adds the PopupComponent in the module's `entryComponents` list, to exclude it from compilation and avoid startup warnings or errors.
- `app.component.ts` defines the app's root component, which uses the PopupService to add the pop-up to the DOM at run time. When the app runs, the root component's constructor converts PopupComponent to a custom element.
- `app.component.ts` defines the app's root component, which uses the PopupService to add the pop-up to the DOM at run time. When the app runs, the root component's constructor converts PopupComponent to a custom element.
For comparison, the demo shows both methods. One button adds the popup using the dynamic-loading method, and the other uses the custom element. You can see that the result is the same; only the preparation is different.
@ -158,3 +158,59 @@ For comparison, the demo shows both methods. One button adds the popup using the
</code-pane>
</code-tabs>
<!--
StackBlitz transpiles code to ES5. The live example will not work without a polyfill.
Only offer a `.zip` to download for now.
-->
You can download the full code for the example <live-example downloadOnly>here</live-example>.
## Typings for custom elements
Generic DOM APIs, such as `document.createElement()` or `document.querySelector()`, return an element type that is appropriate for the specified arguments. For example, calling `document.createElement('a')` will return an `HTMLAnchorElement`, which TypeScript knows has an `href` property. Similarly, `document.createElement('div')` will return an `HTMLDivElement`, which TypeScript knows has no `href` property.
When called with unknown elements, such as a custom element name (`popup-element` in our example), the methods will return a generic type, such as `HTMLELement`, since TypeScript can't infer the correct type of the returned element.
Custom elements created with Angular extend `NgElement` (which in turn extends `HTMLElement`). Additionally, these custom elements will have a property for each input of the corresponding component. For example, our `popup-element` will have a `message` property of type `string`.
There are a few options if you want to get correct types for your custom elements. Let's assume you create a `my-dialog` custom element based on the following component:
```ts
@Component(...)
class MyDialog {
@Input() content: string;
}
```
The most straight forward way to get accurate typings is to cast the return value of the relevant DOM methods to the correct type. For that, you can use the `NgElement` and `WithProperties` types (both exported from `@angular/elements`):
```ts
const aDialog = document.createElement('my-dialog') as NgElement & WithProperties<{content: string}>;
aDialog.content = 'Hello, world!';
aDialog.content = 123; // <-- ERROR: TypeScript knows this should be a string.
aDialog.body = 'News'; // <-- ERROR: TypeScript knows there is no `body` property on `aDialog`.
```
This is a good way to quickly get TypeScript features, such as type checking and autocomplete support, for you custom element. But it can get cumbersome if you need it in several places, because you have to cast the return type on every occurrence.
An alternative way, that only requires defining each custom element's type once, is augmenting the `HTMLELementTagNameMap`, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as `document.createElement()`, `document.querySelector()`, etc.):
```ts
declare global {
interface HTMLElementTagNameMap {
'my-dialog': NgElement & WithProperties<{content: string}>;
'my-other-element': NgElement & WithProperties<{foo: 'bar'}>;
...
}
}
```
Now, TypeScript can infer the correct type the same way it does for built-in elements:
```ts
document.createElement('div') //--> HTMLDivElement (built-in element)
document.querySelector('foo') //--> Element (unknown element)
document.createElement('my-dialog') //--> NgElement & WithProperties<{content: string}> (custom element)
document.querySelector('my-other-element') //--> NgElement & WithProperties<{foo: 'bar'}> (custom element)
```

View File

@ -36,7 +36,7 @@ The following is an example of specifying a bootstrapped component,
imports: [
BrowserModule,
FormsModule,
HttpModule,
HttpClientModule,
AppRoutingModule
],
providers: [],

View File

@ -13,7 +13,7 @@ The sample application and all tests in this guide are available for inspection
## Setup
The Angular CLI downloads and install everything you need to test an Angular application with the [Jasmine test framework](http://jasmine.github.io/2.4/introduction.html).
The Angular CLI downloads and install everything you need to test an Angular application with the [Jasmine test framework](https://jasmine.github.io/).
The project you create with the CLI is immediately ready to test.
Just run this one CLI command:
@ -920,7 +920,7 @@ so it is safe to call `TestBed.get()` as follows:
<div class="alert is-helpful">
For a use case in which `TestBed.get()` does not work,
see the section [_Override a component's providers_](#component-override), which
see the [_Override component providers_](#component-override) section that
explains when and why you must get the service from the component's injector instead.
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -653,5 +653,15 @@
"twitter": "elanathellama",
"bio": "Elana is a Developer Relations intern on the Angular team at Google. She is working on migration paths from AngularJS to Angular and would love to chat about your experience with upgrading.",
"group": "Angular"
},
"kevinyang": {
"name": "Kevin Yang",
"picture": "kevinyang.jpg",
"twitter": "chgc",
"website": "https://blog.kevinyang.net/",
"bio":
"Kevin is a Angular Taiwan, Angular Girls Taiwan community organzier. He loves sharing knowledge with other developers through blogging, speaking, workshops.",
"group": "GDE"
}
}

View File

@ -13,12 +13,6 @@
</tr>
</thead>
<tbody>
<!-- ReactiveConf -->
<tr>
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
<td>Bratislava, Slovakia</td>
<td>October 25, 2017</td>
</tr>
<!-- AngularConnect-->
<tr>
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
@ -61,6 +55,12 @@
<td>Melbourne, Australia</td>
<td>Jun 22, 2018</td>
</tr>
<!-- ReactiveConf -->
<tr>
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
<td>Bratislava, Slovakia</td>
<td>October 29-31, 2018</td>
</tr>
<!-- AngularConnect-->
<tr>
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>

View File

@ -181,7 +181,7 @@
"rev": true,
"title": "Amexio Canvas Web Based Drag and Drop IDE by MetaMagic",
"url": "https://amexio.tech/"
}
}
}
},
"Tooling": {
@ -317,7 +317,7 @@
"url": "http://www.primefaces.org/primeng/"
},
"a3b": {
"desc": "One of the first major UI frameworks to support Angular",
"desc": "A professional grade library of Angular UI components written in TypeScript that includes our Data Grid, TreeView, Charts, Editors, DropDowns, DatePickers, and many more. Features include support for AOT compilation, Tree Shaking for high-performance, localization, and accessibility.",
"logo": "",
"rev": true,
"title": "Kendo UI",
@ -736,6 +736,12 @@
"rev": true,
"title": "Angular.Schule (German)",
"url": "https://angular.schule/"
},
"strbrw": {
"desc": "Angular and RxJS trainings, Code Reviews and consultancy. We help software engineers all over the world to create better web-applications...",
"rev": true,
"title": "StrongBrew",
"url": "https://strongbrew.io/"
}
}
}

View File

@ -89,15 +89,8 @@ Registering the provider in the `@Injectable` metadata also allows Angular to op
<div class="alert is-helpful">
If you need to, you can register providers at different levels:
in the `HeroesComponent`, in the `AppComponent`, in the `AppModule`.
For instance, you could have told the CLI to provide the service at the module level automatically by appending `--module=app`.
<code-example language="sh" class="code-shell">
ng generate service hero --module=app
</code-example>
To learn more about providers and injectors, see the [Dependency Injection guide](guide/dependency-injection).
To learn more about providers, see the [Providers section](guide/providers).
To learn more about injectors, see the [Dependency Injection guide](guide/dependency-injection).
</div>

View File

@ -250,7 +250,7 @@ There are three significant differences from `getHeroes()`.
## Update heroes
Editing a hero's name in the _hero detail_ view.
Edit a hero's name in the _hero detail_ view.
As you type, the hero name updates the heading at the top of the page.
But when you click the "go back button", the changes are lost.
@ -294,8 +294,7 @@ That header is in the `httpOptions` constant defined in the `HeroService`.
region="http-options">
</code-example>
Refresh the browser, change a hero name, save your change,
and click the "go back" button.
Refresh the browser, change a hero name and save your change. Navigating to the previous view is implemented in the `save()` method defined in `HeroDetailComponent`.
The hero now appears in the list with the changed name.
## Add a new hero
@ -440,7 +439,7 @@ Create a `HeroSearchComponent` with the CLI.
ng generate component hero-search
</code-example>
The CLI generates the three `HeroSearchComponent` and adds the component to the `AppModule' declarations
The CLI generates the three `HeroSearchComponent` files and adds the component to the `AppModule` declarations
Replace the generated `HeroSearchComponent` _template_ with a text box and a list of matching search results like this.

View File

@ -7,14 +7,15 @@
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
<mat-toolbar-row class="notification-container">
<aio-notification
icon="insert_comment"
iconLabel="Announcement"
buttonText="Learn More"
actionUrl="https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4"
notificationId="angular-v6-announcement"
expirationDate="2018-07-01"
[dismissOnContentClick]="true"
(dismissed)="notificationDismissed()">
Version 6 of Angular Now Available!
<a href="https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4">
<mat-icon class="icon" svgIcon="insert_comment" aria-label="Announcement"></mat-icon>
<span class="message">Version 6 of Angular Now Available!</span>
<span class="action-button">Learn More</span>
</a>
</aio-notification>
</mat-toolbar-row>
<mat-toolbar-row>

View File

@ -115,7 +115,6 @@ describe('ApiListComponent', () => {
component.filteredSections.subscribe(filtered => {
filtered = filtered.filter(s => s.items);
console.log(filtered);
expect(filtered.length).toBe(1, 'sections');
expect(filtered[0].name).toBe(section, 'section name');
const items = filtered[0].items!;

View File

@ -1,8 +1,6 @@
<a href="{{actionUrl}}" class="content" (click)="dismiss()">
<mat-icon class="icon" [svgIcon]="icon" [attr.aria-label]="iconLabel"></mat-icon>
<span class="message"><ng-content></ng-content></span>
<span class="action-button">{{buttonText}}</span>
</a>
<span class="content" (click)="contentClick()">
<ng-content></ng-content>
</span>
<button mat-icon-button class="close-button" aria-label="Close" (click)="dismiss()">
<mat-icon svgIcon="close" aria-label="Dismiss notification"></mat-icon>

View File

@ -30,37 +30,49 @@ describe('NotificationComponent', () => {
fixture.detectChanges();
}
it('should display the message', () => {
configTestingModule();
createComponent();
expect(fixture.nativeElement.innerHTML).toContain('Help Angular by taking a <strong>1 minute survey</strong>!');
describe('content projection', () => {
it('should display the message text', () => {
configTestingModule();
createComponent();
expect(fixture.nativeElement.innerHTML).toContain('Version 6 of Angular Now Available!');
});
it('should render HTML elements', () => {
configTestingModule();
createComponent();
const button = fixture.debugElement.query(By.css('.action-button'));
expect(button.nativeElement.textContent).toEqual('Learn More');
});
it('should process Angular directives', () => {
configTestingModule();
createComponent();
const badSpans = fixture.debugElement.queryAll(By.css('.bad'));
expect(badSpans.length).toEqual(0);
});
});
it('should display an icon', () => {
configTestingModule();
createComponent();
const iconElement = fixture.debugElement.query(By.css('.icon'));
expect(iconElement.properties['svgIcon']).toEqual('insert_comment');
expect(iconElement.attributes['aria-label']).toEqual('Survey');
});
it('should display a button', () => {
configTestingModule();
createComponent();
const button = fixture.debugElement.query(By.css('.action-button'));
expect(button.nativeElement.textContent).toEqual('Go to survey');
});
it('should call dismiss when the message link is clicked', () => {
it('should call dismiss() when the message link is clicked, if dismissOnContentClick is true', () => {
configTestingModule();
createComponent();
spyOn(component, 'dismiss');
fixture.debugElement.query(By.css('a')).triggerEventHandler('click', null);
fixture.detectChanges();
component.dismissOnContentClick = true;
const message: HTMLSpanElement = fixture.debugElement.query(By.css('.messageholder')).nativeElement;
message.click();
expect(component.dismiss).toHaveBeenCalled();
});
it('should call dismiss when the close button is clicked', () => {
it('should not call dismiss() when the message link is clicked, if dismissOnContentClick is false', () => {
configTestingModule();
createComponent();
spyOn(component, 'dismiss');
component.dismissOnContentClick = false;
const message: HTMLSpanElement = fixture.debugElement.query(By.css('.messageholder')).nativeElement;
message.click();
expect(component.dismiss).not.toHaveBeenCalled();
});
it('should call dismiss() when the close button is clicked', () => {
configTestingModule();
createComponent();
spyOn(component, 'dismiss');
@ -104,13 +116,15 @@ describe('NotificationComponent', () => {
@Component({
template: `
<aio-notification
icon="insert_comment"
iconLabel="Survey"
buttonText="Go to survey"
actionUrl="https://bit.ly/angular-survey-2018"
notificationId="survey-january-2018"
expirationDate="2018-01-22">
Help Angular by taking a <strong>1 minute survey</strong>!
<span class="messageholder">
<a href="https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4">
<span *ngIf="false" class="bad">This should not appear</span>
<span class="message">Version 6 of Angular Now Available!</span>
<span class="action-button">Learn More</span>
</a>
</span>
</aio-notification>`
})
class TestComponent {

View File

@ -22,10 +22,7 @@ const LOCAL_STORAGE_NAMESPACE = 'aio-notification/';
export class NotificationComponent implements OnInit {
private get localStorage() { return this.window.localStorage; }
@Input() icon: string;
@Input() iconLabel: string;
@Input() buttonText: string;
@Input() actionUrl: string;
@Input() dismissOnContentClick: boolean;
@Input() notificationId: string;
@Input() expirationDate: string;
@Output() dismissed = new EventEmitter();
@ -44,6 +41,12 @@ export class NotificationComponent implements OnInit {
this.showNotification = previouslyHidden || expired ? 'hide' : 'show';
}
contentClick() {
if (this.dismissOnContentClick) {
this.dismiss();
}
}
dismiss() {
this.localStorage.setItem(LOCAL_STORAGE_NAMESPACE + this.notificationId, 'hide');
this.showNotification = 'hide';

View File

@ -74,6 +74,13 @@ describe('GaService', () => {
});
});
describe('sendEvent', () => {
it('should send "event" with associated data', () => {
gaService.sendEvent('some source', 'some campaign', 'a label', 45);
expect(gaSpy).toHaveBeenCalledWith('send', 'event', 'some source', 'some campaign', 'a label', 45);
});
});
it('should support replacing the `window.ga` function', () => {
const gaSpy2 = jasmine.createSpy('new ga');
mockWindow.ga = gaSpy2;

View File

@ -29,6 +29,10 @@ export class GaService {
this.ga('send', 'pageview');
}
sendEvent(source: string, action: string, label?: string, value?: number) {
this.ga('send', 'event', source, action, label, value);
}
ga(...args: any[]) {
const gaFn = (this.window as any)['ga'];
if (gaFn) {

View File

@ -64,18 +64,22 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
margin: $hamburgerShownMargin;
padding: 0;
&:not(.starting) {
transition-duration: 0.4s;
transition-property: color, margin;
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
}
@media (min-width: 992px) {
// Hamburger hidden by default on large screens.
// (Will be shown per doc.)
margin: $hamburgerHiddenMargin;
}
@media (max-width: 480px) {
min-width: 15%;
}
&:not(.starting) {
transition-duration: 0.4s;
transition-property: color, margin;
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
}
&:hover {
color: $offwhite;
}
@ -92,6 +96,10 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
margin: 0 16px 0 0;
padding: 21px 0;
@media screen and (max-width: 480px) {
margin-right: 8px;
}
img {
position: relative;
margin-top: -21px;
@ -194,12 +202,17 @@ aio-search-box.search-container {
.toolbar-external-icons-container {
display: flex;
flex-direction: row;
height: 100%;
a {
display: flex;
align-items: center;
margin-left: 16px;
@media screen and (max-width: 480px) {
margin-left: 8px;
}
&:hover {
opacity: 0.8;
}

View File

@ -31,11 +31,14 @@ aio-notification {
}
.content {
display: flex;
max-width: calc(100% - #{$notificationHeight});
text-transform: none;
padding: 0;
> * {
display: flex;
}
.icon {
margin-right: 10px;
@media (max-width: 464px) {
@ -46,10 +49,10 @@ aio-notification {
.message {
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
}
.action-button {
margin-left: 10px;
background: $brightred;
border-radius: 15px;
text-transform: uppercase;

View File

@ -0,0 +1,21 @@
{
"scripts": [
{ "name": "ng", "command": "ng" },
{ "name": "build", "command": "ng build" },
{ "name": "start", "command": "ng serve" },
{ "name": "test", "command": "ng test" },
{ "name": "lint", "command": "ng lint" },
{ "name": "e2e", "command": "ng e2e" }
],
"dependencies": [
"@angular/elements"
],
"devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2",
"jasmine-spec-reporter",
"karma-coverage-istanbul-reporter",
"ts-node"
]
}

26
index.bzl Normal file
View File

@ -0,0 +1,26 @@
# 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
""" Public API surface is re-exported here.
This API is exported for users building angular from source in downstream
projects. The rules from packages/bazel are re-exported here as well
as the ng_setup_workspace repository rule needed when building angular
from source downstream. Alternately, this API is available from the
@angular/bazel npm package if the npm distribution of angular is
used in a downstream project.
"""
load("//packages/bazel:index.bzl",
_ng_module = "ng_module",
_ng_package = "ng_package",
_protractor_web_test = "protractor_web_test",
_protractor_web_test_suite = "protractor_web_test_suite")
load("//tools:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace")
ng_module = _ng_module
ng_package = _ng_package
protractor_web_test = _protractor_web_test
protractor_web_test_suite = _protractor_web_test_suite
ng_setup_workspace = _ng_setup_workspace

View File

@ -6,9 +6,16 @@ workspace(name = "bazel_integration_test")
http_archive(
name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
strip_prefix = "rules_nodejs-0.10.1",
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.2.zip"],
strip_prefix = "rules_nodejs-0.11.2",
sha256 = "c00d5381adeefb56e0ef959a7b168cae628535dab933cfad1c2cd1870cd7c9de",
)
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
strip_prefix = "bazel-skylib-0.3.1",
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
)
http_archive(
@ -20,9 +27,9 @@ http_archive(
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
strip_prefix = "rules_typescript-0.15.0",
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
url = "https://github.com/bazelbuild/rules_typescript/archive/1d9a4b0087f307e31af91e2b221a6447288994c6.zip",
strip_prefix = "rules_typescript-1d9a4b0087f307e31af91e2b221a6447288994c6",
sha256 = "e17ac3f33d5d3cd2a0c385c4fd28b814d0ad46c6c67ccaef97160be99d7a24eb",
)
http_archive(
@ -38,6 +45,20 @@ http_archive(
sha256 = "b243c4d64f054c174051785862ab079050d90b37a1cef7da93821c6981cb9ad4",
)
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
)
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
#
# Load and install our dependencies downloaded above.
#
@ -68,24 +89,6 @@ load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
sass_repositories()
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
)
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
#
# Load and install our dependencies from local repositories
#
load("@angular//:index.bzl", "ng_setup_workspace")
ng_setup_workspace()

View File

@ -32,6 +32,7 @@ ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(["*.spec.ts"]),
tsconfig = "//src:tsconfig.json",
deps = [":hello-world"],
)

View File

@ -5,6 +5,7 @@ ts_library(
name = "e2e",
testonly = 1,
srcs = ["app.spec.ts"],
tsconfig = ":tsconfig.json",
)
ts_library(

View File

@ -3,47 +3,50 @@
"@angular/animations@file:../../dist/packages-dist/animations":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
"@angular/bazel@file:../../dist/packages-dist/bazel":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
"@bazel/typescript" "^0.15.0"
"@types/node" "6.0.84"
protobufjs "5.0.0"
"@angular/common@file:../../dist/packages-dist/common":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
chokidar "^1.4.2"
convert-source-map "^1.5.1"
magic-string "^0.25.0"
minimist "^1.2.0"
reflect-metadata "^0.1.2"
tsickle "^0.30.0"
source-map "^0.6.1"
tsickle "^0.32.1"
"@angular/compiler@file:../../dist/packages-dist/compiler":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
"@angular/core@file:../../dist/packages-dist/core":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
version "6.1.0-beta.3"
version "6.1.0"
dependencies:
tslib "^1.9.0"
@ -316,6 +319,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
convert-source-map@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -826,6 +833,12 @@ long@~3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
magic-string@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.0.tgz#1f3696f9931ff0a1ed4c132250529e19cad6759b"
dependencies:
sourcemap-codec "^1.4.1"
math-random@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
@ -1305,10 +1318,14 @@ source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
source-map@^0.6.0:
source-map@^0.6.0, source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
sourcemap-codec@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2"
sshpk@^1.7.0:
version "1.14.2"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
@ -1393,9 +1410,9 @@ tough-cookie@~2.3.3:
dependencies:
punycode "^1.4.1"
tsickle@^0.30.0:
version "0.30.0"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.30.0.tgz#7941146ae92933854a8742fa1047606c4536649b"
tsickle@^0.32.1:
version "0.32.1"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.32.1.tgz#f16e94ba80b32fc9ebe320dc94fbc2ca7f3521a5"
dependencies:
jasmine-diff "^0.1.3"
minimist "^1.2.0"

View File

@ -10,7 +10,7 @@
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"google-closure-compiler": "20180319.0.0",
"google-closure-compiler": "20180716.0.0",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "file:../../node_modules/typescript",
"zone.js": "file:../../node_modules/zone.js"

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,8 @@ module.exports = function(config) {
// Serve AngularJS for `ngUpgrade` testing.
{pattern: 'node_modules/angular-1.5/angular.js', included: false, watched: false},
{pattern: 'node_modules/angular-mocks-1.5/angular-mocks.js', included: false, watched: false},
{pattern: 'node_modules/angular-1.6/angular.js', included: false, watched: false},
{pattern: 'node_modules/angular-mocks-1.6/angular-mocks.js', included: false, watched: false},
{pattern: 'node_modules/angular/angular.js', included: false, watched: false},
{pattern: 'node_modules/angular-mocks/angular-mocks.js', included: false, watched: false},
@ -110,6 +112,7 @@ module.exports = function(config) {
// don't need this entire config file.
proxies: {
'/base/angular/': '/base/',
'/base/angular_deps/': '/base/',
},
reporters: ['dots'],

View File

@ -29,7 +29,7 @@ describe('largeform benchmark perf', () => {
[CreateAndDestroyWorker].forEach((worker) => {
describe(worker.id, () => {
it('should run for ng2', (done) => {
it('should run for ng2', done => {
runLargeFormBenchmark({
id: `largeform.ng2.${worker.id}`,
url: 'all/benchmarks/src/largeform/ng2/index.html',

View File

@ -40,7 +40,7 @@ describe('largetable benchmark perf', () => {
[CreateOnlyWorker, CreateAndDestroyWorker, UpdateWorker].forEach((worker) => {
describe(worker.id, () => {
it('should run for ng2', (done) => {
it('should run for ng2', done => {
runTableBenchmark({
id: `largeTable.ng2.${worker.id}`,
url: 'all/benchmarks/src/largetable/ng2/index.html',
@ -48,7 +48,7 @@ describe('largetable benchmark perf', () => {
}).then(done, done.fail);
});
it('should run for ng2 with ngSwitch', (done) => {
it('should run for ng2 with ngSwitch', done => {
runTableBenchmark({
id: `largeTable.ng2_switch.${worker.id}`,
url: 'all/benchmarks/src/largetable/ng2_switch/index.html',
@ -56,7 +56,7 @@ describe('largetable benchmark perf', () => {
}).then(done, done.fail);
});
it('should run for render3', (done) => {
it('should run for render3', done => {
runTableBenchmark({
id: `largeTable.render3.${worker.id}`,
url: 'all/benchmarks/src/largetable/render3/index.html',
@ -65,7 +65,7 @@ describe('largetable benchmark perf', () => {
}).then(done, done.fail);
});
it('should run for iv', (done) => {
it('should run for iv', done => {
runTableBenchmark({
id: `largeTable.iv.${worker.id}`,
url: 'all/benchmarks/src/largetable/iv/index.html',
@ -74,7 +74,7 @@ describe('largetable benchmark perf', () => {
}).then(done, done.fail);
});
it('should run for the baseline', (done) => {
it('should run for the baseline', done => {
runTableBenchmark({
id: `largeTable.baseline.${worker.id}`,
url: 'all/benchmarks/src/largetable/baseline/index.html',
@ -83,7 +83,7 @@ describe('largetable benchmark perf', () => {
}).then(done, done.fail);
});
it('should run for incremental-dom', (done) => {
it('should run for incremental-dom', done => {
runTableBenchmark({
id: `largeTable.incremental_dom.${worker.id}`,
url: 'all/benchmarks/src/largetable/incremental_dom/index.html',

View File

@ -24,7 +24,7 @@ describe('tree benchmark perf', () => {
Benchmarks.forEach(benchmark => {
describe(benchmark.id, () => {
// This is actually a destroyOnly benchmark
it('should work for createOnly', (done) => {
it('should work for createOnly', done => {
runTreeBenchmark({
id: 'createOnly',
benchmark,
@ -33,7 +33,7 @@ describe('tree benchmark perf', () => {
}).then(done, done.fail);
});
it('should work for createOnlyForReal', (done) => {
it('should work for createOnlyForReal', done => {
runTreeBenchmark({
id: 'createOnlyForReal',
benchmark,
@ -42,7 +42,7 @@ describe('tree benchmark perf', () => {
}).then(done, done.fail);
});
it('should work for createDestroy', (done) => {
it('should work for createDestroy', done => {
runTreeBenchmark({
id: 'createDestroy',
benchmark,
@ -53,13 +53,13 @@ describe('tree benchmark perf', () => {
}).then(done, done.fail);
});
it('should work for update', (done) => {
it('should work for update', done => {
runTreeBenchmark({id: 'update', benchmark, work: () => $(CreateBtn).click()})
.then(done, done.fail);
});
if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) {
it('should work for detectChanges', (done) => {
it('should work for detectChanges', done => {
runTreeBenchmark({
id: 'detectChanges',
benchmark,

View File

@ -41,7 +41,7 @@ describe('largetable benchmark perf', () => {
[CreateOnlyWorker, CreateAndDestroyWorker, UpdateWorker].forEach((worker) => {
describe(worker.id, () => {
it('should run for render3', (done) => {
it('should run for render3', done => {
runTableBenchmark({
id: `largeTable.render3.${worker.id}`,
url: 'index.html',

10
modules/types.d.ts vendored
View File

@ -8,11 +8,11 @@
// This file contains all ambient imports needed to compile the modules/ source code
/// <reference path="../node_modules/@types/hammerjs/index.d.ts" />
/// <reference path="../node_modules/@types/jasmine/index.d.ts" />
/// <reference path="../node_modules/@types/jasminewd2/index.d.ts" />
/// <reference path="../node_modules/@types/node/index.d.ts" />
/// <reference path="../node_modules/zone.js/dist/zone.js.d.ts" />
/// <reference types="hammerjs" />
/// <reference types="jasmine" />
/// <reference types="jasminewd2" />
/// <reference types="node" />
/// <reference types="zone.js" />
/// <reference path="../tools/types-ext/jasminewd2.d.ts" />
/// <reference path="./es6-subset.d.ts" />
/// <reference path="./system.d.ts" />

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "6.1.0",
"version": "7.0.0-beta.0",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -21,7 +21,9 @@
"prebuildifier": "bazel build --noshow_progress @com_github_bazelbuild_buildtools//buildifier",
"buildifier": "find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier",
"preinstall": "node tools/yarn/check-yarn.js",
"postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js",
"postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js && yarn patch-types",
"//patch-types": "work-around for issue https://github.com/angular/angular/issues/25051",
"patch-types": "node -e \"var sh = require('shelljs'); sh.set('-e'); sh.mkdir('-p', 'node_modules/@types/zone.js'); sh.cp('-f', 'node_modules/zone.js/dist/zone.js.d.ts', 'node_modules/@types/zone.js/index.d.ts')\"",
"update-webdriver": "webdriver-manager update --gecko false $CHROMEDRIVER_VERSION_ARG",
"check-env": "gulp check-env",
"commitmsg": "node ./scripts/git/commit-msg.js"
@ -44,22 +46,26 @@
"@types/base64-js": "1.2.5",
"@types/chai": "^4.1.2",
"@types/chokidar": "1.7.3",
"@types/convert-source-map": "^1.5.1",
"@types/diff": "^3.2.2",
"@types/fs-extra": "4.0.2",
"@types/hammerjs": "2.0.35",
"@types/jasmine": "^2.8.8",
"@types/jasminewd2": "^2.0.3",
"@types/minimist": "^1.2.0",
"@types/mock-fs": "^3.6.30",
"@types/node": "6.0.88",
"@types/selenium-webdriver": "3.0.7",
"@types/shelljs": "^0.7.8",
"@types/source-map": "^0.5.1",
"@types/systemjs": "0.19.32",
"@webcomponents/custom-elements": "^1.0.4",
"angular": "npm:angular@1.6",
"angular": "npm:angular@1.7",
"angular-1.5": "npm:angular@1.5",
"angular-mocks": "npm:angular-mocks@1.6",
"angular-1.6": "npm:angular@1.6",
"angular-mocks": "npm:angular-mocks@1.7",
"angular-mocks-1.5": "npm:angular-mocks@1.5",
"angular-mocks-1.6": "npm:angular-mocks@1.6",
"base64-js": "1.2.1",
"bower": "1.8.2",
"browserstacktunnel-wrapper": "2.0.1",
@ -70,6 +76,7 @@
"cldr-data-downloader": "0.3.2",
"cldrjs": "0.5.0",
"conventional-changelog": "1.1.0",
"convert-source-map": "^1.5.1",
"cors": "2.8.4",
"diff": "^3.5.0",
"domino": "2.0.1",
@ -95,7 +102,9 @@
"karma-sauce-launcher": "^1.2.0",
"karma-sourcemap-loader": "^0.3.7",
"madge": "0.5.0",
"magic-string": "^0.25.0",
"minimist": "1.2.0",
"mock-fs": "^4.5.0",
"mutation-observer": "^1.0.3",
"node-uuid": "1.4.8",
"protobufjs": "5.0.0",
@ -108,7 +117,7 @@
"selenium-webdriver": "3.5.0",
"semver": "5.4.1",
"shelljs": "^0.8.1",
"source-map": "0.5.7",
"source-map": "^0.6.1",
"source-map-support": "0.4.18",
"systemjs": "0.18.10",
"tsickle": "0.32",

View File

@ -26,7 +26,7 @@ export interface AnimationPlayer {
finish(): void;
destroy(): void;
reset(): void;
setPosition(p: any /** TODO #9100 */): void;
setPosition(position: any /** TODO #9100 */): void;
getPosition(): number;
parentPlayer: AnimationPlayer|null;
readonly totalTime: number;
@ -93,7 +93,7 @@ export class NoopAnimationPlayer implements AnimationPlayer {
}
}
reset(): void {}
setPosition(p: number): void {}
setPosition(position: number): void {}
getPosition(): number { return 0; }
/** @internal */

View File

@ -14,7 +14,7 @@ load(":rules_typescript.bzl",
"ts_providers_dict_to_struct",
)
def _compile_strategy(ctx):
def compile_strategy(ctx):
"""Detect which strategy should be used to implement ng_module.
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
@ -50,7 +50,7 @@ def _compiler_name(ctx):
the name of the current compiler to be displayed in build output
"""
strategy = _compile_strategy(ctx)
strategy = compile_strategy(ctx)
if strategy == 'legacy':
return 'ngc'
elif strategy == 'global':
@ -72,7 +72,7 @@ def _enable_ivy_value(ctx):
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
"""
strategy = _compile_strategy(ctx)
strategy = compile_strategy(ctx)
if strategy == 'legacy':
return False
elif strategy == 'global':
@ -95,7 +95,7 @@ def _include_ng_files(ctx):
factory files), false otherwise
"""
strategy = _compile_strategy(ctx)
strategy = compile_strategy(ctx)
return strategy == 'legacy' or strategy == 'global'
def _basename_of(ctx, file):

View File

@ -30,7 +30,7 @@ nodejs_binary(
name = "ngc-wrapped",
data = [
":ngc_lib",
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
"@build_bazel_rules_typescript//third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
],
entry_point = "angular/packages/bazel/src/ngc-wrapped/index.js",
visibility = ["//visibility:public"],

View File

@ -207,6 +207,12 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
if (fileName ===
path.join(compilerOpts.baseUrl, bazelOpts.package, compilerOpts.flatModuleOutFile + '.ts'))
return true;
// Also handle the case when angular is built from source as an external repository
if (fileName ===
path.join(
compilerOpts.baseUrl, 'external/angular', bazelOpts.package,
compilerOpts.flatModuleOutFile + '.ts'))
return true;
return origBazelHostShouldNameModule(fileName) || NGC_GEN_FILES.test(fileName);
};

View File

@ -30,6 +30,6 @@ jasmine_node_test(
":angular_core",
"//packages/bazel/test/ngc-wrapped/empty:empty_tsconfig.json",
"//packages/bazel/test/ngc-wrapped/empty:tsconfig.json",
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
"@build_bazel_rules_typescript//third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
],
)

View File

@ -70,7 +70,7 @@ export function createTsConfig(options: TsConfigOptions) {
'tsickleExternsPath': '',
// we don't copy the node_modules into our tmp dir, so we should look in
// the original workspace directory for it
'nodeModulesPrefix': '../angular/node_modules',
'nodeModulesPrefix': '../angular/external/angular_deps/node_modules',
},
'files': options.files,
'angularCompilerOptions': {

View File

@ -6,6 +6,7 @@ load("//tools/http-server:http_server.bzl", "http_server")
ts_library(
name = "app",
srcs = ["app.ts"],
tsconfig = ":tsconfig.json",
)
ts_devserver(
@ -33,6 +34,7 @@ ts_library(
name = "ts_spec",
testonly = True,
srcs = ["test.spec.ts"],
tsconfig = ":tsconfig.json",
)
protractor_web_test_suite(
@ -49,6 +51,6 @@ protractor_web_test_suite(
configuration = ":conf.js",
data = ["//packages/bazel/src/protractor/utils"],
on_prepare = ":on-prepare.js",
server = ":prodserver",
server = ":devserver",
deps = [":ts_spec"],
)

View File

@ -1,5 +1,5 @@
{
"compilerOptions": {
"lib": ["es2015"]
"lib": ["dom", "es2015"]
}
}

View File

@ -5,6 +5,7 @@ ts_library(
name = "ts_spec",
testonly = True,
srcs = ["test.spec.ts"],
tsconfig = ":tsconfig.json",
)
ts_library(

View File

@ -6,15 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {HttpHandler} from '../src/backend';
import {HttpHeaders} from '../src/headers';
import {HttpRequest} from '../src/request';
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor} from '../src/xsrf';
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor} from '../src/xsrf';
import {HttpClientTestingBackend} from '../testing/src/backend';
class SampleTokenExtractor {
constructor(private token: string|null) {}
class SampleTokenExtractor extends HttpXsrfTokenExtractor {
constructor(private token: string|null) { super(); }
getToken(): string|null { return this.token; }
}

View File

@ -21,7 +21,7 @@ ts_library(
],
),
module_name = "@angular/compiler-cli",
node_modules = "@//:node_modules",
node_modules = "@angular_deps//:node_modules",
tsconfig = ":tsconfig",
deps = [
"//packages/compiler",
@ -41,5 +41,8 @@ npm_package(
"ivy-local",
"release-with-framework",
],
deps = [":compiler-cli"],
deps = [
":compiler-cli",
"//packages/compiler-cli/src/ngcc",
],
)

View File

@ -5,14 +5,18 @@
"main": "index.js",
"typings": "index.d.ts",
"bin": {
"ivy-ngcc": "./src/ngcc/main-ngcc.js",
"ngc": "./src/main.js",
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0",
"tsickle": "^0.30.0",
"chokidar": "^1.4.2"
"tsickle": "^0.32.1",
"chokidar": "^1.4.2",
"convert-source-map": "^1.5.1",
"magic-string": "^0.25.0",
"source-map": "^0.6.1"
},
"peerDependencies": {
"typescript": ">=2.7.2 <2.10",

View File

@ -0,0 +1,21 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "ngcc",
srcs = glob([
"*.ts",
"**/*.ts",
]),
module_name = "@angular/compiler-cli/src/ngcc",
deps = [
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/host",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/transform",
],
)

View File

@ -0,0 +1,30 @@
# Angular Compatibility Compiler (ngcc)
This compiler will convert `node_modules` compiled with `ngc`, into `node_modules` which
appear to have been compiled with `ngtsc`.
This conversion will allow such "legacy" packages to be used by the Ivy rendering engine.
## Building
The project is built using Bazel:
```bash
bazel build //packages/compiler-cli/src/ngcc
```
## Unit Testing
The unit tests are built and run using Bazel:
```bash
bazel test //packages/compiler-cli/src/ngcc/test
```
## Integration Testing
There are tests that check the behaviour of the overall executable:
```bash
bazel test //packages/compiler-cli/test/ngcc
```

View File

@ -0,0 +1,9 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export {mainNgcc} from './src/main';

View File

@ -0,0 +1,16 @@
#!/usr/bin/env node
/**
* @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 {mainNgcc} from './src/main';
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2);
process.exitCode = mainNgcc(args);
}

View File

@ -0,0 +1,97 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from '../../ngtsc/annotations';
import {Decorator} from '../../ngtsc/host';
import {CompileResult, DecoratorHandler} from '../../ngtsc/transform';
import {NgccReflectionHost} from './host/ngcc_host';
import {ParsedClass} from './parsing/parsed_class';
import {ParsedFile} from './parsing/parsed_file';
import {isDefined} from './utils';
export interface AnalyzedClass<T = any> extends ParsedClass {
handler: DecoratorHandler<T>;
analysis: any;
diagnostics?: ts.Diagnostic[];
compilation: CompileResult[];
}
export interface AnalyzedFile {
analyzedClasses: AnalyzedClass[];
sourceFile: ts.SourceFile;
}
export interface MatchingHandler<T> {
handler: DecoratorHandler<T>;
decorator: Decorator;
}
/**
* `ResourceLoader` which directly uses the filesystem to resolve resources synchronously.
*/
export class FileResourceLoader implements ResourceLoader {
load(url: string): string { return fs.readFileSync(url, 'utf8'); }
}
export class Analyzer {
resourceLoader = new FileResourceLoader();
scopeRegistry = new SelectorScopeRegistry(this.typeChecker, this.host);
handlers: DecoratorHandler<any>[] = [
new ComponentDecoratorHandler(
this.typeChecker, this.host, this.scopeRegistry, false, this.resourceLoader),
new DirectiveDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, false),
new InjectableDecoratorHandler(this.host, false),
new NgModuleDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, false),
new PipeDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, false),
];
constructor(private typeChecker: ts.TypeChecker, private host: NgccReflectionHost) {}
/**
* Analyize a parsed file to generate the information about decorated classes that
* should be converted to use ivy definitions.
* @param file The file to be analysed for decorated classes.
*/
analyzeFile(file: ParsedFile): AnalyzedFile {
const analyzedClasses =
file.decoratedClasses.map(clazz => this.analyzeClass(file.sourceFile, clazz))
.filter(isDefined);
return {
analyzedClasses,
sourceFile: file.sourceFile,
};
}
protected analyzeClass(file: ts.SourceFile, clazz: ParsedClass): AnalyzedClass|undefined {
const matchingHandlers =
this.handlers.map(handler => ({handler, decorator: handler.detect(clazz.decorators)}))
.filter(isMatchingHandler);
if (matchingHandlers.length > 1) {
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
}
if (matchingHandlers.length == 0) {
return undefined;
}
const {handler, decorator} = matchingHandlers[0];
const {analysis, diagnostics} = handler.analyze(clazz.declaration, decorator);
let compilation = handler.compile(clazz.declaration, analysis);
if (!Array.isArray(compilation)) {
compilation = [compilation];
}
return {...clazz, handler, analysis, diagnostics, compilation};
}
}
function isMatchingHandler<T>(handler: Partial<MatchingHandler<T>>): handler is MatchingHandler<T> {
return !!handler.decorator;
}

View File

@ -0,0 +1,425 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, Decorator, Parameter} from '../../../ngtsc/host';
import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata';
import {getNameText} from '../utils';
import {NgccReflectionHost} from './ngcc_host';
export const DECORATORS = 'decorators' as ts.__String;
export const PROP_DECORATORS = 'propDecorators' as ts.__String;
export const CONSTRUCTOR = '__constructor' as ts.__String;
export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
/**
* Esm2015 packages contain ECMAScript 2015 classes, etc.
* Decorators are defined via static properties on the class. For example:
*
* ```
* class SomeDirective {
* }
* SomeDirective.decorators = [
* { type: Directive, args: [{ selector: '[someDirective]' },] }
* ];
* SomeDirective.ctorParameters = () => [
* { type: ViewContainerRef, },
* { type: TemplateRef, },
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
* ];
* SomeDirective.propDecorators = {
* "input1": [{ type: Input },],
* "input2": [{ type: Input },],
* };
* ```
*
* * Classes are decorated if they have a static property called `decorators`.
* * Members are decorated if there is a matching key on a static property
* called `propDecorators`.
* * Constructor parameters decorators are found on an object returned from
* a static method called `ctorParameters`.
*/
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
constructor(checker: ts.TypeChecker) { super(checker); }
/**
* Examine a declaration (for example, of a class or function) and return metadata about any
* decorators present on the declaration.
*
* @param declaration a TypeScript `ts.Declaration` node representing the class or function over
* which to reflect. For example, if the intent is to reflect the decorators of a class and the
* source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the source is in ES5
* format, this might be a `ts.VariableDeclaration` as classes in ES5 are represented as the
* result of an IIFE execution.
*
* @returns an array of `Decorator` metadata if decorators are present on the declaration, or
* `null` if either no decorators were present or if the declaration is not of a decoratable type.
*/
getDecoratorsOfDeclaration(declaration: ts.Declaration): Decorator[]|null {
const symbol = this.getClassSymbol(declaration);
if (symbol) {
if (symbol.exports && symbol.exports.has(DECORATORS)) {
// Symbol of the identifier for `SomeDirective.decorators`.
const decoratorsSymbol = symbol.exports.get(DECORATORS) !;
const decoratorsIdentifier = decoratorsSymbol.valueDeclaration;
if (decoratorsIdentifier && decoratorsIdentifier.parent) {
if (ts.isBinaryExpression(decoratorsIdentifier.parent)) {
// AST of the array of decorator values
const decoratorsArray = decoratorsIdentifier.parent.right;
return this.reflectDecorators(decoratorsArray);
}
}
}
}
return null;
}
/**
* Examine a declaration which should be of a class, and return metadata about the members of the
* class.
*
* @param declaration a TypeScript `ts.Declaration` node representing the class over which to
* reflect. If the source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the
* source is in ES5 format, this might be a `ts.VariableDeclaration` as classes in ES5 are
* represented as the result of an IIFE execution.
*
* @returns an array of `ClassMember` metadata representing the members of the class.
*
* @throws if `declaration` does not resolve to a class declaration.
*/
getMembersOfClass(clazz: ts.Declaration): ClassMember[] {
const members: ClassMember[] = [];
const symbol = this.getClassSymbol(clazz);
if (!symbol) {
throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`);
}
// The decorators map contains all the properties that are decorated
const decoratorsMap = this.getMemberDecorators(symbol);
// The member map contains all the method (instance and static); and any instance properties
// that are initialized in the class.
if (symbol.members) {
symbol.members.forEach((value, key) => {
const decorators = removeFromMap(decoratorsMap, key);
const member = this.reflectMember(value, decorators);
if (member) {
members.push(member);
}
});
}
// The static property map contains all the static properties
if (symbol.exports) {
symbol.exports.forEach((value, key) => {
const decorators = removeFromMap(decoratorsMap, key);
const member = this.reflectMember(value, decorators, true);
if (member) {
members.push(member);
}
});
}
// Deal with any decorated properties that were not initialized in the class
decoratorsMap.forEach((value, key) => {
members.push({
implementation: null,
decorators: value,
isStatic: false,
kind: ClassMemberKind.Property,
name: key,
nameNode: null,
node: null,
type: null,
value: null
});
});
return members;
}
/**
* Reflect over the constructor of a class and return metadata about its parameters.
*
* This method only looks at the constructor of a class directly and not at any inherited
* constructors.
*
* @param declaration a TypeScript `ts.Declaration` node representing the class over which to
* reflect. If the source is in ES6 format, this will be a `ts.ClassDeclaration` node. If the
* source is in ES5 format, this might be a `ts.VariableDeclaration` as classes in ES5 are
* represented as the result of an IIFE execution.
*
* @returns an array of `Parameter` metadata representing the parameters of the constructor, if
* a constructor exists. If the constructor exists and has 0 parameters, this array will be empty.
* If the class has no constructor, this method returns `null`.
*
* @throws if `declaration` does not resolve to a class declaration.
*/
getConstructorParameters(clazz: ts.Declaration): Parameter[]|null {
const classSymbol = this.getClassSymbol(clazz);
if (!classSymbol) {
throw new Error(
`Attempted to get constructor parameters of a non-class: "${clazz.getText()}"`);
}
const parameterNodes = this.getConstructorParameterDeclarations(classSymbol);
if (parameterNodes) {
const parameters: Parameter[] = [];
const decoratorInfo = this.getConstructorDecorators(classSymbol);
parameterNodes.forEach((node, index) => {
const info = decoratorInfo[index];
const decorators =
info && info.has('decorators') && this.reflectDecorators(info.get('decorators') !) ||
null;
const type = info && info.get('type') || null;
const nameNode = node.name;
parameters.push({name: getNameText(nameNode), nameNode, type, decorators});
});
return parameters;
}
return null;
}
/**
* Find a symbol for a declaration that we think is a class.
* @param declaration The declaration whose symbol we are finding
* @returns the symbol for the declaration or `undefined` if it is not
* a "class" or has no symbol.
*/
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined {
return ts.isClassDeclaration(declaration) ?
declaration.name && this.checker.getSymbolAtLocation(declaration.name) :
undefined;
}
/**
* Member decorators are declared as static properties of the class in ES2015:
*
* ```
* SomeDirective.propDecorators = {
* "ngForOf": [{ type: Input },],
* "ngForTrackBy": [{ type: Input },],
* "ngForTemplate": [{ type: Input },],
* };
* ```
*/
protected getMemberDecorators(classSymbol: ts.Symbol): Map<string, Decorator[]> {
const memberDecorators = new Map<string, Decorator[]>();
if (classSymbol.exports && classSymbol.exports.has(PROP_DECORATORS)) {
// Symbol of the identifier for `SomeDirective.propDecorators`.
const propDecoratorsMap =
getPropertyValueFromSymbol(classSymbol.exports.get(PROP_DECORATORS) !);
if (propDecoratorsMap && ts.isObjectLiteralExpression(propDecoratorsMap)) {
const propertiesMap = reflectObjectLiteral(propDecoratorsMap);
propertiesMap.forEach(
(value, name) => { memberDecorators.set(name, this.reflectDecorators(value)); });
}
}
return memberDecorators;
}
/**
* Reflect over the given expression and extract decorator information.
* @param decoratorsArray An expression that contains decorator information.
*/
protected reflectDecorators(decoratorsArray: ts.Expression): Decorator[] {
const decorators: Decorator[] = [];
if (ts.isArrayLiteralExpression(decoratorsArray)) {
// Add each decorator that is imported from `@angular/core` into the `decorators` array
decoratorsArray.elements.forEach(node => {
// If the decorator is not an object literal expression then we are not interested
if (ts.isObjectLiteralExpression(node)) {
// We are only interested in objects of the form: `{ type: DecoratorType, args: [...] }`
const decorator = reflectObjectLiteral(node);
// Is the value of the `type` property an identifier?
const typeIdentifier = decorator.get('type');
if (typeIdentifier && ts.isIdentifier(typeIdentifier)) {
decorators.push({
name: typeIdentifier.text,
import: this.getImportOfIdentifier(typeIdentifier), node,
args: getDecoratorArgs(node),
});
}
}
});
}
return decorators;
}
protected reflectMember(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean):
ClassMember|null {
let kind: ClassMemberKind|null = null;
let value: ts.Expression|null = null;
let name: string|null = null;
let nameNode: ts.Identifier|null = null;
let type = null;
const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
if (!node || !isClassMemberType(node)) {
return null;
}
if (symbol.flags & ts.SymbolFlags.Method) {
kind = ClassMemberKind.Method;
} else if (symbol.flags & ts.SymbolFlags.Property) {
kind = ClassMemberKind.Property;
} else if (symbol.flags & ts.SymbolFlags.GetAccessor) {
kind = ClassMemberKind.Getter;
} else if (symbol.flags & ts.SymbolFlags.SetAccessor) {
kind = ClassMemberKind.Setter;
}
if (isStatic && isPropertyAccess(node)) {
name = node.name.text;
value = symbol.flags & ts.SymbolFlags.Property ? node.parent.right : null;
} else if (isThisAssignment(node)) {
kind = ClassMemberKind.Property;
name = node.left.name.text;
value = node.right;
isStatic = false;
} else if (ts.isConstructorDeclaration(node)) {
kind = ClassMemberKind.Constructor;
name = 'constructor';
isStatic = false;
}
if (kind === null) {
console.warn(`Unknown member type: "${node.getText()}`);
return null;
}
if (!name) {
if (isNamedDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
name = node.name.text;
nameNode = node.name;
} else {
return null;
}
}
// If we have still not determined if this is a static or instance member then
// look for the `static` keyword on the declaration
if (isStatic === undefined) {
isStatic = node.modifiers !== undefined &&
node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
}
return {
node,
implementation: node, kind, type, name, nameNode, value, isStatic,
decorators: decorators || []
};
}
/**
* Find the declarations of the constructor parameters of a class identified by its symbol.
* @param classSymbol the class whose parameters we want to find.
* @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in
* the
* class's constructor or null if there is no constructor.
*/
protected getConstructorParameterDeclarations(classSymbol: ts.Symbol):
ts.ParameterDeclaration[]|null {
const constructorSymbol = classSymbol.members && classSymbol.members.get(CONSTRUCTOR);
if (constructorSymbol) {
// For some reason the constructor does not have a `valueDeclaration` ?!?
const constructor = constructorSymbol.declarations &&
constructorSymbol.declarations[0] as ts.ConstructorDeclaration;
if (constructor && constructor.parameters) {
return Array.from(constructor.parameters);
}
return [];
}
return null;
}
/**
* Constructors parameter decorators are declared in the body of static method of the class in
* ES2015:
*
* ```
* SomeDirective.ctorParameters = () => [
* { type: ViewContainerRef, },
* { type: TemplateRef, },
* { type: IterableDiffers, },
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
* ];
* ```
*/
protected getConstructorDecorators(classSymbol: ts.Symbol): (Map<string, ts.Expression>|null)[] {
if (classSymbol.exports && classSymbol.exports.has(CONSTRUCTOR_PARAMS)) {
const paramDecoratorsProperty =
getPropertyValueFromSymbol(classSymbol.exports.get(CONSTRUCTOR_PARAMS) !);
if (paramDecoratorsProperty && ts.isArrowFunction(paramDecoratorsProperty)) {
if (ts.isArrayLiteralExpression(paramDecoratorsProperty.body)) {
return paramDecoratorsProperty.body.elements.map(
element =>
ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null);
}
}
}
return [];
}
}
/**
* The arguments of a decorator are held in the `args` property of its declaration object.
*/
function getDecoratorArgs(node: ts.ObjectLiteralExpression): ts.Expression[] {
const argsProperty = node.properties.filter(ts.isPropertyAssignment)
.find(property => getNameText(property.name) === 'args');
const argsExpression = argsProperty && argsProperty.initializer;
return argsExpression && ts.isArrayLiteralExpression(argsExpression) ?
Array.from(argsExpression.elements) :
[];
}
/**
* Helper method to extract the value of a property given the property's "symbol",
* which is actually the symbol of the identifier of the property.
*/
export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression|undefined {
const propIdentifier = propSymbol.valueDeclaration;
const parent = propIdentifier && propIdentifier.parent;
return parent && ts.isBinaryExpression(parent) ? parent.right : undefined;
}
function removeFromMap<T>(map: Map<string, T>, key: ts.__String): T|undefined {
const mapKey = key as string;
const value = map.get(mapKey);
if (value !== undefined) {
map.delete(mapKey);
}
return value;
}
function isPropertyAccess(node: ts.Node): node is ts.PropertyAccessExpression&
{parent: ts.BinaryExpression} {
return !!node.parent && ts.isBinaryExpression(node.parent) && ts.isPropertyAccessExpression(node);
}
function isThisAssignment(node: ts.Declaration): node is ts.BinaryExpression&
{left: ts.PropertyAccessExpression} {
return ts.isBinaryExpression(node) && ts.isPropertyAccessExpression(node.left) &&
node.left.expression.kind === ts.SyntaxKind.ThisKeyword;
}
function isNamedDeclaration(node: ts.Declaration): node is ts.NamedDeclaration {
return !!(node as any).name;
}
function isClassMemberType(node: ts.Declaration): node is ts.ClassElement|
ts.PropertyAccessExpression|ts.BinaryExpression {
return ts.isClassElement(node) || isPropertyAccess(node) || ts.isBinaryExpression(node);
}

View File

@ -0,0 +1,137 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ClassMember, ClassMemberKind, Decorator} from '../../../ngtsc/host';
import {reflectObjectLiteral} from '../../../ngtsc/metadata';
import {CONSTRUCTOR_PARAMS, Esm2015ReflectionHost, getPropertyValueFromSymbol} from './esm2015_host';
/**
* ESM5 packages contain ECMAScript IIFE functions that act like classes. For example:
*
* ```
* var CommonModule = (function () {
* function CommonModule() {
* }
* CommonModule.decorators = [ ... ];
* ```
*
* * "Classes" are decorated if they have a static property called `decorators`.
* * Members are decorated if there is a matching key on a static property
* called `propDecorators`.
* * Constructor parameters decorators are found on an object returned from
* a static method called `ctorParameters`.
*
*/
export class Esm5ReflectionHost extends Esm2015ReflectionHost {
constructor(checker: ts.TypeChecker) { super(checker); }
/**
* Check whether the given declaration node actually represents a class.
*/
isClass(node: ts.Declaration): boolean { return !!this.getClassSymbol(node); }
/**
* In ESM5 the implementation of a class is a function expression that is hidden inside an IIFE.
* So we need to dig around inside to get hold of the "class" symbol.
* @param declaration the top level declaration that represents an exported class.
*/
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined {
if (ts.isVariableDeclaration(declaration)) {
const iifeBody = getIifeBody(declaration);
if (iifeBody) {
const innerClassIdentifier = getReturnIdentifier(iifeBody);
if (innerClassIdentifier) {
return this.checker.getSymbolAtLocation(innerClassIdentifier);
}
}
}
return undefined;
}
/**
* Find the declarations of the constructor parameters of a class identified by its symbol.
* In ESM5 there is no "class" so the constructor that we want is actually the declaration
* function itself.
*/
protected getConstructorParameterDeclarations(classSymbol: ts.Symbol): ts.ParameterDeclaration[] {
const constructor = classSymbol.valueDeclaration as ts.FunctionDeclaration;
if (constructor && constructor.parameters) {
return Array.from(constructor.parameters);
}
return [];
}
/**
* Constructors parameter decorators are declared in the body of static method of the constructor
* function in ES5. Note that unlike ESM2105 this is a function expression rather than an arrow
* function:
*
* ```
* SomeDirective.ctorParameters = function() { return [
* { type: ViewContainerRef, },
* { type: TemplateRef, },
* { type: IterableDiffers, },
* { type: undefined, decorators: [{ type: Inject, args: [INJECTED_TOKEN,] },] },
* ]; };
* ```
*/
protected getConstructorDecorators(classSymbol: ts.Symbol): (Map<string, ts.Expression>|null)[] {
const declaration = classSymbol.exports && classSymbol.exports.get(CONSTRUCTOR_PARAMS);
const paramDecoratorsProperty = declaration && getPropertyValueFromSymbol(declaration);
const returnStatement = getReturnStatement(paramDecoratorsProperty);
const expression = returnStatement && returnStatement.expression;
return expression && ts.isArrayLiteralExpression(expression) ?
expression.elements.map(reflectArrayElement) :
[];
}
protected reflectMember(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean):
ClassMember|null {
const member = super.reflectMember(symbol, decorators, isStatic);
if (member && member.kind === ClassMemberKind.Method && member.isStatic && member.node &&
ts.isPropertyAccessExpression(member.node) && member.node.parent &&
ts.isBinaryExpression(member.node.parent) &&
ts.isFunctionExpression(member.node.parent.right)) {
// Recompute the implementation for this member:
// ES5 static methods are variable declarations so the declaration is actually the
// initializer of the variable assignment
member.implementation = member.node.parent.right;
}
return member;
}
}
function getIifeBody(declaration: ts.VariableDeclaration): ts.Block|undefined {
if (!declaration.initializer || !ts.isParenthesizedExpression(declaration.initializer)) {
return undefined;
}
const call = declaration.initializer;
return ts.isCallExpression(call.expression) &&
ts.isFunctionExpression(call.expression.expression) ?
call.expression.expression.body :
undefined;
}
function getReturnIdentifier(body: ts.Block): ts.Identifier|undefined {
const returnStatement = body.statements.find(ts.isReturnStatement);
return returnStatement && returnStatement.expression &&
ts.isIdentifier(returnStatement.expression) ?
returnStatement.expression :
undefined;
}
function getReturnStatement(declaration: ts.Expression | undefined): ts.ReturnStatement|undefined {
return declaration && ts.isFunctionExpression(declaration) ?
declaration.body.statements.find(ts.isReturnStatement) :
undefined;
}
function reflectArrayElement(element: ts.Expression) {
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
}

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ReflectionHost} from '../../../ngtsc/host';
/**
* A reflection host that has extra methods for looking at non-Typescript package formats
*/
export interface NgccReflectionHost extends ReflectionHost {
getClassSymbol(declaration: ts.Declaration): ts.Symbol|undefined;
}

View File

@ -0,0 +1,21 @@
/**
* @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 {resolve} from 'path';
import {PackageTransformer} from './transform/package_transformer';
export function mainNgcc(args: string[]): number {
const packagePath = resolve(args[0]);
// TODO: find all the package tyoes to transform
// TODO: error/warning logging/handling etc
const transformer = new PackageTransformer();
transformer.transform(packagePath, 'fesm2015');
return 0;
}

View File

@ -0,0 +1,54 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {NgccReflectionHost} from '../host/ngcc_host';
import {getOriginalSymbol, isDefined} from '../utils';
import {FileParser} from './file_parser';
import {ParsedClass} from './parsed_class';
import {ParsedFile} from './parsed_file';
export class Esm2015FileParser implements FileParser {
checker = this.program.getTypeChecker();
constructor(protected program: ts.Program, protected host: NgccReflectionHost) {}
parseFile(file: ts.SourceFile): ParsedFile[] {
const moduleSymbol = this.checker.getSymbolAtLocation(file);
const map = new Map<ts.SourceFile, ParsedFile>();
if (moduleSymbol) {
const exportClasses = this.checker.getExportsOfModule(moduleSymbol)
.map(getOriginalSymbol(this.checker))
.filter(exportSymbol => exportSymbol.flags & ts.SymbolFlags.Class);
const classDeclarations = exportClasses.map(exportSymbol => exportSymbol.valueDeclaration)
.filter(isDefined)
.filter(ts.isClassDeclaration);
const decoratedClasses =
classDeclarations
.map(declaration => {
const decorators = this.host.getDecoratorsOfDeclaration(declaration);
return decorators && declaration.name &&
new ParsedClass(declaration.name.text, declaration, decorators);
})
.filter(isDefined);
decoratedClasses.forEach(clazz => {
const file = clazz.declaration.getSourceFile();
if (!map.has(file)) {
map.set(file, new ParsedFile(file));
}
map.get(file) !.decoratedClasses.push(clazz);
});
}
return Array.from(map.values());
}
}

View File

@ -0,0 +1,59 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {NgccReflectionHost} from '../host/ngcc_host';
import {getNameText, getOriginalSymbol, isDefined} from '../utils';
import {FileParser} from './file_parser';
import {ParsedClass} from './parsed_class';
import {ParsedFile} from './parsed_file';
/**
* Parses ESM5 package files for decoratrs classes.
* ESM5 "classes" are actually functions wrapped by and returned
* from an IFEE.
*/
export class Esm5FileParser implements FileParser {
checker = this.program.getTypeChecker();
constructor(protected program: ts.Program, protected host: NgccReflectionHost) {}
parseFile(file: ts.SourceFile): ParsedFile[] {
const moduleSymbol = this.checker.getSymbolAtLocation(file);
const map = new Map<ts.SourceFile, ParsedFile>();
const getParsedClass = (declaration: ts.VariableDeclaration) => {
const decorators = this.host.getDecoratorsOfDeclaration(declaration);
if (decorators) {
return new ParsedClass(getNameText(declaration.name), declaration, decorators);
}
};
if (moduleSymbol) {
const classDeclarations = this.checker.getExportsOfModule(moduleSymbol)
.map(getOriginalSymbol(this.checker))
.map(exportSymbol => exportSymbol.valueDeclaration)
.filter(isDefined)
.filter(ts.isVariableDeclaration);
const decoratedClasses = classDeclarations.map(getParsedClass).filter(isDefined);
decoratedClasses.forEach(clazz => {
const file = clazz.declaration.getSourceFile();
if (!map.has(file)) {
map.set(file, new ParsedFile(file));
}
map.get(file) !.decoratedClasses.push(clazz);
});
}
return Array.from(map.values());
}
}

View File

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {ParsedFile} from './parsed_file';
/**
* Classes that implement this interface can parse a file in a package to
* find the "declarations" (representing exported classes), that are decorated with core
* decorators, such as `@Component`, `@Injectable`, etc.
*
* Identifying classes can be different depending upon the format of the source file.
*
* For example:
*
* - ES2015 files contain `class Xxxx {...}` style declarations
* - ES5 files contain `var Xxxx = (function () { function Xxxx() { ... }; return Xxxx; })();` style
* declarations
* - UMD have similar declarations to ES5 files but the whole thing is wrapped in IIFE module
* wrapper
* function.
*/
export interface FileParser {
/**
* Parse a file to identify the decorated classes.
*
* @param file The the entry point file for identifying classes to process.
* @returns A `ParsedFiles` collection that holds the decorated classes and import information.
*/
parseFile(file: ts.SourceFile): ParsedFile[];
}

View File

@ -0,0 +1,26 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Decorator} from '../../../ngtsc/host';
/**
* A simple container that holds the details of a decorated class that has been
* parsed out of a package.
*/
export class ParsedClass {
/**
* Initialize a `DecoratedClass` that was found by parsing a package.
* @param name The name of the class that has been found. This is mostly used
* for informational purposes.
* @param declaration The TypeScript AST node where this class is declared
* @param decorators The collection of decorators that have been found on this class.
*/
constructor(
public name: string, public declaration: ts.Declaration, public decorators: Decorator[], ) {}
}

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 * as ts from 'typescript';
import {ParsedClass} from './parsed_class';
/**
* Information about a source file that has been parsed to
* extract all the decorated exported classes.
*/
export class ParsedFile {
/**
* The decorated exported classes that have been parsed out
* from the file.
*/
public decoratedClasses: ParsedClass[] = [];
constructor(public sourceFile: ts.SourceFile) {}
}

View File

@ -0,0 +1,52 @@
/**
* @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 {readFileSync} from 'fs';
import {dirname, resolve} from 'path';
import {find} from 'shelljs';
/**
* Match paths to package.json files.
*/
const PACKAGE_JSON_REGEX = /\/package\.json$/;
/**
* Match paths that have a `node_modules` segment at the start or in the middle.
*/
const NODE_MODULES_REGEX = /(?:^|\/)node_modules\//;
/**
* Search the `rootDirectory` and its subdirectories to find package.json files.
* It ignores node dependencies, i.e. those under `node_modules` folders.
* @param rootDirectory the directory in which we should search.
*/
export function findAllPackageJsonFiles(rootDirectory: string): string[] {
// TODO(gkalpak): Investigate whether skipping `node_modules/` directories (instead of traversing
// them and filtering out the results later) makes a noticeable difference.
const paths = Array.from(find(rootDirectory));
return paths.filter(
path => PACKAGE_JSON_REGEX.test(path) &&
!NODE_MODULES_REGEX.test(path.slice(rootDirectory.length)));
}
/**
* Identify the entry points of a package.
* @param packageDirectory The absolute path to the root directory that contains this package.
* @param format The format of the entry point within the package.
* @returns A collection of paths that point to entry points for this package.
*/
export function getEntryPoints(packageDirectory: string, format: string): string[] {
const packageJsonPaths = findAllPackageJsonFiles(packageDirectory);
return packageJsonPaths
.map(packageJsonPath => {
const entryPointPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const relativeEntryPointPath = entryPointPackageJson[format];
return relativeEntryPointPath && resolve(dirname(packageJsonPath), relativeEntryPointPath);
})
.filter(entryPointPath => entryPointPath);
}

View File

@ -0,0 +1,62 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {NgccReflectionHost} from '../host/ngcc_host';
import {AnalyzedClass} from '../analyzer';
import {Renderer} from './renderer';
export class Esm2015Renderer extends Renderer {
constructor(protected host: NgccReflectionHost) { super(); }
/**
* Add the imports at the top of the file
*/
addImports(output: MagicString, imports: {name: string; as: string;}[]): void {
// The imports get inserted at the very top of the file.
imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); });
}
/**
* Add the definitions to each decorated class
*/
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void {
const classSymbol = this.host.getClassSymbol(analyzedClass.declaration);
if (!classSymbol) {
throw new Error(`Analyzed class does not have a valid symbol: ${analyzedClass.name}`);
}
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
output.appendLeft(insertionPoint, '\n' + definitions);
}
/**
* Remove static decorator properties from classes
*/
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void {
decoratorsToRemove.forEach((nodesToRemove, containerNode) => {
if (ts.isArrayLiteralExpression(containerNode)) {
const items = containerNode.elements;
if (items.length === nodesToRemove.length) {
// remove any trailing semi-colon
const end = (output.slice(containerNode.getEnd(), containerNode.getEnd() + 1) === ';') ?
containerNode.getEnd() + 1 :
containerNode.getEnd();
output.remove(containerNode.parent !.getFullStart(), end);
} else {
nodesToRemove.forEach(node => {
// remove any trailing comma
const end = (output.slice(node.getEnd(), node.getEnd() + 1) === ',') ?
node.getEnd() + 1 :
node.getEnd();
output.remove(node.getFullStart(), end);
});
}
}
});
}
}

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {NgccReflectionHost} from '../host/ngcc_host';
import {AnalyzedClass, AnalyzedFile} from '../analyzer';
import {Esm2015Renderer} from './esm2015_renderer';
export class Esm5Renderer extends Esm2015Renderer {
constructor(host: NgccReflectionHost) { super(host); }
}

View File

@ -0,0 +1,245 @@
/**
* @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 {dirname} from 'path';
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {commentRegex, mapFileCommentRegex, fromJSON, fromSource, fromMapFileSource, fromObject, generateMapFileComment, removeComments, removeMapFileComments, SourceMapConverter} from 'convert-source-map';
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
import {Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler';
import {AnalyzedClass, AnalyzedFile} from '../analyzer';
import {Decorator} from '../../../ngtsc/host';
import {ImportManager, translateStatement} from '../../../ngtsc/transform';
interface SourceMapInfo {
source: string;
map: SourceMapConverter|null;
isInline: boolean;
}
/**
* The results of rendering an analyzed file.
*/
export interface RenderResult {
/**
* The file that has been rendered.
*/
file: AnalyzedFile;
/**
* The rendered source file.
*/
source: FileInfo;
/**
* The rendered source map file.
*/
map: FileInfo|null;
}
/**
* Information about a file that has been rendered.
*/
export interface FileInfo {
/**
* Path to where the file should be written.
*/
path: string;
/**
* The contents of the file to be be written.
*/
contents: string;
}
/**
* A base-class for rendering an `AnalyzedClass`.
* Package formats have output files that must be rendered differently,
* Concrete sub-classes must implement the `addImports`, `addDefinitions` and
* `removeDecorators` abstract methods.
*/
export abstract class Renderer {
/**
* Render the source code and source-map for an Analyzed file.
* @param file The analyzed file to render.
* @param targetPath The absolute path where the rendered file will be written.
*/
renderFile(file: AnalyzedFile, targetPath: string): RenderResult {
const importManager = new ImportManager(false, 'ɵngcc');
const input = this.extractSourceMap(file.sourceFile);
const outputText = new MagicString(input.source);
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
file.analyzedClasses.forEach(clazz => {
const renderedDefinition = renderDefinitions(file.sourceFile, clazz, importManager);
this.addDefinitions(outputText, clazz, renderedDefinition);
this.trackDecorators(clazz.decorators, decoratorsToRemove);
});
this.addImports(outputText, importManager.getAllImports(file.sourceFile.fileName, null));
// QUESTION: do we need to remove contructor param metadata and property decorators?
this.removeDecorators(outputText, decoratorsToRemove);
return this.renderSourceAndMap(file, input, outputText, targetPath);
}
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
protected abstract addDefinitions(
output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void;
protected abstract removeDecorators(
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
/**
* Add the decorator nodes that are to be removed to a map
* So that we can tell if we should remove the entire decorator property
*/
protected trackDecorators(decorators: Decorator[], decoratorsToRemove: Map<ts.Node, ts.Node[]>):
void {
decorators.forEach(dec => {
const decoratorArray = dec.node.parent !;
if (!decoratorsToRemove.has(decoratorArray)) {
decoratorsToRemove.set(decoratorArray, [dec.node]);
} else {
decoratorsToRemove.get(decoratorArray) !.push(dec.node);
}
});
}
/**
* Get the map from the source (note whether it is inline or external)
*/
protected extractSourceMap(file: ts.SourceFile): SourceMapInfo {
const inline = commentRegex.test(file.text);
const external = mapFileCommentRegex.test(file.text);
if (inline) {
const inlineSourceMap = fromSource(file.text);
return {
source: removeComments(file.text).replace(/\n\n$/, '\n'),
map: inlineSourceMap,
isInline: true,
};
} else if (external) {
let externalSourceMap: SourceMapConverter|null = null;
try {
externalSourceMap = fromMapFileSource(file.text, dirname(file.fileName));
} catch (e) {
console.warn(e);
}
return {
source: removeMapFileComments(file.text).replace(/\n\n$/, '\n'),
map: externalSourceMap,
isInline: false,
};
} else {
return {source: file.text, map: null, isInline: false};
}
}
/**
* Merge the input and output source-maps, replacing the source-map comment in the output file
* with an appropriate source-map comment pointing to the merged source-map.
*/
protected renderSourceAndMap(
file: AnalyzedFile, input: SourceMapInfo, output: MagicString,
outputPath: string): RenderResult {
const outputMapPath = `${outputPath}.map`;
const outputMap = output.generateMap({
source: file.sourceFile.fileName,
includeContent: true,
// hires: true // TODO: This results in accurate but huge sourcemaps. Instead we should fix
// the merge algorithm.
});
// we must set this after generation as magic string does "manipulation" on the path
outputMap.file = outputPath;
const mergedMap =
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
if (input.isInline) {
return {
file,
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
map: null
};
} else {
return {
file,
source: {
path: outputPath,
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
},
map: {path: outputMapPath, contents: mergedMap.toJSON()}
};
}
}
}
/**
* Merge the two specified source-maps into a single source-map that hides the intermediate
* source-map.
* E.g. Consider these mappings:
*
* ```
* OLD_SRC -> OLD_MAP -> INTERMEDIATE_SRC -> NEW_MAP -> NEW_SRC
* ```
*
* this will be replaced with:
*
* ```
* OLD_SRC -> MERGED_MAP -> NEW_SRC
* ```
*/
export function mergeSourceMaps(
oldMap: RawSourceMap | null, newMap: RawSourceMap): SourceMapConverter {
if (!oldMap) {
return fromObject(newMap);
}
const oldMapConsumer = new SourceMapConsumer(oldMap);
const newMapConsumer = new SourceMapConsumer(newMap);
const mergedMapGenerator = SourceMapGenerator.fromSourceMap(newMapConsumer);
mergedMapGenerator.applySourceMap(oldMapConsumer);
const merged = fromJSON(mergedMapGenerator.toString());
return merged;
}
/**
* Render the definitions as source code for the given class.
* @param sourceFile The file containing the class to process.
* @param clazz The class whose definitions are to be rendered.
* @param compilation The results of analyzing the class - this is used to generate the rendered
* definitions.
* @param imports An object that tracks the imports that are needed by the rendered definitions.
*/
export function renderDefinitions(
sourceFile: ts.SourceFile, analyzedClass: AnalyzedClass, imports: ImportManager): string {
const printer = ts.createPrinter();
const name = (analyzedClass.declaration as ts.NamedDeclaration).name !;
const definitions =
analyzedClass.compilation
.map(
c => c.statements.map(statement => translateStatement(statement, imports))
.concat(translateStatement(
createAssignmentStatement(name, c.name, c.initializer), imports))
.map(
statement =>
printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile))
.join('\n'))
.join('\n');
return definitions;
}
/**
* Create an Angular AST statement node that contains the assignment of the
* compiled decorator to be applied to the class.
* @param analyzedClass The info about the class whose statement we want to create.
*/
function createAssignmentStatement(
receiverName: ts.DeclarationName, propName: string, initializer: Expression): Statement {
const receiver = new WrappedNodeExpr(receiverName);
return new WritePropExpr(receiver, propName, initializer).toStmt();
}

View File

@ -0,0 +1,121 @@
/**
* @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 {writeFileSync} from 'fs';
import {dirname, relative, resolve} from 'path';
import {mkdir} from 'shelljs';
import * as ts from 'typescript';
import {Analyzer} from '../analyzer';
import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host';
import {NgccReflectionHost} from '../host/ngcc_host';
import {Esm2015FileParser} from '../parsing/esm2015_parser';
import {Esm5FileParser} from '../parsing/esm5_parser';
import {FileParser} from '../parsing/file_parser';
import {getEntryPoints} from '../parsing/utils';
import {Esm2015Renderer} from '../rendering/esm2015_renderer';
import {Esm5Renderer} from '../rendering/esm5_renderer';
import {FileInfo, Renderer} from '../rendering/renderer';
/**
* A Package is stored in a directory on disk and that directory can contain one or more package
formats - e.g. fesm2015, UMD, etc.
*
* Each of these formats exposes one or more entry points, which are source files that need to be
* parsed to identify the decorated exported classes that need to be analyzed and compiled by one or
* more `DecoratorHandler` objects.
*
* Each entry point to a package is identified by a `SourceFile` that can be parsed and analyzed
* to identify classes that need to be transformed; and then finally rendered and written to disk.
* The actual file which needs to be transformed depends upon the package format.
*
* - Flat file packages have all the classes in a single file.
* - Other packages may re-export classes from other non-entry point files.
* - Some formats may contain multiple "modules" in a single file.
*/
export class PackageTransformer {
transform(packagePath: string, format: string): void {
const sourceNodeModules = this.findNodeModulesPath(packagePath);
const targetNodeModules = sourceNodeModules.replace(/node_modules$/, 'node_modules_ngtsc');
const entryPointPaths = getEntryPoints(packagePath, format);
entryPointPaths.forEach(entryPointPath => {
const options: ts.CompilerOptions = {allowJs: true, rootDir: entryPointPath};
const host = ts.createCompilerHost(options);
const packageProgram = ts.createProgram([entryPointPath], options, host);
const entryPointFile = packageProgram.getSourceFile(entryPointPath) !;
const typeChecker = packageProgram.getTypeChecker();
const reflectionHost = this.getHost(format, packageProgram);
const parser = this.getFileParser(format, packageProgram, reflectionHost);
const analyzer = new Analyzer(typeChecker, reflectionHost);
const renderer = this.getRenderer(format, packageProgram, reflectionHost);
const parsedFiles = parser.parseFile(entryPointFile);
parsedFiles.forEach(parsedFile => {
const analyzedFile = analyzer.analyzeFile(parsedFile);
const targetPath = resolve(
targetNodeModules, relative(sourceNodeModules, analyzedFile.sourceFile.fileName));
const {source, map} = renderer.renderFile(analyzedFile, targetPath);
this.writeFile(source);
if (map) {
this.writeFile(map);
}
});
});
}
getHost(format: string, program: ts.Program): NgccReflectionHost {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015ReflectionHost(program.getTypeChecker());
case 'fesm5':
return new Esm5ReflectionHost(program.getTypeChecker());
default:
throw new Error(`Relection host for "${format}" not yet implemented.`);
}
}
getFileParser(format: string, program: ts.Program, host: NgccReflectionHost): FileParser {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015FileParser(program, host);
case 'fesm5':
return new Esm5FileParser(program, host);
default:
throw new Error(`File parser for "${format}" not yet implemented.`);
}
}
getRenderer(format: string, program: ts.Program, host: NgccReflectionHost): Renderer {
switch (format) {
case 'esm2015':
case 'fesm2015':
return new Esm2015Renderer(host);
case 'fesm5':
return new Esm5Renderer(host);
default:
throw new Error(`Renderer for "${format}" not yet implemented.`);
}
}
findNodeModulesPath(src: string): string {
while (src && !/node_modules$/.test(src)) {
src = dirname(src);
}
return src;
}
writeFile(file: FileInfo): void {
mkdir('-p', dirname(file.path));
writeFileSync(file.path, file.contents, 'utf8');
}
}

View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol {
return function(symbol: ts.Symbol) {
return ts.SymbolFlags.Alias & symbol.flags ? checker.getAliasedSymbol(symbol) : symbol;
};
}
export function isDefined<T>(value: T | undefined | null): value is T {
return !!value;
}
export function getNameText(name: ts.PropertyName | ts.BindingName): string {
return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText();
}

View File

@ -0,0 +1,27 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob([
"**/*.ts",
]),
deps = [
"//packages/compiler-cli/src/ngcc",
"//packages/compiler-cli/src/ngtsc/host",
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/compiler-cli/src/ngtsc/transform",
],
)
jasmine_node_test(
name = "test",
bootstrap = ["angular/tools/testing/init_node_no_angular_spec.js"],
deps = [
":test_lib",
"//tools/testing:node_no_angular",
],
)

View File

@ -0,0 +1,109 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Decorator} from '../../ngtsc/host';
import {DecoratorHandler} from '../../ngtsc/transform';
import {AnalyzedFile, Analyzer} from '../src/analyzer';
import {Esm2015ReflectionHost} from '../src/host/esm2015_host';
import {ParsedClass} from '../src/parsing/parsed_class';
import {ParsedFile} from '../src/parsing/parsed_file';
import {getDeclaration, makeProgram} from './helpers/utils';
const TEST_PROGRAM = {
name: 'test.js',
contents: `
import {Component, Injectable} from '@angular/core';
@Component()
export class MyComponent {}
@Injectable()
export class MyService {}
`
};
function createTestHandler() {
const handler = jasmine.createSpyObj<DecoratorHandler<any>>('TestDecoratorHandler', [
'detect',
'analyze',
'compile',
]);
// Only detect the Component decorator
handler.detect.and.callFake(
(decorators: Decorator[]) => decorators.find(d => d.name === 'Component'));
// The "test" analysis is just the name of the decorator being analyzed
handler.analyze.and.callFake(
((decl: ts.Declaration, dec: Decorator) => ({analysis: dec.name, diagnostics: null})));
// The "test" compilation result is just the name of the decorator being compiled
handler.compile.and.callFake(((decl: ts.Declaration, analysis: any) => ({analysis})));
return handler;
}
function createParsedFile(program: ts.Program) {
const file = new ParsedFile(program.getSourceFile('test.js') !);
const componentClass = getDeclaration(program, 'test.js', 'MyComponent', ts.isClassDeclaration);
file.decoratedClasses.push(new ParsedClass('MyComponent', {} as any, [{
name: 'Component',
import: {from: '@angular/core', name: 'Component'},
node: null as any,
args: null
}]));
const serviceClass = getDeclaration(program, 'test.js', 'MyService', ts.isClassDeclaration);
file.decoratedClasses.push(new ParsedClass('MyService', {} as any, [{
name: 'Injectable',
import: {from: '@angular/core', name: 'Injectable'},
node: null as any,
args: null
}]));
return file;
}
describe('Analyzer', () => {
describe('analyzeFile()', () => {
let program: ts.Program;
let testHandler: jasmine.SpyObj<DecoratorHandler<any>>;
let result: AnalyzedFile;
beforeEach(() => {
program = makeProgram(TEST_PROGRAM);
const file = createParsedFile(program);
const analyzer = new Analyzer(
program.getTypeChecker(), new Esm2015ReflectionHost(program.getTypeChecker()));
testHandler = createTestHandler();
analyzer.handlers = [testHandler];
result = analyzer.analyzeFile(file);
});
it('should return an object containing a reference to the original source file',
() => { expect(result.sourceFile).toBe(program.getSourceFile('test.js') !); });
it('should call detect on the decorator handlers with each class from the parsed file', () => {
expect(testHandler.detect).toHaveBeenCalledTimes(2);
expect(testHandler.detect.calls.allArgs()[0][0]).toEqual([jasmine.objectContaining(
{name: 'Component'})]);
expect(testHandler.detect.calls.allArgs()[1][0]).toEqual([jasmine.objectContaining(
{name: 'Injectable'})]);
});
it('should return an object containing the classes that were analyzed', () => {
expect(result.analyzedClasses.length).toEqual(1);
expect(result.analyzedClasses[0].name).toEqual('MyComponent');
});
it('should analyze and compile the classes that are detected', () => {
expect(testHandler.analyze).toHaveBeenCalledTimes(1);
expect(testHandler.analyze.calls.allArgs()[0][1].name).toEqual('Component');
expect(testHandler.compile).toHaveBeenCalledTimes(1);
expect(testHandler.compile.calls.allArgs()[0][1]).toEqual('Component');
});
});
});

View File

@ -0,0 +1,52 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {makeProgram as _makeProgram} from '../../../ngtsc/testing/in_memory_typescript';
export {getDeclaration} from '../../../ngtsc/testing/in_memory_typescript';
export function makeProgram(...files: {name: string, contents: string}[]): ts.Program {
return _makeProgram([getFakeCore(), ...files], {allowJs: true, checkJs: false}).program;
}
// TODO: unify this with the //packages/compiler-cli/test/ngtsc/fake_core package
export function getFakeCore() {
return {
name: 'node_modules/@angular/core/index.ts',
contents: `
type FnWithArg<T> = (arg?: any) => T;
function callableClassDecorator(): FnWithArg<(clazz: any) => any> {
return null !;
}
function callableParamDecorator(): FnWithArg<(a: any, b: any, c: any) => void> {
return null !;
}
function makePropDecorator(): any {
}
export const Component = callableClassDecorator();
export const Directive = callableClassDecorator();
export const Injectable = callableClassDecorator();
export const NgModule = callableClassDecorator();
export const Input = makePropDecorator();
export const Inject = callableParamDecorator();
export const Self = callableParamDecorator();
export const SkipSelf = callableParamDecorator();
export const Optional = callableParamDecorator();
export class InjectionToken {
constructor(name: string) {}
}
`
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {makeProgram} from '../helpers/utils';
const BASIC_FILE = {
name: '/primary.js',
contents: `
class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
class B {}
B.decorators = [
{ type: Directive, args: [{ selector: '[b]' }] }
];
function x() {}
function y() {}
class C {}
export { A, x, C };
`
};
describe('Esm2015PackageParser', () => {
describe('getDecoratedClasses()', () => {
it('should return an array of object for each class that is exported and decorated', () => {
const program = makeProgram(BASIC_FILE);
const host = new Esm2015ReflectionHost(program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const parsedFiles = parser.parseFile(program.getSourceFile(BASIC_FILE.name) !);
expect(parsedFiles.length).toEqual(1);
const decoratedClasses = parsedFiles[0].decoratedClasses;
expect(decoratedClasses.length).toEqual(1);
const decoratedClass = decoratedClasses[0];
expect(decoratedClass.name).toEqual('A');
expect(ts.isClassDeclaration(decoratedClass.declaration)).toBeTruthy();
expect(decoratedClass.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
});
});

View File

@ -0,0 +1,64 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5FileParser} from '../../src/parsing/esm5_parser';
import {makeProgram} from '../helpers/utils';
const BASIC_FILE = {
name: '/primary.js',
contents: `
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
return A;
}());
var B = (function() {
function B() {}
B.decorators = [
{ type: Directive, args: [{ selector: '[b]' }] }
];
return B;
}());
function x() {}
function y() {}
var C = (function() {
function C() {}
return C;
});
export { A, x, C };
`
};
describe('Esm5FileParser', () => {
describe('getDecoratedClasses()', () => {
it('should return an array of object for each class that is exported and decorated', () => {
const program = makeProgram(BASIC_FILE);
const host = new Esm5ReflectionHost(program.getTypeChecker());
const parser = new Esm5FileParser(program, host);
const parsedFiles = parser.parseFile(program.getSourceFile(BASIC_FILE.name) !);
expect(parsedFiles.length).toEqual(1);
const decoratedClasses = parsedFiles[0].decoratedClasses;
expect(decoratedClasses.length).toEqual(1);
const decoratedClass = decoratedClasses[0];
expect(decoratedClass.name).toEqual('A');
expect(decoratedClass.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
});
});
});

View File

@ -0,0 +1,124 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as mockFs from 'mock-fs';
import {findAllPackageJsonFiles, getEntryPoints} from '../../src/parsing/utils';
function createMockFileSystem() {
mockFs({
'/node_modules/@angular/common': {
'package.json': '{ "fesm2015": "./fesm2015/common.js", "fesm5": "./fesm5/common.js" }',
'fesm2015': {
'common.js': 'DUMMY CONTENT',
'http.js': 'DUMMY CONTENT',
'http/testing.js': 'DUMMY CONTENT',
'testing.js': 'DUMMY CONTENT',
},
'http': {
'package.json': '{ "fesm2015": "../fesm2015/http.js", "fesm5": "../fesm5/http.js" }',
'testing': {
'package.json':
'{ "fesm2015": "../../fesm2015/http/testing.js", "fesm5": "../../fesm5/http/testing.js" }',
},
},
'testing': {
'package.json': '{ "fesm2015": "../fesm2015/testing.js", "fesm5": "../fesm5/testing.js" }',
},
'node_modules': {
'tslib': {
'package.json': '{ }',
'node_modules': {
'other-lib': {
'package.json': '{ }',
},
},
},
},
},
'/node_modules/@angular/other': {
'not-package.json': '{ "fesm2015": "./fesm2015/other.js" }',
'package.jsonot': '{ "fesm5": "./fesm5/other.js" }',
},
'/node_modules/@angular/other2': {
'node_modules_not': {
'lib1': {
'package.json': '{ }',
},
},
'not_node_modules': {
'lib2': {
'package.json': '{ }',
},
},
},
});
}
function restoreRealFileSystem() {
mockFs.restore();
}
describe('findAllPackageJsonFiles()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should find the `package.json` files below the specified directory', () => {
const paths = findAllPackageJsonFiles('/node_modules/@angular/common');
expect(paths.sort()).toEqual([
'/node_modules/@angular/common/http/package.json',
'/node_modules/@angular/common/http/testing/package.json',
'/node_modules/@angular/common/package.json',
'/node_modules/@angular/common/testing/package.json',
]);
});
it('should not find `package.json` files under `node_modules/`', () => {
const paths = findAllPackageJsonFiles('/node_modules/@angular/common');
expect(paths).not.toContain('/node_modules/@angular/common/node_modules/tslib/package.json');
expect(paths).not.toContain(
'/node_modules/@angular/common/node_modules/tslib/node_modules/other-lib/package.json');
});
it('should exactly match the name of `package.json` files', () => {
const paths = findAllPackageJsonFiles('/node_modules/@angular/other');
expect(paths).toEqual([]);
});
it('should exactly match the name of `node_modules/` directory', () => {
const paths = findAllPackageJsonFiles('/node_modules/@angular/other2');
expect(paths).toEqual([
'/node_modules/@angular/other2/node_modules_not/lib1/package.json',
'/node_modules/@angular/other2/not_node_modules/lib2/package.json',
]);
});
});
describe('getEntryPoints()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should return the paths for the specified format from each package.json', () => {
const paths = getEntryPoints('/node_modules/@angular/common', 'fesm2015');
expect(paths.sort()).toEqual([
'/node_modules/@angular/common/fesm2015/common.js',
'/node_modules/@angular/common/fesm2015/http.js',
'/node_modules/@angular/common/fesm2015/http/testing.js',
'/node_modules/@angular/common/fesm2015/testing.js',
]);
});
it('should return an empty array if there are no matching package.json files', () => {
const paths = getEntryPoints('/node_modules/@angular/other', 'fesm2015');
expect(paths).toEqual([]);
});
it('should return an empty array if there are no matching formats', () => {
const paths = getEntryPoints('/node_modules/@angular/other', 'main');
expect(paths).toEqual([]);
});
});

View File

@ -0,0 +1,188 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {Analyzer} from '../../src/analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {Esm2015Renderer} from '../../src/rendering/esm2015_renderer';
function setup(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Esm2015ReflectionHost(program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host);
const renderer = new Esm2015Renderer(host);
return {analyzer, host, parser, program, renderer};
}
function analyze(parser: Esm2015FileParser, analyzer: Analyzer, file: ts.SourceFile) {
const parsedFiles = parser.parseFile(file);
return parsedFiles.map(file => analyzer.analyzeFile(file))[0];
}
describe('Esm2015Renderer', () => {
describe('addImports', () => {
it('should insert the given imports at the start of the source file', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
// Some other content`
};
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
expect(output.toString())
.toEqual(
`import * as i0 from '@angular/core';\n` +
`import * as i1 from '@angular/common';\n` + PROGRAM.contents);
});
});
describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
// Some other content`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
SOME DEFINITION TEXT
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
// Some other content`);
});
});
describe('removeDecorators', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
// Some other content`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[0].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Other }
];
// Some other content`);
});
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Other },
{ type: Directive, args: [{ selector: '[a]' }] }
];
// Some other content`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[1].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Other },
];
// Some other content`);
});
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
// Some other content`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[0].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
export class A {}
// Some other content`);
});
});
});

View File

@ -0,0 +1,223 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {makeProgram} from '../helpers/utils';
import {Analyzer} from '../../src/analyzer';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5FileParser} from '../../src/parsing/esm5_parser';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
function setup(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Esm5ReflectionHost(program.getTypeChecker());
const parser = new Esm5FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host);
const renderer = new Esm5Renderer(host);
return {analyzer, host, parser, program, renderer};
}
function analyze(parser: Esm5FileParser, analyzer: Analyzer, file: ts.SourceFile) {
const parsedFiles = parser.parseFile(file);
return parsedFiles.map(file => analyzer.analyzeFile(file))[0];
}
describe('Esm5Renderer', () => {
describe('addImports', () => {
it('should insert the given imports at the start of the source file', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
return A;
}());
// Some other content
export {A};`
};
const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output, [{name: '@angular/core', as: 'i0'}, {name: '@angular/common', as: 'i1'}]);
expect(output.toString())
.toEqual(
`import * as i0 from '@angular/core';\n` +
`import * as i1 from '@angular/common';\n` + PROGRAM.contents);
});
});
describe('addDefinitions', () => {
it('should insert the definitions directly after the class declaration', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
return A;
}());
// Some other content
export {A};`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
renderer.addDefinitions(output, analyzedFile.analyzedClasses[0], 'SOME DEFINITION TEXT');
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
SOME DEFINITION TEXT
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
return A;
}());
// Some other content
export {A};`);
});
});
describe('removeDecorators', () => {
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] },
{ type: Other }
];
return A;
}());
// Some other content
export {A};`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[0].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Other }
];
return A;
}());
// Some other content
export {A};`);
});
it('should delete the decorator (but cope with no trailing comma) that was matched in the analysis',
() => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Other },
{ type: Directive, args: [{ selector: '[a]' }] }
];
return A;
}());
// Some other content
export {A};`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[1].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Other },
];
return A;
}());
// Some other content
export {A};`);
});
it('should delete the decorator (and its container if there are not other decorators left) that was matched in the analysis',
() => {
const PROGRAM = {
name: 'some/file.js',
contents: `
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
A.decorators = [
{ type: Directive, args: [{ selector: '[a]' }] }
];
return A;
}());
// Some other content
export {A};`
};
const {analyzer, parser, program, renderer} = setup(PROGRAM);
const analyzedFile = analyze(parser, analyzer, program.getSourceFile(PROGRAM.name) !);
const output = new MagicString(PROGRAM.contents);
const analyzedClass = analyzedFile.analyzedClasses[0];
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
decoratorsToRemove.set(
analyzedClass.decorators[0].node.parent !, [analyzedClass.decorators[0].node]);
renderer.removeDecorators(output, decoratorsToRemove);
expect(output.toString()).toEqual(`
/* A copyright notice */
import {Directive} from '@angular/core';
var A = (function() {
function A() {}
return A;
}());
// Some other content
export {A};`);
});
});
});

View File

@ -0,0 +1,182 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
import MagicString from 'magic-string';
import {fromObject, generateMapFileComment} from 'convert-source-map';
import {makeProgram} from '../helpers/utils';
import {AnalyzedClass, Analyzer} from '../../src/analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Esm2015FileParser} from '../../src/parsing/esm2015_parser';
import {Renderer} from '../../src/rendering/renderer';
class TestRenderer extends Renderer {
addImports(output: MagicString, imports: {name: string, as: string}[]) {
output.prepend('\n// ADD IMPORTS\n');
}
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string) {
output.prepend('\n// ADD DEFINITIONS\n');
}
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>) {
output.prepend('\n// REMOVE DECORATORS\n');
}
}
function createTestRenderer() {
const renderer = new TestRenderer();
spyOn(renderer, 'addImports').and.callThrough();
spyOn(renderer, 'addDefinitions').and.callThrough();
spyOn(renderer, 'removeDecorators').and.callThrough();
return renderer as jasmine.SpyObj<TestRenderer>;
}
function analyze(file: {name: string, contents: string}) {
const program = makeProgram(file);
const host = new Esm2015ReflectionHost(program.getTypeChecker());
const parser = new Esm2015FileParser(program, host);
const analyzer = new Analyzer(program.getTypeChecker(), host);
const parsedFiles = parser.parseFile(program.getSourceFile(file.name) !);
return parsedFiles.map(file => analyzer.analyzeFile(file));
}
describe('Renderer', () => {
const INPUT_PROGRAM = {
name: '/file.js',
contents:
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
};
const INPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': '/file.js',
'sourceRoot': '',
'sources': ['/file.ts'],
'names': [],
'mappings':
'AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,MAAM;IACF,GAAG,CAAC,CAAS;QACT,OAAO,CAAC,CAAC;IACb,CAAC;;AACM,YAAU,GAAG;IAChB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE;CACnD,CAAC',
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x: string): string {\n return x;\n }\n static decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n ];\n}'
]
});
const RENDERED_CONTENTS =
`\n// REMOVE DECORATORS\n\n// ADD IMPORTS\n\n// ADD DEFINITIONS\n` + INPUT_PROGRAM.contents;
const OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': '/output_file.js',
'sources': ['/file.js'],
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n];\n'
],
'names': [],
'mappings': ';;;;;;AAAA;;;;;;;;;'
});
const MERGED_OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'sources': ['/file.ts'],
'names': [],
'mappings': ';;;;;;AAAA',
'file': '/output_file.js',
'sourcesContent': [
'import { Directive } from \'@angular/core\';\nexport class A {\n foo(x: string): string {\n return x;\n }\n static decorators = [\n { type: Directive, args: [{ selector: \'[a]\' }] }\n ];\n}'
]
});
describe('renderFile()', () => {
it('should render the modified contents; and a new map file, if the original provided no map file.',
() => {
const renderer = createTestRenderer();
const analyzedFiles = analyze(INPUT_PROGRAM);
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
expect(result.map !.path).toEqual('/output_file.js.map');
expect(result.map !.contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
});
it('should call addImports with the source code and info about the core Angular library.',
() => {
const renderer = createTestRenderer();
const analyzedFiles = analyze(INPUT_PROGRAM);
renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(renderer.addImports.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(renderer.addImports.calls.first().args[1]).toEqual([
{name: '@angular/core', as: 'ɵngcc0'}
]);
});
it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.',
() => {
const renderer = createTestRenderer();
const analyzedFile = analyze(INPUT_PROGRAM)[0];
renderer.renderFile(analyzedFile, '/output_file.js');
expect(renderer.addDefinitions.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS);
expect(renderer.addDefinitions.calls.first().args[1])
.toBe(analyzedFile.analyzedClasses[0]);
expect(renderer.addDefinitions.calls.first().args[2])
.toEqual(
`A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory() { return new A(); } });`);
});
it('should call removeDecorators with the source code, a map of class decorators that have been analyzed',
() => {
const renderer = createTestRenderer();
const analyzedFile = analyze(INPUT_PROGRAM)[0];
renderer.renderFile(analyzedFile, '/output_file.js');
expect(renderer.removeDecorators.calls.first().args[0].toString())
.toEqual(RENDERED_CONTENTS);
// Each map key is the TS node of the decorator container
// Each map value is an array of TS nodes that are the decorators to remove
const map = renderer.removeDecorators.calls.first().args[1] as Map<ts.Node, ts.Node[]>;
const keys = Array.from(map.keys());
expect(keys.length).toEqual(1);
expect(keys[0].getText())
.toEqual(`[\n { type: Directive, args: [{ selector: '[a]' }] }\n]`);
const values = Array.from(map.values());
expect(values.length).toEqual(1);
expect(values[0].length).toEqual(1);
expect(values[0][0].getText()).toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`);
});
it('should merge any inline source map from the original file and write the output as an inline source map',
() => {
const renderer = createTestRenderer();
const analyzedFiles = analyze({
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment()
});
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
expect(result.map).toBe(null);
});
it('should merge any external source map from the original file and write the output to an external source map',
() => {
// Mock out reading the map file from disk
const readFileSyncSpy =
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const renderer = createTestRenderer();
const analyzedFiles = analyze({
...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
});
const result = renderer.renderFile(analyzedFiles[0], '/output_file.js');
expect(result.source.path).toEqual('/output_file.js');
expect(result.source.contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/output_file.js.map'));
expect(result.map !.path).toEqual('/output_file.js.map');
expect(result.map !.contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON());
});
});
});

View File

@ -14,7 +14,7 @@ import {Reference, filterToMembersWithDecorator, reflectObjectLiteral, staticall
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {SelectorScopeRegistry} from './selector_scope';
import {getConstructorDependencies, isAngularCore, unwrapExpression} from './util';
import {getConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util';
const EMPTY_OBJECT: {[key: string]: string} = {};
@ -156,12 +156,13 @@ export function extractQueryMetadata(
throw new Error(`@${name} must have arguments`);
}
const first = name === 'ViewChild' || name === 'ContentChild';
const arg = staticallyResolve(args[0], reflector, checker);
const node = unwrapForwardRef(args[0], reflector);
const arg = staticallyResolve(node, reflector, checker);
// Extract the predicate
let predicate: Expression|string[]|null = null;
if (arg instanceof Reference) {
predicate = new WrappedNodeExpr(args[0]);
predicate = new WrappedNodeExpr(node);
} else if (typeof arg === 'string') {
predicate = [arg];
} else if (isStringArrayOrDie(arg, '@' + name)) {

View File

@ -14,7 +14,7 @@ import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
import {SelectorScopeRegistry} from './selector_scope';
import {getConstructorDependencies, isAngularCore, referenceToExpression, unwrapExpression} from './util';
import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util';
export interface NgModuleAnalysis {
ngModuleDef: R3NgModuleMetadata;
@ -67,14 +67,14 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
if (ngModule.has('imports')) {
const importsMeta = staticallyResolve(
ngModule.get('imports') !, this.reflector, this.checker,
node => this._extractModuleFromModuleWithProvidersFn(node));
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
imports = resolveTypeList(importsMeta, 'imports');
}
let exports: Reference[] = [];
if (ngModule.has('exports')) {
const exportsMeta = staticallyResolve(
ngModule.get('exports') !, this.reflector, this.checker,
node => this._extractModuleFromModuleWithProvidersFn(node));
ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
exports = resolveTypeList(exportsMeta, 'exports');
}
@ -87,9 +87,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const ngModuleDef: R3NgModuleMetadata = {
type: new WrappedNodeExpr(node.name !),
bootstrap: [],
declarations: declarations.map(decl => referenceToExpression(decl, context)),
exports: exports.map(exp => referenceToExpression(exp, context)),
imports: imports.map(imp => referenceToExpression(imp, context)),
declarations: declarations.map(decl => toR3Reference(decl, context)),
exports: exports.map(exp => toR3Reference(exp, context)),
imports: imports.map(imp => toR3Reference(imp, context)),
emitInline: false,
};

View File

@ -10,10 +10,10 @@ import {Expression, ExternalExpr, ExternalReference} from '@angular/compiler';
import * as ts from 'typescript';
import {ReflectionHost} from '../../host';
import {AbsoluteReference, Reference, reflectTypeEntityToDeclaration} from '../../metadata';
import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDeclaration} from '../../metadata';
import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector';
import {referenceToExpression} from './util';
import {toR3Reference} from './util';
@ -199,10 +199,6 @@ export class SelectorScopeRegistry {
} else {
// The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type
// annotation that specifies the needed metadata.
if (ngModuleImportedFrom === null) {
// TODO(alxhub): handle hand-compiled ngModuleDef in the current Program.
throw new Error(`Need to read .d.ts module but ngModuleImportedFrom is unspecified`);
}
data = this._readMetadataFromCompiledClass(node, ngModuleImportedFrom);
// Note that data here could still be null, if the class didn't have a precompiled
// ngModuleDef.
@ -267,7 +263,7 @@ export class SelectorScopeRegistry {
* @param ngModuleImportedFrom module specifier of the import path to assume for all declarations
* stemming from this module.
*/
private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string):
private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string|null):
ModuleData|null {
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`.
// TODO(alxhub): investigate caching of .d.ts module metadata.
@ -348,7 +344,8 @@ export class SelectorScopeRegistry {
* This operation assumes that these types should be imported from `ngModuleImportedFrom` unless
* they themselves were imported from another absolute path.
*/
private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string): Reference[] {
private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string|null):
Reference[] {
if (!ts.isTupleTypeNode(def)) {
return [];
}
@ -357,11 +354,16 @@ export class SelectorScopeRegistry {
throw new Error(`Expected TypeQueryNode`);
}
const type = element.exprName;
const {node, from} = reflectTypeEntityToDeclaration(type, this.checker);
const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
const clazz = node as ts.Declaration;
const id = reflectIdentifierOfDeclaration(clazz);
return new AbsoluteReference(node, id !, moduleName, id !.text);
if (ngModuleImportedFrom !== null) {
const {node, from} = reflectTypeEntityToDeclaration(type, this.checker);
const moduleName = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
const id = reflectIdentifierOfDeclaration(node);
return new AbsoluteReference(node, id !, moduleName, id !.text);
} else {
const {node} = reflectTypeEntityToDeclaration(type, this.checker);
const id = reflectIdentifierOfDeclaration(node);
return new ResolvedReference(node, id !);
}
});
}
}
@ -384,7 +386,7 @@ function convertReferenceMap(
map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> {
return new Map<string, Expression>(Array.from(map.entries()).map(([selector, ref]): [
string, Expression
] => [selector, referenceToExpression(ref, context)]));
] => [selector, toR3Reference(ref, context).value]));
}
function convertScopeToExpressions(

View File

@ -6,11 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Expression, R3DependencyMetadata, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
import {Expression, R3DependencyMetadata, R3Reference, R3ResolvedDependencyType, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {Decorator, ReflectionHost} from '../../host';
import {Reference} from '../../metadata';
import {AbsoluteReference, ImportMode, Reference} from '../../metadata';
export function getConstructorDependencies(
clazz: ts.ClassDeclaration, reflector: ReflectionHost,
@ -79,12 +79,13 @@ export function getConstructorDependencies(
return useType;
}
export function referenceToExpression(ref: Reference, context: ts.SourceFile): Expression {
const exp = ref.toExpression(context);
if (exp === null) {
export function toR3Reference(ref: Reference, context: ts.SourceFile): R3Reference {
const value = ref.toExpression(context, ImportMode.UseExistingImport);
const type = ref.toExpression(context, ImportMode.ForceNewImport);
if (value === null || type === null) {
throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`);
}
return exp;
return {value, type};
}
export function isAngularCore(decorator: Decorator): boolean {
@ -103,3 +104,69 @@ export function unwrapExpression(node: ts.Expression): ts.Expression {
}
return node;
}
function expandForwardRef(arg: ts.Expression): ts.Expression|null {
if (!ts.isArrowFunction(arg) && !ts.isFunctionExpression(arg)) {
return null;
}
const body = arg.body;
// Either the body is a ts.Expression directly, or a block with a single return statement.
if (ts.isBlock(body)) {
// Block body - look for a single return statement.
if (body.statements.length !== 1) {
return null;
}
const stmt = body.statements[0];
if (!ts.isReturnStatement(stmt) || stmt.expression === undefined) {
return null;
}
return stmt.expression;
} else {
// Shorthand body - return as an expression.
return body;
}
}
/**
* Possibly resolve a forwardRef() expression into the inner value.
*
* @param node the forwardRef() expression to resolve
* @param reflector a ReflectionHost
* @returns the resolved expression, if the original expression was a forwardRef(), or the original
* expression otherwise
*/
export function unwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression {
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) ||
node.arguments.length !== 1) {
return node;
}
const expr = expandForwardRef(node.arguments[0]);
if (expr === null) {
return node;
}
const imp = reflector.getImportOfIdentifier(node.expression);
if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
return node;
} else {
return expr;
}
}
/**
* A foreign function resolver for `staticallyResolve` which unwraps forwardRef() expressions.
*
* @param ref a Reference to the declaration of the function being called (which might be
* forwardRef)
* @param args the arguments to the invocation of the forwardRef expression
* @returns an unwrapped argument if `ref` pointed to forwardRef, or null otherwise
*/
export function forwardRefResolver(
ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration>,
args: ts.Expression[]): ts.Expression|null {
if (!(ref instanceof AbsoluteReference) || ref.moduleName !== '@angular/core' ||
ref.symbolName !== 'forwardRef' || args.length !== 1) {
return null;
}
return expandForwardRef(args[0]);
}

View File

@ -1,77 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/**
* The TypeScript compiler host used by `ngtsc`.
*
* It's mostly identical to the native `CompilerHost`, but also includes the ability to
* asynchronously resolve resources.
*/
export interface CompilerHost extends ts.CompilerHost {
/**
* Begin processing a resource file.
*
* When the returned Promise resolves, `loadResource` should be able to synchronously produce a
* `string` for the given file.
*/
preloadResource(file: string): Promise<void>;
/**
* Like `readFile`, but reads the contents of a resource file which may have been pre-processed
* by `preloadResource`.
*/
loadResource(file: string): string|undefined;
}
/**
* Implementation of `CompilerHost` which delegates to a native TypeScript host in most cases.
*/
export class NgtscCompilerHost implements CompilerHost {
constructor(private delegate: ts.CompilerHost) {}
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined,
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
return this.delegate.getSourceFile(
fileName, languageVersion, onError, shouldCreateNewSourceFile);
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return this.delegate.getDefaultLibFileName(options);
}
writeFile(
fileName: string, data: string, writeByteOrderMark: boolean,
onError: ((message: string) => void)|undefined,
sourceFiles: ReadonlyArray<ts.SourceFile>): void {
return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
getDirectories(path: string): string[] { return this.delegate.getDirectories(path); }
getCanonicalFileName(fileName: string): string {
return this.delegate.getCanonicalFileName(fileName);
}
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
getNewLine(): string { return this.delegate.getNewLine(); }
fileExists(fileName: string): boolean { return this.delegate.fileExists(fileName); }
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
loadResource(file: string): string|undefined { throw new Error('Method not implemented.'); }
preloadResource(file: string): Promise<void> { throw new Error('Method not implemented.'); }
}

View File

@ -53,9 +53,9 @@ export enum ClassMemberKind {
*/
export interface ClassMember {
/**
* TypeScript reference to the class member itself.
* TypeScript reference to the class member itself, or null if it is not applicable.
*/
node: ts.Node;
node: ts.Node|null;
/**
* Indication of which type of member this is (property, method, etc).

View File

@ -7,4 +7,4 @@
*/
export {TypeScriptReflectionHost, filterToMembersWithDecorator, findMember, reflectObjectLiteral, reflectTypeEntityToDeclaration} from './src/reflector';
export {AbsoluteReference, Reference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';
export {AbsoluteReference, ImportMode, Reference, ResolvedReference, ResolvedValue, isDynamicValue, staticallyResolve} from './src/resolver';

View File

@ -119,7 +119,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
}
this.checker.getExportsOfModule(symbol).forEach(exportSymbol => {
// Map each exported Symbol to a Declaration and add it to the map.
const decl = this._getDeclarationOfSymbol(exportSymbol);
const decl = this.getDeclarationOfSymbol(exportSymbol);
if (decl !== null) {
map.set(exportSymbol.name, decl);
}
@ -127,7 +127,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
return map;
}
isClass(node: ts.Declaration): node is ts.ClassDeclaration {
isClass(node: ts.Declaration): boolean {
// In TypeScript code, classes are ts.ClassDeclarations.
return ts.isClassDeclaration(node);
}
@ -138,7 +138,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
if (symbol === undefined) {
return null;
}
return this._getDeclarationOfSymbol(symbol);
return this.getDeclarationOfSymbol(symbol);
}
/**
@ -146,7 +146,7 @@ export class TypeScriptReflectionHost implements ReflectionHost {
*
* @internal
*/
protected _getDeclarationOfSymbol(symbol: ts.Symbol): Declaration|null {
protected getDeclarationOfSymbol(symbol: ts.Symbol): Declaration|null {
let viaModule: string|null = null;
// Look through the Symbol's immediate declarations, and see if any of them are import-type
// statements.

View File

@ -78,14 +78,19 @@ export interface ResolvedValueArray extends Array<ResolvedValue> {}
*/
type Scope = Map<ts.ParameterDeclaration, ResolvedValue>;
export enum ImportMode {
UseExistingImport,
ForceNewImport,
}
/**
* A reference to a `ts.Node`.
*
* For example, if an expression evaluates to a function or class definition, it will be returned
* as a `Reference` (assuming references are allowed in evaluation).
*/
export abstract class Reference {
constructor(readonly node: ts.Node) {}
export abstract class Reference<T extends ts.Node = ts.Node> {
constructor(readonly node: T) {}
/**
* Whether an `Expression` can be generated which references the node.
@ -99,9 +104,9 @@ export abstract class Reference {
* This could be a local variable reference, if the symbol is imported, or it could be a new
* import if needed.
*/
abstract toExpression(context: ts.SourceFile): Expression|null;
abstract toExpression(context: ts.SourceFile, importMode?: ImportMode): Expression|null;
abstract withIdentifier(identifier: ts.Identifier): Reference;
abstract addIdentifier(identifier: ts.Identifier): void;
}
/**
@ -110,12 +115,12 @@ export abstract class Reference {
* This is used for returning references to things like method declarations, which are not directly
* referenceable.
*/
export class NodeReference extends Reference {
constructor(node: ts.Node, readonly moduleName: string|null) { super(node); }
export class NodeReference<T extends ts.Node = ts.Node> extends Reference<T> {
constructor(node: T, readonly moduleName: string|null) { super(node); }
toExpression(context: ts.SourceFile): null { return null; }
withIdentifier(identifier: ts.Identifier): NodeReference { return this; }
addIdentifier(identifier: ts.Identifier): void {}
}
/**
@ -123,14 +128,19 @@ export class NodeReference extends Reference {
*
* Imports generated by `ResolvedReference`s are always relative.
*/
export class ResolvedReference extends Reference {
constructor(node: ts.Node, protected identifier: ts.Identifier) { super(node); }
export class ResolvedReference<T extends ts.Node = ts.Node> extends Reference<T> {
protected identifiers: ts.Identifier[] = [];
constructor(node: T, protected primaryIdentifier: ts.Identifier) { super(node); }
readonly expressable = true;
toExpression(context: ts.SourceFile): Expression {
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.identifier).getSourceFile()) {
return new WrappedNodeExpr(this.identifier);
toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport):
Expression {
const localIdentifier =
pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode);
if (localIdentifier !== null) {
return new WrappedNodeExpr(localIdentifier);
} else {
// Relative import from context -> this.node.getSourceFile().
// TODO(alxhub): investigate the impact of multiple source roots here.
@ -148,16 +158,14 @@ export class ResolvedReference extends Reference {
// same.
if (relative === './') {
// Same file after all.
return new WrappedNodeExpr(this.identifier);
return new WrappedNodeExpr(this.primaryIdentifier);
} else {
return new ExternalExpr(new ExternalReference(relative, this.identifier.text));
return new ExternalExpr(new ExternalReference(relative, this.primaryIdentifier.text));
}
}
}
withIdentifier(identifier: ts.Identifier): ResolvedReference {
return new ResolvedReference(this.node, identifier);
}
addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); }
}
/**
@ -167,24 +175,40 @@ export class ResolvedReference extends Reference {
* the module specifier will be an absolute module name, not a relative path.
*/
export class AbsoluteReference extends Reference {
private identifiers: ts.Identifier[] = [];
constructor(
node: ts.Node, private identifier: ts.Identifier, readonly moduleName: string,
private symbolName: string) {
node: ts.Node, private primaryIdentifier: ts.Identifier, readonly moduleName: string,
readonly symbolName: string) {
super(node);
}
readonly expressable = true;
toExpression(context: ts.SourceFile): Expression {
if (ts.getOriginalNode(context) === ts.getOriginalNode(this.identifier).getSourceFile()) {
return new WrappedNodeExpr(this.identifier);
toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport):
Expression {
const localIdentifier =
pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode);
if (localIdentifier !== null) {
return new WrappedNodeExpr(localIdentifier);
} else {
return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName));
}
}
withIdentifier(identifier: ts.Identifier): AbsoluteReference {
return new AbsoluteReference(this.node, identifier, this.moduleName, this.symbolName);
addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); }
}
function pickIdentifier(
context: ts.SourceFile, primary: ts.Identifier, secondaries: ts.Identifier[],
mode: ImportMode): ts.Identifier|null {
context = ts.getOriginalNode(context) as ts.SourceFile;
let localIdentifier: ts.Identifier|null = null;
if (ts.getOriginalNode(primary).getSourceFile() === context) {
return primary;
} else if (mode === ImportMode.UseExistingImport) {
return secondaries.find(id => ts.getOriginalNode(id).getSourceFile() === context) || null;
} else {
return null;
}
}
@ -203,8 +227,9 @@ export class AbsoluteReference extends Reference {
*/
export function staticallyResolve(
node: ts.Expression, host: ReflectionHost, checker: ts.TypeChecker,
foreignFunctionResolver?: (node: ts.FunctionDeclaration | ts.MethodDeclaration) =>
ts.Expression | null): ResolvedValue {
foreignFunctionResolver?:
(node: Reference<ts.FunctionDeclaration|ts.MethodDeclaration>, args: ts.Expression[]) =>
ts.Expression | null): ResolvedValue {
return new StaticInterpreter(host, checker).visit(node, {
absoluteModuleName: null,
scope: new Map<ts.ParameterDeclaration, ResolvedValue>(), foreignFunctionResolver,
@ -253,7 +278,9 @@ const UNARY_OPERATORS = new Map<ts.SyntaxKind, (a: any) => any>([
interface Context {
absoluteModuleName: string|null;
scope: Scope;
foreignFunctionResolver?(node: ts.FunctionDeclaration|ts.MethodDeclaration): ts.Expression|null;
foreignFunctionResolver?
(ref: Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression>,
args: ReadonlyArray<ts.Expression>): ts.Expression|null;
}
class StaticInterpreter {
@ -394,10 +421,9 @@ class StaticInterpreter {
const result = this.visitDeclaration(
decl.node, {...context, absoluteModuleName: decl.viaModule || context.absoluteModuleName});
if (result instanceof Reference) {
return result.withIdentifier(node);
} else {
return result;
result.addIdentifier(node);
}
return result;
}
private visitDeclaration(node: ts.Declaration, context: Context): ResolvedValue {
@ -502,7 +528,7 @@ class StaticInterpreter {
value = this.visitExpression(member.value, context);
} else if (member.implementation !== null) {
value = new NodeReference(member.implementation, absoluteModuleName);
} else {
} else if (member.node) {
value = new NodeReference(member.node, absoluteModuleName);
}
}
@ -516,9 +542,9 @@ class StaticInterpreter {
const lhs = this.visitExpression(node.expression, context);
if (!(lhs instanceof Reference)) {
throw new Error(`attempting to call something that is not a function: ${lhs}`);
} else if (!isFunctionOrMethodDeclaration(lhs.node)) {
} else if (!isFunctionOrMethodReference(lhs)) {
throw new Error(
`calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]}`);
`calling something that is not a function declaration? ${ts.SyntaxKind[lhs.node.kind]} (${node.getText()})`);
}
const fn = lhs.node;
@ -528,10 +554,11 @@ class StaticInterpreter {
if (fn.body === undefined) {
let expr: ts.Expression|null = null;
if (context.foreignFunctionResolver) {
expr = context.foreignFunctionResolver(fn);
expr = context.foreignFunctionResolver(lhs, node.arguments);
}
if (expr === null) {
throw new Error(`could not resolve foreign function declaration`);
throw new Error(
`could not resolve foreign function declaration: ${node.getSourceFile().fileName} ${(lhs.node.name as ts.Identifier).text}`);
}
// If the function is declared in a different file, resolve the foreign function expression
@ -642,9 +669,9 @@ class StaticInterpreter {
}
}
function isFunctionOrMethodDeclaration(node: ts.Node): node is ts.FunctionDeclaration|
ts.MethodDeclaration {
return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node);
function isFunctionOrMethodReference(ref: Reference<ts.Node>):
ref is Reference<ts.FunctionDeclaration|ts.MethodDeclaration|ts.FunctionExpression> {
return ts.isFunctionDeclaration(ref.node) || ts.isMethodDeclaration(ref.node);
}
function literal(value: ResolvedValue): any {

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