Compare commits

..

134 Commits

Author SHA1 Message Date
9406104c0a release: cut the v9.0.0-next.2 release 2019-08-12 13:46:30 -07:00
c0194e0115 docs: release notes for the v8.2.2 release 2019-08-12 13:42:07 -07:00
914900a561 refactor(ivy): remove load instruction (#32067)
These changes remove the `ɵɵload` instruction which isn't being generated anymore.

PR Close #32067
2019-08-12 12:55:18 -07:00
4ea3e7e000 refactor(ivy): combine query load instructions (#32100)
Combines the `loadViewQuery` and `loadContentQuery` instructions since they have the exact same internal logic. Based on a discussion here: https://github.com/angular/angular/pull/32067#pullrequestreview-273001730

PR Close #32100
2019-08-12 10:32:08 -07:00
6eb9c2fab0 perf(ivy): don't read global state when interpolated values don't change (#32093)
PR Close #32093
2019-08-12 10:31:48 -07:00
7b9414565e style: fix inline comment typo (#32090)
PR Close #32090
2019-08-12 07:06:00 -07:00
37de490e23 Revert "feat(compiler): allow selector-less directives as base classes (#31379)" (#32089)
This reverts commit f90c7a9df0 due to breakages in G3.

PR Close #32089
2019-08-09 18:20:53 -07:00
753080133b Revert "docs(forms): use a number as input value for formControlName (#30606)" (#32088)
This reverts commit bed680cff8.

PR Close #32088
2019-08-09 17:29:27 -07:00
9a37e827e2 Revert "feat(forms): formControlName also accepts a number (#30606)" (#32088)
This reverts commit a647298412.

PR Close #32088
2019-08-09 17:29:27 -07:00
3df54be9e4 build: ensure fixup commits match an earlier, unmerged commit (#32023)
Previously, `validate-commit-message` would treat `fixup! `-prefixed
commits like this:
- It would strip the `fixup! ` prefix.
- It would validate the rest of the commit message header as any other
  commit.

However, fixup commits are special in that they need to exactly match an
earlier commit message header (sans the `fixup! ` prefix) in order for
git to treat them correctly. Otherwise, they will not be squashed into
the original commits and will be merged as is. Fixup commits can end up
not matching their original commit for several reasons (e.g. accidental
typo, changing the original commit message, etc.).

This commit prevents invalid fixup commits to pass validation by
ensuring that they match an earlier (unmerged) commit (i.e. a commit
between the current HEAD and the BASE commit).

NOTE: This new behavior is currently not activated in the pre-commit git
      hook, that is used to validate commit messages (because the
      preceding, unmerged commits are not available there). It _is_
      activated in `gulp validate-commit-message`, which is run as part
      of the `lint` job on CI and thus will detect invalid commits,
      before their getting merged.

PR Close #32023
2019-08-09 15:12:38 -07:00
c0d5684078 fix: do not allow squash! commits when merging (#32023)
While `fixup! ` is fine, `squash! ` means that the commit message needs
tweaking, which cannot be done automatically during merging (i.e. it
should be done by the PR author).

Previously, `validate-commit-message` would always allow
`squash! `-prefixed commits, which would cause problems during merging.

This commit changes `validate-commit-message` to make it configurable
whether such commits are allowed and configures the
`gulp validate-commit-message` task, which is run as part of the `lint`
job on CI, to not allow them.

NOTE: This new check is disabled in the pre-commit git hook that is used
      to validate commit messages, because these commits might still be
      useful during development.

PR Close #32023
2019-08-09 15:12:37 -07:00
2b289250d8 refactor: clean up validate-commit-message script (#32023)
This sets the ground for adding stricter rules for fixup commits in a
follow-up PR.

PR Close #32023
2019-08-09 15:12:37 -07:00
ddd02044ea test: clean up and re-organize validate-commit-message tests (#32023)
Mainly making the tests more closely follow the order of checks in the
function implementation, so that it is easier to follow.

PR Close #32023
2019-08-09 15:12:37 -07:00
c198a27a3c test: update golden files (#32069)
PR Close #32069
2019-08-09 14:15:25 -07:00
4f37487b1c fix(bazel): disable treeshaking when generating FESM and UMD bundles (#32069)
There has been a regression where enabling rollup treeshaking causes errors during runtime because it will drop const access which will always evaluate to true or false. However, such `const` in `@angular/core` cannot be dropped because their value is changed when NGCC is run on `@angular/core`

VE
```
const SWITCH_IVY_ENABLED__POST_R3__ = true;
const SWITCH_IVY_ENABLED__PRE_R3__ = false;
const ivyEnabled = SWITCH_IVY_ENABLED__PRE_R3__;
```

Ivy (After NGCC)
```
const SWITCH_IVY_ENABLED__POST_R3__ = true;
const SWITCH_IVY_ENABLED__PRE_R3__ = false;
const ivyEnabled = SWITCH_IVY_ENABLED__POST_R3__;
```

FESM2015
```
load(path) {
	/** @type {?} */
	const legacyOfflineMode = this._compiler instanceof Compiler;
	return legacyOfflineMode ? this.loadFactory(path) : this.loadAndCompile(path);
}
```

ESM2015
```
 load(path) {
	/** @type {?} */
	const legacyOfflineMode = !ivyEnabled && this._compiler instanceof Compiler;
	return legacyOfflineMode ? this.loadFactory(path) : this.loadAndCompile(path);
}
```

From the above we can see that `ivyEnabled ` is being treeshaken away when generating the FESM bundle which is causing runtime errors such as `Cannot find module './lazy/lazy.module.ngfactory'` since in Ivy we will always load the factories.

PR Close #32069
2019-08-09 14:15:25 -07:00
0ddf0c4895 fix(compiler): do not remove whitespace wrapping i18n expansions (#31962)
Similar to interpolation, we do not want to completely remove whitespace
nodes that are siblings of an expansion.

For example, the following template

```html
<div>
  <strong>items left<strong> {count, plural, =1 {item} other {items}}
</div>
```

was being collapsed to

```html
<div><strong>items left<strong>{count, plural, =1 {item} other {items}}</div>
```

which results in the text looking like

```
items left4
```

instead it should be collapsed to

```html
<div><strong>items left<strong> {count, plural, =1 {item} other {items}}</div>
```

which results in the text looking like

```
items left 4
```

---

**Analysis of the code and manual testing has shown that this does not cause
the generated ids to change, so there is no breaking change here.**

PR Close #31962
2019-08-09 12:03:50 -07:00
fd6ed1713d ci: remove codefresh config and supporting files (#32058)
PR Close #32058
2019-08-09 10:53:20 -07:00
eb5412d76f fix(ivy): reuse compilation scope for incremental template changes. (#31932)
Previously if only a component template changed then we would know to
rebuild its component source file. But the compilation was incorrect if the
component was part of an NgModule, since we were not capturing the
compilation scope information that had a been acquired from the NgModule
and was not being regenerated since we were not needing to recompile
the NgModule.

Now we register compilation scope information for each component, via the
`ComponentScopeRegistry` interface, so that it is available for incremental
compilation.

The `ComponentDecoratorHandler` now reads the compilation scope from a
`ComponentScopeReader` interface which is implemented as a compound
reader composed of the original `LocalModuleScopeRegistry` and the
`IncrementalState`.

Fixes #31654

PR Close #31932
2019-08-09 10:50:40 -07:00
7533338362 build: update tslint to version ~5.18.0 (#31879)
PR Close #31879
2019-08-09 10:47:43 -07:00
9896d438c0 refactor(core): move renderer2 migration lint rule into google3 folder (#31817)
Moves the `renderer_to_renderer2` migration google3 tslint rule
into the new `google3` directory. This is done for consistency
as we recently moved all google3 migration rules into a new
`google3` folder (see: f69e4e6f77).

PR Close #31817
2019-08-09 10:46:45 -07:00
684579b338 build: create google3 migration tests bazel target (#31817)
Creates a separate bazel target for the google3 migration
tests. The benefit is that it's faster to run tests for
public migrations in development. Google3 lint rules are
usually another story/implementation and the tests are quite
slow due to how TSLint applies replacements.

Additionally if something changes in the google3 tslint rules,
the tests which aren't affected re-run unnecessarily.

PR Close #31817
2019-08-09 10:46:45 -07:00
695f322dc1 docs: clarify pipe naming (#31806)
PR Close #31806
2019-08-09 10:45:49 -07:00
f90c7a9df0 feat(compiler): allow selector-less directives as base classes (#31379)
In Angular today, the following pattern works:

```typescript
export class BaseDir {
  constructor(@Inject(ViewContainerRef) protected vcr: ViewContainerRef) {}
}

@Directive({
  selector: '[child]',
})
export class ChildDir extends BaseDir {
  // constructor inherited from BaseDir
}
```

A decorated child class can inherit a constructor from an undecorated base
class, so long as the base class has metadata of its own (for JIT mode).
This pattern works regardless of metadata in AOT.

In Angular Ivy, this pattern does not work: without the @Directive
annotation identifying the base class as a directive, information about its
constructor parameters will not be captured by the Ivy compiler. This is a
result of Ivy's locality principle, which is the basis behind a number of
compilation optimizations.

As a solution, @Directive() without a selector will be interpreted as a
"directive base class" annotation. Such a directive cannot be declared in an
NgModule, but can be inherited from. To implement this, a few changes are
made to the ngc compiler:

* the error for a selector-less directive is now generated when an NgModule
  declaring it is processed, not when the directive itself is processed.
* selector-less directives are not tracked along with other directives in
  the compiler, preventing other errors (like their absence in an NgModule)
  from being generated from them.

PR Close #31379
2019-08-09 10:45:22 -07:00
f2466cf4ee docs: fix cli builder doc (#31305)
PR Close #31305
2019-08-09 10:42:23 -07:00
bed680cff8 docs(forms): use a number as input value for formControlName (#30606)
PR #29473 changed the docs to use a string as the input value of `formControlName`, as it used to only accept a string.
This has been changed, and `formControlName` now accepts a string or a number, so the example in the docs can use a binding as they used to.

PR Close #30606
2019-08-09 10:39:23 -07:00
a647298412 feat(forms): formControlName also accepts a number (#30606)
This commit relaxes the type of the `formControlName` input to accept both a `string` and a `number`.

Currently, when using a `FormArray`, most templates look like:

```
<div formArrayName="tags">
  <div *ngFor="let tag of tagsArray.controls; index as i">
    <input [formControlName]="i">
  </div>
</div>
```

Here `formControlName` receives a number whereas its input type is a string.

This is fine for VE and `fullTemplateTypeCheck`, but not for Ivy which does a more thorough type checking on inputs with `fullTemplateTypeCheck` enabled and throws `Type 'number' is not assignable to type 'string'`. It is fixable by using `formControlName="{{i}}"` but you have to know the difference between `a="{{b}}"` and `[a]="b"` and change it all over the application codebase. This commit allows the existing code to still type-check.

PR Close #30606
2019-08-09 10:39:22 -07:00
2abbe98e33 release: cut the v9.0.0-next.1 release 2019-08-08 14:03:23 -07:00
7613f13e54 docs: release notes for the v8.2.1 release 2019-08-08 13:53:11 -07:00
4b8cdd4b57 docs(router): rename incorrect class names (#31815)
PR Close #31815
2019-08-08 12:16:46 -07:00
17e289c39f docs: correct description of output decorator and add links to guide (#31780)
PR Close #31780
2019-08-08 12:15:29 -07:00
2913340af7 docs: update rxjs refs (#31780)
PR Close #31780
2019-08-08 12:15:29 -07:00
f8c27d42ed docs: add note on compatibility mode (#31808)
PR Close #31808
2019-08-08 12:13:57 -07:00
17bb633031 docs: remove download for testing examples (#31949)
Examples for testing are designed specifically to run in StackBlitz and
are not regular cli projects (e.g. their `main.ts` files may load and run
tests instead of bootstrapping an app). They are not intended to be
downloaded and built/run locally using the cli.

In order to avoid confusing users, this commit removes the download links
from the guide. If desired, the projects can still be exported locally
via StackBlitz's UI.

Related to #31020 and #31937.

PR Close #31949
2019-08-08 12:13:29 -07:00
9106271f2c refactor(ivy): remove dependency on global state from isCreationMode (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
48a3741d5a refactor(ivy): remove global state access in elementCreate (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
9d1f43f3ba perf(ivy): remove unnecessary view type checks (#31959)
PR Close #31959
2019-08-08 12:13:00 -07:00
6f98107d5e perf(ivy): remove global state access in createEmbeddedViewAndNode (#31959)
PR Close #31959
2019-08-08 12:12:59 -07:00
a8e2ee1343 fix(language-service): Make Definition and QuickInfo compatible with TS LS (#31972)
Now that the Angular LS is a proper tsserver plugin, it does not make
sense for it to maintain its own language service API.

This is part one of the effort to remove our custom LanguageService
interface.
This interface is cumbersome because we have to do two transformations:
  ng def -> ts def -> lsp definition

The TS LS interface is more comprehensive, so this allows the Angular LS
to return more information.

PR Close #31972
2019-08-08 12:00:56 -07:00
e906a4f0d8 docs: fix commit link typo in changelog (#32006)
PR Close #32006
2019-08-08 11:48:25 -07:00
b5b33d12d6 docs: remove extraneous word from sentence in styleguide (#32038)
PR Close #32038
2019-08-08 11:43:12 -07:00
22d3cabc10 docs: add missing Oxford comma in styleguide (#32040)
PR Close #32040
2019-08-08 11:42:36 -07:00
a06043b703 perf(ivy): remove global state access from setUpAttributes (#32041)
PR Close #32041
2019-08-08 11:39:20 -07:00
4689ea2727 refactor(ivy): remove unused code (#32041)
PR Close #32041
2019-08-08 11:39:20 -07:00
939529ce5d docs: remove duplicate sentence in toh-pt5 (#32021)
PR Close #32021
2019-08-08 11:19:37 -07:00
46304a4f83 feat(ivy): show error when trying to publish NGCC'd packages (#32031)
Publishing of NGCC packages should not be allowed. It is easy for a user to publish an NGCC'd version of a library they have workspace libraries which are being used in a workspace application.

If a users builds a library and afterwards the application, the library will be transformed with NGCC and since NGCC taints the distributed files that should be published.

With this change we use the npm/yarn `prepublishOnly` hook to display and error and abort the process with a non zero error code when a user tries to publish an NGCC version of the package.

More info: https://docs.npmjs.com/misc/scripts

PR Close #32031
2019-08-08 11:17:38 -07:00
f7eebd0227 build(docs-infra): upgrade cli command docs sources to bb4be27da (#32036)
Updating [angular#master](https://github.com/angular/angular/tree/master) from [cli-builds#master](https://github.com/angular/cli-builds/tree/master).

##
Relevant changes in [commit range](403bcb01c...bb4be27da):

**Modified**
- help/new.json

##

PR Close #32036
2019-08-08 11:16:49 -07:00
8af2cc1efe docs: use proper case in styleguide sentence (#32039)
PR Close #32039
2019-08-08 11:15:52 -07:00
e5a89e047c docs: update node:8-browsers to 10 in testing (#32049)
Fixes #31993

PR Close #32049
2019-08-08 11:15:23 -07:00
29d3b68554 fix(ivy): ngcc - correctly update package.json when createNewEntryPointFormats is true (#32052)
Previously, when run with `createNewEntryPointFormats: true`, `ngcc`
would only update `package.json` with the new entry-point for the first
format property that mapped to a format-path. Subsequent properties
mapping to the same format-path would be detected as processed and not
have their new entry-point format recorded in `package.json`.

This commit fixes this by ensuring `package.json` is updated for all
matching format properties, when writing an `EntryPointBundle`.

PR Close #32052
2019-08-08 11:14:38 -07:00
93d27eefd5 refactor(ivy): ngcc - remove redundant entryPoint argument from writeBundle() (#32052)
The entry-point is already available through the `bundle` argument, so
passing it separately is redundant.

PR Close #32052
2019-08-08 11:14:38 -07:00
ed70f73794 refactor(ivy): ngcc - remove formatProperty from EntryPointBundle (#32052)
Remove the `formatProperty` property from the `EntryPointBundle`
interface, because the property is not directly related to that type.

It was only used in one place, when calling `fileWriter.writeBundle()`,
but we can pass `formatProperty` directrly to `writeBundle()`.

PR Close #32052
2019-08-08 11:14:38 -07:00
ef12e10e59 refactor(ivy): ngcc - split work into distinct analyze/compile/execute phases (#32052)
This refactoring more clearly separates the different phases of the work
performed by `ngcc`, setting the ground for being able to run each phase
independently in the future and improve performance via parallelization.

Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1

PR Close #32052
2019-08-08 11:14:38 -07:00
2954d1b5ca refactor(ivy): ngcc - only try to process the necessary properties (#32052)
This change basically moves some checks to happen up front and ensures
we don't try to process any more properties than we absolutely need.
(The properties would not be processed before either, but we would
consider them, before finding out that they have already been processed
or that they do not exist in the entry-point's `package.json`.)

This change should make no difference in the work done by `ngcc`, but it
transforms the code in a way that makes the actual work known earlier,
thus making it easier to parallelize the processing of each property in
the future.

PR Close #32052
2019-08-08 11:14:38 -07:00
3077c9a1f8 refactor(ivy): ngcc - make EntryPointJsonProperty-related types and checks a little more strict (#32052)
PR Close #32052
2019-08-08 11:14:38 -07:00
9537b2ff84 refactor(ivy): ngcc - fix return type on makeEntryPointBundle() (#32052)
In commit 7b55ba58b (part of PR #29092), the implementation of
`makeEntryPointBundle()` was changed such that it now always return
`EntryPointBundle` (and not `null`).
However, the return type was not updated and as result we continued to
unnecessarily handle `null` as a potential return value in some places.

This commit fixes the return type to reflect the implementation and
removes the redundant code that was dealing with `null`.

PR Close #32052
2019-08-08 11:14:37 -07:00
961d663fbe fix(ivy): ngcc - report an error if a target has missing dependencies (#31872)
Previously, we either crashed with an obscure error or silently did
nothing. Now we throw an exception but with a helpful message.

PR Close #31872
2019-08-05 13:06:49 -07:00
57e15fc08b fix(ivy): ngcc - do not consider builtin NodeJS modules as missing (#31872)
ngcc analyzes the dependency structure of the entrypoints it needs to
process, as the compilation of entrypoints is ordering sensitive: any
dependent upon entrypoint must be compiled before its dependees. As part
of the analysis of the dependency graph, it is detected when a
dependency of entrypoint is not installed, in which case that entrypoint
will be marked as ignored.

For libraries that work with Angular Universal to run in NodeJS, imports
into builtin NodeJS modules can be present. ngcc's dependency analyzer
can only resolve imports within the TypeScript compilation, which
builtin modules are not part of. Therefore, such imports would
erroneously cause the entrypoint to become ignored.

This commit fixes the problem by taking the NodeJS builtins into account
when dealing with missing imports.

Fixes #31522

PR Close #31872
2019-08-05 13:06:49 -07:00
b70746a113 fix(ivy): ngcc - prevent crash when analyzed target is ignored (#31872)
ngcc analyzes the dependency structure of the entrypoints it needs to
process, as the compilation of entrypoints is ordering sensitive: any
dependent upon entrypoint must be compiled before its dependees. As part
of the analysis of the dependency graph, it is detected when a
dependency of entrypoint is not installed, in which case that entrypoint
will be marked as ignored.

When a target entrypoint to compile is provided, it could occur that
given target is considered ignored because one of its dependencies might
be missing. This situation was not dealt with currently, instead
resulting in a crash of ngcc.

This commit prevents the crash by taking the above scenario into account.

PR Close #31872
2019-08-05 13:06:49 -07:00
0709ed4c2b docs: add doc for compiler options (#31829)
PR Close #31829
2019-08-05 13:01:10 -07:00
fa699f65d7 docs: updated doc for timezone param in DatePipe (#31827)
PR Close #31827
2019-08-05 13:00:26 -07:00
18bc4eda9f docs: updated doc for timezone param in DatePipe (#31827)
PR Close #31827
2019-08-05 13:00:26 -07:00
f542649b2b docs: fix typo in Workspace Config guide code snippet (sourceMaps --> sourceMap) (#31957)
PR Close #31957
2019-08-05 12:57:18 -07:00
a574e462c9 build(docs-infra): ensure the locally built packages exist and are up-to-date in yarn setup-local (#31985)
The `setup-local` npm script uses `NgPackagesInstaller` to replace the
Angular packages with the locally built ones. Previously, it would (a)
assume that the packages were built and (b) it would do anything if the
currently installed versions already correspond to locally built
packages (even if not the latest version).
This could lead to all sorts of errors, such as:
- Confusing error messages, `dist/packages-dist/` was missing.
- Using outdated build artifacts from `dist/packages-dist/` without a
  warning.
- Not installing the latest locally built packages, because the ones
  installed already corresponded to locally built (but older) ones.

This commit fixes these issues by ensuring that:
- The local packages are always built before being used by
  `NgPackagesInstaller`.
- The local packages are installed, even if the ones install already
  correspond to local packages.

NOTE: Special `*-ci` scripts are introduced (for use on CI) that skip
      building the local packages, since that step would have been taken
      care of (in a more efficient way) in a previous CI step.

PR Close #31985
2019-08-05 12:56:44 -07:00
65cafa0eec fix(docs-infra): correctly handle multiple occurrences of an option in NgPackagesInstaller (#31985)
PR Close #31985
2019-08-05 12:56:43 -07:00
18aa173d39 feat(docs-infra): support building the local Angular packages in NgPackagesInstaller (#31985)
Previously, when `NgPackagesInstaller` needed to install the local
Angular packages to a project, it assumed the `dist/packages-dist/`
would exist and contain up-to-date built packages. I.e. it was up to the
user to have built the packages first (by running the appropriate
script).
However, many people not familiar with the `aio/` infrastructure assumed
that `yarn build-local` or `yarn build-with-ivy` would take care of all
the necessary steps.

To avoid getting confusing errors (or worse yet, using outdated local
packages), `NgPackagesInstaller` now has an option to build the packages
before using them.
One caveat is that the build script does not currently work on Windows,
so a warning is printed instead, letting the user know they need to
(somehow) take care of it themselves.

NOTE 1: Since the build script is using bazel, running it should not be
        expensive if the directory is up-to-date (i.e. no changes have
        been made to source code after the last build).
NOTE 2: This commit adds support for `--build-packages`, but does not
        change the default behavior (not building the packages). It will
        be turned on in a subsequent commit.

PR Close #31985
2019-08-05 12:56:43 -07:00
bc8eb8508b refactor(docs-infra): minor refactorings and code simplification in NgPackagesInstaller (#31985)
PR Close #31985
2019-08-05 12:56:43 -07:00
7db269ba6a fix(ivy): ngcc - correctly detect formats processed in previous runs (#32003)
Previously, `ngcc` would avoid processing a `formatPath` that a property
in `package.json` mapped to, if either the _property_ was marked as
processed or the `formatPath` (i.e. the file(s)) was processed in the
same `ngcc` run (since the `compiledFormats` set was not persisted
across runs).
This could lead in a situation where a `formatPath` would be compiled
twice (if for example properties `a` and `b` both mapped to the same
`formatPath` and one would run `ngcc` for property `a` and then `b`).

This commit fixes it by ensuring that as soon as a `formatPath` has been
processed all corresponding properties are marked as processed (which
persists across `ngcc` runs).

PR Close #32003
2019-08-05 12:54:17 -07:00
8e5567d964 perf(ivy): ngcc - avoid unnecessary operations when we only need one format processed (#32003)
Previously, when `ngcc` was called with `compileAllFormats === false`
(i.e. how `@angular/cli` calls it), it would not attempt to process
more properties, once the first was successfully processed. However, it
_would_ continue looping over them and perform some unnecessary
operations, such as:
- Determining the format each property maps to (which can be an
  expensive operation for some properties mapping to either UMD or
  CommonJS).
- Checking whether each property has been processed (which involves
  checking whether any property has been processed with a different
  version of `ngcc` each time).
- Potentially marking properties as processed (which involves a
  file-write operation).

This commit avoids the unnecessary operations by entirely skipping
subsequent properties, once the first one has been successfully
processed. While this theoretically improves performance, it is not
expected to have any noticeable impact in practice, since the list of
`propertiesToConsider` is typically small and the most expensive
operation (marking a property as processed) has low likelihood of
happening (plus these operations are a tiny fraction of `ngcc`'s work).

PR Close #32003
2019-08-05 12:54:17 -07:00
541ce98432 perf(ivy): ngcc - avoid unnecessary file-write operations when marking properties as processed (#32003)
Previously, when `ngcc` needed to mark multiple properties as processed
(e.g. a processed format property and `typings` or all supported
properties for a non-Angular entry-point), it would update each one
separately and write the file to disk multiple times.

This commit changes this, so that multiple properties can be updated at
once with one file-write operation. While this theoretically improves
performance (reducing the I/O operations), it is not expected to have
any noticeable impact in practice, since these operations are a tiny
fraction of `ngcc`'s work.

This change will be useful for a subsequent change to mark all
properties that map to the same `formatPath` as processed, once it is
processed the first time.

PR Close #32003
2019-08-05 12:54:17 -07:00
e7e3f5d952 refactor(ivy): ngcc - remove unused check for format support (#32003)
Now that `ngcc` supports all `EntryPointFormat`s, there is no need to
check if a format is supported, so this operation was a no-op.

PR Close #32003
2019-08-05 12:54:17 -07:00
382d3ed1d2 fix(docs-infra): ui polish (#31013)
- Change margin to the header so that the the label aligns with the header
- Make code in API pages pre-wrap so that you can see the actual text on the screen - helps with issue #27296
- Modified text to be title case in label API to be consistent with rest of app labels
- Removed unused table of contents SCSS file
- TOC SCSS file reorg cleanup
- Soften headers font-weight in API pages
- Make linenums ordered list inside code examples always show as numbers

PR Close #31013
2019-08-05 12:53:02 -07:00
a07de82f79 refactor(upgrade): avoid using static inherited method (#31986)
Using `ParentInjectorPromise.all()` (which is a static method inherited
from `SyncPromise`) causes Closure Compiler (or some related tool) to
complain:

```
TypeError: ...$packages$upgrade$src$common$src$downgrade_component_ParentInjectorPromise.all is not a function
```

Switching to `SyncPromise.all()` (the static method on the parent class)
to avoid this error.

PR Close #31986
2019-08-05 10:01:33 -07:00
2e84f4e0cd build(docs-infra): fix StackBlitz projects and ZIPs for testing guide examples (#31937)
- Update the `stackblitz.json` config files (used for generating
  Stackblitz projects and ZIP archives) to include the correct files
  (taking into account the current layout of the example projects).
- Update the boilerplate files for `testing` examples to match the
  current layout of the example projects.

PR Close #31937
2019-08-05 09:54:52 -07:00
a5b12db7d6 build(docs-infra): correct list of files included in ZIPs to match current layout (#31937)
PR Close #31937
2019-08-05 09:54:52 -07:00
8b94d6a402 build(docs-infra): remove unused StackBlitz/ZIP configurations for setup example (#31937)
The generated StackBlitz project and ZIP archives are no longer used
(and they are broken anyway), so removing them altogether to avoid
confusion.

Note: Still keeping the example project, because that is referenced in
the `setup` guide.

PR Close #31937
2019-08-05 09:54:52 -07:00
96cbcd6da4 feat(zone.js): support Promise.allSettled (#31849)
PR Close #31849
2019-08-05 09:54:37 -07:00
2a6e6c02ed build(zone.js): update gulp task to gen changelog automatically (#31915)
PR Close #31915
2019-08-02 14:28:49 -07:00
fa4e17082c build(zone.js): update changelog of zone.js (#31915)
PR Close #31915
2019-08-02 14:28:49 -07:00
ebb27727e4 docs: add anyComponentStyle budget description (#31955)
In CLI 8.2 we introduced a new budget see: https://github.com/angular/angular-cli/pull/15127

PR Close #31955
2019-08-02 09:35:38 -07:00
046532b661 docs: add links to web.dev/angular guides (#31804)
This PR adds the following links to web.dev/angular:

1. Link to the guides in the resource section under "Online Training"
2. Links to code-splitting & preloading in the lazy-loading guide
3. Link to the accessibility guide on web.dev/angular

PR Close #31804
2019-08-01 17:45:43 -07:00
7b9891d7cd feat(language-service): Introduce 'angularOnly' flag (#31935)
This PR changes the language service to work in two different modes:

1. TS + Angular
   Plugin augments TS language service to provide additonal Angular
   information. This only works with inline template and is meant to be
   used as a local plugin (configured via tsconfig.json).
2. Angular only
   Plugin only provides information on Angular templates, no TS info at
   all. This effectively disables native TS features and is meant for
   internal use only.

Default mode is `angularOnly = false` so that we don't break any users
already using Angular LS as local plugin.

As part of the refactoring, `undefined` is removed from type aliases
because it is considered bad practice.

go/tsstyle#nullableundefined-type-aliases
```
Type aliases must not include |null or |undefined in a union type.
Nullable aliases typically indicate that null values are being passed
around through too many layers of an application, and this clouds the
source of the original issue that resulted in null. They also make it
unclear when specific values on a class or interface might be absent.
```

PR Close #31935
2019-08-01 17:43:21 -07:00
a2183ddb7a fix(ivy): directive matching not working in some cases when preceded by styling attributes (#31942)
Fixes Ivy's directive matching not capturing attribute selectors when there is one class binding, one style binding and a regular binding that precede  the attribute that would match the directive. The issue appears to come from the fact that we weren't skipping over style bindings correctly which was throwing the loop off not to go into `bindingsMode` and to skip some of the bindings when matching.

PR Close #31942
2019-08-01 17:42:42 -07:00
3122f3415a perf(ivy): remove global state acces from postProcessDirective (#31946)
PR Close #31946
2019-08-01 17:42:24 -07:00
aaf29c8099 perf(ivy): remove firstTemplatePass check from directive instantiation (#31946)
PR Close #31946
2019-08-01 17:42:24 -07:00
32e2f4daef refactor(ivy): remove code duplication around root component view creation (#31946)
PR Close #31946
2019-08-01 17:42:23 -07:00
a7c71d1a57 perf(ivy): remove firstTemplatePass check for component view creation (#31946)
PR Close #31946
2019-08-01 17:42:23 -07:00
f2d47c96c4 fix(ivy): ngcc emits static fields before extra statements (#31933)
This commit changes the emit order of ngcc when a class has multiple static
fields being assigned. Previously, ngcc would emit each static field
followed immediately by any extra statements specified for that field. This
causes issues with downstream tooling such as build optimizer, which expects
all of the static fields for a class to be grouped together. ngtsc already
groups static fields and additional statements. This commit changes ngcc's
ordering to match.

PR Close #31933
2019-08-01 10:45:36 -07:00
184d270725 fix(ivy): DebugElement.triggerEventHandler not picking up events registered via Renderer2 (#31845)
Fixes Ivy's `DebugElement.triggerEventHandler` to picking up events that have been registered through a `Renderer2`, unlike ViewEngine.

This PR resolves FW-1480.

PR Close #31845
2019-08-01 10:13:07 -07:00
a610d12266 docs(core): add missing closing brace in directive input example (#31901)
PR Close #31901
2019-08-01 10:11:50 -07:00
d0d875a3fe fix(ivy): pass schemas field to nested views (#31913)
Prior to this commit, the `schemas` configuration was applied to top-level view only. That leads to problems when using unknown props with elements inside nested views (for example generated as a result of *ngIf). This commit passes `schemas` information down to nested views to make sure that all the checks are consistent.

PR Close #31913
2019-08-01 10:11:01 -07:00
e8b8f6d09b fix(language-service): getSourceFile() should only be called on TS files (#31920)
PR Close #31920
2019-08-01 10:10:09 -07:00
9e9179e915 docs: update Zenika trainings URL in resources.json (#31910)
PR Close #31910
2019-08-01 10:09:49 -07:00
c1ae6124c8 fix(upgrade): compile downgraded components synchronously (if possible) (#31840)
AngularJS compilation is a synchronous operation (unless having to fetch
a template, which is not supported for downgraded components).
Previously, ngUpgrade tried to retain the synchronous nature of the
compilation for downgraded components (when possible), by using a
synchronous thenable implementation (`ParentInjectorPromise`). This was
accidentally broken in #27217 by replacing a call to
`ParentInjectorPromise#then()` (which can be synchronous) with a call to
`Promise.all()` (which is asynchronous).

This commit fixes this by introducing a `SyncPromise.all()` static
method; similar to `Promise.all()` but retaining the synchronous
capabilities of `SyncPromise` (which `ParentInjectorPromise` inherits
from).

Fixes #30330

PR Close #31840
2019-08-01 10:09:02 -07:00
b3b5c66414 refactor(upgrade): extract promise-related utilities to separate file and add tests (#31840)
PR Close #31840
2019-08-01 10:09:02 -07:00
82b97280f3 fix(ivy): speed up ngtsc if project has no templates to check (#31922)
If a project being built with ngtsc has no templates to check, then ngtsc
previously generated an empty typecheck file. This seems to trigger some
pathological behavior in TS where the entire user program is re-checked,
which is extremely expensive. This likely has to do with the fact that the
empty file is not considered an ES module, meaning the module structure of
the program has changed.

This commit causes an export to be produced in the typecheck file regardless
of its other contents, which guarantees that it will be an ES module. The
pathological behavior is avoided and template type-checking is fast once
again.

PR Close #31922
2019-07-31 16:20:38 -07:00
ecffbda664 test: fix outDir in TS integration tests (#29284)
PR Close #29284
2019-07-31 11:40:27 -07:00
adc39752f3 build(docs-infra): upgrade cli command docs sources to 403bcb01c (#31925)
Updating [angular#master](https://github.com/angular/angular/tree/master) from [cli-builds#master](https://github.com/angular/cli-builds/tree/master).

##
Relevant changes in [commit range](1e83a8ecb...403bcb01c):

**Added**
- help/deploy.json

##

PR Close #31925
2019-07-31 11:39:00 -07:00
584b42343f build: Do not generate *.umd.min.js for language service (#31917)
`language-service.umd.min.js` takes a long time to build (because of
running terser), but it is not used at all.
See https://unpkg.com/browse/@angular/language-service@8.1.3/package.json
where 'main' points to the unminified bundle.

PR Close #31917
2019-07-31 11:38:31 -07:00
8e7a0d4ff9 docs: add ReactiveConf 2019 to events (#31895)
PR Close #31895
2019-07-31 11:37:44 -07:00
4db959260b docs(ivy): Add README to indexer module (#31260)
Describe the indexer module for Angular compiler developers. Include
scope of analysis provided by the module and the indexers it targets as
first-party.

PR Close #31260
2019-07-31 11:37:11 -07:00
76503e65c8 release: cut the v9.0.0-next.0 release 2019-07-31 11:04:40 -07:00
eac993dfce docs: release notes for the v8.2.0 release 2019-07-31 10:46:14 -07:00
975917bafd Revert "fix(zone.js): don't wrap uncaught promise error. (#31443)" (#31918)
This reverts commit 2bb9a65351.

It breaks tests in google3 which rely on the error handling behavior.

PR Close #31918
2019-07-30 15:03:49 -07:00
185b3dd08e build(docs-infra): update main bundle size (#31839)
PR #31839 contains perf and code cleanup changes that add 48 bytes to the
main bundle size. Perf gains and code clarity justify this increase.

Unfortunately the size verification job is rejecting this PR as it reports
size gains from a fixed size and not relative increase of size from a particular PR.

It was decided during the internal team discussion to bump up size limits to
correctly reflect current state of the master and increase from this PR.

PR Close #31839
2019-07-30 13:01:33 -07:00
78659ec0b0 perf(ivy): avoid creating holey LView Arrays (#31839)
PR Close #31839
2019-07-30 13:01:33 -07:00
a9ec3db91a perf(ivy): limit creationMode checks (#31839)
PR Close #31839
2019-07-30 13:01:33 -07:00
561ec6a5be perf(ivy): stricter null checks (#31839)
PR Close #31839
2019-07-30 13:01:33 -07:00
c0317d40c9 perf(ivy): call refreshContentQueries only when there are content queries defined (#31839)
PR Close #31839
2019-07-30 13:01:32 -07:00
a4bc0db474 refactor(ivy): remove unnecessary call to setPreviousOrParentTNode (#31839)
setPreviousOrParentTNode is set in enterView so no need to reset it
just before entering a view.

PR Close #31839
2019-07-30 13:01:32 -07:00
430124a051 perf(ivy): only refresh child components if those are defined in a given view (#31839)
PR Close #31839
2019-07-30 13:01:32 -07:00
e08391b333 refactor(ivy): rewrite refreshDynamicEmbeddedViews for clarity (#31839)
PR Close #31839
2019-07-30 13:01:32 -07:00
a77d0e22bf perf(ivy): avoid repeated tView.firstTemplatePass writes (#31839)
PR Close #31839
2019-07-30 13:01:32 -07:00
4f42eb4e77 test(zone.js): add test codes to ensure not include sourcemap (#31892)
PR Close #31892
2019-07-30 13:00:26 -07:00
f216724c2c build(zone.js): remove sourceMappingUrl from bundle (#31892)
Close #31883

PR Close #31892
2019-07-30 13:00:26 -07:00
5c9a8961da fix(zone.js): don't rely on global node typings outside of node/ directory (#31783)
PR Close #31783
2019-07-30 12:59:40 -07:00
3479fddf68 docs: fix typo in Animation guide (#31888)
PR Close #31888
2019-07-30 12:58:52 -07:00
5bebac42f9 docs: change Angular Material Design to Material Design in animations guide (#31889)
PR Close #31889
2019-07-30 12:56:22 -07:00
cbcbe23fd1 build(docs-infra): upgrade cli command docs sources to 1e83a8ecb (#31906)
Updating [angular#master](https://github.com/angular/angular/tree/master) from [cli-builds#master](https://github.com/angular/cli-builds/tree/master).

##
Relevant changes in [commit range](a8fe15cb6...1e83a8ecb):

**Modified**
- help/generate.json

##

PR Close #31906
2019-07-30 12:55:44 -07:00
fc6f48185c fix(ivy): ngcc - render decorators in UMD and CommonJS bundles correctly (#31614)
In #31426 a fix was implemented to render namespaced decorator imports
correctly, however it turns out that the fix only worked when decorator
information was extracted from static properties, not when using
`__decorate` calls.

This commit fixes the issue by creating the decorator metadata with the
full decorator expression, instead of only its name.

Closes #31394

PR Close #31614
2019-07-29 16:10:58 -07:00
80f290e301 fix(ivy): ngcc - recognize suffixed tslib helpers (#31614)
An identifier may become repeated when bundling multiple source files
into a single bundle, so bundlers have a strategy of suffixing non-unique
identifiers with a suffix like $2. Since ngcc operates on such bundles,
it needs to process potentially suffixed identifiers in their canonical
form without the suffix. The "ngx-pagination" package was previously not
compiled fully, as most decorators were not recognized.

This commit ensures that identifiers are first canonicalized by removing
the suffix, such that they are properly recognized and processed by ngcc.

Fixes #31540

PR Close #31614
2019-07-29 16:10:58 -07:00
5e5be43acd refactor(ivy): ngcc - categorize the various decorate calls upfront (#31614)
Any decorator information present in TypeScript is emitted into the
generated JavaScript sources by means of `__decorate` call. This call
contains both the decorators as they existed in the original source
code, together with calls to `tslib` helpers that convey additional
information on e.g. type information and parameter decorators. These
different kinds of decorator calls were not previously distinguished on
their own, but instead all treated as `Decorator` by themselves. The
"decorators" that were actually `tslib` helper calls were conveniently
filtered out because they were not imported from `@angular/core`, a
characteristic that ngcc uses to drop certain decorators.

Note that this posed an inconsistency in ngcc when it processes
`@angular/core`'s UMD bundle, as the `tslib` helper functions have been
inlined in said bundle. Because of the inlining, the `tslib` helpers
appear to be from `@angular/core`, so ngcc would fail to drop those
apparent "decorators". This inconsistency does not currently cause any
issues, as ngtsc is specifically looking for decorators based on  their
name and any remaining decorators are simply ignored.

This commit rewrites the decorator analysis of a class to occur all in a
single phase, instead of all throughout the `ReflectionHost`. This
allows to categorize the various decorate calls in a single sweep,
instead of constantly needing to filter out undesired decorate calls on
the go. As an added benefit, the computed decorator information is now
cached per class, such that subsequent reflection queries that need
decorator information can reuse the cached info.

PR Close #31614
2019-07-29 16:10:57 -07:00
0386c964b5 build: secure yarn lock files (#31640)
See https://yarnpkg.com/blog/2019/07/12/recommended-security-update/

PR Close #31640
2019-07-29 16:10:23 -07:00
f5c605b608 fix(zone.js): should expose some other internal intefaces (#31866)
PR Close #31866
2019-07-29 16:08:55 -07:00
14dba72aee fix(core): DebugElement.listeners not cleared on destroy (#31820)
Currently the `DebugElement.listeners` array are retained after the node is destroyed. This means that they'll continue to fire through `triggerEventHandler` and can cause memory leaks. This has already been fixed in Ivy, but these changes fix it in ViewEngine for consistency.

PR Close #31820
2019-07-29 10:05:19 -07:00
5f0d5e9ccf build: update to nodejs rules 0.34.0 and bazel 0.28.1 (#31824)
nodejs rules 0.34.0 now includes protractor_web_test_suite rule (via new @bazel/protractor rule) so we switch to that location for that rule in this PR so that /packages/bazel/src/protractor can be removed in a future PR

this PR also brings in node toolchain support which was released in nodejs rules 0.33.0. this is a prerequisite for RBE for mac & windows users

bazel schematics also updated with the same. @bazel/bazel 0.28.1 npm package includes transitive dep on hide-bazel-files so we're able to remove an explicit dep on that as well.

PR Close #31824
2019-07-26 15:01:25 -07:00
5296c04f61 fix(ivy): set LOCALE_ID when using the injector (#31566)
In `BrowserModule` the value of `LOCALE_ID` is defined in the `APPLICATION_MODULE_PROVIDERS` after `APP_INITIALIZER` has run.
This PR ensures that `LOCALE_ID` is also set for ivy at the same moment which allows the application to fetch the locale from a backend (for example).

Fixes #31465

FW-1436 #resolve

PR Close #31566
2019-07-26 14:04:47 -07:00
40a0666651 fix(ivy): error when using forwardRef in Injectable's useClass (#30532)
Fixes Ivy throwing an error when something is passed in as a `forwardRef` into `@Injectable`'s `useClass` option. The error was being thrown, because we were trying to get the provider factory off of the wrapper function, rather than the value itself.

This PR resolves FW-1335.

PR Close #30532
2019-07-26 14:02:49 -07:00
4da805243a docs(core): clarify @ContentChild(ren) behavior (#31846)
Describe that @ContentChild(ren) doesn't search within other component templates (doesn't go across "component boundaries").
PR Close #31846
2019-07-26 14:02:10 -07:00
14ae50b4c3 docs: fix header mistake (#31811)
The code example refers to `src/app/heroes/heroes.component.html` but its header was `src/app/hero.service.ts` which was obviously a mistake.
PR Close #31811
2019-07-26 14:01:28 -07:00
397d0ba9a3 test(ivy): fix broken testcase in Windows (#31860)
In #30181, several testcases were added that were failing in Windows.
The reason was that a recent rebase missed a required change to interact
with the compiler's virtualized filesystems. This commit introduces the
required usage of the VFS layer to fix the testcase.

PR Close #31860
2019-07-26 12:22:12 -07:00
859ebdd836 fix(ivy): correctly bind targetToIdentifier to the TemplateVisitor (#31861)
`TemplateVisitor#visitBoundAttribute` currently has to invoke visiting
expressions manually (this is fixed in #31813). Previously, it did not
bind `targetToIdentifier` to the visitor before deferring to the
expression visitor, which breaks the `targetToIdentifier` code. This
fixes that and adds a test to ensure the closure processed correctly.

This change is urgent; without it, many indexing targets in g3 are
broken.

PR Close #31861
2019-07-26 12:03:16 -07:00
30673090ec build(zone.js): add changelog gulptask for zone.js (#31852)
PR Close #31852
2019-07-26 11:30:08 -07:00
6033446d2d docs: release notes for the v8.2.0-rc.0 release 2019-07-26 10:25:18 -07:00
174770e6f3 docs: release notes for the v release 2019-07-25 20:32:22 -07:00
142 changed files with 3334 additions and 1764 deletions

View File

@ -1,3 +1,21 @@
<a name="9.0.0-next.2"></a>
# [9.0.0-next.2](https://github.com/angular/angular/compare/9.0.0-next.1...9.0.0-next.2) (2019-08-12)
### Bug Fixes
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([4f37487](https://github.com/angular/angular/commit/4f37487))
* **compiler:** do not remove whitespace wrapping i18n expansions ([#31962](https://github.com/angular/angular/issues/31962)) ([0ddf0c4](https://github.com/angular/angular/commit/0ddf0c4))
* **ivy:** reuse compilation scope for incremental template changes. ([#31932](https://github.com/angular/angular/issues/31932)) ([eb5412d](https://github.com/angular/angular/commit/eb5412d)), closes [#31654](https://github.com/angular/angular/issues/31654)
### Performance Improvements
* **ivy:** don't read global state when interpolated values don't change ([#32093](https://github.com/angular/angular/issues/32093)) ([6eb9c2f](https://github.com/angular/angular/commit/6eb9c2f))
<a name="8.2.2"></a>
## [8.2.2](https://github.com/angular/angular/compare/8.2.1...8.2.2) (2019-08-12)
@ -7,6 +25,18 @@
* **bazel:** disable treeshaking when generating FESM and UMD bundles ([#32069](https://github.com/angular/angular/issues/32069)) ([3420d29](https://github.com/angular/angular/commit/3420d29))
<a name="9.0.0-next.1"></a>
# [9.0.0-next.1](https://github.com/angular/angular/compare/9.0.0-next.0...9.0.0-next.1) (2019-08-08)
### Bug Fixes
* **language-service:** getSourceFile() should only be called on TS files ([#31920](https://github.com/angular/angular/issues/31920)) ([e8b8f6d](https://github.com/angular/angular/commit/e8b8f6d))
* **language-service:** Make Definition and QuickInfo compatible with TS LS ([#31972](https://github.com/angular/angular/issues/31972)) ([a8e2ee1](https://github.com/angular/angular/commit/a8e2ee1))
* **upgrade:** compile downgraded components synchronously (if possible) ([#31840](https://github.com/angular/angular/issues/31840)) ([c1ae612](https://github.com/angular/angular/commit/c1ae612)), closes [#27217](https://github.com/angular/angular/issues/27217) [#30330](https://github.com/angular/angular/issues/30330)
<a name="8.2.1"></a>
## [8.2.1](https://github.com/angular/angular/compare/8.2.0...8.2.1) (2019-08-08)
@ -17,38 +47,47 @@
<a name="9.0.0-next.0"></a>
# [9.0.0-next.0](https://github.com/angular/angular/compare/8.2.0-next.2...9.0.0-next.0) (2019-07-31)
* Ivy related improvements and fixes
<a name="8.2.0"></a>
# [8.2.0](https://github.com/angular/angular/compare/8.2.0-rc.0...8.2.0) (2019-07-31)
### Bug Fixes
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
<a name="8.2.0-rc.0"></a>
# [8.2.0-rc.0](https://github.com/angular/angular/compare/8.2.0-next.2...8.2.0-rc.0) (2019-07-26)
### Bug Fixes
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
### Features
* **core:** TypeScript 3.5 support ([#31615](https://github.com/angular/angular/issues/31615)) ([6ece7db](https://github.com/angular/angular/commit/6ece7db))
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
* **bazel:** compile targets used for indexing by Kythe with Ivy ([#31786](https://github.com/angular/angular/issues/31786)) ([82055b2](https://github.com/angular/angular/commit/82055b2))
* **upgrade:** support $element in upgraded component template/templateUrl functions ([#31637](https://github.com/angular/angular/issues/31637)) ([29e1c53](https://github.com/angular/angular/commit/29e1c53))
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
### Performance Improvements
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([24ca582](https://github.com/angular/angular/commit/24ca582)), closes [#31627](https://github.com/angular/angular/issues/31627)
### Bug Fixes
* **core:** DebugElement.listeners not cleared on destroy ([#31820](https://github.com/angular/angular/issues/31820)) ([46b160e](https://github.com/angular/angular/commit/46b160e))
* **bazel:** increase memory limit of ngc under bazel from 2 to 4 GB ([#31784](https://github.com/angular/angular/issues/31784)) ([5a8eb92](https://github.com/angular/angular/commit/5a8eb92))
* **core:** allow Z variations of CSS transforms in sanitizer ([#29264](https://github.com/angular/angular/issues/29264)) ([78e7fdd](https://github.com/angular/angular/commit/78e7fdd))
* **elements:** handle falsy initial value ([#31604](https://github.com/angular/angular/issues/31604)) ([7151eae](https://github.com/angular/angular/commit/7151eae)), closes [angular/angular#30834](https://github.com/angular/angular/issues/30834)
* **platform-browser:** debug element query predicates not compatible with strictFunctionTypes ([#30993](https://github.com/angular/angular/issues/30993)) ([10a1e19](https://github.com/angular/angular/commit/10a1e19))
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
<a name="8.1.3"></a>
@ -65,17 +104,6 @@
* **compiler:** avoid copying from prototype while cloning an object ([#31638](https://github.com/angular/angular/issues/31638)) ([1f3daa0](https://github.com/angular/angular/commit/1f3daa0)), closes [#31627](https://github.com/angular/angular/issues/31627)
<a name="8.2.0-next.2"></a>
# [8.2.0-next.2](https://github.com/angular/angular/compare/8.2.0-next.1...8.2.0-next.2) (2019-07-17)
### Bug Fixes
* use the correct WTF array to iterate over ([#31208](https://github.com/angular/angular/issues/31208)) ([9204de9](https://github.com/angular/angular/commit/9204de9))
* **bazel:** pass custom bazel compiler host rather than rewriting one ([#31496](https://github.com/angular/angular/issues/31496)) ([0c61a35](https://github.com/angular/angular/commit/0c61a35))
* **compiler-cli:** Return original sourceFile instead of redirected sourceFile from getSourceFile ([#26036](https://github.com/angular/angular/issues/26036)) ([3166cff](https://github.com/angular/angular/commit/3166cff)), closes [#22524](https://github.com/angular/angular/issues/22524)
* **language-service:** Eagarly initialize data members ([#31577](https://github.com/angular/angular/issues/31577)) ([0110de2](https://github.com/angular/angular/commit/0110de2))
<a name="8.1.2"></a>
@ -89,23 +117,6 @@
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
<a name="8.2.0-next.1"></a>
# [8.2.0-next.1](https://github.com/angular/angular/compare/8.2.0-next.0...8.2.0-next.1) (2019-07-10)
### Bug Fixes
* **bazel:** revert location of xi18n outputs to bazel-genfiles ([#31410](https://github.com/angular/angular/issues/31410)) ([1d3e227](https://github.com/angular/angular/commit/1d3e227))
* **compiler:** give ASTWithSource its own visit method ([#31347](https://github.com/angular/angular/issues/31347)) ([6aaca21](https://github.com/angular/angular/commit/6aaca21))
### Features
* **core:** add automatic migration from Renderer to Renderer2 ([#30936](https://github.com/angular/angular/issues/30936)) ([c095597](https://github.com/angular/angular/commit/c095597))
<a name="8.1.1"></a>
## [8.1.1](https://github.com/angular/angular/compare/8.1.0...8.1.1) (2019-07-10)
@ -115,23 +126,6 @@
* **core:** export provider interfaces that are part of the public API types ([#31377](https://github.com/angular/angular/issues/31377)) ([bebf089](https://github.com/angular/angular/commit/bebf089)), closes [/github.com/angular/angular/pull/31377#discussion_r299254408](https://github.com//github.com/angular/angular/pull/31377/issues/discussion_r299254408) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L365-L366](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L365-L366) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts#L283-L284](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/interface/provider.ts/issues/L283-L284) [/github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts#L23](https://github.com//github.com/angular/angular/blob/9e34670b2/packages/core/src/di/index.ts/issues/L23)
<a name="8.2.0-next.0"></a>
# [8.2.0-next.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.2.0-next.0) (2019-07-02)
### Bug Fixes
* **core:** handle `undefined` meta in `injectArgs` ([#31333](https://github.com/angular/angular/issues/31333)) ([80ccd6c](https://github.com/angular/angular/commit/80ccd6c)), closes [CLI #14888](https://github.com/angular/angular-cli/issues/14888)
* **service-worker:** cache opaque responses in data groups with `freshness` strategy ([#30977](https://github.com/angular/angular/issues/30977)) ([d7be38f](https://github.com/angular/angular/commit/d7be38f)), closes [#30968](https://github.com/angular/angular/issues/30968)
* **service-worker:** cache opaque responses when requests exceeds timeout threshold ([#30977](https://github.com/angular/angular/issues/30977)) ([93abc35](https://github.com/angular/angular/commit/93abc35))
### Features
* **bazel:** allow passing a custom bazel compiler host to ngc compile ([#31341](https://github.com/angular/angular/issues/31341)) ([a29dc96](https://github.com/angular/angular/commit/a29dc96))
* **bazel:** allow passing and rewriting an old bazel host ([#31381](https://github.com/angular/angular/issues/31381)) ([11a208f](https://github.com/angular/angular/commit/11a208f)), closes [#31341](https://github.com/angular/angular/issues/31341)
<a name="8.1.0"></a>
# [8.1.0](https://github.com/angular/angular/compare/8.1.0-rc.0...8.1.0) (2019-07-02)

View File

@ -23,7 +23,7 @@
"build-with-ivy": "yarn ~~build",
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
"build-with-ivy-ci": "yarn ~~build --progress=false",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js de49294bf",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js bb4be27da",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn update-webdriver",

View File

@ -28,11 +28,11 @@
"uncompressed": {
"runtime-es5": 2932,
"runtime-es2015": 2938,
"main-es5": 555102,
"main-es2015": 572938,
"main-es5": 560811,
"main-es2015": 499846,
"polyfills-es5": 129161,
"polyfills-es2015": 53295
}
}
}
}
}

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime": 1440,
"main": 14021,
"main": 13517,
"polyfills": 43567
}
}
@ -34,4 +34,4 @@
}
}
}
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -45,7 +45,7 @@
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "file:../../node_modules/protractor",
"ts-node": "~4.1.0",
"tslint": "~5.13.0",
"tslint": "~5.18.0",
"typescript": "file:../../node_modules/typescript"
}
}

View File

@ -20,12 +20,12 @@
],
"textSpan": {
"start": {
"line": 7,
"offset": 30
"line": 5,
"offset": 26
},
"end": {
"line": 7,
"offset": 47
"line": 5,
"offset": 30
}
}
}

View File

@ -5,7 +5,7 @@
"request_seq": 2,
"success": true,
"body": {
"kind": "",
"kind": "property",
"kindModifiers": "",
"start": {
"line": 5,
@ -15,7 +15,7 @@
"line": 5,
"offset": 30
},
"displayString": "property name of AppComponent",
"displayString": "(property) AppComponent.name",
"documentation": "",
"tags": []
}

View File

@ -67,8 +67,15 @@ if [[ $? != 0 ]]; then exit 1; fi
grep "_MatMenuBase.ngBaseDef = ɵngcc0.ɵɵdefineBase({ inputs: {" node_modules/@angular/material/esm5/menu.es5.js
if [[ $? != 0 ]]; then exit 1; fi
# Did it handle namespace imported decorators in UMD?
# Did it handle namespace imported decorators in UMD using `__decorate` syntax?
grep "type: core.Injectable" node_modules/@angular/common/bundles/common.umd.js
# (and ensure the @angular/common package is indeed using `__decorate` syntax)
grep "JsonPipe = __decorate(" node_modules/@angular/common/bundles/common.umd.js.__ivy_ngcc_bak
# Did it handle namespace imported decorators in UMD using static properties?
grep "type: core.Injectable," node_modules/@angular/cdk/bundles/cdk-a11y.umd.js
# (and ensure the @angular/cdk/a11y package is indeed using static properties)
grep "FocusMonitor.decorators =" node_modules/@angular/cdk/bundles/cdk-a11y.umd.js.__ivy_ngcc_bak
# Can it be safely run again (as a noop)?
# And check that it logged skipping compilation as expected

View File

@ -4,7 +4,7 @@
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/typings_test_ts32/",
"outDir": "../../dist/typings_test_ts34/",
"rootDir": ".",
"target": "es5",
"lib": [

View File

@ -4,7 +4,7 @@
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/typings_test_ts32/",
"outDir": "../../dist/typings_test_ts35/",
"rootDir": ".",
"target": "es5",
"lib": [

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "8.2.2",
"version": "9.0.0-next.2",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",

View File

@ -75,7 +75,7 @@ export class DecorationAnalyzer {
new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
this.scopeRegistry, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs,
/* defaultPreserveWhitespaces */ false,
/* i18nUseExternalIds */ true, this.moduleResolver, this.cycleAnalyzer, this.refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER),

View File

@ -9,9 +9,11 @@
import {DepGraph} from 'dependency-graph';
import {AbsoluteFsPath, FileSystem, resolve} from '../../../src/ngtsc/file_system';
import {Logger} from '../logging/logger';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from '../packages/entry_point';
import {DependencyHost, DependencyInfo} from './dependency_host';
const builtinNodeJsModules = new Set<string>(require('module').builtinModules);
/**
* Holds information about entry points that are removed because
* they have dependencies that are missing (directly or transitively).
@ -81,7 +83,7 @@ export class DependencyResolver {
let sortedEntryPointNodes: string[];
if (target) {
if (target.compiledByAngular) {
if (target.compiledByAngular && graph.hasNode(target.path)) {
sortedEntryPointNodes = graph.dependenciesOf(target.path);
sortedEntryPointNodes.push(target.path);
} else {
@ -128,10 +130,12 @@ export class DependencyResolver {
angularEntryPoints.forEach(entryPoint => {
const {dependencies, missing, deepImports} = this.getEntryPointDependencies(entryPoint);
if (missing.size > 0) {
const missingDependencies = Array.from(missing).filter(dep => !builtinNodeJsModules.has(dep));
if (missingDependencies.length > 0) {
// This entry point has dependencies that are missing
// so remove it from the graph.
removeNodes(entryPoint, Array.from(missing));
removeNodes(entryPoint, missingDependencies);
} else {
dependencies.forEach(dependencyPath => {
if (!graph.hasNode(entryPoint.path)) {
@ -173,16 +177,16 @@ export class DependencyResolver {
private getEntryPointFormatInfo(entryPoint: EntryPoint):
{format: EntryPointFormat, path: AbsoluteFsPath} {
const properties = Object.keys(entryPoint.packageJson);
for (let i = 0; i < properties.length; i++) {
const property = properties[i] as EntryPointJsonProperty;
const format = getEntryPointFormat(this.fs, entryPoint, property);
for (const property of SUPPORTED_FORMAT_PROPERTIES) {
const formatPath = entryPoint.packageJson[property];
if (formatPath === undefined) continue;
if (format === 'esm2015' || format === 'esm5' || format === 'umd' || format === 'commonjs') {
const formatPath = entryPoint.packageJson[property] !;
return {format, path: resolve(entryPoint.path, formatPath)};
}
const format = getEntryPointFormat(this.fs, entryPoint, property);
if (format === undefined) continue;
return {format, path: resolve(entryPoint.path, formatPath)};
}
throw new Error(
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
}

View File

@ -0,0 +1,74 @@
/**
* @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 {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
/** The type of the function that analyzes entry-points and creates the list of tasks. */
export type AnalyzeFn = () => {
processingMetadataPerEntryPoint: Map<string, EntryPointProcessingMetadata>;
tasks: Task[];
};
/**
* The type of the function that creates the `compile()` function, which in turn can be used to
* process tasks.
*/
export type CreateCompileFn =
(onTaskCompleted: (task: Task, outcome: TaskProcessingOutcome) => void) => (task: Task) => void;
/**
* The type of the function that orchestrates and executes the required work (i.e. analyzes the
* entry-points, processes the resulting tasks, does book-keeping and validates the final outcome).
*/
export type ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => void;
/** Represents metadata related to the processing of an entry-point. */
export interface EntryPointProcessingMetadata {
/**
* Whether the typings for the entry-point have been successfully processed (or were already
* processed).
*/
hasProcessedTypings: boolean;
/**
* Whether at least one format has been successfully processed (or was already processed) for the
* entry-point.
*/
hasAnyProcessedFormat: boolean;
}
/** Represents a unit of work: processing a specific format property of an entry-point. */
export interface Task {
/** The `EntryPoint` which needs to be processed as part of the task. */
entryPoint: EntryPoint;
/**
* The `package.json` format property to process (i.e. the property which points to the file that
* is the program entry-point).
*/
formatProperty: EntryPointJsonProperty;
/**
* The list of all format properties (including `task.formatProperty`) that should be marked as
* processed once the taksk has been completed, because they point to the format-path that will be
* processed as part of the task.
*/
formatPropertiesToMarkAsProcessed: EntryPointJsonProperty[];
/** Whether to also process typings for this entry-point as part of the task. */
processDts: boolean;
}
/** Represents the outcome of processing a `Task`. */
export const enum TaskProcessingOutcome {
/** The target format property was already processed - didn't have to do anything. */
AlreadyProcessed,
/** Successfully processed the target format property. */
Processed,
}

View File

@ -9,10 +9,10 @@
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils';
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
import {ModuleWithProvidersFunction, NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
@ -73,6 +73,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
*/
protected aliasedClassDeclarations = new Map<ts.Declaration, ts.Identifier>();
/**
* Caches the information of the decorators on a class, as the work involved with extracting
* decorators is complex and frequently used.
*
* This map is lazily populated during the first call to `acquireDecoratorInfo` for a given class.
*/
protected decoratorCache = new Map<ClassDeclaration, DecoratorInfo>();
constructor(
protected logger: Logger, protected isCore: boolean, checker: ts.TypeChecker,
dts?: BundleProgram|null) {
@ -247,12 +255,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
/** Gets all decorators of the given class symbol. */
getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null {
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
if (decoratorsProperty) {
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
} else {
return this.getClassDecoratorsFromHelperCall(symbol);
}
const {classDecorators} = this.acquireDecoratorInfo(symbol);
return classDecorators;
}
/**
@ -542,6 +546,72 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return symbol.exports && symbol.exports.get(propertyName);
}
/**
* This is the main entry-point for obtaining information on the decorators of a given class. This
* information is computed either from static properties if present, or using `tslib.__decorate`
* helper calls otherwise. The computed result is cached per class.
*
* @param classSymbol the class for which decorators should be acquired.
* @returns all information of the decorators on the class.
*/
protected acquireDecoratorInfo(classSymbol: ClassSymbol): DecoratorInfo {
if (this.decoratorCache.has(classSymbol.valueDeclaration)) {
return this.decoratorCache.get(classSymbol.valueDeclaration) !;
}
// First attempt extracting decorators from static properties.
let decoratorInfo = this.computeDecoratorInfoFromStaticProperties(classSymbol);
if (decoratorInfo === null) {
// If none were present, use the `__decorate` helper calls instead.
decoratorInfo = this.computeDecoratorInfoFromHelperCalls(classSymbol);
}
this.decoratorCache.set(classSymbol.valueDeclaration, decoratorInfo);
return decoratorInfo;
}
/**
* Attempts to compute decorator information from static properties "decorators", "propDecorators"
* and "ctorParameters" on the class. If neither of these static properties is present the
* library is likely not compiled using tsickle for usage with Closure compiler, in which case
* `null` is returned.
*
* @param classSymbol The class symbol to compute the decorators information for.
* @returns All information on the decorators as extracted from static properties, or `null` if
* none of the static properties exist.
*/
protected computeDecoratorInfoFromStaticProperties(classSymbol: ClassSymbol): DecoratorInfo|null {
let classDecorators: Decorator[]|null = null;
let memberDecorators: Map<string, Decorator[]>|null = null;
let constructorParamInfo: ParamInfo[]|null = null;
const decoratorsProperty = this.getStaticProperty(classSymbol, DECORATORS);
if (decoratorsProperty !== undefined) {
classDecorators = this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
}
const propDecoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
if (propDecoratorsProperty !== undefined) {
memberDecorators = this.getMemberDecoratorsFromStaticProperty(propDecoratorsProperty);
}
const constructorParamsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
if (constructorParamsProperty !== undefined) {
constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty);
}
// If none of the static properties were present, no decorator info could be computed.
if (classDecorators === null && memberDecorators === null && constructorParamInfo === null) {
return null;
}
return {
classDecorators,
memberDecorators: memberDecorators || new Map<string, Decorator[]>(),
constructorParamInfo: constructorParamInfo || [],
};
}
/**
* Get all class decorators for the given class, where the decorators are declared
* via a static property. For example:
@ -570,32 +640,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return null;
}
/**
* Get all class decorators for the given class, where the decorators are declared
* via the `__decorate` helper method. For example:
*
* ```
* let SomeDirective = class SomeDirective {}
* SomeDirective = __decorate([
* Directive({ selector: '[someDirective]' }),
* ], SomeDirective);
* ```
*
* @param symbol the class whose decorators we want to get.
* @returns an array of decorators or null if none where found.
*/
protected getClassDecoratorsFromHelperCall(symbol: ClassSymbol): Decorator[]|null {
const decorators: Decorator[] = [];
const helperCalls = this.getHelperCallsForClass(symbol, '__decorate');
helperCalls.forEach(helperCall => {
const {classDecorators} =
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(symbol.name));
classDecorators.filter(decorator => this.isFromCore(decorator))
.forEach(decorator => decorators.push(decorator));
});
return decorators.length ? decorators : null;
}
/**
* Examine a symbol which should be of a class, and return metadata about its members.
*
@ -606,7 +650,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
const members: ClassMember[] = [];
// The decorators map contains all the properties that are decorated
const decoratorsMap = this.getMemberDecorators(symbol);
const {memberDecorators} = this.acquireDecoratorInfo(symbol);
// Make a copy of the decorators as successfully reflected members delete themselves from the
// map, so that any leftovers can be easily dealt with.
const decoratorsMap = new Map(memberDecorators);
// The member map contains all the method (instance and static); and any instance properties
// that are initialized in the class.
@ -675,21 +723,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return members;
}
/**
* Get all the member decorators for the given class.
* @param classSymbol the class whose member decorators we are interested in.
* @returns a map whose keys are the name of the members and whose values are collections of
* decorators for the given member.
*/
protected getMemberDecorators(classSymbol: ClassSymbol): Map<string, Decorator[]> {
const decoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS);
if (decoratorsProperty) {
return this.getMemberDecoratorsFromStaticProperty(decoratorsProperty);
} else {
return this.getMemberDecoratorsFromHelperCalls(classSymbol);
}
}
/**
* Member decorators may be declared as static properties of the class:
*
@ -724,7 +757,21 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
}
/**
* Member decorators may be declared via helper call statements.
* For a given class symbol, collects all decorator information from tslib helper methods, as
* generated by TypeScript into emitted JavaScript files.
*
* Class decorators are extracted from calls to `tslib.__decorate` that look as follows:
*
* ```
* let SomeDirective = class SomeDirective {}
* SomeDirective = __decorate([
* Directive({ selector: '[someDirective]' }),
* ], SomeDirective);
* ```
*
* The extraction of member decorators is similar, with the distinction that its 2nd and 3rd
* argument correspond with a "prototype" target and the name of the member to which the
* decorators apply.
*
* ```
* __decorate([
@ -733,102 +780,188 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
* ], SomeDirective.prototype, "input1", void 0);
* ```
*
* @param classSymbol the class whose member decorators we are interested in.
* @returns a map whose keys are the name of the members and whose values are collections of
* decorators for the given member.
* @param classSymbol The class symbol for which decorators should be extracted.
* @returns All information on the decorators of the class.
*/
protected getMemberDecoratorsFromHelperCalls(classSymbol: ClassSymbol): Map<string, Decorator[]> {
const memberDecoratorMap = new Map<string, Decorator[]>();
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
helperCalls.forEach(helperCall => {
const {memberDecorators} = this.reflectDecoratorsFromHelperCall(
helperCall, makeMemberTargetFilter(classSymbol.name));
memberDecorators.forEach((decorators, memberName) => {
if (memberName) {
const memberDecorators =
memberDecoratorMap.has(memberName) ? memberDecoratorMap.get(memberName) ! : [];
const coreDecorators = decorators.filter(decorator => this.isFromCore(decorator));
memberDecoratorMap.set(memberName, memberDecorators.concat(coreDecorators));
}
});
});
return memberDecoratorMap;
}
/**
* Extract decorator info from `__decorate` helper function calls.
* @param helperCall the call to a helper that may contain decorator calls
* @param targetFilter a function to filter out targets that we are not interested in.
* @returns a mapping from member name to decorators, where the key is either the name of the
* member or `undefined` if it refers to decorators on the class as a whole.
*/
protected reflectDecoratorsFromHelperCall(
helperCall: ts.CallExpression, targetFilter: TargetFilter):
{classDecorators: Decorator[], memberDecorators: Map<string, Decorator[]>} {
const classDecorators: Decorator[] = [];
protected computeDecoratorInfoFromHelperCalls(classSymbol: ClassSymbol): DecoratorInfo {
let classDecorators: Decorator[]|null = null;
const memberDecorators = new Map<string, Decorator[]>();
const constructorParamInfo: ParamInfo[] = [];
// First check that the `target` argument is correct
if (targetFilter(helperCall.arguments[1])) {
// Grab the `decorators` argument which should be an array of calls
const decoratorCalls = helperCall.arguments[0];
if (decoratorCalls && ts.isArrayLiteralExpression(decoratorCalls)) {
decoratorCalls.elements.forEach(element => {
// We only care about those elements that are actual calls
if (ts.isCallExpression(element)) {
const decorator = this.reflectDecoratorCall(element);
if (decorator) {
const keyArg = helperCall.arguments[2];
const keyName = keyArg && ts.isStringLiteral(keyArg) ? keyArg.text : undefined;
if (keyName === undefined) {
classDecorators.push(decorator);
} else {
const decorators =
memberDecorators.has(keyName) ? memberDecorators.get(keyName) ! : [];
decorators.push(decorator);
memberDecorators.set(keyName, decorators);
}
}
const getConstructorParamInfo = (index: number) => {
let param = constructorParamInfo[index];
if (param === undefined) {
param = constructorParamInfo[index] = {decorators: null, typeExpression: null};
}
return param;
};
// All relevant information can be extracted from calls to `__decorate`, obtain these first.
// Note that although the helper calls are retrieved using the class symbol, the result may
// contain helper calls corresponding with unrelated classes. Therefore, each helper call still
// has to be checked to actually correspond with the class symbol.
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
for (const helperCall of helperCalls) {
if (isClassDecorateCall(helperCall, classSymbol.name)) {
// This `__decorate` call is targeting the class itself.
const helperArgs = helperCall.arguments[0];
for (const element of helperArgs.elements) {
const entry = this.reflectDecorateHelperEntry(element);
if (entry === null) {
continue;
}
});
if (entry.type === 'decorator') {
// The helper arg was reflected to represent an actual decorator
if (this.isFromCore(entry.decorator)) {
(classDecorators || (classDecorators = [])).push(entry.decorator);
}
} else if (entry.type === 'param:decorators') {
// The helper arg represents a decorator for a parameter. Since it's applied to the
// class, it corresponds with a constructor parameter of the class.
const param = getConstructorParamInfo(entry.index);
(param.decorators || (param.decorators = [])).push(entry.decorator);
} else if (entry.type === 'params') {
// The helper arg represents the types of the parameters. Since it's applied to the
// class, it corresponds with the constructor parameters of the class.
entry.types.forEach(
(type, index) => getConstructorParamInfo(index).typeExpression = type);
}
}
} else if (isMemberDecorateCall(helperCall, classSymbol.name)) {
// The `__decorate` call is targeting a member of the class
const helperArgs = helperCall.arguments[0];
const memberName = helperCall.arguments[2].text;
for (const element of helperArgs.elements) {
const entry = this.reflectDecorateHelperEntry(element);
if (entry === null) {
continue;
}
if (entry.type === 'decorator') {
// The helper arg was reflected to represent an actual decorator.
if (this.isFromCore(entry.decorator)) {
const decorators =
memberDecorators.has(memberName) ? memberDecorators.get(memberName) ! : [];
decorators.push(entry.decorator);
memberDecorators.set(memberName, decorators);
}
} else {
// Information on decorated parameters is not interesting for ngcc, so it's ignored.
}
}
}
}
return {classDecorators, memberDecorators};
return {classDecorators, memberDecorators, constructorParamInfo};
}
/**
* Extract the decorator information from a call to a decorator as a function.
* This happens when the decorators has been used in a `__decorate` helper call.
* For example:
* Extract the details of an entry within a `__decorate` helper call. For example, given the
* following code:
*
* ```
* __decorate([
* Directive({ selector: '[someDirective]' }),
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
* ], SomeDirective);
* ```
*
* Here the `Directive` decorator is decorating `SomeDirective` and the options for
* the decorator are passed as arguments to the `Directive()` call.
* it can be seen that there are calls to regular decorators (the `Directive`) and calls into
* `tslib` functions which have been inserted by TypeScript. Therefore, this function classifies
* a call to correspond with
* 1. a real decorator like `Directive` above, or
* 2. a decorated parameter, corresponding with `__param` calls from `tslib`, or
* 3. the type information of parameters, corresponding with `__metadata` call from `tslib`
*
* @param call the call to the decorator.
* @returns a decorator containing the reflected information, or null if the call
* is not a valid decorator call.
* @param expression the expression that needs to be reflected into a `DecorateHelperEntry`
* @returns an object that indicates which of the three categories the call represents, together
* with the reflected information of the call, or null if the call is not a valid decorate call.
*/
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
const decoratorExpression = call.expression;
if (isDecoratorIdentifier(decoratorExpression)) {
// We found a decorator!
const decoratorIdentifier =
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
protected reflectDecorateHelperEntry(expression: ts.Expression): DecorateHelperEntry|null {
// We only care about those elements that are actual calls
if (!ts.isCallExpression(expression)) {
return null;
}
const call = expression;
const helperName = getCalleeName(call);
if (helperName === '__metadata') {
// This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object
// if the metadata key is "design:paramtypes".
const key = call.arguments[0];
if (key === undefined || !ts.isStringLiteral(key) || key.text !== 'design:paramtypes') {
return null;
}
const value = call.arguments[1];
if (value === undefined || !ts.isArrayLiteralExpression(value)) {
return null;
}
return {
name: decoratorIdentifier.text,
identifier: decoratorIdentifier,
import: this.getImportOfIdentifier(decoratorIdentifier),
node: call,
args: Array.from(call.arguments)
type: 'params',
types: Array.from(value.elements),
};
}
return null;
if (helperName === '__param') {
// This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object.
const indexArg = call.arguments[0];
const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN;
if (isNaN(index)) {
return null;
}
const decoratorCall = call.arguments[1];
if (decoratorCall === undefined || !ts.isCallExpression(decoratorCall)) {
return null;
}
const decorator = this.reflectDecoratorCall(decoratorCall);
if (decorator === null) {
return null;
}
return {
type: 'param:decorators',
index,
decorator,
};
}
// Otherwise attempt to reflect it as a regular decorator.
const decorator = this.reflectDecoratorCall(call);
if (decorator === null) {
return null;
}
return {
type: 'decorator',
decorator,
};
}
protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null {
const decoratorExpression = call.expression;
if (!isDecoratorIdentifier(decoratorExpression)) {
return null;
}
// We found a decorator!
const decoratorIdentifier =
ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name;
return {
name: decoratorIdentifier.text,
identifier: decoratorExpression,
import: this.getImportOfIdentifier(decoratorIdentifier),
node: call,
args: Array.from(call.arguments),
};
}
/**
@ -1070,14 +1203,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
*/
protected getConstructorParamInfo(
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] {
const paramsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS);
const paramInfo: ParamInfo[]|null = paramsProperty ?
this.getParamInfoFromStaticProperty(paramsProperty) :
this.getParamInfoFromHelperCall(classSymbol, parameterNodes);
const {constructorParamInfo} = this.acquireDecoratorInfo(classSymbol);
return parameterNodes.map((node, index) => {
const {decorators, typeExpression} = paramInfo && paramInfo[index] ?
paramInfo[index] :
const {decorators, typeExpression} = constructorParamInfo[index] ?
constructorParamInfo[index] :
{decorators: null, typeExpression: null};
const nameNode = node.name;
return {
@ -1153,58 +1283,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return null;
}
/**
* Get the parameter type and decorators for a class where the information is stored via
* calls to `__decorate` helpers.
*
* Reflect over the helpers to find the decorators and types about each of
* the class's constructor parameters.
*
* @param classSymbol the class whose parameter info we want to get.
* @param parameterNodes the array of TypeScript parameter nodes for this class's constructor.
* @returns an array of objects containing the type and decorators for each parameter.
*/
protected getParamInfoFromHelperCall(
classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): ParamInfo[] {
const parameters: ParamInfo[] =
parameterNodes.map(() => ({typeExpression: null, decorators: null}));
const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate');
helperCalls.forEach(helperCall => {
const {classDecorators} =
this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(classSymbol.name));
classDecorators.forEach(call => {
switch (call.name) {
case '__metadata':
const metadataArg = call.args && call.args[0];
const typesArg = call.args && call.args[1];
const isParamTypeDecorator = metadataArg && ts.isStringLiteral(metadataArg) &&
metadataArg.text === 'design:paramtypes';
const types = typesArg && ts.isArrayLiteralExpression(typesArg) && typesArg.elements;
if (isParamTypeDecorator && types) {
types.forEach((type, index) => parameters[index].typeExpression = type);
}
break;
case '__param':
const paramIndexArg = call.args && call.args[0];
const decoratorCallArg = call.args && call.args[1];
const paramIndex = paramIndexArg && ts.isNumericLiteral(paramIndexArg) ?
parseInt(paramIndexArg.text, 10) :
NaN;
const decorator = decoratorCallArg && ts.isCallExpression(decoratorCallArg) ?
this.reflectDecoratorCall(decoratorCallArg) :
null;
if (!isNaN(paramIndex) && decorator) {
const decorators = parameters[paramIndex].decorators =
parameters[paramIndex].decorators || [];
decorators.push(decorator);
}
break;
}
});
});
return parameters;
}
/**
* Search statements related to the given class for calls to the specified helper.
* @param classSymbol the class whose helper calls we are interested in.
@ -1377,6 +1455,72 @@ export type ParamInfo = {
typeExpression: ts.Expression | null
};
/**
* Represents a call to `tslib.__metadata` as present in `tslib.__decorate` calls. This is a
* synthetic decorator inserted by TypeScript that contains reflection information about the
* target of the decorator, i.e. the class or property.
*/
export interface ParameterTypes {
type: 'params';
types: ts.Expression[];
}
/**
* Represents a call to `tslib.__param` as present in `tslib.__decorate` calls. This contains
* information on any decorators were applied to a certain parameter.
*/
export interface ParameterDecorators {
type: 'param:decorators';
index: number;
decorator: Decorator;
}
/**
* Represents a call to a decorator as it was present in the original source code, as present in
* `tslib.__decorate` calls.
*/
export interface DecoratorCall {
type: 'decorator';
decorator: Decorator;
}
/**
* Represents the different kinds of decorate helpers that may be present as first argument to
* `tslib.__decorate`, as follows:
*
* ```
* __decorate([
* Directive({ selector: '[someDirective]' }),
* tslib_1.__param(2, Inject(INJECTED_TOKEN)),
* tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
* ], SomeDirective);
* ```
*/
export type DecorateHelperEntry = ParameterTypes | ParameterDecorators | DecoratorCall;
/**
* The recorded decorator information of a single class. This information is cached in the host.
*/
interface DecoratorInfo {
/**
* All decorators that were present on the class. If no decorators were present, this is `null`
*/
classDecorators: Decorator[]|null;
/**
* All decorators per member of the class they were present on.
*/
memberDecorators: Map<string, Decorator[]>;
/**
* Represents the constructor parameter information, such as the type of a parameter and all
* decorators for a certain parameter. Indices in this array correspond with the parameter's index
* in the constructor. Note that this array may be sparse, i.e. certain constructor parameters may
* not have any info recorded.
*/
constructorParamInfo: ParamInfo[];
}
/**
* A statement node that represents an assignment.
*/
@ -1397,27 +1541,55 @@ export function isAssignment(node: ts.Node): node is ts.AssignmentExpression<ts.
}
/**
* The type of a function that can be used to filter out helpers based on their target.
* This is used in `reflectDecoratorsFromHelperCall()`.
* Tests whether the provided call expression targets a class, by verifying its arguments are
* according to the following form:
*
* ```
* __decorate([], SomeDirective);
* ```
*
* @param call the call expression that is tested to represent a class decorator call.
* @param className the name of the class that the call needs to correspond with.
*/
export type TargetFilter = (target: ts.Expression) => boolean;
export function isClassDecorateCall(call: ts.CallExpression, className: string):
call is ts.CallExpression&{arguments: [ts.ArrayLiteralExpression, ts.Expression]} {
const helperArgs = call.arguments[0];
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
return false;
}
/**
* Creates a function that tests whether the given expression is a class target.
* @param className the name of the class we want to target.
*/
export function makeClassTargetFilter(className: string): TargetFilter {
return (target: ts.Expression): boolean => ts.isIdentifier(target) && target.text === className;
const target = call.arguments[1];
return target !== undefined && ts.isIdentifier(target) && target.text === className;
}
/**
* Creates a function that tests whether the given expression is a class member target.
* @param className the name of the class we want to target.
* Tests whether the provided call expression targets a member of the class, by verifying its
* arguments are according to the following form:
*
* ```
* __decorate([], SomeDirective.prototype, "member", void 0);
* ```
*
* @param call the call expression that is tested to represent a member decorator call.
* @param className the name of the class that the call needs to correspond with.
*/
export function makeMemberTargetFilter(className: string): TargetFilter {
return (target: ts.Expression): boolean => ts.isPropertyAccessExpression(target) &&
ts.isIdentifier(target.expression) && target.expression.text === className &&
target.name.text === 'prototype';
export function isMemberDecorateCall(call: ts.CallExpression, className: string):
call is ts.CallExpression&
{arguments: [ts.ArrayLiteralExpression, ts.StringLiteral, ts.StringLiteral]} {
const helperArgs = call.arguments[0];
if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) {
return false;
}
const target = call.arguments[1];
if (target === undefined || !ts.isPropertyAccessExpression(target) ||
!ts.isIdentifier(target.expression) || target.expression.text !== className ||
target.name.text !== 'prototype') {
return false;
}
const memberName = call.arguments[2];
return memberName !== undefined && ts.isStringLiteral(memberName);
}
/**
@ -1435,10 +1607,10 @@ export function getPropertyValueFromSymbol(propSymbol: ts.Symbol): ts.Expression
*/
function getCalleeName(call: ts.CallExpression): string|null {
if (ts.isIdentifier(call.expression)) {
return call.expression.text;
return stripDollarSuffix(call.expression.text);
}
if (ts.isPropertyAccessExpression(call.expression)) {
return call.expression.name.text;
return stripDollarSuffix(call.expression.name.text);
}
return null;
}

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
import {getNameText, hasNameIdentifier} from '../utils';
import {getNameText, hasNameIdentifier, stripDollarSuffix} from '../utils';
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
@ -659,7 +659,9 @@ function reflectArrayElement(element: ts.Expression) {
* helper.
*/
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
const name = node.name !== undefined && ts.isIdentifier(node.name) ?
stripDollarSuffix(node.name.text) :
null;
if (name === '__spread') {
return TsHelperFn.Spread;

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, FileSystem, absoluteFrom, dirname, getFileSystem, resolve} from '../../src/ngtsc/file_system';
import {CommonJsDependencyHost} from './dependencies/commonjs_dependency_host';
import {DependencyResolver, InvalidEntryPoint, SortedEntryPointsInfo} from './dependencies/dependency_resolver';
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
@ -13,11 +14,12 @@ import {ModuleResolver} from './dependencies/module_resolver';
import {UmdDependencyHost} from './dependencies/umd_dependency_host';
import {DirectoryWalkerEntryPointFinder} from './entry_point_finder/directory_walker_entry_point_finder';
import {TargetedEntryPointFinder} from './entry_point_finder/targeted_entry_point_finder';
import {AnalyzeFn, CreateCompileFn, EntryPointProcessingMetadata, ExecuteFn, Task, TaskProcessingOutcome} from './execution/api';
import {ConsoleLogger, LogLevel} from './logging/console_logger';
import {Logger} from './logging/logger';
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
import {NgccConfiguration} from './packages/configuration';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point';
import {makeEntryPointBundle} from './packages/entry_point_bundle';
import {Transformer} from './packages/transformer';
import {PathMappings} from './utils';
@ -25,6 +27,7 @@ import {FileWriter} from './writing/file_writer';
import {InPlaceFileWriter} from './writing/in_place_file_writer';
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
/**
* The options to configure the ngcc compiler.
*/
@ -68,8 +71,6 @@ export interface NgccOptions {
fileSystem?: FileSystem;
}
const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'umd', 'commonjs'];
/**
* This is the main entry-point into ngcc (aNGular Compatibility Compiler).
*
@ -83,82 +84,171 @@ export function mainNgcc(
compileAllFormats = true, createNewEntryPointFormats = false,
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
const fileSystem = getFileSystem();
const transformer = new Transformer(fileSystem, logger);
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
const resolver = new DependencyResolver(fileSystem, logger, {
esm5: esmDependencyHost,
esm2015: esmDependencyHost,
umd: umdDependencyHost,
commonjs: commonJsDependencyHost
});
const absBasePath = absoluteFrom(basePath);
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
const entryPoints = getEntryPoints(
fileSystem, config, logger, resolver, absBasePath, targetEntryPointPath, pathMappings,
propertiesToConsider, compileAllFormats);
for (const entryPoint of entryPoints) {
// Are we compiling the Angular core?
const isCore = entryPoint.name === '@angular/core';
const compiledFormats = new Set<string>();
const entryPointPackageJson = entryPoint.packageJson;
const entryPointPackageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
// The function for performing the analysis.
const analyzeFn: AnalyzeFn = () => {
const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider);
const hasProcessedDts = hasBeenProcessed(entryPointPackageJson, 'typings');
const moduleResolver = new ModuleResolver(fileSystem, pathMappings);
const esmDependencyHost = new EsmDependencyHost(fileSystem, moduleResolver);
const umdDependencyHost = new UmdDependencyHost(fileSystem, moduleResolver);
const commonJsDependencyHost = new CommonJsDependencyHost(fileSystem, moduleResolver);
const dependencyResolver = new DependencyResolver(fileSystem, logger, {
esm5: esmDependencyHost,
esm2015: esmDependencyHost,
umd: umdDependencyHost,
commonjs: commonJsDependencyHost
});
for (let i = 0; i < propertiesToConsider.length; i++) {
const property = propertiesToConsider[i] as EntryPointJsonProperty;
const formatPath = entryPointPackageJson[property];
const format = getEntryPointFormat(fileSystem, entryPoint, property);
const absBasePath = absoluteFrom(basePath);
const config = new NgccConfiguration(fileSystem, dirname(absBasePath));
const entryPoints = getEntryPoints(
fileSystem, config, logger, dependencyResolver, absBasePath, targetEntryPointPath,
pathMappings, supportedPropertiesToConsider, compileAllFormats);
// No format then this property is not supposed to be compiled.
if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue;
const processingMetadataPerEntryPoint = new Map<string, EntryPointProcessingMetadata>();
const tasks: Task[] = [];
if (hasBeenProcessed(entryPointPackageJson, property)) {
compiledFormats.add(formatPath);
logger.debug(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
continue;
for (const entryPoint of entryPoints) {
const packageJson = entryPoint.packageJson;
const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings');
const {propertiesToProcess, propertyToPropertiesToMarkAsProcessed} =
getPropertiesToProcessAndMarkAsProcessed(packageJson, supportedPropertiesToConsider);
let processDts = !hasProcessedTypings;
for (const formatProperty of propertiesToProcess) {
const formatPropertiesToMarkAsProcessed =
propertyToPropertiesToMarkAsProcessed.get(formatProperty) !;
tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts});
// Only process typings for the first property (if not already processed).
processDts = false;
}
const isFirstFormat = compiledFormats.size === 0;
const processDts = !hasProcessedDts && isFirstFormat;
// We don't break if this if statement fails because we still want to mark
// the property as processed even if its underlying format has been built already.
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
const bundle = makeEntryPointBundle(
fileSystem, entryPoint, formatPath, isCore, property, format, processDts, pathMappings,
true);
if (bundle) {
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
const transformedFiles = transformer.transform(bundle);
fileWriter.writeBundle(entryPoint, bundle, transformedFiles);
compiledFormats.add(formatPath);
} else {
logger.warn(
`Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
}
}
// Either this format was just compiled or its underlying format was compiled because of a
// previous property.
if (compiledFormats.has(formatPath)) {
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, property);
if (processDts) {
markAsProcessed(fileSystem, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
}
}
processingMetadataPerEntryPoint.set(entryPoint.path, {
hasProcessedTypings,
hasAnyProcessedFormat: false,
});
}
if (compiledFormats.size === 0) {
return {processingMetadataPerEntryPoint, tasks};
};
// The function for creating the `compile()` function.
const createCompileFn: CreateCompileFn = onTaskCompleted => {
const fileWriter = getFileWriter(fileSystem, createNewEntryPointFormats);
const transformer = new Transformer(fileSystem, logger);
return (task: Task) => {
const {entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts} = task;
const isCore = entryPoint.name === '@angular/core'; // Are we compiling the Angular core?
const packageJson = entryPoint.packageJson;
const formatPath = packageJson[formatProperty];
const format = getEntryPointFormat(fileSystem, entryPoint, formatProperty);
// All properties listed in `propertiesToProcess` are guaranteed to point to a format-path
// (i.e. they exist in `entryPointPackageJson`). Furthermore, they are also guaranteed to be
// among `SUPPORTED_FORMAT_PROPERTIES`.
// Based on the above, `formatPath` should always be defined and `getEntryPointFormat()`
// should always return a format here (and not `undefined`).
if (!formatPath || !format) {
// This should never happen.
throw new Error(
`Invariant violated: No format-path or format for ${entryPoint.path} : ` +
`${formatProperty} (formatPath: ${formatPath} | format: ${format})`);
}
// The format-path which the property maps to is already processed - nothing to do.
if (hasBeenProcessed(packageJson, formatProperty)) {
logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`);
onTaskCompleted(task, TaskProcessingOutcome.AlreadyProcessed);
return;
}
const bundle = makeEntryPointBundle(
fileSystem, entryPoint, formatPath, isCore, format, processDts, pathMappings, true);
logger.info(`Compiling ${entryPoint.name} : ${formatProperty} as ${format}`);
const transformedFiles = transformer.transform(bundle);
fileWriter.writeBundle(bundle, transformedFiles, formatPropertiesToMarkAsProcessed);
onTaskCompleted(task, TaskProcessingOutcome.Processed);
};
};
// The function for actually planning and getting the work done.
const executeFn: ExecuteFn = (analyzeFn: AnalyzeFn, createCompileFn: CreateCompileFn) => {
const {processingMetadataPerEntryPoint, tasks} = analyzeFn();
const compile = createCompileFn((task, outcome) => {
const {entryPoint, formatPropertiesToMarkAsProcessed, processDts} = task;
const processingMeta = processingMetadataPerEntryPoint.get(entryPoint.path) !;
processingMeta.hasAnyProcessedFormat = true;
if (outcome === TaskProcessingOutcome.Processed) {
const packageJsonPath = fileSystem.resolve(entryPoint.path, 'package.json');
const propsToMarkAsProcessed: (EntryPointJsonProperty | 'typings')[] =
[...formatPropertiesToMarkAsProcessed];
if (processDts) {
processingMeta.hasProcessedTypings = true;
propsToMarkAsProcessed.push('typings');
}
markAsProcessed(
fileSystem, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed);
}
});
// Process all tasks.
for (const task of tasks) {
const processingMeta = processingMetadataPerEntryPoint.get(task.entryPoint.path) !;
// If we only need one format processed and we already have one for the corresponding
// entry-point, skip the task.
if (!compileAllFormats && processingMeta.hasAnyProcessedFormat) continue;
compile(task);
}
// Check for entry-points for which we could not process any format at all.
const unprocessedEntryPointPaths =
Array.from(processingMetadataPerEntryPoint.entries())
.filter(([, processingMeta]) => !processingMeta.hasAnyProcessedFormat)
.map(([entryPointPath]) => `\n - ${entryPointPath}`)
.join('');
if (unprocessedEntryPointPaths) {
throw new Error(
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToConsider}.`);
'Failed to compile any formats for the following entry-points (tried ' +
`${propertiesToConsider.join(', ')}): ${unprocessedEntryPointPaths}`);
}
};
return executeFn(analyzeFn, createCompileFn);
}
function ensureSupportedProperties(properties: string[]): EntryPointJsonProperty[] {
// Short-circuit the case where `properties` has fallen back to the default value:
// `SUPPORTED_FORMAT_PROPERTIES`
if (properties === SUPPORTED_FORMAT_PROPERTIES) return SUPPORTED_FORMAT_PROPERTIES;
const supportedProperties: EntryPointJsonProperty[] = [];
for (const prop of properties as EntryPointJsonProperty[]) {
if (SUPPORTED_FORMAT_PROPERTIES.indexOf(prop) !== -1) {
supportedProperties.push(prop);
}
}
if (supportedProperties.length === 0) {
throw new Error(
`No supported format property to consider among [${properties.join(', ')}]. ` +
`Supported properties: ${SUPPORTED_FORMAT_PROPERTIES.join(', ')}`);
}
return supportedProperties;
}
function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
@ -192,8 +282,15 @@ function getTargetedEntryPoints(
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, absoluteTargetEntryPointPath, pathMappings);
const entryPointInfo = finder.findEntryPoints();
const invalidTarget = entryPointInfo.invalidEntryPoints.find(
i => i.entryPoint.path === absoluteTargetEntryPointPath);
if (invalidTarget !== undefined) {
throw new Error(
`The target entry-point "${invalidTarget.entryPoint.name}" has missing dependencies:\n` +
invalidTarget.missingDependencies.map(dep => ` - ${dep}\n`));
}
if (entryPointInfo.entryPoints.length === 0) {
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath);
}
return entryPointInfo;
}
@ -242,14 +339,13 @@ function hasProcessedTargetEntryPoint(
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid
* triggering ngcc for this entry-point in the future.
*/
function markNonAngularPackageAsProcessed(
fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
function markNonAngularPackageAsProcessed(fs: FileSystem, path: AbsoluteFsPath) {
const packageJsonPath = resolve(path, 'package.json');
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
propertiesToConsider.forEach(formatProperty => {
if (packageJson[formatProperty])
markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
});
// Note: We are marking all supported properties as processed, even if they don't exist in the
// `package.json` file. While this is redundant, it is also harmless.
markAsProcessed(fs, packageJson, packageJsonPath, SUPPORTED_FORMAT_PROPERTIES);
}
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
@ -260,3 +356,60 @@ function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryP
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
});
}
/**
* This function computes and returns the following:
* - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed,
* based on the specified `propertiesToConsider`, the properties in `package.json` and their
* corresponding format-paths. NOTE: Only one property per format-path needs to be processed.
* - `propertyToPropertiesToMarkAsProcessed`: A mapping from each property in `propertiesToProcess`
* to the list of other properties in `package.json` that need to be marked as processed as soon
* as of the former being processed.
*/
function getPropertiesToProcessAndMarkAsProcessed(
packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[]): {
propertiesToProcess: EntryPointJsonProperty[];
propertyToPropertiesToMarkAsProcessed: Map<EntryPointJsonProperty, EntryPointJsonProperty[]>;
} {
const formatPathsToConsider = new Set<string>();
const propertiesToProcess: EntryPointJsonProperty[] = [];
for (const prop of propertiesToConsider) {
// Ignore properties that are not in `package.json`.
if (!packageJson.hasOwnProperty(prop)) continue;
const formatPath = packageJson[prop] !;
// Ignore properties that map to the same format-path as a preceding property.
if (formatPathsToConsider.has(formatPath)) continue;
// Process this property, because it is the first one to map to this format-path.
formatPathsToConsider.add(formatPath);
propertiesToProcess.push(prop);
}
const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {};
for (const prop of SUPPORTED_FORMAT_PROPERTIES) {
// Ignore properties that are not in `package.json`.
if (!packageJson.hasOwnProperty(prop)) continue;
const formatPath = packageJson[prop] !;
// Ignore properties that do not map to a format-path that will be considered.
if (!formatPathsToConsider.has(formatPath)) continue;
// Add this property to the map.
const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []);
list.push(prop);
}
const propertyToPropertiesToMarkAsProcessed =
new Map<EntryPointJsonProperty, EntryPointJsonProperty[]>();
for (const prop of propertiesToConsider) {
const formatPath = packageJson[prop] !;
const propertiesToMarkAsProcessed = formatPathToProperties[formatPath];
propertyToPropertiesToMarkAsProcessed.set(prop, propertiesToMarkAsProcessed);
}
return {propertiesToProcess, propertyToPropertiesToMarkAsProcessed};
}

View File

@ -23,7 +23,7 @@ export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
* @throws Error if the entry-point has already been processed with a different ngcc version.
*/
export function hasBeenProcessed(
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty): boolean {
packageJson: EntryPointPackageJson, format: EntryPointJsonProperty | 'typings'): boolean {
if (!packageJson.__processed_by_ivy_ngcc__) {
return false;
}
@ -38,17 +38,36 @@ export function hasBeenProcessed(
}
/**
* Write a build marker for the given entry-point and format property, to indicate that it has
* Write a build marker for the given entry-point and format properties, to indicate that they have
* been compiled by this version of ngcc.
*
* @param entryPoint the entry-point to write a marker.
* @param format the property in the package.json of the format for which we are writing the marker.
* @param fs The current file-system being used.
* @param packageJson The parsed contents of the `package.json` file for the entry-point.
* @param packageJsonPath The absolute path to the `package.json` file.
* @param properties The properties in the `package.json` of the formats for which we are writing
* the marker.
*/
export function markAsProcessed(
fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
format: EntryPointJsonProperty) {
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
properties: (EntryPointJsonProperty | 'typings')[]) {
const processed =
packageJson.__processed_by_ivy_ngcc__ || (packageJson.__processed_by_ivy_ngcc__ = {});
for (const prop of properties) {
processed[prop] = NGCC_VERSION;
}
const scripts = packageJson.scripts || (packageJson.scripts = {});
scripts.prepublishOnly__ivy_ngcc_bak =
scripts.prepublishOnly__ivy_ngcc_bak || scripts.prepublishOnly;
scripts.prepublishOnly = 'node --eval \"console.error(\'' +
'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' +
'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' +
'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' +
'\')\" ' +
'&& exit 1';
// Just in case this package.json was synthesized due to a custom configuration
// we will ensure that the path to the containing folder exists before we write the file.
fs.ensureDir(dirname(packageJsonPath));

View File

@ -55,10 +55,11 @@ export interface PackageJsonFormatProperties {
*/
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
name: string;
__processed_by_ivy_ngcc__?: {[key: string]: string};
scripts?: Record<string, string>;
__processed_by_ivy_ngcc__?: Record<string, string>;
}
export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
export type EntryPointJsonProperty = Exclude<keyof PackageJsonFormatProperties, 'types'|'typings'>;
// We need to keep the elements of this const and the `EntryPointJsonProperty` type in sync.
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
@ -122,7 +123,8 @@ export function getEntryPointInfo(
* @returns An entry-point format or `undefined` if none match the given property.
*/
export function getEntryPointFormat(
fs: FileSystem, entryPoint: EntryPoint, property: string): EntryPointFormat|undefined {
fs: FileSystem, entryPoint: EntryPoint, property: EntryPointJsonProperty): EntryPointFormat|
undefined {
switch (property) {
case 'fesm2015':
return 'esm2015';

View File

@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../../src/ngtsc/file_
import {NgtscCompilerHost} from '../../../src/ngtsc/file_system/src/compiler_host';
import {PathMappings} from '../utils';
import {BundleProgram, makeBundleProgram} from './bundle_program';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from './entry_point';
import {EntryPoint, EntryPointFormat} from './entry_point';
import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
/**
@ -19,7 +19,6 @@ import {NgccSourcesCompilerHost} from './ngcc_compiler_host';
*/
export interface EntryPointBundle {
entryPoint: EntryPoint;
formatProperty: EntryPointJsonProperty;
format: EntryPointFormat;
isCore: boolean;
isFlatCore: boolean;
@ -34,7 +33,6 @@ export interface EntryPointBundle {
* @param entryPoint The entry-point that contains the bundle.
* @param formatPath The path to the source files for this bundle.
* @param isCore This entry point is the Angular core package.
* @param formatProperty The property in the package.json that holds the formatPath.
* @param format The underlying format of the bundle.
* @param transformDts Whether to transform the typings along with this bundle.
* @param pathMappings An optional set of mappings to use when compiling files.
@ -43,8 +41,8 @@ export interface EntryPointBundle {
*/
export function makeEntryPointBundle(
fs: FileSystem, entryPoint: EntryPoint, formatPath: string, isCore: boolean,
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean,
pathMappings?: PathMappings, mirrorDtsFromSrc: boolean = false): EntryPointBundle|null {
format: EntryPointFormat, transformDts: boolean, pathMappings?: PathMappings,
mirrorDtsFromSrc: boolean = false): EntryPointBundle {
// Create the TS program and necessary helpers.
const options: ts.CompilerOptions = {
allowJs: true,
@ -69,7 +67,7 @@ export function makeEntryPointBundle(
null;
const isFlatCore = isCore && src.r3SymbolsFile === null;
return {entryPoint, format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};
return {entryPoint, format, rootDirs, isCore, isFlatCore, src, dts};
}
function computePotentialDtsFilesFromJsFiles(
@ -88,4 +86,4 @@ function computePotentialDtsFilesFromJsFiles(
}
}
return additionalFiles;
}
}

View File

@ -165,14 +165,12 @@ export function renderDefinitions(
translateStatement(stmt, imports, NOOP_DEFAULT_IMPORT_RECORDER);
const print = (stmt: Statement) =>
printer.printNode(ts.EmitHint.Unspecified, translate(stmt), sourceFile);
const definitions = compiledClass.compilation
.map(
c => [createAssignmentStatement(name, c.name, c.initializer)]
.concat(c.statements)
.map(print)
.join('\n'))
.join('\n');
return definitions;
const statements: Statement[] =
compiledClass.compilation.map(c => createAssignmentStatement(name, c.name, c.initializer));
for (const c of compiledClass.compilation) {
statements.push(...c.statements);
}
return statements.map(print).join('\n');
}
/**

View File

@ -82,3 +82,14 @@ export function resolveFileWithPostfixes(
}
return null;
}
/**
* An identifier may become repeated when bundling multiple source files into a single bundle, so
* bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function
* strips off such suffixes, so that ngcc deals with the canonical name of an identifier.
* @param value The value to strip any suffix of, if applicable.
* @returns The canonical representation of the value, without any suffix.
*/
export function stripDollarSuffix(value: string): string {
return value.replace(/\$\d+$/, '');
}

View File

@ -6,7 +6,7 @@
* 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 {EntryPoint} from '../packages/entry_point';
import {EntryPointJsonProperty} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileToWrite} from '../rendering/utils';
@ -14,6 +14,7 @@ import {FileToWrite} from '../rendering/utils';
* Responsible for writing out the transformed files to disk.
*/
export interface FileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]):
void;
writeBundle(
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
formatProperties: EntryPointJsonProperty[]): void;
}

View File

@ -7,7 +7,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
import {EntryPoint} from '../packages/entry_point';
import {EntryPointJsonProperty} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileToWrite} from '../rendering/utils';
import {FileWriter} from './file_writer';
@ -19,7 +19,9 @@ import {FileWriter} from './file_writer';
export class InPlaceFileWriter implements FileWriter {
constructor(protected fs: FileSystem) {}
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
writeBundle(
_bundle: EntryPointBundle, transformedFiles: FileToWrite[],
_formatProperties?: EntryPointJsonProperty[]) {
transformedFiles.forEach(file => this.writeFileAndBackup(file));
}

View File

@ -25,12 +25,15 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
* `InPlaceFileWriter`).
*/
export class NewEntryPointFileWriter extends InPlaceFileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileToWrite[]) {
writeBundle(
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
formatProperties: EntryPointJsonProperty[]) {
// The new folder is at the root of the overall package
const entryPoint = bundle.entryPoint;
const ngccFolder = join(entryPoint.package, NGCC_DIRECTORY);
this.copyBundle(bundle, entryPoint.package, ngccFolder);
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
}
protected copyBundle(
@ -60,12 +63,18 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
}
protected updatePackageJson(
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
const newFormatProperty = formatProperty + '_ivy_ngcc';
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
entryPoint: EntryPoint, formatProperties: EntryPointJsonProperty[],
ngccFolder: AbsoluteFsPath) {
const packageJson = entryPoint.packageJson;
for (const formatProperty of formatProperties) {
const formatPath = join(entryPoint.path, packageJson[formatProperty] !);
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath));
const newFormatProperty = formatProperty + '_ivy_ngcc';
(packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath);
}
this.fs.writeFile(
join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson));
join(entryPoint.path, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
}
}

View File

@ -96,8 +96,7 @@ runInEachFileSystem(() => {
loadTestFiles(testFiles);
loadFakeCore(getFileSystem());
const rootFiles = getRootFiles(testFiles);
const bundle =
makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
program = bundle.src.program;
const reflectionHost =
@ -373,4 +372,4 @@ class MockMigration implements Migration {
this.log.push(`${this.name}:${clazz.name.text}`);
return null;
}
}
}

View File

@ -330,7 +330,7 @@ runInEachFileSystem(() => {
loadTestFiles(TEST_PROGRAM);
loadTestFiles(TEST_DTS_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test-package', 'esm2015', 'esm2015', false, getRootFiles(TEST_PROGRAM),
'test-package', 'esm2015', false, getRootFiles(TEST_PROGRAM),
getRootFiles(TEST_DTS_PROGRAM));
program = bundle.src.program;
dtsProgram = bundle.dts;

View File

@ -236,8 +236,7 @@ runInEachFileSystem(() => {
loadTestFiles(jsProgram);
loadTestFiles(dtsProgram);
const {src: {program}, dts} = makeTestEntryPointBundle(
'test-package', 'esm2015', 'esm2015', false, getRootFiles(jsProgram),
getRootFiles(dtsProgram));
'test-package', 'esm2015', false, getRootFiles(jsProgram), getRootFiles(dtsProgram));
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker(), dts);
const referencesRegistry = new NgccReferencesRegistry(host);
const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry);

View File

@ -72,7 +72,7 @@ runInEachFileSystem(() => {
it('should check for switchable markers in all the files of the program', () => {
loadTestFiles(TEST_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
@ -103,7 +103,7 @@ runInEachFileSystem(() => {
it('should ignore files that are outside the package', () => {
loadTestFiles(TEST_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
'test', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
@ -114,4 +114,4 @@ runInEachFileSystem(() => {
});
});
});
});
});

View File

@ -179,6 +179,32 @@ runInEachFileSystem(() => {
expect(sorted.entryPoints).toEqual([fifth]);
});
it('should not process the provided target if it has missing dependencies', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['/missing']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([]);
expect(sorted.invalidEntryPoints[0].entryPoint).toEqual(first);
expect(sorted.invalidEntryPoints[0].missingDependencies).toEqual(['/missing']);
});
it('should not consider builtin NodeJS modules as missing dependency', () => {
spyOn(host, 'findDependencies').and.callFake(createFakeComputeDependencies({
[_('/first/index.js')]: {resolved: [], missing: ['fs']},
}));
const entryPoints = [first];
let sorted: SortedEntryPointsInfo;
sorted = resolver.sortEntryPointsByDependency(entryPoints, first);
expect(sorted.entryPoints).toEqual([first]);
expect(sorted.invalidEntryPoints).toEqual([]);
expect(sorted.ignoredDependencies).toEqual([]);
});
it('should use the appropriate DependencyHost for each entry-point', () => {
const esm5Host = new EsmDependencyHost(fs, moduleResolver);
const esm2015Host = new EsmDependencyHost(fs, moduleResolver);

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {TestFile} from '../../../src/ngtsc/file_system/testing';
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
@ -32,19 +32,13 @@ export function makeTestEntryPoint(
* @param dtsFiles The typings files to include the bundle.
*/
export function makeTestEntryPointBundle(
packageName: string, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
isCore: boolean, srcRootNames: AbsoluteFsPath[],
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
const entryPoint = makeTestEntryPoint(packageName);
const src = makeTestBundleProgram(srcRootNames[0], isCore);
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
const isFlatCore = isCore && src.r3SymbolsFile === null;
return {
entryPoint,
formatProperty,
format,
rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore
};
return {entryPoint, format, rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore};
}
export function makeTestBundleProgram(
@ -78,6 +72,22 @@ export function convertToDirectTsLibImport(filesystem: TestFile[]) {
});
}
export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') {
return filesystem.map(file => {
const contents = file.contents
.replace(`import * as tslib_1 from 'tslib';`, `
var __decorate${suffix} = null;
var __metadata${suffix} = null;
var __read${suffix} = null;
var __values${suffix} = null;
var __param${suffix} = null;
var __extends${suffix} = null;
var __assign${suffix} = null;
`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$'));
return {...file, contents};
});
}
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
}

View File

@ -86,6 +86,7 @@ exports.OtherDirective = OtherDirective;
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('core.Directive');
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',

View File

@ -951,23 +951,6 @@ exports.ExternalModule = ExternalModule;
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
});
describe('(returned decorators `args`)', () => {
it('should be an empty array if decorator has no `args` property', () => {
loadTestFiles([INVALID_DECORATOR_ARGS_FILE]);
@ -1185,22 +1168,17 @@ exports.ExternalModule = ExternalModule;
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
});
describe('(returned prop decorators `args`)', () => {
@ -1430,24 +1408,19 @@ exports.ExternalModule = ExternalModule;
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
const spy = spyOn(CommonJsReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
const parameters = host.getConstructorParameters(classNode);
const decorators = parameters ![2].decorators !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Inject');
expect(decorators[0].name).toBe('Inject');
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
});
});

View File

@ -15,7 +15,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger';
import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils';
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
@ -111,14 +111,18 @@ runInEachFileSystem(() => {
];
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
FILES = {
'namespaced': NAMESPACED_IMPORT_FILES,
'direct import': DIRECT_IMPORT_FILES,
'inline': INLINE_FILES,
'inline suffixed': INLINE_SUFFIXED_FILES,
};
});
['namespaced', 'direct import'].forEach(label => {
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
describe(`[${label}]`, () => {
beforeEach(() => {
const fs = getFileSystem();
@ -141,35 +145,13 @@ runInEachFileSystem(() => {
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('Directive');
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',
]);
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const spy =
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
.and.callFake(
(identifier: ts.Identifier) => identifier.getText() === 'Directive' ?
{from: '@angular/core', name: 'Directive'} :
{});
const {program} = makeTestBundleProgram(_('/some_directive.js'));
const host =
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'});
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy();
});
it('should support decorators being used inside @angular/core', () => {
const {program} =
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
@ -185,6 +167,7 @@ runInEachFileSystem(() => {
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('Directive');
expect(decorator.import).toEqual({name: 'Directive', from: './directives'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',
@ -272,21 +255,6 @@ runInEachFileSystem(() => {
expect(staticProperty.value !.getText()).toEqual(`'static'`);
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const spy =
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({});
const {program} = makeTestBundleProgram(_('/some_directive.js'));
const host =
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
host.getMembersOfClass(classNode);
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy();
});
it('should support decorators being used inside @angular/core', () => {
const {program} =
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));

View File

@ -763,11 +763,7 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const mockImportInfo = { from: '@angular/core' } as Import;
const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
@ -776,10 +772,7 @@ runInEachFileSystem(() => {
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
});
describe('(returned decorators `args`)', () => {
@ -839,11 +832,13 @@ runInEachFileSystem(() => {
expect(input1.kind).toEqual(ClassMemberKind.Property);
expect(input1.isStatic).toEqual(false);
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
const input2 = members.find(member => member.name === 'input2') !;
expect(input2.kind).toEqual(ClassMemberKind.Property);
expect(input2.isStatic).toEqual(false);
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
expect(input2.decorators !.map(d => d.name)).toEqual(['Input']);
expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
});
it('should find non decorated properties on a class', () => {
@ -991,35 +986,6 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
let callCount = 0;
const spy =
spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => {
callCount++;
return {name: `name${callCount}`, from: '@angular/core'};
});
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration);
const members = host.getMembersOfClass(classNode);
expect(spy).toHaveBeenCalled();
expect(spy.calls.allArgs().map(arg => arg[0].getText())).toEqual([
'Input',
'Input',
'HostBinding',
'Input',
'HostListener',
]);
const member = members.find(member => member.name === 'input1') !;
expect(member.decorators !.length).toBe(1);
expect(member.decorators ![0].import).toEqual({name: 'name1', from: '@angular/core'});
});
describe('(returned prop decorators `args`)', () => {
it('should be an empty array if prop decorator has no `args` property', () => {
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
@ -1311,11 +1277,7 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const mockImportInfo: Import = {name: 'mock', from: '@angular/core'};
const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
@ -1325,10 +1287,7 @@ runInEachFileSystem(() => {
const decorators = parameters[2].decorators !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Inject');
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
});
});

View File

@ -14,7 +14,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers';
import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {MockLogger} from '../helpers/mock_logger';
import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils';
import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
@ -132,14 +132,18 @@ export { SomeDirective };
];
const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES);
const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES);
const INLINE_SUFFIXED_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES, '$2');
FILES = {
'namespaced': NAMESPACED_IMPORT_FILES,
'direct import': DIRECT_IMPORT_FILES,
'inline': INLINE_FILES,
'inline suffixed': INLINE_SUFFIXED_FILES,
};
});
['namespaced', 'direct import'].forEach(label => {
['namespaced', 'direct import', 'inline', 'inline suffixed'].forEach(label => {
describe(`[${label}]`, () => {
beforeEach(() => {
const fs = getFileSystem();
@ -161,34 +165,13 @@ export { SomeDirective };
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('Directive');
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',
]);
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const spy =
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
.and.callFake(
(identifier: ts.Identifier) => identifier.getText() === 'Directive' ?
{from: '@angular/core', name: 'Directive'} :
{});
const {program} = makeTestBundleProgram(_('/some_directive.js'));
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'});
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy();
});
it('should support decorators being used inside @angular/core', () => {
const {program} =
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
@ -203,6 +186,7 @@ export { SomeDirective };
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('Directive');
expect(decorator.import).toEqual({name: 'Directive', from: './directives'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',
@ -270,20 +254,6 @@ export { SomeDirective };
expect(staticProperty.value !.getText()).toEqual(`'static'`);
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const spy =
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({});
const {program} = makeTestBundleProgram(_('/some_directive.js'));
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration);
host.getMembersOfClass(classNode);
const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text);
expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy();
});
it('should support decorators being used inside @angular/core', () => {
const {program} =
makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js'));
@ -319,12 +289,7 @@ export { SomeDirective };
});
describe('(returned parameters `decorators`)', () => {
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const mockImportInfo = {} as Import;
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
it('should have import information on decorators', () => {
const {program} = makeTestBundleProgram(_('/some_directive.js'));
const host =
new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
@ -334,10 +299,7 @@ export { SomeDirective };
const decorators = parameters ![2].decorators !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Inject');
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
});
});
});

View File

@ -942,11 +942,7 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
@ -955,10 +951,7 @@ runInEachFileSystem(() => {
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
});
describe('(returned decorators `args`)', () => {
@ -1019,11 +1012,13 @@ runInEachFileSystem(() => {
expect(input1.kind).toEqual(ClassMemberKind.Property);
expect(input1.isStatic).toEqual(false);
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
const input2 = members.find(member => member.name === 'input2') !;
expect(input2.kind).toEqual(ClassMemberKind.Property);
expect(input2.isStatic).toEqual(false);
expect(input2.decorators !.map(d => d.name)).toEqual(['Input']);
expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'});
});
it('should find decorated members on a class', () => {
@ -1232,30 +1227,6 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
let callCount = 0;
const spy =
spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => {
callCount++;
return {name: `name${callCount}`, from: `@angular/core`};
});
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const members = host.getMembersOfClass(classNode);
expect(spy).toHaveBeenCalled();
spy.calls.allArgs().forEach(arg => expect(arg[0].getText()).toEqual('Input'));
const index = members.findIndex(member => member.name === 'input1');
expect(members[index].decorators !.length).toBe(1);
expect(members[index].decorators ![0].import)
.toEqual({name: 'name1', from: '@angular/core'});
});
describe('(returned prop decorators `args`)', () => {
it('should be an empty array if prop decorator has no `args` property', () => {
loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]);
@ -1466,11 +1437,7 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
const mockImportInfo = { name: 'mock', from: '@angulare/core' } as Import;
const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier')
.and.returnValue(mockImportInfo);
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
@ -1480,10 +1447,7 @@ runInEachFileSystem(() => {
const decorators = parameters ![2].decorators !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Inject');
expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'});
});
});
@ -1705,6 +1669,30 @@ runInEachFileSystem(() => {
expect(definition.helper).toBe(TsHelperFn.Spread);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __spread helper function implementation when suffixed',
() => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __spread$2 = (this && this.__spread$2) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};`,
};
loadTestFiles([file]);
const {program} = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const node =
getDeclaration(program, file.name, '__spread$2', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Spread);
expect(definition.parameters.length).toEqual(0);
});
});
describe('getImportOfIdentifier()', () => {

View File

@ -0,0 +1,132 @@
/**
* @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 {absoluteFrom} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadTestFiles} from '../../../test/helpers';
import {UmdReflectionHost} from '../../src/host/umd_host';
import {MockLogger} from '../helpers/mock_logger';
import {makeTestBundleProgram} from '../helpers/utils';
import {expectTypeValueReferencesForParameters} from './util';
runInEachFileSystem(() => {
describe('UmdReflectionHost [import helper style]', () => {
let _: typeof absoluteFrom;
let SOME_DIRECTIVE_FILE: TestFile;
beforeEach(() => {
_ = absoluteFrom;
SOME_DIRECTIVE_FILE = {
name: _('/some_directive.umd.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) :
(factory(global.some_directive,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var __decorate = null;
var __metadata = null;
var __param = null;
var INJECTED_TOKEN = new InjectionToken('injected');
var ViewContainerRef = {};
var TemplateRef = {};
var SomeDirective = (function() {
function SomeDirective(_viewContainer, _template, injected) {}
__decorate([
core.Input(),
__metadata("design:type", String)
], SomeDirective.prototype, "input1", void 0);
__decorate([
core.Input(),
__metadata("design:type", Number)
], SomeDirective.prototype, "input2", void 0);
SomeDirective = __decorate([
core.Directive({ selector: '[someDirective]' }),
__param(2, core.Inject(INJECTED_TOKEN)),
__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String])
], SomeDirective);
return SomeDirective;
}());
exports.SomeDirective = SomeDirective;
})));`,
};
});
describe('getDecoratorsOfDeclaration()', () => {
it('should find the decorators on a class', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators).toBeDefined();
expect(decorators.length).toEqual(1);
const decorator = decorators[0];
expect(decorator.name).toEqual('Directive');
expect(decorator.identifier.getText()).toEqual('core.Directive');
expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'});
expect(decorator.args !.map(arg => arg.getText())).toEqual([
'{ selector: \'[someDirective]\' }',
]);
});
});
describe('getMembersOfClass()', () => {
it('should find decorated members on a class', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const members = host.getMembersOfClass(classNode);
const input1 = members.find(member => member.name === 'input1') !;
expect(input1.kind).toEqual(ClassMemberKind.Property);
expect(input1.isStatic).toEqual(false);
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
const input2 = members.find(member => member.name === 'input2') !;
expect(input2.kind).toEqual(ClassMemberKind.Property);
expect(input2.isStatic).toEqual(false);
expect(input1.decorators !.map(d => d.name)).toEqual(['Input']);
});
describe('getConstructorParameters', () => {
it('should find the decorated constructor parameters', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const parameters = host.getConstructorParameters(classNode);
expect(parameters).toBeDefined();
expect(parameters !.map(parameter => parameter.name)).toEqual([
'_viewContainer', '_template', 'injected'
]);
expectTypeValueReferencesForParameters(parameters !, [
'ViewContainerRef',
'TemplateRef',
null,
]);
});
});
});
});
});

View File

@ -1057,22 +1057,17 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'};
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
});
it('should find decorated members on a class at the top level', () => {
@ -1290,22 +1285,17 @@ runInEachFileSystem(() => {
expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'}));
});
it('should use `getImportOfIdentifier()` to retrieve import info', () => {
it('should have import information on decorators', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost);
const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import;
const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo);
const classNode = getDeclaration(
program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration);
const decorators = host.getDecoratorsOfDeclaration(classNode) !;
expect(decorators.length).toEqual(1);
expect(decorators[0].import).toBe(mockImportInfo);
const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier;
expect(typeIdentifier.text).toBe('Directive');
expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'});
});
describe('(returned prop decorators `args`)', () => {

View File

@ -86,12 +86,26 @@ runInEachFileSystem(() => {
// `test-package` has no Angular but is marked as processed.
expect(loadPackage('test-package').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
esm2015: '0.0.0-PLACEHOLDER',
esm5: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
main: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
});
// * `core` is a dependency of `test-package`, but it is not processed, since test-package
// was not processed.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toBeUndefined();
});
it('should report an error if a dependency of the target does not exist', () => {
expect(() => {
mainNgcc({basePath: '/node_modules', targetEntryPointPath: 'invalid-package'});
})
.toThrowError(
'The target entry-point "invalid-package" has missing dependencies:\n - @angular/missing\n');
});
});
describe('early skipping of target entry-point', () => {
@ -157,6 +171,23 @@ runInEachFileSystem(() => {
]);
});
});
it('should skip all processing if the first matching `propertyToConsider` is marked as processed',
() => {
const logger = new MockLogger();
markPropertiesAsProcessed('@angular/common/http/testing', ['esm2015']);
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: '@angular/common/http/testing',
// Simulate a property that does not exist on the package.json and will be ignored.
propertiesToConsider: ['missing', 'esm2015', 'esm5'],
compileAllFormats: false, logger,
});
expect(logger.logs.debug).toContain([
'The target entry-point has already been processed'
]);
});
});
@ -164,13 +195,22 @@ runInEachFileSystem(() => {
const basePath = _('/node_modules');
const targetPackageJsonPath = join(basePath, packagePath, 'package.json');
const targetPackage = loadPackage(packagePath);
markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings');
properties.forEach(
property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property));
markAsProcessed(fs, targetPackage, targetPackageJsonPath, ['typings', ...properties]);
}
describe('with propertiesToConsider', () => {
it('should complain if none of the properties in the `propertiesToConsider` list is supported',
() => {
const propertiesToConsider = ['es1337', 'fesm42'];
const errorMessage =
'No supported format property to consider among [es1337, fesm42]. Supported ' +
'properties: fesm2015, fesm5, es2015, esm2015, esm5, main, module';
expect(() => mainNgcc({basePath: '/node_modules', propertiesToConsider}))
.toThrowError(errorMessage);
});
it('should only compile the entry-point formats given in the `propertiesToConsider` list',
() => {
mainNgcc({
@ -210,6 +250,33 @@ runInEachFileSystem(() => {
typings: '0.0.0-PLACEHOLDER',
});
});
it('should mark all matching properties as processed in order not to compile them on a subsequent run',
() => {
const logger = new MockLogger();
const logs = logger.logs.debug;
// `fesm2015` and `es2015` map to the same file: `./fesm2015/common.js`
mainNgcc({
basePath: '/node_modules/@angular/common',
propertiesToConsider: ['fesm2015'], logger,
});
expect(logs).not.toContain(['Skipping @angular/common : es2015 (already compiled).']);
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
// Now, compiling `es2015` should be a no-op.
mainNgcc({
basePath: '/node_modules/@angular/common',
propertiesToConsider: ['es2015'], logger,
});
expect(logs).toContain(['Skipping @angular/common : es2015 (already compiled).']);
});
});
describe('with compileAllFormats set to false', () => {
@ -256,6 +323,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
@ -268,6 +336,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
esm5: '0.0.0-PLACEHOLDER',
fesm5: '0.0.0-PLACEHOLDER',
module: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
@ -313,6 +382,28 @@ runInEachFileSystem(() => {
.toMatch(ANGULAR_CORE_IMPORT_REGEX);
expect(fs.exists(_(`/node_modules/@angular/common/common.d.ts.__ivy_ngcc_bak`))).toBe(true);
});
it('should update `package.json` for all matching format properties', () => {
mainNgcc({
basePath: '/node_modules/@angular/core',
createNewEntryPointFormats: true,
propertiesToConsider: ['fesm2015', 'fesm5'],
});
const pkg: any = loadPackage('@angular/core');
// `es2015` is an alias of `fesm2015`.
expect(pkg.fesm2015).toEqual('./fesm2015/core.js');
expect(pkg.es2015).toEqual('./fesm2015/core.js');
expect(pkg.fesm2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
expect(pkg.es2015_ivy_ngcc).toEqual('__ivy_ngcc__/fesm2015/core.js');
// `module` is an alias of `fesm5`.
expect(pkg.fesm5).toEqual('./fesm5/core.js');
expect(pkg.module).toEqual('./fesm5/core.js');
expect(pkg.fesm5_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
expect(pkg.module_ivy_ngcc).toEqual('__ivy_ngcc__/fesm5/core.js');
});
});
describe('logger', () => {
@ -342,6 +433,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('local-package', _('/dist')).__processed_by_ivy_ngcc__).toEqual({
@ -399,6 +491,7 @@ runInEachFileSystem(() => {
});
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -425,6 +518,7 @@ runInEachFileSystem(() => {
// We process core but not core/testing.
expect(loadPackage('@angular/core').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
expect(loadPackage('@angular/core/testing').__processed_by_ivy_ngcc__).toBeUndefined();
@ -432,6 +526,7 @@ runInEachFileSystem(() => {
expect(loadPackage('@angular/common').__processed_by_ivy_ngcc__).toBeUndefined();
expect(loadPackage('@angular/common/http').__processed_by_ivy_ngcc__).toEqual({
es2015: '0.0.0-PLACEHOLDER',
fesm2015: '0.0.0-PLACEHOLDER',
typings: '0.0.0-PLACEHOLDER',
});
});
@ -482,6 +577,30 @@ runInEachFileSystem(() => {
contents: `export declare class AppComponent {};`
},
]);
// An Angular package that has a missing dependency
loadTestFiles([
{
name: _('/node_modules/invalid-package/package.json'),
contents: '{"name": "invalid-package", "es2015": "./index.js", "typings": "./index.d.ts"}'
},
{
name: _('/node_modules/invalid-package/index.js'),
contents: `
import {AppModule} from "@angular/missing";
import {Component} from '@angular/core';
export class AppComponent {};
AppComponent.decorators = [
{ type: Component, args: [{selector: 'app', template: '<h2>Hello</h2>'}] }
];
`
},
{
name: _('/node_modules/invalid-package/index.d.ts'),
contents: `export declare class AppComponent {}`
},
{name: _('/node_modules/invalid-package/index.metadata.json'), contents: 'DUMMY DATA'},
]);
}
});
});

View File

@ -161,7 +161,7 @@ runInEachFileSystem(() => {
loadFakeCore(getFileSystem());
const errors: ts.Diagnostic[] = [];
const rootFiles = getRootFiles(testFiles);
const bundle = makeTestEntryPointBundle('test-package', 'es2015', 'esm2015', false, rootFiles);
const bundle = makeTestEntryPointBundle('test-package', 'esm2015', false, rootFiles);
const program = bundle.src.program;
const reflectionHost =

View File

@ -79,31 +79,73 @@ runInEachFileSystem(() => {
});
describe('markAsProcessed', () => {
it('should write a property in the package.json containing the version placeholder', () => {
it('should write properties in the package.json containing the version placeholder', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should update the packageJson object in-place', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should one perform one write operation for all updated properties', () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
const writeFileSpy = spyOn(fs, 'writeFile');
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']);
expect(writeFileSpy).toHaveBeenCalledTimes(1);
});
it(`should keep backup of existing 'prepublishOnly' script`, () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
const prepublishOnly = 'existing script';
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
pkg.scripts = {prepublishOnly};
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.scripts.prepublishOnly).toContain('This is not allowed');
expect(pkg.scripts.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly);
});
});

View File

@ -144,8 +144,7 @@ runInEachFileSystem(() => {
typings: absoluteFrom('/node_modules/test/index.d.ts'),
compiledByAngular: true,
};
const esm5bundle =
makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', 'esm5', true) !;
const esm5bundle = makeEntryPointBundle(fs, entryPoint, './index.js', false, 'esm5', true);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toEqual(jasmine.arrayWithExactContents([
@ -192,8 +191,8 @@ runInEachFileSystem(() => {
compiledByAngular: true,
};
const esm5bundle = makeEntryPointBundle(
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true) !;
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toContain(absoluteFrom('/node_modules/test/internal.js'));
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))
@ -213,8 +212,8 @@ runInEachFileSystem(() => {
compiledByAngular: true,
};
const esm5bundle = makeEntryPointBundle(
fs, entryPoint, './index.js', false, 'esm5', 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false) !;
fs, entryPoint, './index.js', false, 'esm5', /* transformDts */ true,
/* pathMappings */ undefined, /* mirrorDtsFromSrc */ false);
expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
.toContain(absoluteFrom('/node_modules/test/internal.js'));
expect(esm5bundle.dts !.program.getSourceFiles().map(sf => sf.fileName))

View File

@ -149,8 +149,7 @@ exports.D = D;
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle =
makeTestEntryPointBundle('test-package', 'module', 'commonjs', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'commonjs', false, [file.name]);
const typeChecker = bundle.src.program.getTypeChecker();
const host = new CommonJsReflectionHost(logger, false, bundle.src.program, bundle.src.host);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -61,8 +61,7 @@ function createTestRenderer(
const fs = getFileSystem();
const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles));
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -26,7 +26,7 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('test-package', 'module', 'esm5', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -31,8 +31,7 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) {
}
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', false, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles)) !;
'test-package', 'esm2015', false, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles)) !;
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, false, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -63,8 +63,7 @@ function createTestRenderer(
const fs = getFileSystem();
const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', isCore, getRootFiles(files),
dtsFiles && getRootFiles(dtsFiles));
'test-package', 'esm2015', isCore, getRootFiles(files), dtsFiles && getRootFiles(dtsFiles));
const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host);
@ -98,6 +97,7 @@ runInEachFileSystem(() => {
let _: typeof absoluteFrom;
let INPUT_PROGRAM: TestFile;
let COMPONENT_PROGRAM: TestFile;
let NGMODULE_PROGRAM: TestFile;
let INPUT_PROGRAM_MAP: SourceMapConverter;
let RENDERED_CONTENTS: string;
let OUTPUT_PROGRAM_MAP: SourceMapConverter;
@ -118,6 +118,12 @@ runInEachFileSystem(() => {
`import { Component } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }\n];\n`
};
NGMODULE_PROGRAM = {
name: _('/node_modules/test-package/src/ngmodule.js'),
contents:
`import { NgModule } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: NgModule, args: [{}] }\n];\n`
};
INPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': _('/node_modules/test-package/src/file.js'),
@ -254,6 +260,25 @@ runInEachFileSystem(() => {
.toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`);
});
it('should render static fields before any additional statements', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
testFormatter} = createTestRenderer('test-package', [NGMODULE_PROGRAM]);
renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
const addDefinitionsSpy = testFormatter.addDefinitions as jasmine.Spy;
const definitions: string = addDefinitionsSpy.calls.first().args[2];
const ngModuleDef = definitions.indexOf('ngModuleDef');
expect(ngModuleDef).not.toEqual(-1, 'ngModuleDef should exist');
const ngInjectorDef = definitions.indexOf('ngInjectorDef');
expect(ngInjectorDef).not.toEqual(-1, 'ngInjectorDef should exist');
const setClassMetadata = definitions.indexOf('setClassMetadata');
expect(setClassMetadata).not.toEqual(-1, 'setClassMetadata call should exist');
expect(setClassMetadata)
.toBeGreaterThan(ngModuleDef, 'setClassMetadata should follow ngModuleDef');
expect(setClassMetadata)
.toBeGreaterThan(ngInjectorDef, 'setClassMetadata should follow ngInjectorDef');
});
it('should render classes without decorators if handler matches', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
testFormatter} =
@ -283,7 +308,7 @@ runInEachFileSystem(() => {
ɵngcc0.ɵɵstaticViewQuery(_c0, true);
} if (rf & 2) {
var _t;
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadViewQuery()) && (ctx.test = _t.first);
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first);
} } });`);
});

View File

@ -25,7 +25,7 @@ function setup(file: TestFile) {
loadTestFiles([file]);
const fs = getFileSystem();
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('test-package', 'esm5', 'esm5', false, [file.name]);
const bundle = makeTestEntryPointBundle('test-package', 'esm5', false, [file.name]);
const src = bundle.src;
const host = new UmdReflectionHost(logger, false, src.program, src.host);
const referencesRegistry = new NgccReferencesRegistry(host);

View File

@ -8,7 +8,6 @@
import {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
@ -32,7 +31,7 @@ runInEachFileSystem(() => {
it('should write all the FileInfo to the disk', () => {
const fs = getFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
fileWriter.writeBundle({} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
@ -49,7 +48,7 @@ runInEachFileSystem(() => {
it('should create backups of all files that previously existed', () => {
const fs = getFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
fileWriter.writeBundle({} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
@ -72,10 +71,7 @@ runInEachFileSystem(() => {
const absoluteBackupPath = _('/package/path/already-backed-up.js');
expect(
() => fileWriter.writeBundle(
{} as EntryPoint, {} as EntryPointBundle,
[
{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'},
]))
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
.toThrowError(
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
});

View File

@ -32,8 +32,15 @@ runInEachFileSystem(() => {
{
name: _('/node_modules/test/package.json'),
contents:
'{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
contents: `
{
"module": "./esm5.js",
"fesm2015": "./es2015/index.js",
"fesm5": "./esm5.js",
"es2015": "./es2015/index.js",
"typings": "./index.d.ts"
}
`,
},
{name: _('/node_modules/test/index.d.ts'), contents: 'export declare class FooTop {}'},
{name: _('/node_modules/test/index.d.ts.map'), contents: 'ORIGINAL MAPPING DATA'},
@ -44,8 +51,15 @@ runInEachFileSystem(() => {
{name: _('/node_modules/test/es2015/foo.js'), contents: 'export class FooTop {}'},
{
name: _('/node_modules/test/a/package.json'),
contents:
`{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}`
contents: `
{
"module": "./esm5.js",
"fesm2015": "./es2015/index.js",
"fesm5": "./esm5.js",
"es2015": "./es2015/index.js",
"typings": "./index.d.ts"
}
`,
},
{name: _('/node_modules/test/a/index.d.ts'), contents: 'export declare class FooA {}'},
{name: _('/node_modules/test/a/index.metadata.json'), contents: '...'},
@ -95,13 +109,16 @@ runInEachFileSystem(() => {
});
it('should write the modified files to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
.toEqual('export function FooTop() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
@ -111,12 +128,15 @@ runInEachFileSystem(() => {
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
.toEqual('export class FooTop {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/es2015/foo.js')))
@ -128,36 +148,77 @@ runInEachFileSystem(() => {
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
}));
});
it('should be able to update multiple package.json properties at once', () => {
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
],
['module', 'fesm5']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
}));
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
],
['es2015', 'fesm2015']);
expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
fesm5_ivy_ngcc: '__ivy_ngcc__/esm5.js',
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
fesm2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
}));
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED'
},
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED'
},
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/index.d.ts')))
.toEqual('export declare class FooTop {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
@ -184,24 +245,30 @@ runInEachFileSystem(() => {
});
it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
.toEqual('export function FooA() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
.toEqual('export class FooA {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js')))
@ -213,35 +280,76 @@ runInEachFileSystem(() => {
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
}));
});
it('should be able to update multiple package.json properties at once', () => {
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
],
['module', 'fesm5']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
}));
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
],
['es2015', 'fesm2015']);
expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
fesm5_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
fesm2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
}));
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/a/index.d.ts'),
contents: 'export declare class FooA {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/a/index.d.ts'),
contents: 'export declare class FooA {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
.toEqual('export declare class FooA {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
@ -262,12 +370,15 @@ runInEachFileSystem(() => {
});
it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
],
['module']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
.toEqual('export function FooB() {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/lib/esm5.js')))
@ -275,12 +386,15 @@ runInEachFileSystem(() => {
});
it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
.toEqual('export class FooB {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
@ -293,43 +407,55 @@ runInEachFileSystem(() => {
it('should not copy typings files within the package (i.e. from a different entry-point)',
() => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
});
it('should not copy files outside of the package', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
});
it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm5bundle,
[
{
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
],
['module']);
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
}));
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED'
},
],
['es2015']);
expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
@ -337,12 +463,15 @@ runInEachFileSystem(() => {
});
it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [
{
path: _('/node_modules/test/typings/index.d.ts'),
contents: 'export declare class FooB {} // MODIFIED'
},
]);
fileWriter.writeBundle(
esm2015bundle,
[
{
path: _('/node_modules/test/typings/index.d.ts'),
contents: 'export declare class FooB {} // MODIFIED'
},
],
['es2015']);
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
.toEqual('export declare class FooB {} // MODIFIED');
expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
@ -356,7 +485,6 @@ runInEachFileSystem(() => {
fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
format: EntryPointFormat): EntryPointBundle {
return makeEntryPointBundle(
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, formatProperty, format,
true) !;
fs, entryPoint, entryPoint.packageJson[formatProperty] !, false, format, true);
}
});

View File

@ -18,7 +18,7 @@ import {DirectiveMeta, MetadataReader, MetadataRegistry, extractDirectiveGuards}
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope';
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck';
import {NoopResourceDependencyRecorder, ResourceDependencyRecorder} from '../../util/src/resource_recorder';
@ -47,8 +47,8 @@ export class ComponentDecoratorHandler implements
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
private scopeRegistry: LocalModuleScopeRegistry, private isCore: boolean,
private resourceLoader: ResourceLoader, private rootDirs: string[],
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: string[],
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
private refEmitter: ReferenceEmitter, private defaultImportRecorder: DefaultImportRecorder,
@ -327,7 +327,7 @@ export class ComponentDecoratorHandler implements
preserveWhitespaces: true,
leadingTriviaChars: [],
});
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
const selector = analysis.meta.selector;
const matcher = new SelectorMatcher<DirectiveMeta>();
if (scope !== null) {
@ -353,7 +353,7 @@ export class ComponentDecoratorHandler implements
if (!ts.isClassDeclaration(node)) {
return;
}
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
const matcher = new SelectorMatcher<DirectiveMeta>();
if (scope !== null) {
for (const meta of scope.compilation.directives) {
@ -377,7 +377,7 @@ export class ComponentDecoratorHandler implements
const context = node.getSourceFile();
// Check whether this component was registered with an NgModule. If so, it should be compiled
// under that module's compilation scope.
const scope = this.scopeRegistry.getScopeForComponent(node);
const scope = this.scopeReader.getScopeForComponent(node);
let metadata = analysis.meta;
if (scope !== null) {
// Replace the empty components and directives from the analyze() step with a fully expanded

View File

@ -60,7 +60,7 @@ runInEachFileSystem(() => {
const refEmitter = new ReferenceEmitter([]);
const handler = new ComponentDecoratorHandler(
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, false,
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry, false,
new NoopResourceLoader(), [''], false, true, moduleResolver, cycleAnalyzer, refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER);
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);

View File

@ -12,6 +12,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],

View File

@ -12,13 +12,14 @@ import {Reference} from '../../imports';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {DependencyTracker} from '../../partial_evaluator';
import {ClassDeclaration} from '../../reflection';
import {ComponentScopeReader, ComponentScopeRegistry, LocalModuleScope} from '../../scope';
import {ResourceDependencyRecorder} from '../../util/src/resource_recorder';
/**
* Accumulates state between compilations.
*/
export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry,
ResourceDependencyRecorder {
ResourceDependencyRecorder, ComponentScopeRegistry, ComponentScopeReader {
private constructor(
private unchangedFiles: Set<ts.SourceFile>,
private metadata: Map<ts.SourceFile, FileMetadata>,
@ -69,32 +70,56 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
}
getFileDependencies(file: ts.SourceFile): ts.SourceFile[] {
const meta = this.metadata.get(file);
return meta ? Array.from(meta.fileDependencies) : [];
if (!this.metadata.has(file)) {
return [];
}
const meta = this.metadata.get(file) !;
return Array.from(meta.fileDependencies);
}
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.ngModuleMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.ngModuleMeta.has(ref.node)) {
return null;
}
return metadata.ngModuleMeta.get(ref.node) !;
}
registerNgModuleMetadata(meta: NgModuleMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.ngModuleMeta.set(meta.ref.node, meta);
}
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.directiveMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.directiveMeta.has(ref.node)) {
return null;
}
return metadata.directiveMeta.get(ref.node) !;
}
registerDirectiveMetadata(meta: DirectiveMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.directiveMeta.set(meta.ref.node, meta);
}
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
const metadata = this.metadata.get(ref.node.getSourceFile()) || null;
return metadata && metadata.pipeMeta.get(ref.node) || null;
if (!this.metadata.has(ref.node.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
if (!metadata.pipeMeta.has(ref.node)) {
return null;
}
return metadata.pipeMeta.get(ref.node) !;
}
registerPipeMetadata(meta: PipeMeta): void {
const metadata = this.ensureMetadata(meta.ref.node.getSourceFile());
metadata.pipeMeta.set(meta.ref.node, meta);
@ -105,6 +130,40 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
metadata.resourcePaths.add(resourcePath);
}
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {
const metadata = this.ensureMetadata(clazz.getSourceFile());
metadata.componentScope.set(clazz, scope);
}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.metadata.has(clazz.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(clazz.getSourceFile()) !;
if (!metadata.componentScope.has(clazz)) {
return null;
}
return metadata.componentScope.get(clazz) !;
}
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {
const metadata = this.ensureMetadata(clazz.getSourceFile());
metadata.remoteScoping.add(clazz);
}
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
// TODO: https://angular-team.atlassian.net/browse/FW-1501
// Handle the incremental build case where a component requires remote scoping.
// This means that if the the component's template changes, it requires the module to be
// re-emitted.
// Also, we need to make sure the cycle detector works well across rebuilds.
if (!this.metadata.has(clazz.getSourceFile())) {
return null;
}
const metadata = this.metadata.get(clazz.getSourceFile()) !;
return metadata.remoteScoping.has(clazz);
}
private ensureMetadata(sf: ts.SourceFile): FileMetadata {
const metadata = this.metadata.get(sf) || new FileMetadata();
this.metadata.set(sf, metadata);
@ -131,4 +190,6 @@ class FileMetadata {
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
pipeMeta = new Map<ClassDeclaration, PipeMeta>();
componentScope = new Map<ClassDeclaration, LocalModuleScope>();
remoteScoping = new Set<ClassDeclaration>();
}

View File

@ -0,0 +1,25 @@
# `indexer`
The `indexer` module generates semantic analysis about components used in an
Angular project. The module is consumed by a semantic analysis API on an Angular
program, which can be invoked separately from the regular Angular compilation
pipeline.
The module is _not_ a fully-featured source code indexer. Rather, it is designed
to produce semantic information about an Angular project that can then be used
by language analysis tools to generate, for example, cross-references in Angular
templates.
The `indexer` module is developed primarily with the
[Kythe](https://github.com/kythe/kythe) ecosystem in mind as an indexing
service.
### Scope of Analysis
The scope of analysis performed by the module includes
- indexing template syntax identifiers in a component template
- generating information about directives used in a template
- generating metadata about component and template source files
The module does not support indexing TypeScript source code.

View File

@ -201,7 +201,7 @@ class TemplateVisitor extends TmplAstRecursiveVisitor {
const identifiers = ExpressionVisitor.getIdentifiers(
attribute.value, expressionSrc, expressionAbsolutePosition, this.boundTemplate,
this.targetToIdentifier);
this.targetToIdentifier.bind(this));
identifiers.forEach(id => this.identifiers.add(id));
}
visitBoundEvent(attribute: TmplAstBoundEvent) { this.visitExpression(attribute.handler); }

View File

@ -124,6 +124,32 @@ runInEachFileSystem(() => {
});
});
it('should discover variables in bound attributes', () => {
const template = '<div #div [value]="div.innerText"></div>';
const refs = getTemplateIdentifiers(bind(template));
const elementReference: ElementIdentifier = {
name: 'div',
kind: IdentifierKind.Element,
span: new AbsoluteSourceSpan(1, 4),
attributes: new Set(),
usedDirectives: new Set(),
};
const reference: ReferenceIdentifier = {
name: 'div',
kind: IdentifierKind.Reference,
span: new AbsoluteSourceSpan(6, 9),
target: {node: elementReference, directive: null},
};
const refArr = Array.from(refs);
expect(refArr).toContain({
name: 'div',
kind: IdentifierKind.Property,
span: new AbsoluteSourceSpan(19, 22),
target: reference,
});
});
it('should discover properties in template expressions', () => {
const template = '<div [bar]="bar ? bar1 : bar2"></div>';
const refs = getTemplateIdentifiers(bind(template));

View File

@ -28,7 +28,7 @@ import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
import {TypeScriptReflectionHost} from './reflection';
import {HostResourceLoader} from './resource_loader';
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
import {ivySwitchTransform} from './switch';
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
@ -476,7 +476,8 @@ export class NgtscProgram implements api.Program {
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
const scopeRegistry = new LocalModuleScopeRegistry(
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator);
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator, this.incrementalState);
const scopeReader = new CompoundComponentScopeReader([scopeRegistry, this.incrementalState]);
const metaRegistry =
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
@ -502,10 +503,11 @@ export class NgtscProgram implements api.Program {
const handlers = [
new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeRegistry, this.isCore,
this.resourceManager, this.rootDirs, this.options.preserveWhitespaces || false,
this.options.i18nUseExternalIds !== false, this.moduleResolver, this.cycleAnalyzer,
this.refEmitter, this.defaultImportTracker, this.incrementalState),
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry,
this.isCore, this.resourceManager, this.rootDirs,
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, this.defaultImportTracker,
this.incrementalState),
new DirectiveDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
new InjectableDecoratorHandler(

View File

@ -7,5 +7,6 @@
*/
export {ExportScope, ScopeData} from './src/api';
export {ComponentScopeReader, ComponentScopeRegistry, CompoundComponentScopeReader} from './src/component_scope';
export {DtsModuleScopeResolver, MetadataDtsModuleScopeResolver} from './src/dependency';
export {LocalModuleScope, LocalModuleScopeRegistry, LocalNgModuleData} from './src/local';

View File

@ -0,0 +1,67 @@
/**
* @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 {ClassDeclaration} from '../../reflection';
import {LocalModuleScope} from './local';
/**
* Register information about the compilation scope of components.
*/
export interface ComponentScopeRegistry {
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void;
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void;
}
/**
* Read information about the compilation scope of components.
*/
export interface ComponentScopeReader {
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null;
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null;
}
/**
* A noop registry that doesn't do anything.
*
* This can be used in tests and cases where we don't care about the compilation scopes
* being registered.
*/
export class NoopComponentScopeRegistry implements ComponentScopeRegistry {
registerComponentScope(clazz: ClassDeclaration, scope: LocalModuleScope): void {}
setComponentAsRequiringRemoteScoping(clazz: ClassDeclaration): void {}
}
/**
* A `ComponentScopeReader` that reads from an ordered set of child readers until it obtains the
* requested scope.
*
* This is used to combine `ComponentScopeReader`s that read from different sources (e.g. from a
* registry and from the incremental state).
*/
export class CompoundComponentScopeReader implements ComponentScopeReader {
constructor(private readers: ComponentScopeReader[]) {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
for (const reader of this.readers) {
const meta = reader.getScopeForComponent(clazz);
if (meta !== null) {
return meta;
}
}
return null;
}
getRequiresRemoteScope(clazz: ClassDeclaration): boolean|null {
for (const reader of this.readers) {
const requiredScoping = reader.getRequiresRemoteScope(clazz);
if (requiredScoping !== null) {
return requiredScoping;
}
}
return null;
}
}

View File

@ -11,11 +11,12 @@ import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, LocalMetadataRegistry, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData} from './api';
import {ComponentScopeReader, ComponentScopeRegistry, NoopComponentScopeRegistry} from './component_scope';
import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData {
@ -58,7 +59,7 @@ export interface CompilationScope extends ScopeData {
* The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
* semantics are violated.
*/
export class LocalModuleScopeRegistry implements MetadataRegistry {
export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScopeReader {
/**
* Tracks whether the registry has been asked to produce scopes for a module or component. Once
* this is true, the registry cannot accept registrations of new directives/pipes/modules as it
@ -102,7 +103,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
constructor(
private localReader: MetadataReader, private dependencyScopeReader: DtsModuleScopeResolver,
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null) {}
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null,
private componentScopeRegistry: ComponentScopeRegistry = new NoopComponentScopeRegistry()) {}
/**
* Add an NgModule's data to the registry.
@ -120,10 +122,13 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
registerPipeMetadata(pipe: PipeMeta): void {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.declarationToModule.has(clazz)) {
return null;
const scope = !this.declarationToModule.has(clazz) ?
null :
this.getScopeOfModule(this.declarationToModule.get(clazz) !);
if (scope !== null) {
this.componentScopeRegistry.registerComponentScope(clazz, scope);
}
return this.getScopeOfModule(this.declarationToModule.get(clazz) !);
return scope;
}
/**
@ -383,6 +388,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry {
*/
setComponentAsRequiringRemoteScoping(node: ClassDeclaration): void {
this.remoteScoping.add(node);
this.componentScopeRegistry.setComponentAsRequiringRemoteScoping(node);
}
/**

View File

@ -1535,8 +1535,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
consts: 1,
@ -1593,8 +1593,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
}
},
@ -1646,8 +1646,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.foo = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
}
},
consts: 1,
@ -1711,10 +1711,10 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
@ -1775,8 +1775,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirList = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirList = $tmp$);
}
},
ngContentSelectors: _c0,
@ -1835,8 +1835,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
}
},
@ -1897,8 +1897,8 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.foo = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.foo = $tmp$.first);
}
},
ngContentSelectors: $_c1$,
@ -1964,10 +1964,10 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRef = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDir = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.someDirs = $tmp$);
}
},
@ -3144,7 +3144,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
}
}
});
@ -3189,7 +3189,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
}
}
});
@ -3232,7 +3232,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$.first);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$.first);
}
}
});
@ -3277,7 +3277,7 @@ describe('compiler compliance', () => {
}
if (rf & 2) {
var $tmp$;
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$);
$r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadQuery())) && (ctx.something = $tmp$);
}
}
});

View File

@ -2600,14 +2600,16 @@ describe('i18n support in the view compiler', () => {
function MyComponent_div_2_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelementStart(0, "div", $_c2$);
$r3$.ɵɵi18n(1, $I18N_3$);
i0.ɵɵtext(1, " ");
$r3$.ɵɵi18n(2, $I18N_3$);
i0.ɵɵtext(3, " ");
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
const $ctx_r0$ = $r3$.ɵɵnextContext();
$r3$.ɵɵselect(1);
$r3$.ɵɵselect(2);
$r3$.ɵɵi18nExp($ctx_r0$.age);
$r3$.ɵɵi18nApply(1);
$r3$.ɵɵi18nApply(2);
}
}
const $_c3$ = ["title", "icu and text"];
@ -2646,7 +2648,7 @@ describe('i18n support in the view compiler', () => {
$r3$.ɵɵelementStart(0, "div");
$r3$.ɵɵi18n(1, $I18N_0$);
$r3$.ɵɵelementEnd();
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 2, 1, "div", $_c0$);
$r3$.ɵɵtemplate(2, MyComponent_div_2_Template, 4, 1, "div", $_c0$);
$r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 4, 2, "div", $_c1$);
}
if (rf & 2) {
@ -2730,7 +2732,7 @@ describe('i18n support in the view compiler', () => {
const $_c2$ = [1, "other"];
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
const $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
"startBoldText": "\uFFFD#2\uFFFD",
"closeBoldText": "\uFFFD/#2\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD",
@ -2742,7 +2744,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_5791551881115084301$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startBoldText}Other content{$closeBoldText}{$startTagDiv}{$startItalicText}Another content{$closeItalicText}{$closeTagDiv}", {
"startBoldText": "\uFFFD#2\uFFFD",
"closeBoldText": "\uFFFD/#2\uFFFD",
"startTagDiv": "\uFFFD#3\uFFFD",
@ -2848,14 +2850,14 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$icu_1}", {
const $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$icu_1} ", {
"icu": $I18N_1$,
"icu_1": $I18N_2$
});
$I18N_0$ = $MSG_EXTERNAL_2967249209167308918$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$icu_1}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$icu_1} ", {
"icu": $I18N_1$,
"icu_1": $I18N_2$
});
@ -2930,7 +2932,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
const $MSG_APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
"startTagDiv": "\uFFFD#2\uFFFD",
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
@ -2939,7 +2941,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagDiv}{$icu}{$closeTagDiv}{$startTagDiv_1}{$icu}{$closeTagDiv}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", {
"startTagDiv": "\uFFFD#2\uFFFD",
"closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]",
"startTagDiv_1": "\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD",
@ -3000,19 +3002,26 @@ describe('i18n support in the view compiler', () => {
`;
const output = String.raw `
var $I18N_0$;
var $I18N_1$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
$I18N_0$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
$I18N_1$ = $MSG_EXTERNAL_343563413083115114$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
$I18N_1$ = $r3$.ɵɵi18nLocalize("{VAR_SELECT_1, select, male {male of age: {VAR_SELECT, select, 10 {ten} 20 {twenty} 30 {thirty} other {other}}} female {female} other {other}}");
}
$I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, {
$I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$, {
"VAR_SELECT": "\uFFFD0\uFFFD",
"VAR_SELECT_1": "\uFFFD1\uFFFD"
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$ = goog.getMsg(" {$icu} ", { "icu": $I18N_1$ });
$I18N_0$ = $MSG_EXTERNAL_3052001905251380936$$APP_SPEC_TS_3$;
}
else {
$I18N_0$ = i0.ɵɵi18nLocalize(" {$icu} ", { "icu": $I18N_1$ });
} …
consts: 2,
vars: 2,
template: function MyComponent_Template(rf, ctx) {
@ -3117,7 +3126,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
const $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3126,7 +3135,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_1194472282609532229$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3207,7 +3216,7 @@ describe('i18n support in the view compiler', () => {
});
var $I18N_0$;
if (ngI18nClosureMode) {
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
const $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,
@ -3216,7 +3225,7 @@ describe('i18n support in the view compiler', () => {
$I18N_0$ = $MSG_EXTERNAL_7186042105600518133$$APP_SPEC_TS_0$;
}
else {
$I18N_0$ = $r3$.ɵɵi18nLocalize("{$icu}{$startTagSpan}{$icu_1}{$closeTagSpan}", {
$I18N_0$ = $r3$.ɵɵi18nLocalize(" {$icu} {$startTagSpan} {$icu_1} {$closeTagSpan}", {
"startTagSpan": "\uFFFD*2:1\uFFFD\uFFFD#1:1\uFFFD",
"closeTagSpan": "\uFFFD/#1:1\uFFFD\uFFFD/*2:1\uFFFD",
"icu": $I18N_1$,

View File

@ -195,6 +195,25 @@ runInEachFileSystem(() => {
expect(written).toContain('/foo_module.js');
});
it('should rebuild only a Component (but with the correct CompilationScope) if its template has changed',
() => {
setupFooBarProgram(env);
// Make a change to the template of BarComponent.
env.write('bar_component.html', '<div bar>changed</div>');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/bar_directive.js');
expect(written).toContain('/bar_component.js');
expect(written).not.toContain('/bar_module.js');
expect(written).not.toContain('/foo_component.js');
expect(written).not.toContain('/foo_pipe.js');
expect(written).not.toContain('/foo_module.js');
// Ensure that the used directives are included in the component's generated template.
expect(env.getContents('/built/bar_component.js')).toMatch(/directives:\s*\[.+\.BarDir\]/);
});
it('should rebuild everything if a typings file changes', () => {
setupFooBarProgram(env);
@ -280,9 +299,10 @@ runInEachFileSystem(() => {
env.write('bar_component.ts', `
import {Component} from '@angular/core';
@Component({selector: 'bar', template: 'bar'})
@Component({selector: 'bar', templateUrl: './bar_component.html'})
export class BarCmp {}
`);
env.write('bar_component.html', '<div bar></div>');
env.write('bar_directive.ts', `
import {Directive} from '@angular/core';

View File

@ -60,18 +60,20 @@ export class WhitespaceVisitor implements html.Visitor {
}
return new html.Element(
element.name, element.attrs, html.visitAll(this, element.children), element.sourceSpan,
element.startSourceSpan, element.endSourceSpan, element.i18n);
element.name, element.attrs, visitAllWithSiblings(this, element.children),
element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
}
visitAttribute(attribute: html.Attribute, context: any): any {
return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
}
visitText(text: html.Text, context: any): any {
visitText(text: html.Text, context: SiblingVisitorContext|null): any {
const isNotBlank = text.value.match(NO_WS_REGEXP);
const hasExpansionSibling = context &&
(context.prev instanceof html.Expansion || context.next instanceof html.Expansion);
if (isNotBlank) {
if (isNotBlank || hasExpansionSibling) {
return new html.Text(
replaceNgsp(text.value).replace(WS_REPLACE_REGEXP, ' '), text.sourceSpan, text.i18n);
}
@ -91,3 +93,21 @@ export function removeWhitespaces(htmlAstWithErrors: ParseTreeResult): ParseTree
html.visitAll(new WhitespaceVisitor(), htmlAstWithErrors.rootNodes),
htmlAstWithErrors.errors);
}
interface SiblingVisitorContext {
prev: html.Node|undefined;
next: html.Node|undefined;
}
function visitAllWithSiblings(visitor: WhitespaceVisitor, nodes: html.Node[]): any[] {
const result: any[] = [];
nodes.forEach((ast, i) => {
const context: SiblingVisitorContext = {prev: nodes[i - 1], next: nodes[i + 1]};
const astResult = ast.visit(visitor, context);
if (astResult) {
result.push(astResult);
}
});
return result;
}

View File

@ -202,8 +202,6 @@ export class Identifiers {
static i18nPostprocess: o.ExternalReference = {name: 'ɵɵi18nPostprocess', moduleName: CORE};
static i18nLocalize: o.ExternalReference = {name: 'ɵɵi18nLocalize', moduleName: CORE};
static load: o.ExternalReference = {name: 'ɵɵload', moduleName: CORE};
static pipe: o.ExternalReference = {name: 'ɵɵpipe', moduleName: CORE};
static projection: o.ExternalReference = {name: 'ɵɵprojection', moduleName: CORE};
@ -279,9 +277,8 @@ export class Identifiers {
static viewQuery: o.ExternalReference = {name: 'ɵɵviewQuery', moduleName: CORE};
static staticViewQuery: o.ExternalReference = {name: 'ɵɵstaticViewQuery', moduleName: CORE};
static staticContentQuery: o.ExternalReference = {name: 'ɵɵstaticContentQuery', moduleName: CORE};
static loadViewQuery: o.ExternalReference = {name: 'ɵɵloadViewQuery', moduleName: CORE};
static loadQuery: o.ExternalReference = {name: 'ɵɵloadQuery', moduleName: CORE};
static contentQuery: o.ExternalReference = {name: 'ɵɵcontentQuery', moduleName: CORE};
static loadContentQuery: o.ExternalReference = {name: 'ɵɵloadContentQuery', moduleName: CORE};
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵɵNgOnChangesFeature', moduleName: CORE};

View File

@ -487,9 +487,9 @@ function createContentQueriesFunction(
.callFn([o.variable('dirIndex'), ...prepareQueryParams(query, constantPool) as any])
.toStmt());
// update, e.g. (r3.queryRefresh(tmp = r3.loadContentQuery()) && (ctx.someDir = tmp));
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
const temporary = tempAllocator();
const getQueryList = o.importExpr(R3.loadContentQuery).callFn([]);
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
const updateDirective = o.variable(CONTEXT_NAME)
.prop(query.propertyName)
@ -561,9 +561,9 @@ function createViewQueriesFunction(
o.importExpr(queryInstruction).callFn(prepareQueryParams(query, constantPool));
createStatements.push(queryDefinition.toStmt());
// update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp));
// update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
const temporary = tempAllocator();
const getQueryList = o.importExpr(R3.loadViewQuery).callFn([]);
const getQueryList = o.importExpr(R3.loadQuery).callFn([]);
const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]);
const updateDirective = o.variable(CONTEXT_NAME)
.prop(query.propertyName)

View File

@ -40,7 +40,7 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
it('should not create a message for plain elements',
() => { expect(_humanizeMessages('<div></div>')).toEqual([]); });
it('should suppoprt void elements', () => {
it('should support void elements', () => {
expect(_humanizeMessages('<div i18n="m|d"><p><br></p></div>')).toEqual([
[
[
@ -173,6 +173,13 @@ import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/inte
]);
});
it('should extract as ICU + ph when wrapped in whitespace in an element', () => {
expect(_humanizeMessages('<div i18n="m|d"> {count, plural, =0 {zero}} </div>')).toEqual([
[[' ', '<ph icu name="ICU">{count, plural, =0 {[zero]}}</ph>', ' '], 'm', 'd'],
[['{count, plural, =0 {[zero]}}'], '', ''],
]);
});
it('should extract as ICU when single child of a block', () => {
expect(_humanizeMessages('<!-- i18n:m|d -->{count, plural, =0 {zero}}<!-- /i18n -->'))
.toEqual([

View File

@ -9,14 +9,15 @@
import * as html from '../../src/ml_parser/ast';
import {HtmlParser} from '../../src/ml_parser/html_parser';
import {PRESERVE_WS_ATTR_NAME, removeWhitespaces} from '../../src/ml_parser/html_whitespaces';
import {TokenizeOptions} from '../../src/ml_parser/lexer';
import {humanizeDom} from './ast_spec_utils';
{
describe('removeWhitespaces', () => {
function parseAndRemoveWS(template: string): any[] {
return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp')));
function parseAndRemoveWS(template: string, options?: TokenizeOptions): any[] {
return humanizeDom(removeWhitespaces(new HtmlParser().parse(template, 'TestComp', options)));
}
it('should remove blank text nodes', () => {
@ -97,6 +98,17 @@ import {humanizeDom} from './ast_spec_utils';
]);
});
it('should preserve whitespaces around ICU expansions', () => {
expect(parseAndRemoveWS(`<span> {a, b, =4 {c}} </span>`, {tokenizeExpansionForms: true}))
.toEqual([
[html.Element, 'span', 0],
[html.Text, ' ', 1],
[html.Expansion, 'a', 'b', 1],
[html.ExpansionCase, '=4', 2],
[html.Text, ' ', 1],
]);
});
it('should preserve whitespaces inside <pre> elements', () => {
expect(parseAndRemoveWS(`<pre><strong>foo</strong>\n<strong>bar</strong></pre>`)).toEqual([
[html.Element, 'pre', 0],

View File

@ -798,6 +798,30 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
]);
});
it('should parse an expansion form with whitespace surrounding it', () => {
expect(tokenizeAndHumanizeParts(
'<div><span> {a, b, =4 {c}} </span></div>', {tokenizeExpansionForms: true}))
.toEqual([
[lex.TokenType.TAG_OPEN_START, '', 'div'],
[lex.TokenType.TAG_OPEN_END],
[lex.TokenType.TAG_OPEN_START, '', 'span'],
[lex.TokenType.TAG_OPEN_END],
[lex.TokenType.TEXT, ' '],
[lex.TokenType.EXPANSION_FORM_START],
[lex.TokenType.RAW_TEXT, 'a'],
[lex.TokenType.RAW_TEXT, 'b'],
[lex.TokenType.EXPANSION_CASE_VALUE, '=4'],
[lex.TokenType.EXPANSION_CASE_EXP_START],
[lex.TokenType.TEXT, 'c'],
[lex.TokenType.EXPANSION_CASE_EXP_END],
[lex.TokenType.EXPANSION_FORM_END],
[lex.TokenType.TEXT, ' '],
[lex.TokenType.TAG_CLOSE, '', 'span'],
[lex.TokenType.TAG_CLOSE, '', 'div'],
[lex.TokenType.EOF],
]);
});
it('should parse an expansion forms with elements in it', () => {
expect(tokenizeAndHumanizeParts(
'{one.two, three, =4 {four <b>a</b>}}', {tokenizeExpansionForms: true}))

View File

@ -4,11 +4,12 @@ ts_library(
name = "google3",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = ["//packages/core/schematics/test:__pkg__"],
visibility = ["//packages/core/schematics/test/google3:__pkg__"],
deps = [
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/missing-injectable/google3",
"//packages/core/schematics/migrations/renderer-to-renderer2",
"//packages/core/schematics/migrations/static-queries",
"//packages/core/schematics/migrations/template-var-assignment",
"//packages/core/schematics/utils",

View File

@ -9,9 +9,9 @@
import {Replacement, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {HelperFunction, getHelper} from '../helpers';
import {migrateExpression, replaceImport} from '../migration';
import {findCoreImport, findRendererReferences} from '../util';
import {HelperFunction, getHelper} from '../renderer-to-renderer2/helpers';
import {migrateExpression, replaceImport} from '../renderer-to-renderer2/migration';
import {findCoreImport, findRendererReferences} from '../renderer-to-renderer2/util';
/**
* TSLint rule that migrates from `Renderer` to `Renderer2`. More information on how it works:

View File

@ -6,7 +6,7 @@ ts_library(
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = [
"//packages/core/schematics:__pkg__",
"//packages/core/schematics/migrations/renderer-to-renderer2/google3:__pkg__",
"//packages/core/schematics/migrations/google3:__pkg__",
"//packages/core/schematics/test:__pkg__",
],
deps = [

View File

@ -1,13 +0,0 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "google3",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = ["//packages/core/schematics/test:__pkg__"],
deps = [
"//packages/core/schematics/migrations/renderer-to-renderer2",
"@npm//tslint",
"@npm//typescript",
],
)

View File

@ -9,12 +9,10 @@ ts_library(
"//packages/core/schematics:migrations.json",
],
deps = [
"//packages/core/schematics/migrations/google3",
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/renderer-to-renderer2",
"//packages/core/schematics/migrations/renderer-to-renderer2/google3",
"//packages/core/schematics/migrations/static-queries",
"//packages/core/schematics/migrations/template-var-assignment",
"//packages/core/schematics/utils",
@ -26,7 +24,7 @@ ts_library(
)
jasmine_node_test(
name = "test",
name = "google3",
deps = [
":test_lib",
"@npm//shelljs",

View File

@ -0,0 +1,20 @@
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
ts_library(
name = "test_lib",
testonly = True,
srcs = glob(["**/*.ts"]),
deps = [
"//packages/core/schematics/migrations/google3",
"@npm//@types/shelljs",
"@npm//tslint",
],
)
jasmine_node_test(
name = "test",
deps = [
":test_lib",
"@npm//shelljs",
],
)

View File

@ -12,8 +12,8 @@ import * as shx from 'shelljs';
import {Configuration, Linter} from 'tslint';
describe('Google3 Renderer to Renderer2 TSLint rule', () => {
const rulesDirectory = dirname(
require.resolve('../../migrations/renderer-to-renderer2/google3/rendererToRenderer2Rule'));
const rulesDirectory =
dirname(require.resolve('../../migrations/google3/rendererToRenderer2Rule'));
let tmpDir: string;

View File

@ -95,9 +95,8 @@ export {
ɵɵviewQuery,
ɵɵstaticViewQuery,
ɵɵstaticContentQuery,
ɵɵloadViewQuery,
ɵɵcontentQuery,
ɵɵloadContentQuery,
ɵɵloadQuery,
ɵɵelementEnd,
ɵɵhostProperty,
ɵɵproperty,
@ -153,7 +152,6 @@ export {
ɵɵtemplate,
ɵɵembeddedViewEnd,
store as ɵstore,
ɵɵload,
ɵɵpipe,
ɵɵBaseDef,
ComponentDef as ɵComponentDef,

View File

@ -377,11 +377,28 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
}
triggerEventHandler(eventName: string, eventObj: any): void {
this.listeners.forEach((listener) => {
const node = this.nativeNode as any;
const invokedListeners: Function[] = [];
this.listeners.forEach(listener => {
if (listener.name === eventName) {
listener.callback(eventObj);
const callback = listener.callback;
callback(eventObj);
invokedListeners.push(callback);
}
});
// We need to check whether `eventListeners` exists, because it's something
// that Zone.js only adds to `EventTarget` in browser environments.
if (typeof node.eventListeners === 'function') {
// Note that in Ivy we wrap event listeners with a call to `event.preventDefault` in some
// cases. We use `Function` as a special token that gives us access to the actual event
// listener.
node.eventListeners(eventName).forEach((listener: Function) => {
const unwrappedListener = listener(Function);
return invokedListeners.indexOf(unwrappedListener) === -1 && unwrappedListener(eventObj);
});
}
}
}

View File

@ -57,11 +57,11 @@ export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
* @publicApi
*/
export function resolveForwardRef<T>(type: T): T {
const fn: any = type;
if (typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef) {
return fn();
} else {
return type;
}
return isForwardRef(type) ? type() : type;
}
/** Checks whether a function is wrapped by a `forwardRef`. */
export function isForwardRef(fn: any): fn is() => any {
return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef;
}

View File

@ -7,6 +7,7 @@
*/
import {Type} from '../../interface/type';
import {isForwardRef, resolveForwardRef} from '../forward_ref';
import {ɵɵinject} from '../injector_compatibility';
import {getInjectableDef, getInjectorDef, ɵɵdefineInjectable, ɵɵdefineInjector} from '../interface/defs';
@ -26,6 +27,14 @@ export const angularCoreDiEnv: {[name: string]: Function} = {
function getFactoryOf<T>(type: Type<any>): ((type?: Type<T>) => T)|null {
const typeAny = type as any;
if (isForwardRef(type)) {
return (() => {
const factory = getFactoryOf<T>(resolveForwardRef(typeAny));
return factory ? factory() : null;
}) as any;
}
const def = getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
if (!def || def.factory === undefined) {
return null;

View File

@ -140,7 +140,7 @@ export interface Directive {
* class BankAccount {
* bankName: string;
* id: string;
*
* }
* ```
*
*/

View File

@ -22,6 +22,21 @@ Great reads:
See benchmark [here](https://jsperf.com/mono-vs-megamorphic-property-access).
## Packed vs. holey Array
V8 represents arrays internally in a different way depending on:
- type of elements in the array;
- presence of holes (indexes that were never assigned).
Generally speaking packed arrays (a set of continuous, initialized indexes) perform better as compared to arrays with holes. To assure that arrays are packed follow those guidelines:
* create array literals with known values whenever possible (ex. `a = [0];` is better than `a = []; a.push[0];`;
* don't use `Array` constructor with the size value (ex. `new Array(5)`) - this will create a `HOLEY_ELEMENTS` array (even if this array is filled in later on!);
* don't delete elements from an array (ex. `delete a[0]`) - this will create a hole;
* don't write past the array length as this will create holes;
Great reads:
- [Elements kinds in V8](https://v8.dev/blog/elements-kinds)
## Exporting top level variables
Exporting top level variables should be avoided where possible where performance

View File

@ -70,6 +70,7 @@ export function assertLView(value: any) {
assertEqual(isLView(value), true, 'Expecting LView');
}
export function assertFirstTemplatePass(tView: TView, errMessage: string) {
assertEqual(tView.firstTemplatePass, true, errMessage);
export function assertFirstTemplatePass(tView: TView, errMessage?: string) {
assertEqual(
tView.firstTemplatePass, true, errMessage || 'Should only be called in first template pass.');
}

View File

@ -17,9 +17,9 @@ import {assertComponentType} from './assert';
import {getComponentDef} from './definition';
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions/shared';
import {CLEAN_PROMISE, addToViewTree, createLView, createTView, getOrCreateTNode, getOrCreateTView, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshDescendantViews} from './instructions/shared';
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {TElementNode, TNode, TNodeType} from './interfaces/node';
import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {CONTEXT, FLAGS, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
@ -144,8 +144,6 @@ export function renderComponent<T>(
component = createRootComponent(
componentView, componentDef, rootView, rootContext, opts.hostFeatures || null);
addToViewTree(rootView, componentView);
refreshDescendantViews(rootView); // creation mode pass
rootView[FLAGS] &= ~LViewFlags.CreationMode;
resetPreOrderHookFlags(rootView);
@ -184,11 +182,12 @@ export function createRootComponentView(
if (tView.firstTemplatePass) {
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
tNode.flags = TNodeFlags.isComponent;
markAsComponentHost(tView, tNode);
initNodeFlags(tNode, rootView.length, 1);
queueComponentIndexForCheck(tNode);
}
addToViewTree(rootView, componentView);
// Store component view at node index, with node as the HOST
return rootView[HEADER_OFFSET] = componentView;
}

View File

@ -138,7 +138,7 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
namespaceHTMLInternal();
const hostRNode = rootSelectorOrNode ?
locateHostElement(rendererFactory, rootSelectorOrNode) :
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef));
elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef), null);
const rootFlags = this.componentDef.onPush ? LViewFlags.Dirty | LViewFlags.IsRoot :
LViewFlags.CheckAlways | LViewFlags.IsRoot;
@ -193,7 +193,6 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
component = createRootComponent(
componentView, this.componentDef, rootLView, rootContext, [LifecycleHooksFeature]);
addToViewTree(rootLView, componentView);
refreshDescendantViews(rootLView);
safeToRunHooks = true;
} finally {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isForwardRef, resolveForwardRef} from '../di/forward_ref';
import {InjectionToken} from '../di/injection_token';
import {Injector} from '../di/injector';
import {injectRootLimpMode, setInjectImplementation} from '../di/injector_compatibility';
@ -633,6 +634,14 @@ export class NodeInjector implements Injector {
*/
export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
const typeAny = type as any;
if (isForwardRef(type)) {
return (() => {
const factory = ɵɵgetFactoryOf<T>(resolveForwardRef(typeAny));
return factory ? factory() : null;
}) as any;
}
const def = getComponentDef<T>(typeAny) || getDirectiveDef<T>(typeAny) ||
getPipeDef<T>(typeAny) || getInjectableDef<T>(typeAny) || getInjectorDef<T>(typeAny);
if (!def || def.factory === undefined) {

View File

@ -185,18 +185,23 @@ export function executeHooks(
checkNoChangesMode: boolean, initPhaseState: InitPhaseState,
currentNodeIndex: number | null | undefined): void {
if (checkNoChangesMode) return;
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
firstPassHooks :
checkHooks;
if (hooksToCall) {
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
if (checkHooks !== null || firstPassHooks !== null) {
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
firstPassHooks :
checkHooks;
if (hooksToCall !== null) {
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
}
}
// The init phase state must be always checked here as it may have been recursively updated
if (currentNodeIndex == null &&
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState &&
let flags = currentView[FLAGS];
if (currentNodeIndex == null && (flags & LViewFlags.InitPhaseStateMask) === initPhaseState &&
initPhaseState !== InitPhaseState.InitPhaseCompleted) {
currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset;
currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer;
flags &= LViewFlags.IndexWithinInitPhaseReset;
flags += LViewFlags.InitPhaseStateIncrementer;
currentView[FLAGS] = flags;
}
}

View File

@ -14,7 +14,7 @@ import {_sanitizeUrl, sanitizeSrcset} from '../sanitization/url_sanitizer';
import {addAllToArray} from '../util/array_utils';
import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
import {attachPatchData} from './context_discovery';
import {bind, setDelayProjection, ɵɵload} from './instructions/all';
import {bind, setDelayProjection} from './instructions/all';
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
import {TsickleIssue1009, allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal} from './instructions/shared';
import {LContainer, NATIVE} from './interfaces/container';
@ -29,7 +29,7 @@ import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPrev
import {NO_CHANGE} from './tokens';
import {renderStringify} from './util/misc_utils';
import {findComponentView} from './util/view_traversal_utils';
import {getNativeByIndex, getNativeByTNode, getTNode} from './util/view_utils';
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
const MARKER = `<EFBFBD>`;
@ -912,7 +912,7 @@ function removeNode(index: number, viewData: LView) {
nativeRemoveNode(viewData[RENDERER], removedPhRNode);
}
const slotValue = ɵɵload(index) as RElement | RComment | LContainer;
const slotValue = load(viewData, index) as RElement | RComment | LContainer;
if (isLContainer(slotValue)) {
const lContainer = slotValue as LContainer;
if (removedPhTNode.type !== TNodeType.Container) {

View File

@ -72,7 +72,6 @@ export {
ɵɵinjectAttribute,
ɵɵlistener,
ɵɵload,
ɵɵnamespaceHTML,
ɵɵnamespaceMathML,
@ -178,9 +177,8 @@ export {
ɵɵqueryRefresh,
ɵɵviewQuery,
ɵɵstaticViewQuery,
ɵɵloadViewQuery,
ɵɵloadQuery,
ɵɵcontentQuery,
ɵɵloadContentQuery,
ɵɵstaticContentQuery
} from './query';

View File

@ -41,11 +41,11 @@ import {TsickleIssue1009, elementAttributeInternal} from './shared';
export function ɵɵattributeInterpolate1(
attrName: string, prefix: string, v0: any, suffix: string, sanitizer?: SanitizerFn,
namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate1;
}
@ -79,11 +79,11 @@ export function ɵɵattributeInterpolate1(
export function ɵɵattributeInterpolate2(
attrName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string,
sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate2;
}
@ -120,11 +120,11 @@ export function ɵɵattributeInterpolate2(
export function ɵɵattributeInterpolate3(
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate3;
}
@ -163,11 +163,11 @@ export function ɵɵattributeInterpolate3(
export function ɵɵattributeInterpolate4(
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate4;
}
@ -209,12 +209,12 @@ export function ɵɵattributeInterpolate5(
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn,
namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue =
interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate5;
}
@ -258,12 +258,12 @@ export function ɵɵattributeInterpolate6(
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, sanitizer?: SanitizerFn,
namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue =
interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate6;
}
@ -362,12 +362,12 @@ export function ɵɵattributeInterpolate8(
attrName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
suffix: string, sanitizer?: SanitizerFn, namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolatedValue = interpolation8(
lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolatedValue, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolatedValue, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolate8;
}
@ -401,11 +401,11 @@ export function ɵɵattributeInterpolate8(
export function ɵɵattributeInterpolateV(
attrName: string, values: any[], sanitizer?: SanitizerFn,
namespace?: string): TsickleIssue1009 {
const index = getSelectedIndex();
const lView = getLView();
const interpolated = interpolationV(lView, values);
if (interpolated !== NO_CHANGE) {
elementAttributeInternal(index, attrName, interpolated, lView, sanitizer, namespace);
elementAttributeInternal(
getSelectedIndex(), attrName, interpolated, lView, sanitizer, namespace);
}
return ɵɵattributeInterpolateV;
}

View File

@ -16,7 +16,7 @@ import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../i
import {assertNodeType} from '../node_assert';
import {appendChild, removeView} from '../node_manipulation';
import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {getNativeByTNode, loadInternal} from '../util/view_utils';
import {getNativeByTNode, load} from '../util/view_utils';
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode, resolveDirectives} from './shared';
@ -76,7 +76,8 @@ export function ɵɵtemplate(
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
const embeddedTView = tContainerNode.tViews = createTView(
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null,
tView.schemas);
if (tView.queries !== null) {
tView.queries.template(tView, tContainerNode);
embeddedTView.queries = tView.queries.embeddedTView(tContainerNode);
@ -99,7 +100,7 @@ export function ɵɵtemplate(
export function ɵɵcontainerRefreshStart(index: number): void {
const lView = getLView();
const tView = lView[TVIEW];
let previousOrParentTNode = loadInternal(tView.data, index) as TNode;
let previousOrParentTNode = load(tView.data, index) as TNode;
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container);
setPreviousOrParentTNode(previousOrParentTNode, true);

View File

@ -16,7 +16,7 @@ import {isContentQueryHost} from '../interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
import {StylingMapArray, TStylingContext} from '../styling_next/interfaces';
import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_next/util';
@ -52,13 +52,13 @@ export function ɵɵelementStart(
ngDevMode && ngDevMode.rendererCreateElement++;
ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET);
const native = lView[index + HEADER_OFFSET] = elementCreate(name);
const renderer = lView[RENDERER];
const native = lView[index + HEADER_OFFSET] = elementCreate(name, renderer, getNamespace());
const tNode =
getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs || null);
if (attrs != null) {
const lastAttrIndex = setUpAttributes(native, attrs);
const lastAttrIndex = setUpAttributes(renderer, native, attrs);
if (tView.firstTemplatePass) {
registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
}
@ -209,7 +209,7 @@ export function ɵɵelementHostAttrs(attrs: TAttributes) {
// errors...
if (tNode.type === TNodeType.Element) {
const native = getNativeByTNode(tNode, lView) as RElement;
const lastAttrIndex = setUpAttributes(native, attrs);
const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs);
if (tView.firstTemplatePass) {
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);

View File

@ -11,11 +11,11 @@ import {assertLContainerOrUndefined} from '../assert';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {RenderFlags} from '../interfaces/definition';
import {TContainerNode, TNodeType} from '../interfaces/node';
import {FLAGS, LView, LViewFlags, PARENT, QUERIES, TVIEW, TView, T_HOST} from '../interfaces/view';
import {FLAGS, LView, LViewFlags, PARENT, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {insertView, removeView} from '../node_manipulation';
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
import {resetPreOrderHookFlags} from '../util/view_utils';
import {enterView, getIsParent, getLView, getPreviousOrParentTNode, leaveView, setIsParent, setPreviousOrParentTNode} from '../state';
import {isCreationMode, resetPreOrderHookFlags} from '../util/view_utils';
import {assignTViewNodeToLView, createLView, createTView, refreshDescendantViews} from './shared';
/**

View File

@ -235,7 +235,13 @@ function wrapListener(
wrapWithPreventDefault: boolean): EventListener {
// Note: we are performing most of the work in the listener function itself
// to optimize listener registration.
return function wrapListenerIn_markDirtyAndPreventDefault(e: Event) {
return function wrapListenerIn_markDirtyAndPreventDefault(e: any) {
// Ivy uses `Function` as a special token that allows us to unwrap the function
// so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
if (e === Function) {
return listenerFn;
}
// In order to be backwards compatible with View Engine, events on component host nodes
// must also mark the component view itself dirty (i.e. the view that it owns).
const startView =

View File

@ -81,10 +81,9 @@ export function ɵɵpropertyInterpolate(
export function ɵɵpropertyInterpolate1(
propName: string, prefix: string, v0: any, suffix: string,
sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation1(getLView(), prefix, v0, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate1;
}
@ -122,10 +121,9 @@ export function ɵɵpropertyInterpolate1(
export function ɵɵpropertyInterpolate2(
propName: string, prefix: string, v0: any, i0: string, v1: any, suffix: string,
sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation2(getLView(), prefix, v0, i0, v1, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate2;
}
@ -166,10 +164,9 @@ export function ɵɵpropertyInterpolate2(
export function ɵɵpropertyInterpolate3(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any,
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation3(getLView(), prefix, v0, i0, v1, i1, v2, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate3;
}
@ -212,10 +209,9 @@ export function ɵɵpropertyInterpolate3(
export function ɵɵpropertyInterpolate4(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation4(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate4;
}
@ -260,11 +256,10 @@ export function ɵɵpropertyInterpolate4(
export function ɵɵpropertyInterpolate5(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue =
interpolation5(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate5;
}
@ -312,11 +307,10 @@ export function ɵɵpropertyInterpolate6(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string,
sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue =
interpolation6(getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate6;
}
@ -366,11 +360,10 @@ export function ɵɵpropertyInterpolate7(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string,
sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation7(
getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate7;
}
@ -422,11 +415,10 @@ export function ɵɵpropertyInterpolate8(
propName: string, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string,
v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any,
suffix: string, sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolation8(
getLView(), prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolate8;
}
@ -463,11 +455,9 @@ export function ɵɵpropertyInterpolate8(
*/
export function ɵɵpropertyInterpolateV(
propName: string, values: any[], sanitizer?: SanitizerFn): TsickleIssue1009 {
const index = getSelectedIndex();
const interpolatedValue = interpolationV(getLView(), values);
if (interpolatedValue !== NO_CHANGE) {
elementPropertyInternal(index, propName, interpolatedValue, sanitizer);
elementPropertyInternal(getSelectedIndex(), propName, interpolatedValue, sanitizer);
}
return ɵɵpropertyInterpolateV;
}

View File

@ -7,14 +7,13 @@
*/
import {Injector} from '../../di';
import {ErrorHandler} from '../../error_handler';
import {Type} from '../../interface/type';
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../../metadata/schema';
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../../sanitization/sanitization';
import {Sanitizer} from '../../sanitization/security';
import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertNotEqual, assertNotSame} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../../util/ng_reflect';
import {assertLView, assertPreviousIsParent} from '../assert';
import {assertFirstTemplatePass, assertLView} from '../assert';
import {attachPatchData, getComponentViewByInstance} from '../context_discovery';
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
import {throwMultipleComponentError} from '../errors';
@ -26,16 +25,16 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {isComponent, isComponentDef, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, incrementActiveDirectiveId, leaveView, namespaceHTMLInternal, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {renderStylingMap} from '../styling_next/bindings';
import {NO_CHANGE} from '../tokens';
import {ANIMATION_PROP_PREFIX, isAnimationProp} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialData, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLView, cloneToTViewData} from './lview_debug';
import {selectInternal} from './select';
@ -63,15 +62,11 @@ export function refreshDescendantViews(lView: LView) {
const tView = lView[TVIEW];
const creationMode = isCreationMode(lView);
// This needs to be set before children are processed to support recursive components
tView.firstTemplatePass = false;
// Resetting the bindingIndex of the current LView as the next steps may trigger change detection.
lView[BINDING_INDEX] = tView.bindingStartIndex;
// If this is a creation pass, we should not call lifecycle hooks or evaluate bindings.
// This will be done in the update pass.
if (!creationMode) {
// Resetting the bindingIndex of the current LView as the next steps may trigger change
// detection.
lView[BINDING_INDEX] = tView.bindingStartIndex;
const checkNoChangesMode = getCheckNoChangesMode();
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
@ -79,7 +74,9 @@ export function refreshDescendantViews(lView: LView) {
refreshDynamicEmbeddedViews(lView);
// Content query results must be refreshed before content hooks are called.
refreshContentQueries(tView, lView);
if (tView.contentQueries !== null) {
refreshContentQueries(tView, lView);
}
resetPreOrderHookFlags(lView);
executeHooks(
@ -87,14 +84,22 @@ export function refreshDescendantViews(lView: LView) {
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
setHostBindings(tView, lView);
} else {
// This needs to be set before children are processed to support recursive components.
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstTemplatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
tView.firstTemplatePass = false;
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
}
// We resolve content queries specifically marked as `static` in creation mode. Dynamic
// content queries are resolved during change detection (i.e. update mode), after embedded
// views are refreshed (see block above).
if (creationMode && tView.staticContentQueries) {
refreshContentQueries(tView, lView);
}
// We must materialize query results before child components are processed
// in case a child component has projected a container. The LContainer needs
@ -103,7 +108,10 @@ export function refreshDescendantViews(lView: LView) {
executeViewQueryFn(RenderFlags.Update, tView, lView[CONTEXT]);
}
refreshChildComponents(lView, tView.components);
const components = tView.components;
if (components !== null) {
refreshChildComponents(lView, components);
}
}
@ -111,7 +119,7 @@ export function refreshDescendantViews(lView: LView) {
export function setHostBindings(tView: TView, viewData: LView): void {
const selectedIndex = getSelectedIndex();
try {
if (tView.expandoInstructions) {
if (tView.expandoInstructions !== null) {
let bindingRootIndex = viewData[BINDING_INDEX] = tView.expandoStartIndex;
setBindingRoot(bindingRootIndex);
let currentDirectiveIndex = -1;
@ -179,11 +187,9 @@ function refreshContentQueries(tView: TView, lView: LView): void {
}
/** Refreshes child components in the current view. */
function refreshChildComponents(hostLView: LView, components: number[] | null): void {
if (components != null) {
for (let i = 0; i < components.length; i++) {
componentRefresh(hostLView, components[i]);
}
function refreshChildComponents(hostLView: LView, components: number[]): void {
for (let i = 0; i < components.length; i++) {
componentRefresh(hostLView, components[i]);
}
}
@ -191,26 +197,19 @@ function refreshChildComponents(hostLView: LView, components: number[] | null):
/**
* Creates a native element from a tag name, using a renderer.
* @param name the tag name
* @param overriddenRenderer Optional A renderer to override the default one
* @param renderer A renderer to use
* @returns the element created
*/
export function elementCreate(name: string, overriddenRenderer?: Renderer3): RElement {
let native: RElement;
const rendererToUse = overriddenRenderer || getLView()[RENDERER];
const namespace = getNamespace();
if (isProceduralRenderer(rendererToUse)) {
native = rendererToUse.createElement(name, namespace);
export function elementCreate(
name: string, renderer: Renderer3, namespace: string | null): RElement {
if (isProceduralRenderer(renderer)) {
return renderer.createElement(name, namespace);
} else {
if (namespace === null) {
native = rendererToUse.createElement(name);
} else {
native = rendererToUse.createElementNS(namespace, name);
}
return namespace === null ? renderer.createElement(name) :
renderer.createElementNS(namespace, name);
}
return native;
}
export function createLView<T>(
parentLView: LView | null, tView: TView, context: T | null, flags: LViewFlags,
host: RElement | null, tHostNode: TViewNode | TElementNode | null,
@ -363,16 +362,11 @@ export function allocExpando(view: LView, numSlotsToAlloc: number) {
//////////////////////////
/**
* Used for creating the LViewNode of a dynamic embedded view,
* either through ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
* Used for creating the LView of a dynamic embedded view, either through
* ViewContainerRef.createEmbeddedView() or TemplateRef.createEmbeddedView().
*/
export function createEmbeddedViewAndNode<T>(
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
const _isParent = getIsParent();
const _previousOrParentTNode = getPreviousOrParentTNode();
setPreviousOrParentTNode(null !, true);
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
lView[DECLARATION_VIEW] = declarationView;
@ -382,12 +376,12 @@ export function createEmbeddedViewAndNode<T>(
tView.node !.injectorIndex = injectorIndex;
}
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
return lView;
}
/**
* Used for rendering embedded views (e.g. dynamically created views)
* Used for rendering views in a LContainer (embedded views or root component views for dynamically
* created components).
*
* Dynamically created views must store/retrieve their TViews differently from component views
* because their template functions are nested in the template functions of their hosts, creating
@ -401,31 +395,20 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
const _isParent = getIsParent();
const _previousOrParentTNode = getPreviousOrParentTNode();
let oldView: LView;
if (viewToRender[FLAGS] & LViewFlags.IsRoot) {
// This is a root view inside the view tree
tickRootContext(getRootContext(viewToRender));
} else {
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
setPreviousOrParentTNode(null !, true);
oldView = enterView(viewToRender, viewToRender[T_HOST]);
resetPreOrderHookFlags(viewToRender);
executeTemplate(viewToRender, tView.template !, getRenderFlags(viewToRender), context);
// This must be set to false immediately after the first creation run because in an
// ngFor loop, all the views will be created together before update mode runs and turns
// off firstTemplatePass. If we don't set it here, instances will perform directive
// matching, etc again and again.
tView.firstTemplatePass = false;
refreshDescendantViews(viewToRender);
safeToRunHooks = true;
} finally {
leaveView(oldView !, safeToRunHooks);
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
// Will become true if the `try` block executes with no errors.
let safeToRunHooks = false;
try {
oldView = enterView(viewToRender, viewToRender[T_HOST]);
resetPreOrderHookFlags(viewToRender);
const templateFn = tView.template;
if (templateFn !== null) {
executeTemplate(viewToRender, templateFn, getRenderFlags(viewToRender), context);
}
refreshDescendantViews(viewToRender);
safeToRunHooks = true;
} finally {
leaveView(oldView !, safeToRunHooks);
setPreviousOrParentTNode(_previousOrParentTNode, _isParent);
}
}
@ -644,11 +627,14 @@ export function createTView(
}
function createViewBlueprint(bindingStartIndex: number, initialViewLength: number): LView {
const blueprint = new (ngDevMode ? LViewBlueprint ! : Array)(initialViewLength)
.fill(null, 0, bindingStartIndex)
.fill(NO_CHANGE, bindingStartIndex) as LView;
const blueprint = ngDevMode ? new LViewBlueprint !() : [];
for (let i = 0; i < initialViewLength; i++) {
blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
}
blueprint[BINDING_INDEX] = bindingStartIndex;
return blueprint;
return blueprint as LView;
}
export function createError(text: string, token: any) {
@ -1029,7 +1015,7 @@ export function resolveDirectives(
localRefs: string[] | null): void {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle.
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
ngDevMode && assertFirstTemplatePass(tView);
if (!getBindingsEnabled()) return;
@ -1061,6 +1047,10 @@ export function resolveDirectives(
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
if (def.contentQueries) {
tNode.flags |= TNodeFlags.hasContentQuery;
}
// Init hooks are queued now so ngOnInit is called in host components before
// any projected components.
registerPreOrderHooks(
@ -1087,7 +1077,7 @@ function instantiateAllDirectives(tView: TView, lView: LView, tNode: TNode) {
addComponentLogic(lView, tNode, def as ComponentDef<any>);
}
const directive = getNodeInjectable(tView.data, lView !, i, tNode as TElementNode);
postProcessDirective(lView, directive, def, i);
postProcessDirective(lView, tNode, directive, def, i);
}
}
@ -1162,20 +1152,15 @@ export function generateExpandoInstructionBlock(
* Process a directive on the current node after its creation.
*/
function postProcessDirective<T>(
viewData: LView, directive: T, def: DirectiveDef<T>, directiveDefIdx: number): void {
const previousOrParentTNode = getPreviousOrParentTNode();
postProcessBaseDirective(viewData, previousOrParentTNode, directive);
ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode');
if (previousOrParentTNode && previousOrParentTNode.attrs) {
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
}
if (viewData[TVIEW].firstTemplatePass && def.contentQueries) {
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
lView: LView, hostTNode: TNode, directive: T, def: DirectiveDef<T>,
directiveDefIdx: number): void {
postProcessBaseDirective(lView, hostTNode, directive);
if (hostTNode.attrs !== null) {
setInputsFromAttrs(directiveDefIdx, directive, def, hostTNode);
}
if (isComponentDef(def)) {
const componentView = getComponentViewByIndex(previousOrParentTNode.index, viewData);
const componentView = getComponentViewByIndex(hostTNode.index, lView);
componentView[CONTEXT] = directive;
}
}
@ -1183,16 +1168,12 @@ function postProcessDirective<T>(
/**
* A lighter version of postProcessDirective() that is used for the root component.
*/
function postProcessBaseDirective<T>(
lView: LView, previousOrParentTNode: TNode, directive: T): void {
const native = getNativeByTNode(previousOrParentTNode, lView);
function postProcessBaseDirective<T>(lView: LView, hostTNode: TNode, directive: T): void {
ngDevMode && assertEqual(
lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex,
'directives should be created before any bindings');
ngDevMode && assertPreviousIsParent(getIsParent());
attachPatchData(directive, lView);
const native = getNativeByTNode(hostTNode, lView);
if (native) {
attachPatchData(native, lView);
}
@ -1206,7 +1187,9 @@ function postProcessBaseDirective<T>(
function findDirectiveMatches(
tView: TView, viewData: LView,
tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef<any>[]|null {
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
ngDevMode && assertFirstTemplatePass(tView);
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.Container);
const registry = tView.directiveRegistry;
let matches: any[]|null = null;
if (registry) {
@ -1218,8 +1201,7 @@ function findDirectiveMatches(
if (isComponentDef(def)) {
if (tNode.flags & TNodeFlags.isComponent) throwMultipleComponentError(tNode);
tNode.flags = TNodeFlags.isComponent;
markAsComponentHost(tView, tNode);
// The component is always stored first with directives after.
matches.unshift(def);
} else {
@ -1231,13 +1213,16 @@ function findDirectiveMatches(
return matches;
}
/** Stores index of component's host element so it will be queued for view refresh during CD. */
export function queueComponentIndexForCheck(previousOrParentTNode: TNode): void {
const tView = getLView()[TVIEW];
ngDevMode &&
assertEqual(tView.firstTemplatePass, true, 'Should only be called in first template pass.');
/**
* Marks a given TNode as a component's host. This consists of:
* - setting appropriate TNode flags;
* - storing index of component's host element so it will be queued for view refresh during CD.
*/
export function markAsComponentHost(tView: TView, hostTNode: TNode): void {
ngDevMode && assertFirstTemplatePass(tView);
hostTNode.flags = TNodeFlags.isComponent;
(tView.components || (tView.components = ngDevMode ? new TViewComponents !() : [
])).push(previousOrParentTNode.index);
])).push(hostTNode.index);
}
@ -1305,10 +1290,8 @@ function baseResolveDirective<T>(
viewData.push(nodeInjectorFactory);
}
function addComponentLogic<T>(
lView: LView, previousOrParentTNode: TNode, def: ComponentDef<T>): void {
const native = getNativeByTNode(previousOrParentTNode, lView);
function addComponentLogic<T>(lView: LView, hostTNode: TNode, def: ComponentDef<T>): void {
const native = getNativeByTNode(hostTNode, lView);
const tView = getOrCreateTView(def);
// Only component views should be added to the view tree directly. Embedded views are
@ -1317,18 +1300,14 @@ function addComponentLogic<T>(
const componentView = addToViewTree(
lView, createLView(
lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
lView[previousOrParentTNode.index], previousOrParentTNode as TElementNode,
rendererFactory, rendererFactory.createRenderer(native as RElement, def)));
lView[hostTNode.index], hostTNode as TElementNode, rendererFactory,
rendererFactory.createRenderer(native as RElement, def)));
componentView[T_HOST] = previousOrParentTNode as TElementNode;
componentView[T_HOST] = hostTNode as TElementNode;
// Component view will always be created before any injected LContainers,
// so this is a regular element, wrap it with the component view
lView[previousOrParentTNode.index] = componentView;
if (lView[TVIEW].firstTemplatePass) {
queueComponentIndexForCheck(previousOrParentTNode);
}
lView[hostTNode.index] = componentView;
}
export function elementAttributeInternal(
@ -1494,19 +1473,20 @@ export function createLContainer(
* by executing an associated template function.
*/
function refreshDynamicEmbeddedViews(lView: LView) {
for (let current = lView[CHILD_HEAD]; current !== null; current = current[NEXT]) {
// Note: current can be an LView or an LContainer instance, but here we are only interested
// in LContainer. We can tell it's an LContainer because its length is less than the LView
// header.
if (current[ACTIVE_INDEX] === -1 && isLContainer(current)) {
for (let i = CONTAINER_HEADER_OFFSET; i < current.length; i++) {
const dynamicViewData = current[i];
let viewOrContainer = lView[CHILD_HEAD];
while (viewOrContainer !== null) {
// Note: viewOrContainer can be an LView or an LContainer instance, but here we are only
// interested in LContainer
if (isLContainer(viewOrContainer) && viewOrContainer[ACTIVE_INDEX] === -1) {
for (let i = CONTAINER_HEADER_OFFSET; i < viewOrContainer.length; i++) {
const embeddedLView = viewOrContainer[i];
// The directives and pipes are not needed here as an existing view is only being
// refreshed.
ngDevMode && assertDefined(dynamicViewData[TVIEW], 'TView must be allocated');
renderEmbeddedTemplate(dynamicViewData, dynamicViewData[TVIEW], dynamicViewData[CONTEXT] !);
ngDevMode && assertDefined(embeddedLView[TVIEW], 'TView must be allocated');
renderEmbeddedTemplate(embeddedLView, embeddedLView[TVIEW], embeddedLView[CONTEXT] !);
}
}
viewOrContainer = viewOrContainer[NEXT];
}
}
@ -1563,7 +1543,7 @@ export function componentRefresh(hostLView: LView, adjustedElementIndex: number)
function syncViewWithBlueprint(componentView: LView) {
const componentTView = componentView[TVIEW];
for (let i = componentView.length; i < componentTView.blueprint.length; i++) {
componentView[i] = componentTView.blueprint[i];
componentView.push(componentTView.blueprint[i]);
}
}

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