Compare commits

...

45 Commits
9.0.5 ... 9.0.6

Author SHA1 Message Date
ecac8c032b release: cut the v9.0.6 release 2020-03-10 17:32:16 -07:00
ce728dd323 build: adding a script to compare commits in master and stable branches (#35130)
Adding a script that compares commits in master and patch branches and finds a delta between them. This is useful for release reviews, to make sure all the necessary commits are included into the patch branch and there is no discrepancy.

PR Close #35130
2020-03-10 17:40:56 -04:00
20dd0ac4f2 build: Add pullapprove verification tool to dev-infra-private package (#35911)
Migrates pullapprove verification tool to be available in the dev-infra-private
package

PR Close #35911
2020-03-10 14:17:56 -04:00
0f4d424da9 build: creates a script to get contributor stats from across the angular org (#35834)
This script gets all of the current users for the organization and retrieves
information about PR/Issue contributions/authorship since a provided date.
Returning this information as a CSV.

PR Close #35834
2020-03-10 14:17:25 -04:00
6f2fd6e4b4 fix(compiler): process imports first and declarations second while calculating scopes (#35850)
Prior to this commit, while calculating the scope for a module, Ivy compiler processed `declarations` field first and `imports` after that. That results in a couple issues:

* for Pipes with the same `name` and present in `declarations` and in an imported module, Pipe from imported module was selected. In View Engine the logic is opposite: Pipes from `declarations` field receive higher priority.
* for Directives with the same selector and present in `declarations` and in an imported module, we first invoked the logic of a Directive from `declarations` field and after that - imported Directive logic. In View Engine, it was the opposite and the logic of a Directive from the `declarations` field was invoked last.

In order to align Ivy and View Engine behavior, this commit updates the logic in which we populate module scope: we first process all imports and after that handle `declarations` field. As a result, in Ivy both use-cases listed above work similar to View Engine.

Resolves #35502.

PR Close #35850
2020-03-10 14:16:59 -04:00
8a68a7c490 build: fix elements test failures on IE (#35940)
Fixes the following issues which caused the `elements` unit tests to break on IE:
1. `core.js` wasn't included which caused an error about `Promise` and `Symbol` to be thrown.
2. We were using a version of `@webcomponents/custom-elements` which was shipping ES6 code to npm. As a result, IE was throwing a syntax error.

PR Close #35940
2020-03-10 14:16:34 -04:00
800e31b6d3 build: set dev-infra package to be private (#35910)
Setting the dev-infra package to private will prevent us
from accidentally publishing the package to npm.

PR Close #35910
2020-03-10 14:15:58 -04:00
0d784e0b00 test(compiler): add a public API guard for the public compiler options (#35885)
This commit adds a public API test which guards against unintentional
changes to the accepted keys in `angularCompilerOptions`.

PR Close #35885
2020-03-10 14:15:29 -04:00
8b0e26f786 refactor(compiler): split core/api.ts into multiple files (#35885)
This commit splits the ngtsc `core` package's api entrypoint, which
previously was a single `api.ts` file, into an api/ directory with multiple
files. This is done to isolate the parts of the API definitions pertaining
to the public-facing `angularCompilerOptions` field in tsconfig.json into a
single file, which will enable a public API guard test to be added in a
future commit.

PR Close #35885
2020-03-10 14:15:29 -04:00
6ff6159e32 ci: update components-repo-unit-tests job commit (#35961)
Updates the commit that the `components-repo-unit-tests` job runs
against to the latest available commit at time of writing.

The motivation for updating is that a lot of changes have been made, and
that a upcoming framework PR that fixes check no changes for OnPush
components exposed a test failure in `angular/components`.

See: eae5cf886d

PR Close #35961
2020-03-10 13:29:03 -04:00
db9704a433 fix(platform-browser): add missing peerDependency on @angular/animations (#35949)
`@angular/platform-browser/animations` has a dependency on `@angular/animations` however, this is not listed in `peerDependencies`

With this change we add this package as an optional peerDependency as it's only required when using the `@angular/platform-browser/animations` entrypoint.

Fixes #35888

PR Close #35949
2020-03-10 13:28:33 -04:00
add3e18e9d test(router): use pageYOffset in testing when scrollY is not available (#35976)
IE 9, 10, and 11 use the non-standard name `pageYOffset` instead of
`scrollY`.

PR Close #35976
2020-03-10 13:28:04 -04:00
b71cdf99fc refactor(compiler): optionalOperator -> consumeOptionalOperator (#35980)
PR Close #35980
2020-03-10 13:27:37 -04:00
f882ff01f3 fix(language-service): resolve the variable from the template context first (#35982)
PR Close #35982
2020-03-10 13:27:04 -04:00
2184ad5436 feat(dev-infra): add dev-infra to the commit message scopes (#35992)
Adds dev-infra to the commit message scopes.  Also, sets the scope to be ignored
in changelogs.

PR Close #35992
2020-03-10 13:26:14 -04:00
83d7819bfc fix(localize): merge translation from all XLIFF <file> elements (#35936)
XLIFF translation files can contain multiple `<file>` elements,
each of which contains translations. In ViewEngine all these
translations are merged into a single translation bundle.

Previously in Ivy only the translations from the last `<file>`
element were being loaded. Now all the translations from each
`<file>` are merged into a single translation bundle.

Fixes #35839

PR Close #35936
2020-03-09 13:06:05 -04:00
4cc519eb10 refactor(benchpress): remove benchmarks_external from ..pullapprove.yml (#35670)
PR Close #35670
2020-03-09 12:19:25 -04:00
8bcb0784bc refactor(benchpress): delete unused code (#35670)
PR Close #35670
2020-03-09 12:19:25 -04:00
9f68ff9e5b fix(localize): improve matching and parsing of XTB translation files (#35793)
This commit improves the `canParse()` method to check that the file is
valid XML and has the expected root node. Previously it was relying upon
a regular expression to do this.

PR Close #35793
2020-03-09 12:12:00 -04:00
689964b20f fix(localize): improve matching and parsing of XLIFF 2.0 translation files (#35793)
Previously, the `Xliff2TranslationParser` only matched files that had a narrow
choice of extensions (e.g. `xlf`) and also relied upon a regular expression
match of an optional XML namespace directive.

This commit relaxes the requirement on both of these and, instead, relies
upon parsing the file into XML and identifying an element of the form
`<xliff version="2.0">` which is the minimal requirement for such files.

PR Close #35793
2020-03-09 12:11:59 -04:00
677d666a20 fix(localize): improve matching and parsing of XLIFF 1.2 translation files (#35793)
Previously, the `Xliff1TranslationParser` only matched files that had a narrow
choice of extensions (e.g. `xlf`) and also relied upon a regular expression
match of an optional XML namespace directive.

This commit relaxes the requirement on both of these and, instead, relies
upon parsing the file into XML and identifying an element of the form
`<xliff version="1.2">` which is the minimal requirement for such files.

PR Close #35793
2020-03-09 12:11:59 -04:00
4d7b176329 refactor(localize): allow hints from canParse() to parse() (#35793)
This enables complex work to be done in `TranslationParser.canParse()`
without duplicating the work in `TranslationParser.parse()`.

PR Close #35793
2020-03-09 12:11:59 -04:00
6b0e247412 refactor(localize): add support for TranslationParser diagnostics (#35793)
This modifies the internal (but shared with CLI) API for loading/parsing
translation files. Now the parsers will return a new `Diagnostics` object
along with any translations and locale extracted from the file.

It is up to the caller to decide what to do about this, if there are errors
it is suggested that an error is thrown, which is what the `TranslationLoader`
class does.

PR Close #35793
2020-03-09 12:11:59 -04:00
9e23a69d1b refactor: export silentLogger not SilentLoggerFn (#34079)
Export SilentLogger object instead of the SilentLoggerFn and capitalise silentLoggerFn instead of the SilentLoggerFn
PR Close #34079
2020-03-06 17:37:12 -05:00
63bde80ca7 fix(zone.js): tickOptions's processNewMacroTasksSynchronously should default to true when flag omitted (#35814)
Calling `tick(0, null)` defaults `processNewMacroTasksSynchronously` flag to `true`, however calling  `tick(0, null, {})` defaults `processNewMacroTasksSynchronously` to `undefined`. This is undesirable behavior since unless the flag is set explicitly it should still default to `true`.

PR Close #35814
2020-03-06 17:33:57 -05:00
5ea9a61a87 fix(bazel): do not use manifest paths for generated imports within compilation unit (#35841)
Currently, the `ng_module` rule incorrectly uses manifest paths for
generated imports from the Angular compiler.

This breaks packaging as prodmode output (i.e. `esnext`) is copied in
various targets (`es5` and `es2015`) to the npm package output.

e.g. imports are generated like:

_node_modules/my-pkg/es2015/imports/public-api.js_
```ts
import * as i1 from "angular/packages/bazel/test/ng_package/example/imports/second";
```

while it should be actually:

```ts
import * as i1 from "./second";
```

The imports can, and should be relative so that the files are
self-contained and do not rely on custom module resolution.

PR Close #35841
2020-03-06 17:31:11 -05:00
5a09ea328f test: add entry-point with generated imports to ng_package test (#35841)
PR Close #35841
2020-03-06 17:31:11 -05:00
000c834554 fix(core): remove side effects from ɵɵgetInheritedFactory() (#35769) (#35846)
`ɵɵgetInheritedFactory()` is called from generated code for a component which extends another class. This function is detected by Closure to have a side effect and is not able to tree shake the component as a result. Marking it with `noSideEffects()` tells Closure it can remove this function under the relevant tree shaking conditions.

PR Close #35769

(cherry picked from commit c195d22f68)

PR Close #35846
2020-03-06 17:30:47 -05:00
d24ce21c45 fix(core): remove side effects from ɵɵNgOnChangesFeature() (#35769) (#35846)
`ɵɵNgOnChangesFeature()` would set `ngInherit`, which is a side effect and also not necessary. This was pulled out to module scope so the function itself can be pure. Since it only curries another function, the call is entirely unnecessary. Updated the compiler to only generate a reference to this function, rather than a call to it, and removed the extra curry indirection.

PR Close #35769

(cherry picked from commit 9cf85d2177)

PR Close #35846
2020-03-06 17:30:47 -05:00
68ca32fe51 fix(core): add noSideEffects() to ɵɵdefineComponent() (#35769) (#35846)
This marks the function are "pure" and eligible to be tree shaken by Closure. Without this, initializing `ngDevMode` is considered a side effect which prevents this function from being tree shaken and also any component which calls it.

PR Close #35769

(cherry picked from commit ba3612774f)

PR Close #35846
2020-03-06 17:30:47 -05:00
4fe3f37b3c fix(core): add noSideEffects() to make*Decorator() functions (#35769) (#35846)
This causes all the `make*Decorator()` functions to be considered pure and to be eligible for associated tree shaking by Closure.

PR Close #35769

(cherry picked from commit dc6a7918e3)

PR Close #35846
2020-03-06 17:30:47 -05:00
e0cf3ad4b3 refactor(core): update noSideEffects() to return the result of the inner function (#35769) (#35846)
This is useful for propagating return values without them being converted to a string. It still provides the same guarantees to Closure, which will assume that the function invoked is pure and can be tree-shaken accordingly.

PR Close #35769

(cherry picked from commit 4052dd8188)

PR Close #35846
2020-03-06 17:30:47 -05:00
5abf068b23 build(docs-infra): use local version of Zone.js when testing against local packages (#35858)
In some cases, we want to test the AIO app or docs examples against the
locally built Angular packages (for example to ensure that the changes
in a commit do not introduce a breaking change). In order to achieve
this, we have the `ng-packages-installer` script that handles updating
a project's `package.json` file to use the locally built Angular
packages (and appropriate versions for their (dev-/peer-)dependencies).

Previously, `ng-packages-installer` would only consider the locally
built Angular packages (from `dist/packages-dist/`). However, given that
Zone.js is now part of the `angular/angular` repo, it makes sense to
also use the locally built Zone.js package (from `dist/zone.js-dist/`).
Otherwise, the tests might fail for commits that update both the Angular
packages (and related docs examples) and the Zone.js package. An example
of such a simultaneous change (that would have broken tests) is #33838.

This commit updates the script to install the locally built Zone.js
package (in addition to the Angular ones). The commit ensures that the
Zone.js package will always be available alongside the Angular packages
(i.e. that the Zone.js package will be built by the same script that
builds the Angular packages and that the `dist/zone.js-dist/` directory
will be cached on CI).

Note: This problem was discovered while enabling docs examples unit
tests in #34374.

PR Close #35858
2020-03-06 17:30:20 -05:00
28d2bf7d7c docs(zone.js): update comment and chapter format of ngzone doc. (#35738)
PR Close #35738
2020-03-06 16:51:44 -05:00
51c1911c56 docs: markup mini fix (#35883)
PR Close #35883
2020-03-06 16:50:10 -05:00
eaf5b5856d fix(core): undecorated-classes-with-di migration should handle libraries generated with CLI versions past v6.2.0 (#35824)
The options for `flatModuleId` and `flatModuleOutFile` had been removed in the CLI
from generated libraries with 718ee15b9a.

This has been done because `ng-packagr` (which is used to build the
libraries) automatically set these options in-memory when it compiles the library.
No migration has been created for this because there was no actual need to get rid of
this. Keeping the options in the library `tsconfig` does not cause any problems unless
the `tsconfig` is used outside of `ng-packagr`. This was not anticipated, but is now
commonly done in `ng update` migrations.

The `ng update` migrations try to create an instance of the `AngularCompilerProgram` by
simply parsing the `tsconfig`. The migrations make the valid assumption that `tsconfig` files
are not incomplete/invalid. They _definitely_ are in the file system though. It just works for
libraries because `ng-packagr` in-memory completes the invalid `tsconfig` files, so that they
can be passed to the `@angular/compiler-cli`.

We can't have this logic in the `ng update` migrations because it's
out-of-scope for individual migrations to distinguish between libraries
and applications. Also it would be out-of-scope to parse the
`ng-packagr` configuration and handle the tsconfig in-memory completion.

As a workaround though, we can remove the flat-module bundle options
in-memory when creating the compiler program. This is acceptable since
we don't emit the program and the flat module bundles are not needed.

Fixes #34985.

PR Close #35824
2020-03-06 12:40:18 -05:00
9a9cc01be1 refactor(compiler): rename _ParseAST.optionalCharacter TemplateBinding.expression (#35886)
This commit renames
1. _ParseAST.optionalCharacter -> consumeOptionalCharacter
2. TemplateBinding.expression -> value

PR Close #35886
2020-03-06 12:39:49 -05:00
6330200004 docs: move ngIndia 2020 to past events on events page (#35901)
ng india was an outdated event removed it from upcoming events on the events page and added to already presented events

PR Close #35901
2020-03-06 12:39:25 -05:00
645fa40571 docs: add missing closing bracket (#35890)
Closes #35887
PR Close #35890
2020-03-06 12:38:30 -05:00
2cccf59415 build: create dev-infra-private npm package (#35862)
Creates the scaffolding for an @angular/dev-infra-private package
which will not be published to npm but will be pushed to
https://github.com/angular/dev-infra-private-builds repo for each
commit to master.

The contents of this npm package will then be depended on via
package.json dependency for angular/angular angular/angular-cli and
angular/components.

PR Close #35862
2020-03-05 18:55:40 -05:00
2a9b254ca5 refactor(compiler): Break up parseTemplateBindings() for microsyntax (#35812)
This commit is purely a refactoring of the logic in
`parseTemplateBindings` method for parsing the microsyntax expression.
This is done to enable the introduction of `keySpan` and `valueSpan` in
subsequent PR.

For a detailed explanation of this work and the subsequent work items,
please see https://docs.google.com/document/d/1mEVF2pSSMSnOloqOPQTYNiAJO0XQxA1H0BZyESASOrE/edit?usp=sharing

PR Close #35812
2020-03-05 16:04:42 -05:00
78f968b7dd fix(docs-infra): in 404 page some text is not visible (#35866)
In pr #34978 colors were not properly set, if we type wrong url in the browser and we are directed to the 404 page there some text is set to white color which as not visible set it to dark gray for visibility

PR Close #35866
2020-03-05 16:00:53 -05:00
2eaf420bdf perf(ngcc): reduce directory traversing (#35756)
This reduces the time that `findEntryPoints` takes from 9701.143ms to 4177.278ms, by reducing the file operations done.

Reference: #35717

PR Close #35756
2020-03-05 15:57:31 -05:00
07efe2a6aa build: fix integration tests flakes using local yarn cache for bazel-schematics & ng_elements_schematics demos (#35877)
ng_update_migrations will still access the global yarn cache on its `ng update` call and there is no way to avoid this that I can see but if no other integration tests access the global yarn cache then that one test can have free reign over it.

PR Close #35877
2020-03-05 15:30:20 -05:00
773d7b86aa fix(router): state data missing in routerLink (#33203)
Fixes 33173

PR Close #33203
2020-03-04 16:51:14 -05:00
124 changed files with 5739 additions and 2208 deletions

View File

@ -29,7 +29,7 @@ var_4_win: &cache_key_win_fallback v5-angular-win-node-12-
# Cache key for the `components-repo-unit-tests` job. **Note** when updating the SHA in the
# cache keys also update the SHA for the "COMPONENTS_REPO_COMMIT" environment variable.
var_5: &components_repo_unit_tests_cache_key v5-angular-components-2ec7254f88c4865e0de251f74c27e64c9d00d40a
var_5: &components_repo_unit_tests_cache_key v5-angular-components-598db096e668aa7e9debd56eedfd127b7a55e371
var_6: &components_repo_unit_tests_cache_key_fallback v5-angular-components-
# Workspace initially persisted by the `setup` job, and then enhanced by `build-npm-packages` and
@ -465,12 +465,14 @@ jobs:
- when:
condition: << parameters.ivy >>
steps:
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
# package installer picks up the locally built packages from that location.
# Rename the "dist/*-dist-ivy-aot" packages directories (persisted to the workspace by
# the `build-ivy-npm-packages` job) to "dist/*-dist" as the AIO package installer
# picks up the locally built packages from that location.
# *Note*: We could also adjust the packages installer, but given we won't have
# two different folders of Angular distributions in the future, we should keep
# the packages installer unchanged.
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
- run: mv dist/zone.js-dist-ivy-aot dist/zone.js-dist
# Run examples tests. The "CIRCLE_NODE_INDEX" will be set if "parallelism" is enabled.
# Since the parallelism is set to "5", there will be five parallel CircleCI containers.
# with either "0", "1", etc as node index. This can be passed to the "--shard" argument.
@ -552,6 +554,7 @@ jobs:
root: *workspace_location
paths:
- ng/dist/packages-dist-ivy-aot
- ng/dist/zone.js-dist-ivy-aot
# We run a subset of the integration tests outside of Bazel that track
# payload size.

View File

@ -68,7 +68,7 @@ setPublicVar COMPONENTS_REPO_TMP_DIR "/tmp/angular-components-repo"
setPublicVar COMPONENTS_REPO_URL "https://github.com/angular/components.git"
setPublicVar COMPONENTS_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI `config.yml`.
setPublicVar COMPONENTS_REPO_COMMIT "2ec7254f88c4865e0de251f74c27e64c9d00d40a"
setPublicVar COMPONENTS_REPO_COMMIT "598db096e668aa7e9debd56eedfd127b7a55e371"
####################################################################################################

View File

@ -546,7 +546,6 @@ groups:
conditions:
- >
contains_any_globs(files, [
'modules/benchmarks_external/**',
'modules/benchmarks/**'
])
reviewers:
@ -928,6 +927,7 @@ groups:
'.github/**',
'.vscode/**',
'.yarn/**',
'dev-infra/**',
'docs/BAZEL.md',
'docs/CARETAKER.md',
'docs/COMMITTER.md',
@ -952,6 +952,7 @@ groups:
'tools/browsers/**',
'tools/build/**',
'tools/circular_dependency_test/**',
'tools/contributing-stats/**',
'tools/gulp-tasks/**',
'tools/ng_rollup_bundle/**',
'tools/ngcontainer/**',

View File

@ -1,3 +1,31 @@
<a name="9.0.6"></a>
## [9.0.6](https://github.com/angular/angular/compare/9.0.5...9.0.6) (2020-03-11)
### Bug Fixes
* **bazel:** do not use manifest paths for generated imports within compilation unit ([#35841](https://github.com/angular/angular/issues/35841)) ([5ea9a61](https://github.com/angular/angular/commit/5ea9a61))
* **compiler:** process `imports` first and `declarations` second while calculating scopes ([#35850](https://github.com/angular/angular/issues/35850)) ([6f2fd6e](https://github.com/angular/angular/commit/6f2fd6e)), closes [#35502](https://github.com/angular/angular/issues/35502)
* **core:** add `noSideEffects()` to `make*Decorator()` functions ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([4fe3f37](https://github.com/angular/angular/commit/4fe3f37))
* **core:** add `noSideEffects()` to `ɵɵdefineComponent()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([68ca32f](https://github.com/angular/angular/commit/68ca32f))
* **core:** remove side effects from `ɵɵgetInheritedFactory()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([000c834](https://github.com/angular/angular/commit/000c834))
* **core:** remove side effects from `ɵɵNgOnChangesFeature()` ([#35769](https://github.com/angular/angular/issues/35769)) ([#35846](https://github.com/angular/angular/issues/35846)) ([d24ce21](https://github.com/angular/angular/commit/d24ce21))
* **core:** undecorated-classes-with-di migration should handle libraries generated with CLI versions past v6.2.0 ([#35824](https://github.com/angular/angular/issues/35824)) ([eaf5b58](https://github.com/angular/angular/commit/eaf5b58)), closes [#34985](https://github.com/angular/angular/issues/34985)
* **language-service:** resolve the variable from the template context first ([#35982](https://github.com/angular/angular/issues/35982)) ([f882ff0](https://github.com/angular/angular/commit/f882ff0))
* **localize:** improve matching and parsing of XLIFF 1.2 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([677d666](https://github.com/angular/angular/commit/677d666))
* **localize:** improve matching and parsing of XLIFF 2.0 translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([689964b](https://github.com/angular/angular/commit/689964b))
* **localize:** improve matching and parsing of XTB translation files ([#35793](https://github.com/angular/angular/issues/35793)) ([9f68ff9](https://github.com/angular/angular/commit/9f68ff9))
* **localize:** merge translation from all XLIFF `<file>` elements ([#35936](https://github.com/angular/angular/issues/35936)) ([83d7819](https://github.com/angular/angular/commit/83d7819)), closes [#35839](https://github.com/angular/angular/issues/35839)
* **platform-browser:** add missing peerDependency on `[@angular](https://github.com/angular)/animations` ([#35949](https://github.com/angular/angular/issues/35949)) ([db9704a](https://github.com/angular/angular/commit/db9704a)), closes [#35888](https://github.com/angular/angular/issues/35888)
* **router:** state data missing in routerLink ([#33203](https://github.com/angular/angular/issues/33203)) ([773d7b8](https://github.com/angular/angular/commit/773d7b8))
### Performance Improvements
* **ngcc:** reduce directory traversing ([#35756](https://github.com/angular/angular/issues/35756)) ([2eaf420](https://github.com/angular/angular/commit/2eaf420)), closes [#35717](https://github.com/angular/angular/issues/35717)
<a name="9.0.5"></a>
## [9.0.5](https://github.com/angular/angular/compare/9.0.4...9.0.5) (2020-03-04)

View File

@ -158,11 +158,11 @@ export class Provider6bComponent {
// #docregion silent-logger
// An object in the shape of the logger service
export function SilentLoggerFn() {}
function silentLoggerFn() {}
const silentLogger = {
export const SilentLogger = {
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
log: SilentLoggerFn
log: silentLoggerFn
};
// #enddocregion silent-logger
@ -171,7 +171,7 @@ const silentLogger = {
template: template,
providers:
// #docregion providers-7
[{ provide: Logger, useValue: silentLogger }]
[{ provide: Logger, useValue: SilentLogger }]
// #enddocregion providers-7
})
export class Provider7Component {

View File

@ -185,7 +185,7 @@ searchHeroes(term: string): Observable {
let heroesURL = `${this.heroesURL}?${term}`;
return this.http.jsonp(heroesUrl, 'callback').pipe(
catchError(this.handleError('searchHeroes', []) // then handle the error
catchError(this.handleError('searchHeroes', [])) // then handle the error
);
};
```

View File

@ -218,7 +218,7 @@ zone.run(() => {
This new context, `zoneThis`, can be retrieved from the `setTimeout()` callback function, and this context is the same when the `setTimeout()` is scheduled.
To get the context, you can call [`Zone.current`](https://github.com/angular/angular/blob/master/packages/zone.js/lib/zone.ts).
### Zones and async lifecycle hooks
## Zones and async lifecycle hooks
Zone.js can create contexts that persist across asynchronous operations as well as provide lifecycle hooks for asynchronous operations.
@ -303,7 +303,7 @@ This service creates a zone named `angular` to automatically trigger change dete
### NgZone `run()`/`runOutsideOfAngular()`
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then(),and `addEventListener()`.
`Zone` handles most asynchronous APIs such as `setTimeout()`, `Promise.then()`, and `addEventListener()`.
For the full list, see the [Zone Module document](https://github.com/angular/angular/blob/master/packages/zone.js/MODULE.md).
Therefore in those asynchronous APIs, you don't need to trigger change detection manually.
@ -315,12 +315,12 @@ This function, and all asynchronous operations in that function, trigger change
export class AppComponent implements OnInit {
constructor(private ngZone: NgZone) {}
ngOnInit() {
// new async API is not handled by Zone, so you need to
// use ngZone.run to make the asynchronous operation in angular zone
// and trigger change detection automatically
// New async API is not handled by Zone, so you need to
// use ngZone.run() to make the asynchronous operation in the angular zone
// and trigger change detection automatically.
this.ngZone.run(() => {
someNewAsyncAPI(() => {
// update data of component
// update the data of the component
});
});
}
@ -335,19 +335,20 @@ In that situation, you can use another NgZone method: [runOutsideAngular()](api/
export class AppComponent implements OnInit {
constructor(private ngZone: NgZone) {}
ngOnInit() {
// you know no data will be updated
// you don't want to do change detection in this
// specified operation, you can call runOutsideAngular
// You know no data will be updated,
// so you don't want to trigger change detection in this
// specified operation. Instead, call ngZone.runOutsideAngular()
this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
// do something will not update component data
// update component data
// but don't trigger change detection.
});
});
}
}
```
### Seting up Zone.js
### Setting up Zone.js
To make Zone.js available in Angular, you need to import the zone.js package.
If you are using the Angular CLI, this step is done automatically, and you will see the following line in the `src/polyfills.ts`:

View File

@ -13,12 +13,6 @@
</tr>
</thead>
<tbody>
<!-- ngIndia 2020 -->
<tr>
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
<td>Delhi, India</td>
<td>Feb 29, 2020</td>
</tr>
<!-- ng-conf 2020 -->
<tr>
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
@ -38,6 +32,12 @@
</tr>
</thead>
<tbody>
<!-- ngIndia 2020 -->
<tr>
<th><a href="https://www.ng-ind.com/" title="ngIndia">ngIndia</a></th>
<td>Delhi, India</td>
<td>Feb 29, 2020</td>
</tr>
<!-- ReactiveConf 2019 -->
<tr>
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>

View File

@ -106,7 +106,7 @@ aio-search-results {
}
}
.not-found {
.no-results {
color: $darkgray;
}

View File

@ -14,11 +14,12 @@ const LOCAL_MARKER_PATH = 'node_modules/_local_.json';
const ANGULAR_ROOT_DIR = path.resolve(__dirname, '../../..');
const ANGULAR_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/packages-dist');
const ZONEJS_DIST_PACKAGES_DIR = path.join(ANGULAR_ROOT_DIR, 'dist/zone.js-dist');
const DIST_PACKAGES_BUILD_SCRIPT = path.join(ANGULAR_ROOT_DIR, 'scripts/build/build-packages-dist.js');
const DIST_PACKAGES_BUILD_CMD = `"${process.execPath}" "${DIST_PACKAGES_BUILD_SCRIPT}"`;
/**
* A tool that can install Angular dependencies for a project from NPM or from the
* A tool that can install Angular/Zone.js dependencies for a project from NPM or from the
* locally built distributables.
*
* This tool is used to change dependencies of the `aio` application and the example
@ -33,7 +34,7 @@ class NgPackagesInstaller {
* @param {object} options - a hash of options for the install:
* * `debug` (`boolean`) - whether to display debug messages.
* * `force` (`boolean`) - whether to force a local installation even if there is a local marker file.
* * `buildPackages` (`boolean`) - whether to build the local Angular packages before using them.
* * `buildPackages` (`boolean`) - whether to build the local Angular/Zone.js packages before using them.
* (NOTE: Building the packages is currently not supported on Windows, so a message is printed instead.)
* * `ignorePackages` (`string[]`) - a collection of names of packages that should not be copied over.
*/
@ -52,7 +53,7 @@ class NgPackagesInstaller {
/**
* Check whether the dependencies have been overridden with locally built
* Angular packages. This is done by checking for the `_local_.json` marker file.
* Angular/Zone.js packages. This is done by checking for the `_local_.json` marker file.
* This will emit a warning to the console if the dependencies have been overridden.
*/
checkDependencies() {
@ -62,8 +63,8 @@ class NgPackagesInstaller {
}
/**
* Install locally built Angular dependencies, overriding the dependencies in the package.json
* This will also write a "marker" file (`_local_.json`), which contains the overridden package.json
* Install locally built Angular/Zone.js dependencies, overriding the dependencies in the `package.json`.
* This will also write a "marker" file (`_local_.json`), which contains the overridden `package.json`
* contents and acts as an indicator that dependencies have been overridden.
*/
installLocalDependencies() {
@ -86,13 +87,13 @@ class NgPackagesInstaller {
// Prevent accidental publishing of the package, if something goes wrong.
tmpConfig.private = true;
// Overwrite project dependencies/devDependencies to Angular packages with local files.
// Overwrite project dependencies/devDependencies to Angular/Zone.js packages with local files.
['dependencies', 'devDependencies'].forEach(prop => {
const deps = tmpConfig[prop] || {};
Object.keys(deps).forEach(key2 => {
const pkg2 = packages[key2];
if (pkg2) {
// point the core Angular packages at the distributable folder
// point the local packages at the distributable folder
deps[key2] = `file:${pkg2.packageDir}`;
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
}
@ -125,8 +126,8 @@ class NgPackagesInstaller {
fs.writeFileSync(pathToPackageConfig, packageConfigFile);
}
} finally {
// Restore local Angular packages dependencies to other Angular packages.
this._log(`Restoring original ${PACKAGE_JSON} for local Angular packages.`);
// Restore local Angular/Zone.js packages dependencies to other Angular packages.
this._log(`Restoring original ${PACKAGE_JSON} for local packages.`);
Object.keys(packages).forEach(key => {
const pkg = packages[key];
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(pkg.config, null, 2));
@ -167,7 +168,7 @@ class NgPackagesInstaller {
}
/**
* Build the local Angular packages.
* Build the local Angular/Zone.js packages.
*
* NOTE:
* Building the packages is currently not supported on Windows, so a message is printed instead, prompting the user to
@ -177,14 +178,14 @@ class NgPackagesInstaller {
const canBuild = process.platform !== 'win32';
if (canBuild) {
this._log(`Building the Angular packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
this._log(`Building the local packages with: ${DIST_PACKAGES_BUILD_SCRIPT}`);
shelljs.exec(DIST_PACKAGES_BUILD_CMD);
} else {
this._warn([
'Automatically building the local Angular packages is currently not supported on Windows.',
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' exists and is up-to-date (e.g. by running ` +
`'${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or a Linux docker ` +
'container or VM).',
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.',
`Please, ensure '${ANGULAR_DIST_PACKAGES_DIR}' and '${ZONEJS_DIST_PACKAGES_DIR}' exist and are up-to-date ` +
`(e.g. by running '${DIST_PACKAGES_BUILD_SCRIPT}' in Git Bash for Windows, Windows Subsystem for Linux or ` +
'a Linux docker container or VM).',
'',
'Proceeding anyway...',
].join('\n'));
@ -204,7 +205,7 @@ class NgPackagesInstaller {
// grab peer dependencies
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
Object.keys(sourcePackagePeerDeps)
// ignore peerDependencies which are already core Angular packages
// ignore peerDependencies which are already core Angular/Zone.js packages
.filter(key => !packages[key])
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
}
@ -214,11 +215,13 @@ class NgPackagesInstaller {
}
/**
* A hash of Angular package configs.
* (Detected as directories in '/dist/packages-dist/' that contain a top-level 'package.json' file.)
* A hash of Angular/Zone.js package configs.
* (Detected as directories in '/dist/packages-dist/' and '/dist/zone.js-dist/' that contain a top-level
* 'package.json' file.)
*/
_getDistPackages() {
this._log(`Angular distributable directory: ${ANGULAR_DIST_PACKAGES_DIR}.`);
this._log(`Zone.js distributable directory: ${ZONEJS_DIST_PACKAGES_DIR}.`);
if (this.buildPackages) {
this._buildDistPackages();
@ -254,7 +257,10 @@ class NgPackagesInstaller {
return packages;
};
const packageConfigs = collectPackages(ANGULAR_DIST_PACKAGES_DIR);
const packageConfigs = {
...collectPackages(ANGULAR_DIST_PACKAGES_DIR),
...collectPackages(ZONEJS_DIST_PACKAGES_DIR),
};
this._log('Found the following Angular distributables:', Object.keys(packageConfigs).map(key => `\n - ${key}`));
return packageConfigs;
@ -343,7 +349,7 @@ class NgPackagesInstaller {
// Log a warning.
this._warn([
`The project at "${absoluteProjectDir}" is running against the local Angular build.`,
`The project at "${absoluteProjectDir}" is running against the local Angular/Zone.js build.`,
'',
'To restore the npm packages run:',
'',
@ -396,10 +402,10 @@ function main() {
.option('debug', { describe: 'Print additional debug information.', default: false })
.option('force', { describe: 'Force the command to execute even if not needed.', default: false })
.option('build-packages', { describe: 'Build the local Angular packages, before using them.', default: false })
.option('ignore-packages', { describe: 'List of Angular packages that should not be used in local mode.', default: [], array: true })
.option('build-packages', { describe: 'Build the local Angular/Zone.js packages, before using them.', default: false })
.option('ignore-packages', { describe: 'List of Angular/Zone.js packages that should not be used in local mode.', default: [], array: true })
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular distributables.', () => {}, argv => {
.command('overwrite <projectDir> [--force] [--debug] [--ignore-packages package1 package2]', 'Install dependencies from the locally built Angular/Zone.js distributables.', () => {}, argv => {
createInstaller(argv).installLocalDependencies();
})
.command('restore <projectDir> [--debug]', 'Install dependencies from the npm registry.', () => {}, argv => {

View File

@ -15,6 +15,7 @@ describe('NgPackagesInstaller', () => {
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
const ngRootDir = path.resolve(__dirname, '../../..');
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
const zoneJsDir = path.join(ngRootDir, 'dist/zone.js-dist');
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
let installer;
@ -51,7 +52,7 @@ describe('NgPackagesInstaller', () => {
describe('installLocalDependencies()', () => {
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
let dummyNgPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
let dummyLocalPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
beforeEach(() => {
spyOn(installer, '_checkLocalMarker');
@ -60,17 +61,18 @@ describe('NgPackagesInstaller', () => {
spyOn(installer, '_parseLockfile').and.returnValue({
'rxjs@^6.3.0': {version: '6.3.3'},
'zone.js@^0.8.26': {version: '0.8.27'}
'rxjs-dev@^6.3.0': {version: '6.4.2'}
});
// These are the packages that are "found" in the dist directory
dummyNgPackages = {
dummyLocalPackages = {
'@angular/core': {
packageDir: `${packagesDir}/core`,
packageJsonPath: `${packagesDir}/core/package.json`,
config: {
peerDependencies: {
'rxjs': '^6.4.0',
'rxjs-dev': '^6.4.0',
'some-package': '5.0.1',
'zone.js': '~0.8.26'
}
@ -101,32 +103,40 @@ describe('NgPackagesInstaller', () => {
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
peerDependencies: { tsickle: '^1.4.0' }
}
}
},
'zone.js': {
packageDir: `${zoneJsDir}/zone.js`,
packageJsonPath: `${zoneJsDir}/zone.js/package.json`,
config: {
devDependencies: { typescript: '^2.4.2' }
}
},
};
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyNgPackages));
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyLocalPackages));
// This is the package.json in the "test" folder
dummyPackage = {
dependencies: {
'@angular/core': '4.4.1',
'@angular/common': '4.4.1',
rxjs: '^6.3.0'
rxjs: '^6.3.0',
'zone.js': '^0.8.26'
},
devDependencies: {
'@angular/compiler-cli': '4.4.1',
'zone.js': '^0.8.26'
'rxjs-dev': '^6.3.0'
}
};
dummyPackageJson = JSON.stringify(dummyPackage);
fs.readFileSync.and.returnValue(dummyPackageJson);
// This is the package.json that is temporarily written to the "test" folder
// Note that the Angular (dev)dependencies have been modified to use a "file:" path
// And that the peerDependencies from `dummyNgPackages` have been updated or added as
// Note that the Angular/Zone.js (dev)dependencies have been modified to use a "file:" path
// and that the peerDependencies from `dummyLocalPackages` have been updated or added as
// (dev)dependencies (unless the current version in lockfile satisfies semver).
//
// For example, `zone.js@0.8.27` (from lockfile) satisfies `zone.js@~0.8.26` (from
// `@angular/core`), thus `zone.js: ^0.8.26` (from original `package.json`) is retained.
// For example, `rxjs-dev@6.4.2` (from lockfile) satisfies `rxjs-dev@^6.4.0` (from
// `@angular/core`), thus `rxjs-dev: ^6.3.0` (from original `package.json`) is retained.
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
// `rxjs: ^6.4.0` (from `@angular/core`).
@ -134,11 +144,12 @@ describe('NgPackagesInstaller', () => {
dependencies: {
'@angular/core': `file:${packagesDir}/core`,
'@angular/common': `file:${packagesDir}/common`,
'rxjs': '^6.4.0'
'rxjs': '^6.4.0',
'zone.js': `file:${zoneJsDir}/zone.js`,
},
devDependencies: {
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
'zone.js': '^0.8.26',
'rxjs-dev': '^6.3.0',
'some-package': '5.0.1',
typescript: '^2.4.2'
},
@ -182,31 +193,56 @@ describe('NgPackagesInstaller', () => {
});
it('should temporarily overwrite the package.json files of local Angular packages', () => {
const pkgJsonFor = pkgName => dummyNgPackages[`@angular/${pkgName}`].packageJsonPath;
const pkgConfigFor = pkgName => copyJsonObj(dummyNgPackages[`@angular/${pkgName}`].config);
const pkgJsonPathFor = pkgName => dummyLocalPackages[pkgName].packageJsonPath;
const pkgConfigFor = pkgName => copyJsonObj(dummyLocalPackages[pkgName].config);
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
const stringifyConfig = config => JSON.stringify(config, null, 2);
const allArgs = fs.writeFileSync.calls.allArgs();
const firstFiveArgs = allArgs.slice(0, 5);
const lastFiveArgs = allArgs.slice(-5);
const firstSixArgs = allArgs.slice(0, 6);
const lastSixArgs = allArgs.slice(-6);
expect(firstFiveArgs).toEqual([
[pkgJsonFor('core'), stringifyConfig(overwriteConfigFor('core', {private: true}))],
[pkgJsonFor('common'), stringifyConfig(overwriteConfigFor('common', {private: true}))],
[pkgJsonFor('compiler'), stringifyConfig(overwriteConfigFor('compiler', {private: true}))],
[pkgJsonFor('compiler-cli'), stringifyConfig(overwriteConfigFor('compiler-cli', {
private: true,
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` }
}))],
[pkgJsonFor('tsc-wrapped'), stringifyConfig(overwriteConfigFor('tsc-wrapped', {
private: true,
devDependencies: { '@angular/common': `file:${packagesDir}/common` }
}))],
expect(firstSixArgs).toEqual([
[
pkgJsonPathFor('@angular/core'),
stringifyConfig(overwriteConfigFor('@angular/core', {private: true})),
],
[
pkgJsonPathFor('@angular/common'),
stringifyConfig(overwriteConfigFor('@angular/common', {private: true})),
],
[
pkgJsonPathFor('@angular/compiler'),
stringifyConfig(overwriteConfigFor('@angular/compiler', {private: true})),
],
[
pkgJsonPathFor('@angular/compiler-cli'),
stringifyConfig(overwriteConfigFor('@angular/compiler-cli', {
private: true,
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` },
})),
],
[
pkgJsonPathFor('@angular/tsc-wrapped'),
stringifyConfig(overwriteConfigFor('@angular/tsc-wrapped', {
private: true,
devDependencies: { '@angular/common': `file:${packagesDir}/common` },
})),
],
[
pkgJsonPathFor('zone.js'),
stringifyConfig(overwriteConfigFor('zone.js', {private: true})),
],
]);
expect(lastFiveArgs).toEqual(['core', 'common', 'compiler', 'compiler-cli', 'tsc-wrapped']
.map(pkgName => [pkgJsonFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
expect(lastSixArgs).toEqual([
'@angular/core',
'@angular/common',
'@angular/compiler',
'@angular/compiler-cli',
'@angular/tsc-wrapped',
'zone.js',
].map(pkgName => [pkgJsonPathFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
});
it('should load the package.json', () => {
@ -280,7 +316,7 @@ describe('NgPackagesInstaller', () => {
expect(shelljs.exec).not.toHaveBeenCalled();
expect(warning).toContain(
'Automatically building the local Angular packages is currently not supported on Windows.');
'Automatically building the local Angular/Zone.js packages is currently not supported on Windows.');
expect(warning).toContain('Git Bash for Windows');
expect(warning).toContain('Windows Subsystem for Linux');
expect(warning).toContain('Linux docker container or VM');
@ -309,8 +345,8 @@ describe('NgPackagesInstaller', () => {
expect(installer._buildDistPackages).not.toHaveBeenCalled();
});
it('should include top level Angular packages', () => {
const ngPackages = installer._getDistPackages();
it('should include top level Angular and Zone.js packages', () => {
const localPackages = installer._getDistPackages();
const expectedValue = jasmine.objectContaining({
packageDir: jasmine.any(String),
packageJsonPath: jasmine.any(String),
@ -318,28 +354,30 @@ describe('NgPackagesInstaller', () => {
});
// For example...
expect(ngPackages['@angular/common']).toEqual(expectedValue);
expect(ngPackages['@angular/core']).toEqual(expectedValue);
expect(ngPackages['@angular/router']).toEqual(expectedValue);
expect(ngPackages['@angular/upgrade']).toEqual(expectedValue);
expect(localPackages['@angular/common']).toEqual(expectedValue);
expect(localPackages['@angular/core']).toEqual(expectedValue);
expect(localPackages['@angular/router']).toEqual(expectedValue);
expect(localPackages['@angular/upgrade']).toEqual(expectedValue);
expect(localPackages['zone.js']).toEqual(expectedValue);
expect(ngPackages['@angular/upgrade/static']).not.toBeDefined();
expect(localPackages['@angular/upgrade/static']).not.toBeDefined();
});
it('should store each package\'s directory', () => {
const ngPackages = installer._getDistPackages();
const localPackages = installer._getDistPackages();
// For example...
expect(ngPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
expect(ngPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
expect(localPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
expect(localPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
expect(localPackages['zone.js'].packageDir).toBe(path.join(zoneJsDir, 'zone.js'));
});
it('should not include packages that have been ignored', () => {
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
const ngPackages = installer._getDistPackages();
const localPackages = installer._getDistPackages();
expect(ngPackages['@angular/common']).toBeDefined();
expect(ngPackages['@angular/router']).toBeUndefined();
expect(localPackages['@angular/common']).toBeDefined();
expect(localPackages['@angular/router']).toBeUndefined();
});
});
@ -480,7 +518,7 @@ describe('NgPackagesInstaller', () => {
describe('_printWarning()', () => {
it('should mention the message passed in the warning', () => {
installer._printWarning();
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular build');
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular/Zone.js build');
});
it('should mention the command to restore the Angular packages in any warning', () => {

38
dev-infra/BUILD.bazel Normal file
View File

@ -0,0 +1,38 @@
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
load("@npm_bazel_typescript//:index.bzl", "ts_library")
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
ts_library(
name = "cli",
srcs = [
"cli.ts",
],
deps = [
"//dev-infra/pullapprove",
"@npm//@types/node",
],
)
rollup_bundle(
name = "bundle",
config_file = "rollup.config.js",
entry_point = ":cli.ts",
format = "umd",
sourcemap = "hidden",
deps = [
":cli",
"@npm//rollup-plugin-commonjs",
"@npm//rollup-plugin-node-resolve",
],
)
pkg_npm(
name = "npm_package",
srcs = [
"package.json",
],
visibility = ["//visibility:public"],
deps = [
":bundle",
],
)

20
dev-infra/cli.ts Normal file
View File

@ -0,0 +1,20 @@
/**
* @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 {verify} from './pullapprove/verify';
const args = process.argv.slice(2);
// TODO(josephperrott): Set up proper cli flag/command handling
switch (args[0]) {
case 'pullapprove:verify':
verify();
break;
default:
console.info('No commands were matched');
}

10
dev-infra/package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "@angular/dev-infra-private",
"version": "0.0.0",
"description": "INTERNAL USE ONLY - Angular internal DevInfra tooling/scripts - INTERNAL USE ONLY",
"license": "MIT",
"private": true,
"bin": {
"ng-dev": "./bundle.js"
}
}

View File

@ -0,0 +1,19 @@
load("@npm_bazel_typescript//:index.bzl", "ts_library")
ts_library(
name = "pullapprove",
srcs = [
"verify.ts",
],
visibility = ["//dev-infra:__subpackages__"],
deps = [
"@npm//@types/minimatch",
"@npm//@types/node",
"@npm//@types/shelljs",
"@npm//@types/yaml",
"@npm//minimatch",
"@npm//shelljs",
"@npm//tslib",
"@npm//yaml",
],
)

View File

@ -0,0 +1,215 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {readFileSync} from 'fs';
import {IMinimatch, Minimatch} from 'minimatch';
import * as path from 'path';
import {cd, exec, set} from 'shelljs';
import {parse as parseYaml} from 'yaml';
interface GlobMatcher {
group: string;
glob: string;
matcher: IMinimatch;
matchCount: number;
}
export function verify() {
// Exit early on shelljs errors
set('-e');
// Regex Matcher for contains_any_globs conditions
const CONTAINS_ANY_GLOBS_REGEX = /^'([^']+)',?$/;
// Full path of the angular project directory
const ANGULAR_PROJECT_DIR = process.cwd();
// Change to the Angular project directory
cd(ANGULAR_PROJECT_DIR);
// Whether to log verbosely
const VERBOSE_MODE = process.argv.includes('-v');
// Full path to PullApprove config file
const PULL_APPROVE_YAML_PATH = path.resolve(ANGULAR_PROJECT_DIR, '.pullapprove.yml');
// All relative path file names in the git repo, this is retrieved using git rather
// that a glob so that we only get files that are checked in, ignoring things like
// node_modules, .bazelrc.user, etc
const ALL_FILES = exec('git ls-tree --full-tree -r --name-only HEAD', {silent: true})
.trim()
.split('\n')
.filter((_: string) => !!_);
if (!ALL_FILES.length) {
console.error(
`No files were found to be in the git tree, did you run this command from \n` +
`inside the angular repository?`);
process.exit(1);
}
/** Gets the glob matching information from each group's condition. */
function getGlobMatchersFromCondition(
groupName: string, condition: string): [GlobMatcher[], string[]] {
const trimmedCondition = condition.trim();
const globMatchers: GlobMatcher[] = [];
const badConditionLines: string[] = [];
// If the condition starts with contains_any_globs, evaluate all of the globs
if (trimmedCondition.startsWith('contains_any_globs')) {
trimmedCondition.split('\n')
.slice(1, -1)
.map(glob => {
const trimmedGlob = glob.trim();
const match = trimmedGlob.match(CONTAINS_ANY_GLOBS_REGEX);
if (!match) {
badConditionLines.push(trimmedGlob);
return '';
}
return match[1];
})
.filter(globString => !!globString)
.forEach(globString => globMatchers.push({
group: groupName,
glob: globString,
matcher: new Minimatch(globString, {dot: true}),
matchCount: 0,
}));
}
return [globMatchers, badConditionLines];
}
/** Create logs for each review group. */
function logGroups(groups: Map<string, Map<string, GlobMatcher>>) {
Array.from(groups.entries()).sort().forEach(([groupName, globs]) => {
console.groupCollapsed(groupName);
Array.from(globs.values())
.sort((a, b) => b.matchCount - a.matchCount)
.forEach(glob => console.info(`${glob.glob} - ${glob.matchCount}`));
console.groupEnd();
});
}
/** Logs a header within a text drawn box. */
function logHeader(...params: string[]) {
const totalWidth = 80;
const fillWidth = totalWidth - 2;
const headerText = params.join(' ').substr(0, fillWidth);
const leftSpace = Math.ceil((fillWidth - headerText.length) / 2);
const rightSpace = fillWidth - leftSpace - headerText.length;
const fill = (count: number, content: string) => content.repeat(count);
console.info(`${fill(fillWidth, '─')}`);
console.info(`${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}`);
console.info(`${fill(fillWidth, '─')}`);
}
/** Runs the pull approve verification check on provided files. */
function runVerification(files: string[]) {
// All of the globs created for each group's conditions.
const allGlobs: GlobMatcher[] = [];
// The pull approve config file.
const pullApprove = readFileSync(PULL_APPROVE_YAML_PATH, {encoding: 'utf8'});
// All of the PullApprove groups, parsed from the PullApprove yaml file.
const parsedPullApproveGroups =
parseYaml(pullApprove).groups as{[key: string]: {conditions: string}};
// All files which were found to match a condition in PullApprove.
const matchedFiles = new Set<string>();
// All files which were not found to match a condition in PullApprove.
const unmatchedFiles = new Set<string>();
// All PullApprove groups which matched at least one file.
const matchedGroups = new Map<string, Map<string, GlobMatcher>>();
// All PullApprove groups which did not match at least one file.
const unmatchedGroups = new Map<string, Map<string, GlobMatcher>>();
// All condition lines which were not able to be correctly parsed, by group.
const badConditionLinesByGroup = new Map<string, string[]>();
// Total number of condition lines which were not able to be correctly parsed.
let badConditionLineCount = 0;
// Get all of the globs from the PullApprove group conditions.
Object.entries(parsedPullApproveGroups).forEach(([groupName, group]) => {
for (const condition of group.conditions) {
const [matchers, badConditions] = getGlobMatchersFromCondition(groupName, condition);
if (badConditions.length) {
badConditionLinesByGroup.set(groupName, badConditions);
badConditionLineCount += badConditions.length;
}
allGlobs.push(...matchers);
}
});
if (badConditionLineCount) {
console.info(`Discovered ${badConditionLineCount} parsing errors in PullApprove conditions`);
console.info(`Attempted parsing using: ${CONTAINS_ANY_GLOBS_REGEX}`);
console.info();
console.info(`Unable to properly parse the following line(s) by group:`);
badConditionLinesByGroup.forEach((badConditionLines, groupName) => {
console.info(`- ${groupName}:`);
badConditionLines.forEach(line => console.info(` ${line}`));
});
console.info();
console.info(
`Please correct the invalid conditions, before PullApprove verification can be completed`);
process.exit(1);
}
// Check each file for if it is matched by a PullApprove condition.
for (let file of files) {
const matched = allGlobs.filter(glob => glob.matcher.match(file));
matched.length ? matchedFiles.add(file) : unmatchedFiles.add(file);
matched.forEach(glob => glob.matchCount++);
}
// Add each glob for each group to a map either matched or unmatched.
allGlobs.forEach(glob => {
const groups = glob.matchCount ? matchedGroups : unmatchedGroups;
const globs = groups.get(glob.group) || new Map<string, GlobMatcher>();
// Set the globs map in the groups map
groups.set(glob.group, globs);
// Set the glob in the globs map
globs.set(glob.glob, glob);
});
// PullApprove is considered verified if no files or groups are found to be unsed.
const verificationSucceeded = !(unmatchedFiles.size || unmatchedGroups.size);
/**
* Overall result
*/
logHeader('Result');
if (verificationSucceeded) {
console.info('PullApprove verification succeeded!');
} else {
console.info(`PullApprove verification failed.\n`);
console.info(`Please update '.pullapprove.yml' to ensure that all necessary`);
console.info(`files/directories have owners and all patterns that appear in`);
console.info(`the file correspond to actual files/directories in the repo.`);
}
/**
* File by file Summary
*/
logHeader('PullApprove file match results');
console.groupCollapsed(`Matched Files (${matchedFiles.size} files)`);
VERBOSE_MODE && matchedFiles.forEach(file => console.info(file));
console.groupEnd();
console.groupCollapsed(`Unmatched Files (${unmatchedFiles.size} files)`);
unmatchedFiles.forEach(file => console.info(file));
console.groupEnd();
/**
* Group by group Summary
*/
logHeader('PullApprove group matches');
console.groupCollapsed(`Matched Groups (${matchedGroups.size} groups)`);
VERBOSE_MODE && logGroups(matchedGroups);
console.groupEnd();
console.groupCollapsed(`Unmatched Groups (${unmatchedGroups.size} groups)`);
logGroups(unmatchedGroups);
console.groupEnd();
// Provide correct exit code based on verification success.
process.exit(verificationSucceeded ? 0 : 1);
}
runVerification(ALL_FILES);
}

View File

@ -0,0 +1,16 @@
const node = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');
module.exports = {
external: ['shelljs', 'minimatch', 'yaml'],
preferBuiltins: true,
output: {
banner: "#!/usr/bin/env node",
},
plugins: [
node({
mainFields: ['browser', 'es2015', 'module', 'jsnext:main', 'main'],
}),
commonjs(),
],
};

View File

@ -60,7 +60,7 @@
"uncompressed": {
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
"bundle": 175498
"bundle": 170618
}
}
}

View File

@ -38,7 +38,7 @@ function installLocalPackages() {
local_packages+=("puppeteer@file:${pwd}/../node_modules/puppeteer")
local_packages+=("webdriver-manager@file:${pwd}/../node_modules/webdriver-manager")
yarn add --ignore-scripts --silent "${local_packages[@]}"
yarn add --ignore-scripts --silent "${local_packages[@]}" --cache-folder ./.yarn_local_cache
}
function patchKarmaConf() {
@ -58,6 +58,8 @@ function testBazel() {
# Create project
ng new demo --collection=@angular/bazel --routing --skip-git --skip-install --style=scss
cd demo
# Use a local yarn cache folder so we don't access the global yarn cache
mkdir .yarn_local_cache
patchKarmaConf
patchProtractorConf
installLocalPackages
@ -79,7 +81,7 @@ function testNonBazel() {
# disable CLI's version check (if version is 0.0.0, then no version check happens)
yarn --cwd node_modules/@angular/cli version --new-version 0.0.0 --no-git-tag-version
# re-add build-angular
yarn add --dev file:../node_modules/@angular-devkit/build-angular
yarn add --dev file:../node_modules/@angular-devkit/build-angular --cache-folder ./.yarn_local_cache
ng build --progress=false
ng test --progress=false --watch=false
ng e2e --port 0 --configuration=production --webdriver-update=false

View File

@ -53,11 +53,13 @@ rm('-rf', `demo`);
exec('ng version');
exec('ng new demo --skip-git --skip-install --style=css --no-interactive');
cd('demo');
// Use a local yarn cache folder so we don't access the global yarn cache
exec('mkdir .yarn_local_cache');
// Install Angular packages that are built locally from HEAD and npm packages
// from root node modules that are to be kept in sync
const packageList = Object.keys(packages).map(p => `${p}@${packages[p]}`).join(' ');
exec(`yarn add --ignore-scripts --silent ${packageList}`);
exec(`yarn add --ignore-scripts --silent ${packageList} --cache-folder ./.yarn_local_cache`);
// Add @angular/elements
exec(bazelMappings ? `ng add "${bazelMappings['@angular/elements']}"` : `ng add "${__dirname}/../../dist/packages-dist/elements"`);

View File

@ -1,6 +0,0 @@
# How to run the benchmarks_external locally
$ cp -r ./modules/benchmarks_external ./dist/all/
$ ./node_modules/.bin/tsc -p modules --emitDecoratorMetadata -w
$ gulp serve
$ open http://localhost:8000/all/benchmarks_external/src/tree/index.html?bundles=false

View File

@ -1,37 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('ng1.x compiler benchmark', function() {
const URL = 'benchmarks_external/src/compiler/compiler_benchmark.html';
afterEach(verifyNoBrowserErrors);
it('should log withBinding stats', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#compileWithBindings'],
id: 'ng1.compile.withBindings',
params: [{name: 'elements', value: 150, scale: 'linear'}],
waitForAngular2: false
}).then(done, done.fail);
});
it('should log noBindings stats', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#compileNoBindings'],
id: 'ng1.compile.noBindings',
params: [{name: 'elements', value: 150, scale: 'linear'}],
waitForAngular2: false
}).then(done, done.fail);
});
});

View File

@ -1,33 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('ng1.x largetable benchmark', function() {
const URL = 'benchmarks_external/src/largetable/largetable_benchmark.html';
afterEach(verifyNoBrowserErrors);
['baselineBinding', 'baselineInterpolation', 'ngBind', 'ngBindOnce', 'interpolation',
'interpolationAttr', 'ngBindFn', 'interpolationFn', 'ngBindFilter', 'interpolationFilter']
.forEach(function(benchmarkType) {
it('should log the stats with: ' + benchmarkType, function(done) {
runClickBenchmark({
url: URL,
buttons: ['#destroyDom', '#createDom'],
id: 'ng1.largetable.' + benchmarkType,
params: [
{name: 'columns', value: 100, scale: 'sqrt'},
{name: 'rows', value: 20, scale: 'sqrt'},
{name: 'benchmarkType', value: benchmarkType}
],
waitForAngular2: false
}).then(done, done.fail);
});
});
});

View File

@ -1,41 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('ng-dart1.x naive infinite scroll benchmark', function() {
const URL = 'benchmarks_external/src/naive_infinite_scroll/index.html';
afterEach(verifyNoBrowserErrors);
[1, 2, 4].forEach(function(appSize) {
it('should run scroll benchmark and collect stats for appSize = ' + appSize, function(done) {
runBenchmark({
url: URL,
id: 'ng1-dart1.x.naive_infinite_scroll',
work: function() {
$('#reset-btn').click();
$('#run-btn').click();
let s = 1000;
if (appSize > 4) {
s = s + appSize * 100;
}
browser.sleep(s);
},
params: [
{name: 'appSize', value: appSize},
{name: 'iterationCount', value: 20, scale: 'linear'},
{name: 'scrollIncrement', value: 40}
],
waitForAngular2: false
}).then(done, done.fail);
});
});
});

View File

@ -1,37 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('react tree benchmark', function() {
const URL = 'benchmarks_external/src/tree/react/index.html';
afterEach(verifyNoBrowserErrors);
it('should log the stats (create)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#destroyDom', '#createDom'],
id: 'react.tree.create',
params: [{name: 'depth', value: 9, scale: 'log2'}],
waitForAngular2: false
}).then(done, done.fail);
});
it('should log the stats (update)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#createDom'],
id: 'react.tree.update',
params: [{name: 'depth', value: 9, scale: 'log2'}],
waitForAngular2: false
}).then(done, done.fail);
});
});

View File

@ -1,37 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('ng1.x tree benchmark', function() {
const URL = 'benchmarks_external/src/static_tree/tree_benchmark.html';
afterEach(verifyNoBrowserErrors);
it('should log the stats (create)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#destroyDom', '#createDom'],
id: 'ng1.static.tree.create',
params: [],
waitForAngular2: false
}).then(done, done.fail);
});
it('should log the stats (update)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#createDom'],
id: 'ng1.static.tree.update',
params: [],
waitForAngular2: false
}).then(done, done.fail);
});
});

View File

@ -1,37 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runClickBenchmark, verifyNoBrowserErrors} from '@angular/testing/src/perf_util';
describe('ng1.x tree benchmark', function() {
const URL = 'benchmarks_external/src/tree/tree_benchmark.html';
afterEach(verifyNoBrowserErrors);
it('should log the stats (create)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#destroyDom', '#createDom'],
id: 'ng1.tree.create',
params: [{name: 'depth', value: 9, scale: 'log2'}],
waitForAngular2: false
}).then(done, done.fail);
});
it('should log the stats (update)', function(done) {
runClickBenchmark({
url: URL,
buttons: ['#createDom'],
id: 'ng1.tree.update',
params: [{name: 'depth', value: 9, scale: 'log2'}],
waitForAngular2: false
}).then(done, done.fail);
});
});

View File

@ -1,91 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
(function(global: any /** TODO #9100 */) {
writeScriptTag('/all/benchmarks/vendor/core.js');
writeScriptTag('/all/benchmarks/vendor/zone.js');
writeScriptTag('/all/benchmarks/vendor/long-stack-trace-zone.js');
writeScriptTag('/all/benchmarks/vendor/system.src.js');
writeScriptTag('/all/benchmarks/vendor/Reflect.js', 'benchmarksBootstrap()');
(<any>global).benchmarksBootstrap = benchmarksBootstrap;
function benchmarksBootstrap() {
// check query param
const useBundles = location.search.indexOf('bundles=false') == -1;
if (useBundles) {
System.config({
map: {
'index': 'index.js',
'@angular/core': '/packages-dist/core/bundles/core.umd.js',
'@angular/common': '/packages-dist/common/bundles/common.umd.js',
'@angular/forms': '/packages-dist/forms/bundles/forms.umd.js',
'@angular/compiler': '/packages-dist/compiler/bundles/compiler.umd.js',
'@angular/platform-browser':
'/packages-dist/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic':
'/packages-dist/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': '/packages-dist/http/bundles/http.umd.js',
'@angular/upgrade': '/packages-dist/upgrade/bundles/upgrade.umd.js',
'@angular/router': '/packages-dist/router/bundles/router.umd.js',
'@angular/core/src/facade': '/all/@angular/core/src/facade',
'rxjs': 'node_modules/rxjs',
},
packages: {
'app': {defaultExtension: 'js'},
'@angular/core/src/facade': {defaultExtension: 'js'},
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
'rxjs': { main: 'index.js', defaultExtension: 'js' },
}
});
} else {
console.warn(
'Not using the Angular bundles. Don\'t use this configuration for e2e/performance tests!');
System.config({
map: {
'index': 'index.js',
'@angular': '/all/@angular',
'rxjs': 'node_modules/rxjs',
},
packages: {
'app': {defaultExtension: 'js'},
'@angular/core': {main: 'index.js', defaultExtension: 'js'},
'@angular/compiler': {main: 'index.js', defaultExtension: 'js'},
'@angular/router': {main: 'index.js', defaultExtension: 'js'},
'@angular/common': {main: 'index.js', defaultExtension: 'js'},
'@angular/forms': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-browser': {main: 'index.js', defaultExtension: 'js'},
'@angular/platform-browser-dynamic': {main: 'index.js', defaultExtension: 'js'},
'@angular/upgrade': {main: 'index.js', defaultExtension: 'js'},
'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' },
'rxjs/operators': {main: 'index.js', defaultExtension: 'js' },
'rxjs/testing': {main: 'index.js', defaultExtension: 'js' },
'rxjs/websocket': {main: 'index.js', defaultExtension: 'js' },
'rxjs': { main: 'index.js', defaultExtension: 'js' },
}
});
}
// BOOTSTRAP the app!
System.import('index').then(function(m: any /** TODO #9100 */) {
m.main();
}, console.error.bind(console));
}
function writeScriptTag(scriptUrl: any /** TODO #9100 */, onload?: any /** TODO #9100 */) {
document.write(`<script src="${scriptUrl}" onload="${onload}"></script>`);
}
}(window));

View File

@ -1,53 +0,0 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Elements:
<input type="number" name="elements" placeholder="elements" value="150">
<br>
<button>Apply</button>
</form>
<h2>Actions</h2>
<p>
<button id="compileWithBindings">CompileWithBindings</button>
<button id="compileNoBindings">CompileNoBindings</button>
</p>
<template id="templateNoBindings">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
</div>
</div>
</div>
</div>
</div>
</template>
<template id="templateWithBindings">
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
</div>
</div>
</div>
</div>
</div>
</template>
$SCRIPTS$
</body>
</html>

View File

@ -1,117 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// compiler benchmark in AngularJS 1.x
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
declare var angular: any;
export function main() {
const ngEl = document.createElement('div');
angular.bootstrap(ngEl, ['app']);
}
function loadTemplate(templateId, repeatCount) {
const template = document.querySelectorAll(`#${templateId}`)[0];
const content = (<HTMLElement>template).innerHTML;
let result = '';
for (let i = 0; i < repeatCount; i++) {
result += content;
}
// replace [] binding syntax
result = result.replace(/[\[\]]/g, '');
// Use a DIV as container as Angular 1.3 does not know <template> elements...
const div = document.createElement('div');
div.innerHTML = result;
return div;
}
angular.module('app', [])
.directive('dir0',
[
'$parse',
function($parse) {
return {
compile: function($element, $attrs) {
const expr = $parse($attrs.attr0);
return ($scope) => $scope.$watch(expr, angular.noop);
}
};
}
])
.directive('dir1',
[
'$parse',
function($parse) {
return {
compile: function($element, $attrs) {
const expr = $parse($attrs.attr1);
return ($scope) => $scope.$watch(expr, angular.noop);
}
};
}
])
.directive('dir2',
[
'$parse',
function($parse) {
return {
compile: function($element, $attrs) {
const expr = $parse($attrs.attr2);
return ($scope) => $scope.$watch(expr, angular.noop);
}
};
}
])
.directive('dir3',
[
'$parse',
function($parse) {
return {
compile: function($element, $attrs) {
const expr = $parse($attrs.attr3);
return ($scope) => $scope.$watch(expr, angular.noop);
}
};
}
])
.directive('dir4',
[
'$parse',
function($parse) {
return {
compile: function($element, $attrs) {
const expr = $parse($attrs.attr4);
return ($scope) => $scope.$watch(expr, angular.noop);
}
};
}
])
.run([
'$compile',
function($compile) {
const count = getIntParameter('elements');
const templateNoBindings = loadTemplate('templateNoBindings', count);
const templateWithBindings = loadTemplate('templateWithBindings', count);
bindAction('#compileWithBindings', compileWithBindings);
bindAction('#compileNoBindings', compileNoBindings);
function compileNoBindings() {
// Need to clone every time as the compiler might modify the template!
const cloned = templateNoBindings.cloneNode(true);
$compile(cloned);
}
function compileWithBindings() {
// Need to clone every time as the compiler might modify the template!
const cloned = templateWithBindings.cloneNode(true);
$compile(cloned);
}
}
]);

View File

@ -1,26 +0,0 @@
<!doctype html>
<html>
<body>
<ul>
<li>
<a href="compiler/compiler_benchmark.html">Compiler benchmark</a>
</li>
<li>
<a href="tree/tree_benchmark.html">Tree benchmark</a>
</li>
<li>
<a href="static_tree/tree_benchmark.html">Static tree benchmark</a>
</li>
<li>
<a href="tree/react/index.html">React Tree benchmark</a>
</li>
<li>
<a href="largetable/largetable_benchmark.html">Largetable benchmark</a>
</li>
<li>
<a href="naive_infinite_scroll/index.html">Naive infinite scroll benchmark</a>
</li>
</ul>
</body>
</html>

View File

@ -1,58 +0,0 @@
<ng-switch on="benchmarkType">
<baseline-binding-table ng-switch-when="baselineBinding">
</baseline-binding-table>
<baseline-interpolation-table ng-switch-when="baselineInterpolation">
</baseline-interpolation-table>
<div ng-switch-when="ngBind">
<h2>baseline binding</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row">
<span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|
</span>
</div>
</div>
<div ng-switch-when="ngBindOnce">
<h2>baseline binding once</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in ::row">
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
</span>
</div>
</div>
<div ng-switch-when="interpolation">
<h2>baseline interpolation</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
</div>
</div>
<div ng-switch-when="interpolationAttr">
<h2>attribute interpolation</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
</div>
</div>
<div ng-switch-when="ngBindFn">
<h2>bindings with functions</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row"><span ng-bind="column.iFn()"></span>:<span ng-bind="column.jFn()"></span>|</span>
</div>
</div>
<div ng-switch-when="interpolationFn">
<h2>interpolation with functions</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row">{{column.iFn()}}:{{column.jFn()}}|</span>
</div>
</div>
<div ng-switch-when="ngBindFilter">
<h2>bindings with filter</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row"><span ng-bind="column.i | noop"></span>:<span ng-bind="column.j | noop"></span>|</span>
</div>
</div>
<div ng-switch-when="interpolationFilter">
<h2>interpolation with filter</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row">{{column.i | noop}}:{{column.j | noop}}|</span>
</div>
</div>
</ng-switch>

View File

@ -1,98 +0,0 @@
<!doctype html>
<html>
<body>
<h2>AngularJS/Dart 1.x largetable benchmark</h2>
<form>
<div>
rows:
<input type="number" name="rows" value="100">
columns:
<input type="number" name="columns" value="20">
</div>
<div>
baseline binding:
<input type="radio"
name="benchmarkType"
value="baselineBinding"
id="baselineBinding"
checked>
</div>
<div>
baseline interpolation:
<input type="radio"
name="benchmarkType"
value="baselineInterpolation"
id="baselineInterpolation">
</div>
<div>
ngBind:
<input type="radio"
name="benchmarkType"
value="ngBind"
id="ngBind">
</div>
<div>
ngBindOnce:
<input type="radio"
name="benchmarkType"
value="ngBindOnce"
id="ngBindOnce">
</div>
<div>
interpolation:
<input type="radio"
name="benchmarkType"
value="interpolation"
id="interpolation">
</div>
<div>
attribute interpolation:
<input type="radio"
name="benchmarkType"
value="interpolationAttr"
id="interpolationAttr">
</div>
<div>
ngBind + fnInvocation:
<input type="radio"
name="benchmarkType"
value="ngBindFn"
id="ngBindFn">
</div>
<div>
interpolation + fnInvocation:
<input type="radio"
name="benchmarkType"
value="interpolationFn"
id="interpolationFn">
</div>
<div>
ngBind + filter:
<input type="radio"
name="benchmarkType"
value="ngBindFilter"
id="ngBindFilter">
</div>
<div>
interpolation + filter:
<input type="radio"
name="benchmarkType"
value="interpolationFilter"
id="interpolationFilter">
</div>
<button>Apply</button>
</form>
<p>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</button>
</p>
<div>
<largetable></largetable>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -1,119 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getIntParameter, getStringParameter, bindAction} from '@angular/testing/src/benchmark_util';
declare var angular: any;
const totalRows = getIntParameter('rows');
const totalColumns = getIntParameter('columns');
const benchmarkType = getStringParameter('benchmarkType');
export function main() {
angular.bootstrap(document.querySelector('largetable'), ['app']);
}
angular.module('app', [])
.config(function($compileProvider) {
if ($compileProvider.debugInfoEnabled) {
$compileProvider.debugInfoEnabled(false);
}
})
.filter('noop', function() { return function(input) { return input; }; })
.directive('largetable',
function() {
return {
restrict: 'E',
templateUrl: 'largetable-js-template.html',
controller: 'DataController'
};
})
.controller('DataController',
function($scope) {
bindAction('#destroyDom', destroyDom);
bindAction('#createDom', createDom);
function destroyDom() {
$scope.$apply(function() { $scope.benchmarkType = 'none'; });
}
function createDom() {
$scope.$apply(function() { $scope.benchmarkType = benchmarkType; });
}
const data = $scope.data = [];
function iGetter() { return this.i; }
function jGetter() { return this.j; }
for (let i = 0; i < totalRows; i++) {
data[i] = [];
for (let j = 0; j < totalColumns; j++) {
data[i][j] = {i: i, j: j, iFn: iGetter, jFn: jGetter};
}
}
})
.directive('baselineBindingTable',
function() {
return {
restrict: 'E',
link: function($scope, $element) {
let i, j, row, cell, comment;
const template = document.createElement('span');
template.setAttribute('ng-repeat', 'foo in foos');
template.classList.add('ng-scope');
template.appendChild(document.createElement('span'));
template.appendChild(document.createTextNode(':'));
template.appendChild(document.createElement('span'));
template.appendChild(document.createTextNode('|'));
for (i = 0; i < totalRows; i++) {
row = document.createElement('div');
$element[0].appendChild(row);
for (j = 0; j < totalColumns; j++) {
cell = template.cloneNode(true);
row.appendChild(cell);
cell.childNodes[0].textContent = i;
cell.childNodes[2].textContent = j;
cell.ng3992 = 'xxx';
comment = document.createComment('ngRepeat end: bar in foo');
row.appendChild(comment);
}
comment = document.createComment('ngRepeat end: foo in foos');
$element[0].appendChild(comment);
}
}
};
})
.directive('baselineInterpolationTable', function() {
return {
restrict: 'E',
link: function($scope, $element) {
let i, j, row, cell, comment;
const template = document.createElement('span');
template.setAttribute('ng-repeat', 'foo in foos');
template.classList.add('ng-scope');
for (i = 0; i < totalRows; i++) {
row = document.createElement('div');
$element[0].appendChild(row);
for (j = 0; j < totalColumns; j++) {
cell = template.cloneNode(true);
row.appendChild(cell);
cell.textContent = '' + i + ':' + j + '|';
cell.ng3992 = 'xxx';
comment = document.createComment('ngRepeat end: bar in foo');
row.appendChild(comment);
}
comment = document.createComment('ngRepeat end: foo in foos');
$element[0].appendChild(comment);
}
}
};
});

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>AngularDart Scrolling Benchmark</title>
</head>
<body>
<form>
App size: <input type="text" name="appSize" value="1"><br>
Iteration count: <input type="text" name="iterationCount" value="1"><br>
Scroll increment: <input type="text" name="scrollIncrement" value="1"><br>
</form>
<div>
<button id="run-btn">Run</button>
<button id="reset-btn">Reset</button>
</div>
<scroll-app></scroll-app>
$SCRIPTS$
</body>
</html>

View File

@ -1,13 +0,0 @@
<div>
<div id="scrollDiv"
ng-style="scrollDivStyle"
ng-scroll="onScroll()">
<div ng-style="paddingStyle"></div>
<div ng-style="innerStyle">
<scroll-item
ng-repeat="item in visibleItems"
offering="item">
</scroll-item>
</div>
</div>
</div>

View File

@ -1,45 +0,0 @@
<div ng-style="itemStyle">
<company-name company="offering.company"
cell-width="companyNameWidth">
</company-name>
<opportunity-name opportunity="offering.opportunity"
cell-width="opportunityNameWidth">
</opportunity-name>
<offering-name offering="offering"
cell-width="offeringNameWidth">
</offering-name>
<account-cell account="offering.account"
cell-width="accountCellWidth">
</account-cell>
<formatted-cell value="offering.basePoints"
cell-width="basePointsWidth">
</formatted-cell>
<formatted-cell value="offering.kickerPoints"
cell-width="kickerPointsWidth">
</formatted-cell>
<stage-buttons offering="offering"
cell-width="stageButtonsWidth">
</stage-buttons>
<formatted-cell value="offering.bundles"
cell-width="bundlesWidth">
</formatted-cell>
<formatted-cell value="offering.dueDate"
cell-width="dueDateWidth">
</formatted-cell>
<formatted-cell value="offering.endDate"
cell-width="endDateWidth">
</formatted-cell>
<formatted-cell value="offering.aatStatus"
cell-width="aatStatusWidth">
</formatted-cell>
</div>

View File

@ -1,17 +0,0 @@
<!doctype html>
<html>
<body>
<h2>AngularJS/Dart 1.x static tree benchmark (depth 10)</h2>
<p>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</button>
</p>
<div>
<tree9 data="initData" ng-if="initData != null" class="app"></tree9>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -1,77 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// static tree benchmark in AngularJS 1.x
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
declare var angular: any;
const MAX_DEPTH = 10;
export function main() {
angular.bootstrap(document.querySelector('.app'), ['app']);
}
function addTreeDirective(module, level: number) {
let template;
if (level <= 0) {
template = `<span> {{data.value}}</span>`;
} else {
template = `<span> {{data.value}} <tree${level-1} data='data.right'></tree${level-1}><tree${level-1} data='data.left'></tree${level-1}></span>`;
}
module.directive(`tree${level}`, function() { return {scope: {data: '='}, template: template}; });
}
const module = angular.module('app', []);
for (let depth = 0; depth < MAX_DEPTH; depth++) {
addTreeDirective(module, depth);
}
module.config([
'$compileProvider',
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
])
.run([
'$rootScope',
function($rootScope) {
let count = 0;
$rootScope.initData = null;
bindAction('#destroyDom', destroyDom);
bindAction('#createDom', createDom);
function createData(): TreeNode {
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
return buildTree(MAX_DEPTH, values, 0);
}
function destroyDom() {
$rootScope.$apply(function() { $rootScope.initData = null; });
}
function createDom() {
$rootScope.$apply(function() { $rootScope.initData = createData(); });
}
}
]);
class TreeNode {
value: string;
left: TreeNode;
right: TreeNode;
constructor(value, left, right) {
this.value = value;
this.left = left;
this.right = right;
}
}
function buildTree(maxDepth, values, curDepth) {
if (maxDepth === curDepth) return new TreeNode('', null, null);
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
buildTree(maxDepth, values, curDepth + 1));
}

View File

@ -1,23 +0,0 @@
<!doctype html>
<html>
<head>
</head>
<body>
<h2>Params</h2>
<form>
Depth:
<input type="number" name="depth" placeholder="depth" value="9">
<br>
<button>Apply</button>
</form>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</button>
<h2>React Tree Benchmark</h2>
<root-tree id="rootTree"></root-tree>
$SCRIPTS$
</body>
</html>

View File

@ -1,73 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// tree benchmark in React
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
import * as React from './react.min';
const TreeComponent = React.createClass({
displayName: 'TreeComponent',
render: function() {
const treeNode = this.props.treeNode;
let left = null;
if (treeNode.left) {
left = React.createElement(
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.left}, "")]);
}
let right = null;
if (treeNode.right) {
right = React.createElement(
"span", {}, [React.createElement(TreeComponent, {treeNode: treeNode.right}, "")]);
}
const span = React.createElement("span", {}, [" " + treeNode.value, left, right]);
return (React.createElement("tree", {}, [span]));
}
});
export function main() {
let count = 0;
const maxDepth = getIntParameter('depth');
bindAction('#destroyDom', destroyDom);
bindAction('#createDom', createDom);
const empty = new TreeNode(0, null, null);
const rootComponent = React.render(React.createElement(TreeComponent, {treeNode: empty}, ""),
document.getElementById('rootTree'));
function destroyDom() { rootComponent.setProps({treeNode: empty}); }
function createDom() {
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
rootComponent.setProps({treeNode: buildTree(maxDepth, values, 0)});
}
}
class TreeNode {
value: string;
left: TreeNode;
right: TreeNode;
constructor(value, left, right) {
this.value = value;
this.left = left;
this.right = right;
}
}
function buildTree(maxDepth, values, curDepth) {
if (maxDepth === curDepth) return new TreeNode('', null, null);
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
buildTree(maxDepth, values, curDepth + 1));
}

View File

@ -1,3 +0,0 @@
export var createElement: Function;
export var render: Function;
export var createClass: Function;

View File

@ -1,25 +0,0 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Depth:
<input type="number" name="depth" placeholder="depth" value="9">
<br>
<button>Apply</button>
</form>
<h2>AngularJS/Dart 1.x tree benchmark</h2>
<p>
<button id="destroyDom">destroyDom</button>
<button id="createDom">createDom</button>
</p>
<div>
<tree data="initData"></tree>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -1,107 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// tree benchmark in AngularJS 1.x
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
declare var angular: any;
export function main() {
angular.bootstrap(document.querySelector('tree'), ['app']);
}
angular.module('app', [])
.directive('tree',
function() {
return {
scope: {data: '='},
template: '<span> {{data.value}}' +
' <span tree-if="data.left"></span>' +
' <span tree-if="data.right"></span>' +
'</span>'
};
})
// special directive for "if" as angular 1.3 does not support
// recursive components.
.directive('treeIf',
[
'$compile',
'$parse',
function($compile, $parse) {
const transcludeFn;
return {
compile: function(element, attrs) {
const expr = $parse('!!' + attrs.treeIf);
const template = '<tree data="' + attrs.treeIf + '"></tree>';
let transclude;
return function($scope, $element, $attrs) {
if (!transclude) {
transclude = $compile(template);
}
let childScope;
let childElement;
$scope.$watch(expr, function(newValue) {
if (childScope) {
childScope.$destroy();
childElement.remove();
childScope = null;
childElement = null;
}
if (newValue) {
childScope = $scope.$new();
childElement = transclude(childScope,
function(clone) { $element.append(clone); });
}
});
};
}
};
}
])
.config([
'$compileProvider',
function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
])
.run([
'$rootScope',
function($rootScope) {
let count = 0;
const maxDepth = getIntParameter('depth');
bindAction('#destroyDom', destroyDom);
bindAction('#createDom', createDom);
function destroyDom() {
$rootScope.$apply(function() { $rootScope.initData = new TreeNode('', null, null); });
}
function createDom() {
const values = count++ % 2 == 0 ? ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
$rootScope.$apply(function() { $rootScope.initData = buildTree(maxDepth, values, 0); });
}
}
]);
class TreeNode {
value: string;
left: TreeNode;
right: TreeNode;
constructor(value, left, right) {
this.value = value;
this.left = left;
this.right = right;
}
}
function buildTree(maxDepth, values, curDepth) {
if (maxDepth === curDepth) return new TreeNode('', null, null);
return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
buildTree(maxDepth, values, curDepth + 1));
}

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "9.0.5",
"version": "9.0.6",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@ -78,7 +78,7 @@
"@types/systemjs": "0.19.32",
"@types/yaml": "^1.2.0",
"@types/yargs": "^11.1.1",
"@webcomponents/custom-elements": "^1.0.4",
"@webcomponents/custom-elements": "^1.1.0",
"angular": "npm:angular@1.7",
"angular-1.5": "npm:angular@1.5",
"angular-1.6": "npm:angular@1.6",
@ -147,6 +147,7 @@
"@bazel/bazel": "2.1.0",
"@bazel/buildifier": "^0.29.0",
"@bazel/ibazel": "^0.11.1",
"@octokit/graphql": "^4.3.1",
"@types/minimist": "^1.2.0",
"@yarnpkg/lockfile": "^1.1.0",
"browserstacktunnel-wrapper": "2.0.1",
@ -175,9 +176,11 @@
"rewire": "2.5.2",
"sauce-connect": "https://saucelabs.com/downloads/sc-4.5.1-linux.tar.gz",
"semver": "^6.3.0",
"ts-node": "^8.6.2",
"tslint-eslint-rules": "5.4.0",
"tslint-no-toplevel-property-access": "0.0.2",
"tsutils": "2.27.2",
"typed-graphqlify": "^2.3.0",
"universal-analytics": "0.4.15",
"vlq": "0.2.2",
"vrsource-tslint-rules": "5.1.1"

View File

@ -318,7 +318,12 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
"createExternalSymbolFactoryReexports": (not _is_bazel()),
# FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
# We instruct the compiler to use the host for import generation in Blaze. By default,
# module names between source files of the same compilation unit are relative paths. This
# is not desired in google3 where the generated module names are used as qualified names
# for aliased exports. We disable relative paths and always use manifest paths in google3.
"_useHostForImportGeneration": (not _is_bazel()),
"_useManifestPathsAsModuleName": (not _is_bazel()),
}
if _should_produce_flat_module_outs(ctx):

View File

@ -113,19 +113,17 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
}
}
const expectedOuts = config['angularCompilerOptions']['expectedOut'];
// These are options passed through from the `ng_module` rule which aren't supported
// by the `@angular/compiler-cli` and are only intended for `ngc-wrapped`.
const {expectedOut, _useManifestPathsAsModuleName} = config['angularCompilerOptions'];
const {basePath} = ng.calcProjectFileAndBasePath(project);
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
const tsHost = ts.createCompilerHost(compilerOpts, true);
const {diagnostics} = compile({
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
compilerOpts,
tsHost,
bazelOpts,
files,
inputs,
expectedOuts
useManifestPathsAsModuleName: _useManifestPathsAsModuleName,
expectedOuts: expectedOut, compilerOpts, tsHost, bazelOpts, files, inputs,
});
if (diagnostics.length) {
console.error(ng.formatDiagnostics(diagnostics));
@ -144,9 +142,11 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
return filePath;
}
export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files,
inputs, expectedOuts, gatherDiagnostics, bazelHost}: {
export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModuleName,
compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts,
gatherDiagnostics, bazelHost}: {
allDepsCompiledWithBazel?: boolean,
useManifestPathsAsModuleName?: boolean,
compilerOpts: ng.CompilerOptions,
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
bazelOpts: BazelOptions,
@ -199,13 +199,14 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
throw new Error(`Couldn't find bazel bin in the rootDirs: ${compilerOpts.rootDirs}`);
}
const expectedOutsSet = new Set(expectedOuts.map(p => p.replace(/\\/g, '/')));
const expectedOutsSet = new Set(expectedOuts.map(p => convertToForwardSlashPath(p)));
const originalWriteFile = tsHost.writeFile.bind(tsHost);
tsHost.writeFile =
(fileName: string, content: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
const relative = relativeToRootDirs(fileName.replace(/\\/g, '/'), [compilerOpts.rootDir]);
const relative =
relativeToRootDirs(convertToForwardSlashPath(fileName), [compilerOpts.rootDir]);
if (expectedOutsSet.has(relative)) {
expectedOutsSet.delete(relative);
originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
@ -290,20 +291,32 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
const ngHost = ng.createCompilerHost({options: compilerOpts, tsHost: bazelHost});
const fileNameToModuleNameCache = new Map<string, string>();
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath: string) => {
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath?: string) => {
const cacheKey = `${importedFilePath}:${containingFilePath}`;
// Memoize this lookup to avoid expensive re-parses of the same file
// When run as a worker, the actual ts.SourceFile is cached
// but when we don't run as a worker, there is no cache.
// For one example target in g3, we saw a cache hit rate of 7590/7695
if (fileNameToModuleNameCache.has(importedFilePath)) {
return fileNameToModuleNameCache.get(importedFilePath);
if (fileNameToModuleNameCache.has(cacheKey)) {
return fileNameToModuleNameCache.get(cacheKey);
}
const result = doFileNameToModuleName(importedFilePath);
fileNameToModuleNameCache.set(importedFilePath, result);
const result = doFileNameToModuleName(importedFilePath, containingFilePath);
fileNameToModuleNameCache.set(cacheKey, result);
return result;
};
function doFileNameToModuleName(importedFilePath: string): string {
function doFileNameToModuleName(importedFilePath: string, containingFilePath?: string): string {
const relativeTargetPath =
relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
const manifestTargetPath = `${bazelOpts.workspaceName}/${relativeTargetPath}`;
if (useManifestPathsAsModuleName === true) {
return manifestTargetPath;
}
// Unless manifest paths are explicitly enforced, we initially check if a module name is
// set for the given source file. The compiler host from `@bazel/typescript` sets source
// file module names if the compilation targets either UMD or AMD. To ensure that the AMD
// module names match, we first consider those.
try {
const sourceFile = ngHost.getSourceFile(importedFilePath, ts.ScriptTarget.Latest);
if (sourceFile && sourceFile.moduleName) {
@ -342,11 +355,31 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
ngHost.amdModuleName) {
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
}
const result = relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
if (result.startsWith(NODE_MODULES)) {
return result.substr(NODE_MODULES.length);
// If no AMD module name has been set for the source file by the `@bazel/typescript` compiler
// host, and the target file is not part of a flat module node module package, we use the
// following rules (in order):
// 1. If target file is part of `node_modules/`, we use the package module name.
// 2. If no containing file is specified, or the target file is part of a different
// compilation unit, we use a Bazel manifest path. Relative paths are not possible
// since we don't have a containing file, and the target file could be located in the
// output directory, or in an external Bazel repository.
// 3. If both rules above didn't match, we compute a relative path between the source files
// since they are part of the same compilation unit.
// Note that we don't want to always use (2) because it could mean that compilation outputs
// are always leaking Bazel-specific paths, and the output is not self-contained. This could
// break `esm2015` or `esm5` output for Angular package release output
// Omit the `node_modules` prefix if the module name of an NPM package is requested.
if (relativeTargetPath.startsWith(NODE_MODULES)) {
return relativeTargetPath.substr(NODE_MODULES.length);
} else if (
containingFilePath == null || !bazelOpts.compilationTargetSrc.includes(importedFilePath)) {
return manifestTargetPath;
}
return bazelOpts.workspaceName + '/' + result;
const containingFileDir =
path.dirname(relativeToRootDirs(containingFilePath, compilerOpts.rootDirs));
const relativeImportPath = path.posix.relative(containingFileDir, relativeTargetPath);
return relativeImportPath.startsWith('.') ? relativeImportPath : `./${relativeImportPath}`;
}
ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join(
@ -464,6 +497,10 @@ function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolea
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
}
function convertToForwardSlashPath(filePath: string): string {
return filePath.replace(/\\/g, '/');
}
function gatherDiagnosticsForInputsOnly(
options: ng.CompilerOptions, bazelOpts: BazelOptions,
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {

View File

@ -32,6 +32,7 @@ ng_package(
deps = [
":example",
"//packages/bazel/test/ng_package/example/a11y",
"//packages/bazel/test/ng_package/example/imports",
"//packages/bazel/test/ng_package/example/secondary",
],
)

View File

@ -0,0 +1,12 @@
load("//tools:defaults.bzl", "ng_module")
package(default_visibility = ["//packages/bazel/test:__subpackages__"])
ng_module(
name = "imports",
srcs = glob(["*.ts"]),
module_name = "example/imports",
deps = [
"//packages/core",
],
)

View File

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

View File

@ -0,0 +1,15 @@
/**
* @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 {Injectable} from '@angular/core';
import {MySecondService} from './second';
@Injectable({providedIn: 'root'})
export class MyService {
constructor(public secondService: MySecondService) {}
}

View File

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
@Injectable({providedIn: 'root'})
export class MySecondService {
}

View File

@ -13,6 +13,10 @@ bundles
bundles/waffels-a11y.umd.js.map
bundles/waffels-a11y.umd.min.js
bundles/waffels-a11y.umd.min.js.map
bundles/waffels-imports.umd.js
bundles/waffels-imports.umd.js.map
bundles/waffels-imports.umd.min.js
bundles/waffels-imports.umd.min.js.map
bundles/waffels-secondary.umd.js
bundles/waffels-secondary.umd.js.map
bundles/waffels-secondary.umd.min.js
@ -29,6 +33,12 @@ esm2015
esm2015/a11y/public-api.js
esm2015/example.externs.js
esm2015/example.js
esm2015/imports
esm2015/imports/imports.externs.js
esm2015/imports/imports.js
esm2015/imports/index.js
esm2015/imports/public-api.js
esm2015/imports/second.js
esm2015/index.js
esm2015/mymodule.js
esm2015/secondary
@ -42,6 +52,11 @@ esm5
esm5/a11y/index.js
esm5/a11y/public-api.js
esm5/example.js
esm5/imports
esm5/imports/imports.js
esm5/imports/index.js
esm5/imports/public-api.js
esm5/imports/second.js
esm5/index.js
esm5/mymodule.js
esm5/secondary
@ -54,6 +69,8 @@ extra-styles.css
fesm2015
fesm2015/a11y.js
fesm2015/a11y.js.map
fesm2015/imports.js
fesm2015/imports.js.map
fesm2015/secondary.js
fesm2015/secondary.js.map
fesm2015/waffels.js
@ -61,10 +78,18 @@ fesm2015
fesm5
fesm5/a11y.js
fesm5/a11y.js.map
fesm5/imports.js
fesm5/imports.js.map
fesm5/secondary.js
fesm5/secondary.js.map
fesm5/waffels.js
fesm5/waffels.js.map
imports
imports/imports.d.ts
imports/imports.metadata.json
imports/package.json
imports.d.ts
imports.metadata.json
logo.png
package.json
secondary
@ -444,6 +469,291 @@ var o=function n(e,t,o,r){var f,c=arguments.length,l=c<3?t:null===r?r=Object.get
* found in the LICENSE file at https://angular.io/license
*/e.A11yModule=o,Object.defineProperty(e,"__esModule",{value:!0})}));
--- bundles/waffels-imports.umd.js ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('example/imports', ['exports', '@angular/core'], factory) :
(global = global || self, factory((global.example = global.example || {}, global.example.imports = {}), global.ng.core));
}(this, (function (exports, i0) { 'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __exportStar(m, exports) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
function __values(o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
function __spreadArrays() {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}
function __asyncDelegator(o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
function __makeTemplateObject(cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};
function __importStar(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result.default = mod;
return result;
}
function __importDefault(mod) {
return (mod && mod.__esModule) ? mod : { default: mod };
}
var MySecondService = /** @class */ (function () {
function MySecondService() {
}
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
MySecondService = __decorate([
i0.Injectable({ providedIn: 'root' })
], MySecondService);
return MySecondService;
}());
var MyService = /** @class */ (function () {
function MyService(secondService) {
this.secondService = secondService;
}
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
MyService = __decorate([
i0.Injectable({ providedIn: 'root' }),
__metadata("design:paramtypes", [MySecondService])
], MyService);
return MyService;
}());
/**
* @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
*/
/**
* Generated bundle index. Do not edit.
*/
exports.MyService = MyService;
exports.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a = MySecondService;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=waffels-imports.umd.js.map
--- bundles/waffels-imports.umd.min.js ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@angular/core")):"function"==typeof define&&define.amd?define("example/imports",["exports","@angular/core"],t):t(((e=e||self).example=e.example||{},e.example.imports={}),e.ng.core)}(this,(function(e,t){"use strict";
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */function n(e,t,n,o){var r,c=arguments.length,f=c<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,n):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)f=Reflect.decorate(e,t,n,o);else for(var i=e.length-1;i>=0;i--)(r=e[i])&&(f=(c<3?r(f):c>3?r(t,n,f):r(t,n))||f);return c>3&&f&&Object.defineProperty(t,n,f),f}function o(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}var r=function(){function e(){}return e.ɵprov=t.ɵɵdefineInjectable({factory:function t(){return new e},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"})],e)}(),c=function(){function e(e){this.secondService=e}return e.ɵprov=t.ɵɵdefineInjectable({factory:function n(){return new e(t.ɵɵinject(r))},token:e,providedIn:"root"}),e=n([t.Injectable({providedIn:"root"}),o("design:paramtypes",[r])],e)}();
/**
* @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
*/
e.MyService=c,e.ɵangular_packages_bazel_test_ng_package_example_imports_imports_a=r,Object.defineProperty(e,"__esModule",{value:!0})}));
--- bundles/waffels-secondary.umd.js ---
/**
@ -1092,6 +1402,106 @@ A11yModule.decorators = [
export * from './index';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
--- esm2015/imports/imports.externs.js ---
/** @externs */
/**
* @externs
* @suppress {duplicate,checkTypes}
*/
// NOTE: generated by tsickle, do not edit.
--- esm2015/imports/imports.js ---
/**
* Generated bundle index. Do not edit.
*/
export * from './index';
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
--- esm2015/imports/index.js ---
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export { MyService } from './public-api';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQVFBLDBCQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
--- esm2015/imports/public-api.js ---
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @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 { Injectable } from '@angular/core';
import { MySecondService } from './second';
import * as i0 from "@angular/core";
import * as i1 from "./second";
export class MyService {
/**
* @param {?} secondService
*/
constructor(secondService) {
this.secondService = secondService;
}
}
MyService.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
MyService.ctorParameters = () => [
{ type: MySecondService }
];
/** @nocollapse */ MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
if (false) {
/** @type {?} */
MyService.prototype.secondService;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFRQSxPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxVQUFVLENBQUM7OztBQUd6QyxNQUFNLE9BQU8sU0FBUzs7OztJQUNwQixZQUFtQixhQUE4QjtRQUE5QixrQkFBYSxHQUFiLGFBQWEsQ0FBaUI7SUFBRyxDQUFDOzs7WUFGdEQsVUFBVSxTQUFDLEVBQUMsVUFBVSxFQUFFLE1BQU0sRUFBQzs7OztZQUZ4QixlQUFlOzs7OztJQUlULGtDQUFxQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
--- esm2015/imports/second.js ---
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* @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 { Injectable } from '@angular/core';
import * as i0 from "@angular/core";
export class MySecondService {
}
MySecondService.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */ MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBUUEsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQzs7QUFHekMsTUFBTSxPQUFPLGVBQWU7OztZQUQzQixVQUFVLFNBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5pbXBvcnQge0luamVjdGFibGV9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5ASW5qZWN0YWJsZSh7cHJvdmlkZWRJbjogJ3Jvb3QnfSlcbmV4cG9ydCBjbGFzcyBNeVNlY29uZFNlcnZpY2Uge1xufVxuIl19
--- esm2015/index.js ---
/**
@ -1240,6 +1650,79 @@ export { A11yModule };
export * from './index';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2V4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLFNBQVMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9pbmRleCc7XG4iXX0=
--- esm5/imports/imports.js ---
/**
* Generated bundle index. Do not edit.
*/
export * from './index';
export { MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a } from './second';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1wb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvaW1wb3J0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsU0FBUyxDQUFDO0FBRXhCLE9BQU8sRUFBQyxlQUFlLElBQUksaUVBQWlFLEVBQUMsTUFBTSxVQUFVLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vaW5kZXgnO1xuXG5leHBvcnQge015U2Vjb25kU2VydmljZSBhcyDJtWFuZ3VsYXJfcGFja2FnZXNfYmF6ZWxfdGVzdF9uZ19wYWNrYWdlX2V4YW1wbGVfaW1wb3J0c19pbXBvcnRzX2F9IGZyb20gJy4vc2Vjb25kJzsiXX0=
--- esm5/imports/index.js ---
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './public-api';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wYWNrYWdlcy9iYXplbC90ZXN0L25nX3BhY2thZ2UvZXhhbXBsZS9pbXBvcnRzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBJbmMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
--- esm5/imports/public-api.js ---
import { __decorate, __metadata } from "tslib";
/**
* @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 { Injectable } from '@angular/core';
import { MySecondService } from './second';
import * as i0 from "@angular/core";
import * as i1 from "./second";
var MyService = /** @class */ (function () {
function MyService(secondService) {
this.secondService = secondService;
}
MyService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(i0.ɵɵinject(i1.MySecondService)); }, token: MyService, providedIn: "root" });
MyService = __decorate([
Injectable({ providedIn: 'root' }),
__metadata("design:paramtypes", [MySecondService])
], MyService);
return MyService;
}());
export { MyService };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2JhemVsL3Rlc3QvbmdfcGFja2FnZS9leGFtcGxlL2ltcG9ydHMvcHVibGljLWFwaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFDLFVBQVUsRUFBQyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sVUFBVSxDQUFDOzs7QUFHekM7SUFDRSxtQkFBbUIsYUFBOEI7UUFBOUIsa0JBQWEsR0FBYixhQUFhLENBQWlCO0lBQUcsQ0FBQzs7SUFEMUMsU0FBUztRQURyQixVQUFVLENBQUMsRUFBQyxVQUFVLEVBQUUsTUFBTSxFQUFDLENBQUM7eUNBRUcsZUFBZTtPQUR0QyxTQUFTLENBRXJCO29CQWREO0NBY0MsQUFGRCxJQUVDO1NBRlksU0FBUyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7TXlTZWNvbmRTZXJ2aWNlfSBmcm9tICcuL3NlY29uZCc7XG5cbkBJbmplY3RhYmxlKHtwcm92aWRlZEluOiAncm9vdCd9KVxuZXhwb3J0IGNsYXNzIE15U2VydmljZSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBzZWNvbmRTZXJ2aWNlOiBNeVNlY29uZFNlcnZpY2UpIHt9XG59XG4iXX0=
--- esm5/imports/second.js ---
import { __decorate } from "tslib";
/**
* @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 { Injectable } from '@angular/core';
import * as i0 from "@angular/core";
var MySecondService = /** @class */ (function () {
function MySecondService() {
}
MySecondService.ɵprov = i0.ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
MySecondService = __decorate([
Injectable({ providedIn: 'root' })
], MySecondService);
return MySecondService;
}());
export { MySecondService };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vjb25kLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvYmF6ZWwvdGVzdC9uZ19wYWNrYWdlL2V4YW1wbGUvaW1wb3J0cy9zZWNvbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxVQUFVLEVBQUMsTUFBTSxlQUFlLENBQUM7O0FBR3pDO0lBQUE7S0FDQzs7SUFEWSxlQUFlO1FBRDNCLFVBQVUsQ0FBQyxFQUFDLFVBQVUsRUFBRSxNQUFNLEVBQUMsQ0FBQztPQUNwQixlQUFlLENBQzNCOzBCQVpEO0NBWUMsQUFERCxJQUNDO1NBRFksZUFBZSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZSBsaWNlbnNlIHRoYXQgY2FuIGJlXG4gKiBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIGF0IGh0dHBzOi8vYW5ndWxhci5pby9saWNlbnNlXG4gKi9cblxuaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuQEluamVjdGFibGUoe3Byb3ZpZGVkSW46ICdyb290J30pXG5leHBvcnQgY2xhc3MgTXlTZWNvbmRTZXJ2aWNlIHtcbn1cbiJdfQ==
--- esm5/index.js ---
/**
@ -1379,6 +1862,68 @@ export { A11yModule };
//# sourceMappingURL=a11y.js.map
--- fesm2015/imports.js ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
import { Injectable, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/second.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class MySecondService {
}
MySecondService.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */ MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/public-api.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
class MyService {
/**
* @param {?} secondService
*/
constructor(secondService) {
this.secondService = secondService;
}
}
MyService.decorators = [
{ type: Injectable, args: [{ providedIn: 'root' },] }
];
/** @nocollapse */
MyService.ctorParameters = () => [
{ type: MySecondService }
];
/** @nocollapse */ MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
if (false) {
/** @type {?} */
MyService.prototype.secondService;
}
/**
* @fileoverview added by tsickle
* Generated from: packages/bazel/test/ng_package/example/imports/index.ts
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
/**
* Generated bundle index. Do not edit.
*/
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
//# sourceMappingURL=imports.js.map
--- fesm2015/secondary.js ---
/**
@ -1494,6 +2039,55 @@ export { A11yModule };
//# sourceMappingURL=a11y.js.map
--- fesm5/imports.js ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
import { __decorate, __metadata } from 'tslib';
import { ɵɵdefineInjectable, Injectable, ɵɵinject } from '@angular/core';
var MySecondService = /** @class */ (function () {
function MySecondService() {
}
MySecondService.ɵprov = ɵɵdefineInjectable({ factory: function MySecondService_Factory() { return new MySecondService(); }, token: MySecondService, providedIn: "root" });
MySecondService = __decorate([
Injectable({ providedIn: 'root' })
], MySecondService);
return MySecondService;
}());
var MyService = /** @class */ (function () {
function MyService(secondService) {
this.secondService = secondService;
}
MyService.ɵprov = ɵɵdefineInjectable({ factory: function MyService_Factory() { return new MyService(ɵɵinject(MySecondService)); }, token: MyService, providedIn: "root" });
MyService = __decorate([
Injectable({ providedIn: 'root' }),
__metadata("design:paramtypes", [MySecondService])
], MyService);
return MyService;
}());
/**
* @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
*/
/**
* Generated bundle index. Do not edit.
*/
export { MyService, MySecondService as ɵangular_packages_bazel_test_ng_package_example_imports_imports_a };
//# sourceMappingURL=imports.js.map
--- fesm5/secondary.js ---
/**
@ -1581,6 +2175,61 @@ export { MyModule };
//# sourceMappingURL=waffels.js.map
--- imports/imports.d.ts ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
export declare class MyService {
secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a;
constructor(secondService: ɵangular_packages_bazel_test_ng_package_example_imports_imports_a);
}
export declare class ɵangular_packages_bazel_test_ng_package_example_imports_imports_a {
}
export { }
--- imports/imports.metadata.json ---
{"__symbolic":"module","version":4,"metadata":{"MyService":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":11,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","name":"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a"}]}]},"statics":{"ɵprov":{}}},"ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Injectable","line":10,"character":1},"arguments":[{"providedIn":"root"}]}],"members":{},"statics":{"ɵprov":{}}}},"origins":{"MyService":"./imports","ɵangular_packages_bazel_test_ng_package_example_imports_imports_a":"./imports"},"importAs":"example/imports"}
--- imports/package.json ---
{
"name": "example/imports",
"main": "../bundles/example-imports.umd.js",
"fesm5": "../fesm5/imports.js",
"fesm2015": "../fesm2015/imports.js",
"esm5": "../esm5/imports/imports.js",
"esm2015": "../esm2015/imports/imports.js",
"typings": "./imports.d.ts",
"module": "../fesm5/imports.js",
"es2015": "../fesm2015/imports.js"
}
--- imports.d.ts ---
/**
* @license Angular v0.0.0
* (c) 2010-2020 Google LLC. https://angular.io/
* License: MIT
*/
export * from './imports/imports';
--- imports.metadata.json ---
{"__symbolic":"module","version":3,"metadata":{},"exports":[{"from":"./imports/imports"}],"flatModuleIndexRedirect":true,"importAs":"example/imports"}
--- logo.png ---
9db278d630f5fabd8e7ba16c2e329a3a

View File

@ -9,8 +9,9 @@ import {AbsoluteFsPath, FileSystem, join, resolve} from '../../../src/ngtsc/file
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
import {Logger} from '../logging/logger';
import {NgccConfiguration} from '../packages/configuration';
import {EntryPoint, getEntryPointInfo} from '../packages/entry_point';
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
import {PathMappings} from '../utils';
import {NGCC_DIRECTORY} from '../writing/new_entry_point_file_writer';
import {EntryPointFinder} from './interface';
import {getBasePaths} from './utils';
@ -40,10 +41,24 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
* @param sourceDirectory An absolute path to the root directory where searching begins.
*/
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
const entryPoints = this.getEntryPointsForPackage(sourceDirectory);
if (entryPoints === null) {
return [];
}
if (entryPoints.length > 0) {
// The `sourceDirectory` is an entry-point itself so no need to search its sub-directories.
// The `sourceDirectory` is an entry point itself so no need to search its sub-directories.
// Also check for any nested node_modules in this package but only if it was compiled by
// Angular.
// It is unlikely that a non Angular entry point has a dependency on an Angular library.
if (entryPoints.some(e => e.compiledByAngular)) {
const nestedNodeModulesPath = this.fs.join(sourceDirectory, 'node_modules');
if (this.fs.exists(nestedNodeModulesPath)) {
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
}
}
return entryPoints;
}
@ -52,23 +67,16 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
// Not interested in hidden files
.filter(p => !p.startsWith('.'))
// Ignore node_modules
.filter(p => p !== 'node_modules')
.filter(p => p !== 'node_modules' && p !== NGCC_DIRECTORY)
// Only interested in directories (and only those that are not symlinks)
.filter(p => {
const stat = this.fs.lstat(resolve(sourceDirectory, p));
return stat.isDirectory() && !stat.isSymbolicLink();
})
.forEach(p => {
// Either the directory is a potential package or a namespace containing packages (e.g
// `@angular`).
// Package is a potential namespace containing packages (e.g `@angular`).
const packagePath = join(sourceDirectory, p);
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
// Also check for any nested node_modules in this package
const nestedNodeModulesPath = join(packagePath, 'node_modules');
if (this.fs.exists(nestedNodeModulesPath)) {
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
}
});
return entryPoints;
}
@ -76,9 +84,9 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
/**
* Recurse the folder structure looking for all the entry points
* @param packagePath The absolute path to an npm package that may contain entry points
* @returns An array of entry points that were discovered.
* @returns An array of entry points that were discovered or null when it's not a valid entrypoint
*/
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
private getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[]|null {
const entryPoints: EntryPoint[] = [];
// Try to get an entry point from the top level package directory
@ -86,20 +94,30 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, packagePath);
// If there is no primary entry-point then exit
if (topLevelEntryPoint === null) {
if (topLevelEntryPoint === NO_ENTRY_POINT) {
return [];
}
if (topLevelEntryPoint === INVALID_ENTRY_POINT) {
return null;
}
// Otherwise store it and search for secondary entry-points
entryPoints.push(topLevelEntryPoint);
this.walkDirectory(packagePath, packagePath, (path, isDirectory) => {
if (!path.endsWith('.js') && !isDirectory) {
return false;
}
// If the path is a JS file then strip its extension and see if we can match an entry-point.
const possibleEntryPointPath = isDirectory ? path : stripJsExtension(path);
const subEntryPoint =
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, possibleEntryPointPath);
if (subEntryPoint !== null) {
entryPoints.push(subEntryPoint);
if (subEntryPoint === NO_ENTRY_POINT || subEntryPoint === INVALID_ENTRY_POINT) {
return false;
}
entryPoints.push(subEntryPoint);
return true;
});
return entryPoints;
@ -113,26 +131,25 @@ export class DirectoryWalkerEntryPointFinder implements EntryPointFinder {
*/
private walkDirectory(
packagePath: AbsoluteFsPath, dir: AbsoluteFsPath,
fn: (path: AbsoluteFsPath, isDirectory: boolean) => void) {
fn: (path: AbsoluteFsPath, isDirectory: boolean) => boolean) {
return this.fs
.readdir(dir)
// Not interested in hidden files
.filter(path => !path.startsWith('.'))
// Ignore node_modules
.filter(path => path !== 'node_modules')
.map(path => resolve(dir, path))
.filter(path => path !== 'node_modules' && path !== NGCC_DIRECTORY)
.forEach(path => {
const stat = this.fs.lstat(path);
const absolutePath = resolve(dir, path);
const stat = this.fs.lstat(absolutePath);
if (stat.isSymbolicLink()) {
// We are not interested in symbolic links
return;
}
fn(path, stat.isDirectory());
if (stat.isDirectory()) {
this.walkDirectory(packagePath, path, fn);
const containsEntryPoint = fn(absolutePath, stat.isDirectory());
if (containsEntryPoint) {
this.walkDirectory(packagePath, absolutePath, fn);
}
});
}

View File

@ -10,7 +10,7 @@ import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/depende
import {Logger} from '../logging/logger';
import {hasBeenProcessed} from '../packages/build_marker';
import {NgccConfiguration} from '../packages/configuration';
import {EntryPoint, EntryPointJsonProperty, getEntryPointInfo} from '../packages/entry_point';
import {EntryPoint, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../packages/entry_point';
import {PathMappings} from '../utils';
import {EntryPointFinder} from './interface';
import {getBasePaths} from './utils';
@ -78,20 +78,26 @@ export class TargetedEntryPointFinder implements EntryPointFinder {
private processNextPath(): void {
const path = this.unprocessedPaths.shift() !;
const entryPoint = this.getEntryPoint(path);
if (entryPoint !== null && entryPoint.compiledByAngular) {
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
const deps = this.resolver.getEntryPointDependencies(entryPoint);
deps.dependencies.forEach(dep => {
if (!this.unsortedEntryPoints.has(dep)) {
this.unprocessedPaths.push(dep);
}
});
if (entryPoint === null || !entryPoint.compiledByAngular) {
return;
}
this.unsortedEntryPoints.set(entryPoint.path, entryPoint);
const deps = this.resolver.getEntryPointDependencies(entryPoint);
deps.dependencies.forEach(dep => {
if (!this.unsortedEntryPoints.has(dep)) {
this.unprocessedPaths.push(dep);
}
});
}
private getEntryPoint(entryPointPath: AbsoluteFsPath): EntryPoint|null {
const packagePath = this.computePackagePath(entryPointPath);
return getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
const entryPoint =
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
if (entryPoint === NO_ENTRY_POINT || entryPoint === INVALID_ENTRY_POINT) {
return null;
}
return entryPoint;
}
/**

View File

@ -75,41 +75,77 @@ export type EntryPointJsonProperty = Exclude<PackageJsonFormatProperties, 'types
export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
['fesm2015', 'fesm5', 'es2015', 'esm2015', 'esm5', 'main', 'module'];
/**
* The path does not represent an entry-point:
* * there is no package.json at the path and there is no config to force an entry-point
* * or the entrypoint is `ignored` by a config.
*/
export const NO_ENTRY_POINT = 'no-entry-point';
/**
* The path has a package.json, but it is not a valid entry-point for ngcc processing.
*/
export const INVALID_ENTRY_POINT = 'invalid-entry-point';
/**
* The result of calling `getEntryPointInfo()`.
*
* This will be an `EntryPoint` object if an Angular entry-point was identified;
* Otherwise it will be a flag indicating one of:
* * NO_ENTRY_POINT - the path is not an entry-point or ngcc is configured to ignore it
* * INVALID_ENTRY_POINT - the path was a non-processable entry-point that should be searched
* for sub-entry-points
*/
export type GetEntryPointResult = EntryPoint | typeof INVALID_ENTRY_POINT | typeof NO_ENTRY_POINT;
/**
* Try to create an entry-point from the given paths and properties.
*
* @param packagePath the absolute path to the containing npm package
* @param entryPointPath the absolute path to the potential entry-point.
* @returns An entry-point if it is valid, `null` otherwise.
* @returns
* - An entry-point if it is valid.
* - `undefined` when there is no package.json at the path and there is no config to force an
* entry-point or the entrypoint is `ignored`.
* - `null` there is a package.json but it is not a valid Angular compiled entry-point.
*/
export function getEntryPointInfo(
fs: FileSystem, config: NgccConfiguration, logger: Logger, packagePath: AbsoluteFsPath,
entryPointPath: AbsoluteFsPath): EntryPoint|null {
entryPointPath: AbsoluteFsPath): GetEntryPointResult {
const packageJsonPath = resolve(entryPointPath, 'package.json');
const packageVersion = getPackageVersion(fs, packageJsonPath);
const entryPointConfig =
config.getConfig(packagePath, packageVersion).entryPoints[entryPointPath];
if (entryPointConfig === undefined && !fs.exists(packageJsonPath)) {
return null;
const hasConfig = entryPointConfig !== undefined;
if (!hasConfig && !fs.exists(packageJsonPath)) {
// No package.json and no config
return NO_ENTRY_POINT;
}
if (entryPointConfig !== undefined && entryPointConfig.ignore === true) {
return null;
if (hasConfig && entryPointConfig.ignore === true) {
// Explicitly ignored
return NO_ENTRY_POINT;
}
const loadedEntryPointPackageJson =
loadEntryPointPackage(fs, logger, packageJsonPath, entryPointConfig !== undefined);
const entryPointPackageJson = mergeConfigAndPackageJson(
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath);
const loadedEntryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath, hasConfig);
const entryPointPackageJson = hasConfig ?
mergeConfigAndPackageJson(
loadedEntryPointPackageJson, entryPointConfig, packagePath, entryPointPath) :
loadedEntryPointPackageJson;
if (entryPointPackageJson === null) {
return null;
// package.json exists but could not be parsed and there was no redeeming config
return INVALID_ENTRY_POINT;
}
// We must have a typings property
const typings = entryPointPackageJson.typings || entryPointPackageJson.types ||
guessTypingsFromPackageJson(fs, entryPointPath, entryPointPackageJson);
if (typeof typings !== 'string') {
return null;
// Missing the required `typings` property
return INVALID_ENTRY_POINT;
}
// An entry-point is assumed to be compiled by Angular if there is either:
@ -198,22 +234,13 @@ function isUmdModule(fs: FileSystem, sourceFilePath: AbsoluteFsPath): boolean {
}
function mergeConfigAndPackageJson(
entryPointPackageJson: EntryPointPackageJson | null,
entryPointConfig: NgccEntryPointConfig | undefined, packagePath: AbsoluteFsPath,
entryPointPath: AbsoluteFsPath): EntryPointPackageJson|null {
entryPointPackageJson: EntryPointPackageJson | null, entryPointConfig: NgccEntryPointConfig,
packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPointPackageJson {
if (entryPointPackageJson !== null) {
if (entryPointConfig === undefined) {
return entryPointPackageJson;
} else {
return {...entryPointPackageJson, ...entryPointConfig.override};
}
return {...entryPointPackageJson, ...entryPointConfig.override};
} else {
if (entryPointConfig === undefined) {
return null;
} else {
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
return {name, ...entryPointConfig.override};
}
const name = `${basename(packagePath)}/${relative(packagePath, entryPointPath)}`;
return {name, ...entryPointConfig.override};
}
}

View File

@ -136,6 +136,71 @@ runInEachFileSystem(() => {
]);
});
it('should handle try to process nested node_modules of non Angular packages', () => {
const basePath = _Abs('/nested_node_modules/node_modules');
loadTestFiles([
...createPackage(basePath, 'outer', ['inner'], false),
...createPackage(_Abs(`${basePath}/outer/node_modules`), 'inner', undefined, false),
]);
const finder = new DirectoryWalkerEntryPointFinder(
fs, config, logger, resolver, _Abs('/nested_node_modules/node_modules'), undefined);
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
const {entryPoints} = finder.findEntryPoints();
expect(spy.calls.allArgs()).toEqual([
[_Abs(basePath)],
[_Abs(`${basePath}/outer`)],
]);
expect(entryPoints).toEqual([]);
});
it('should not try to process deeply nested folders of non TypeScript packages', () => {
const basePath = _Abs('/namespaced/node_modules');
loadTestFiles([
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
{
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
contents: 'index',
},
]);
const finder =
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
const {entryPoints} = finder.findEntryPoints();
expect(spy.calls.allArgs()).toEqual([
[_Abs(basePath)],
[_Abs(`${basePath}/@schematics`)],
[_Abs(`${basePath}/@schematics/angular`)],
]);
expect(entryPoints).toEqual([]);
});
it('should not try to process nested node_modules of non TypeScript packages', () => {
const basePath = _Abs('/namespaced/node_modules');
loadTestFiles([
...createNonTsPackage(_Abs(`${basePath}/@schematics`), 'angular'),
...createNonTsPackage(_Abs(`${basePath}/@schematics/angular/node_modules`), 'test'),
{
name: _Abs(`${basePath}/@schematics/angular/src/nested/index.js`),
contents: 'index',
},
]);
const finder =
new DirectoryWalkerEntryPointFinder(fs, config, logger, resolver, basePath, undefined);
const spy = spyOn(finder, 'walkDirectoryForEntryPoints').and.callThrough();
const {entryPoints} = finder.findEntryPoints();
expect(spy.calls.allArgs()).toEqual([
[_Abs(basePath)],
[_Abs(`${basePath}/@schematics`)],
[_Abs(`${basePath}/@schematics/angular`)],
]);
expect(entryPoints).toEqual([]);
});
it('should handle dependencies via pathMappings', () => {
const basePath = _Abs('/path_mapped/node_modules');
@ -195,8 +260,9 @@ runInEachFileSystem(() => {
});
function createPackage(
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
return [
basePath: AbsoluteFsPath, packageName: string, deps: string[] = [],
isCompiledByAngular = true): TestFile[] {
const files: TestFile[] = [
{
name: _Abs(`${basePath}/${packageName}/package.json`),
contents: JSON.stringify({
@ -205,8 +271,29 @@ runInEachFileSystem(() => {
})
},
{
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),
contents: deps.map((dep, i) => `import * as i${i} from '${dep}';`).join('\n'),
},
];
if (isCompiledByAngular) {
files.push({
name: _Abs(`${basePath}/${packageName}/${packageName}.metadata.json`),
contents: 'metadata info'
});
}
return files;
}
function createNonTsPackage(
basePath: AbsoluteFsPath, packageName: string, deps: string[] = []): TestFile[] {
return [
{
name: _Abs(`${basePath}/${packageName}/package.json`),
contents: JSON.stringify({
fesm2015: `./fesm2015/${packageName}.js`,
})
},
{
name: _Abs(`${basePath}/${packageName}/fesm2015/${packageName}.js`),

View File

@ -10,7 +10,7 @@ import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem} from '../../../
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {NgccConfiguration} from '../../src/packages/configuration';
import {EntryPoint, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
import {EntryPoint, INVALID_ENTRY_POINT, NO_ENTRY_POINT, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat, getEntryPointInfo} from '../../src/packages/entry_point';
import {MockLogger} from '../helpers/mock_logger';
runInEachFileSystem(() => {
@ -55,7 +55,7 @@ runInEachFileSystem(() => {
});
});
it('should return null if configured to ignore the specified entry-point', () => {
it('should return `NO_ENTRY_POINT` if configured to ignore the specified entry-point', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/valid_entry_point/package.json'),
@ -75,7 +75,7 @@ runInEachFileSystem(() => {
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point'));
expect(entryPoint).toBe(null);
expect(entryPoint).toBe(NO_ENTRY_POINT);
});
it('should override the properties on package.json if the entry-point is configured', () => {
@ -116,7 +116,7 @@ runInEachFileSystem(() => {
});
});
it('should return null if there is no package.json at the entry-point path', () => {
it('should return `NO_ENTRY_POINT` if there is no package.json at the entry-point path', () => {
loadTestFiles([
{
name: _(
@ -128,7 +128,7 @@ runInEachFileSystem(() => {
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_package_json'));
expect(entryPoint).toBe(null);
expect(entryPoint).toBe(NO_ENTRY_POINT);
});
it('should return a configured entry-point if there is no package.json at the entry-point path',
@ -165,26 +165,27 @@ runInEachFileSystem(() => {
});
it('should return null if there is no typings or types field in the package.json', () => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_typings/package.json'),
contents: createPackageJson('missing_typings', {excludes: ['typings']})
},
{
name:
_('/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_typings'));
expect(entryPoint).toBe(null);
});
it('should return `INVALID_ENTRY_POINT` if there is no typings or types field in the package.json',
() => {
loadTestFiles([
{
name: _('/project/node_modules/some_package/missing_typings/package.json'),
contents: createPackageJson('missing_typings', {excludes: ['typings']})
},
{
name: _(
'/project/node_modules/some_package/missing_typings/missing_typings.metadata.json'),
contents: 'some meta data'
},
]);
const config = new NgccConfiguration(fs, _('/project'));
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/missing_typings'));
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
});
it('should return null if the typings or types field is not a string in the package.json',
it('should return `INVALID_ENTRY_POINT` if the typings or types field is not a string in the package.json',
() => {
loadTestFiles([
{
@ -201,7 +202,7 @@ runInEachFileSystem(() => {
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/typings_array'));
expect(entryPoint).toBe(null);
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
});
for (let prop of SUPPORTED_FORMAT_PROPERTIES) {
@ -358,7 +359,7 @@ runInEachFileSystem(() => {
});
});
it('should return null if the package.json is not valid JSON', () => {
it('should return `INVALID_ENTRY_POINT` if the package.json is not valid JSON', () => {
loadTestFiles([
// package.json might not be a valid JSON
// for example, @schematics/angular contains a package.json blueprint
@ -372,7 +373,7 @@ runInEachFileSystem(() => {
const entryPoint = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/unexpected_symbols'));
expect(entryPoint).toBe(null);
expect(entryPoint).toBe(INVALID_ENTRY_POINT);
});
});
@ -391,9 +392,13 @@ runInEachFileSystem(() => {
contents: createPackageJson('valid_entry_point')
}]);
const config = new NgccConfiguration(fs, _('/project'));
entryPoint = getEntryPointInfo(
const result = getEntryPointInfo(
fs, config, new MockLogger(), SOME_PACKAGE,
_('/project/node_modules/some_package/valid_entry_point')) !;
_('/project/node_modules/some_package/valid_entry_point'));
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
return fail(`Expected an entry point but got ${result}`);
}
entryPoint = result;
});
it('should return `esm2015` format for `fesm2015` property',

View File

@ -9,7 +9,7 @@ import {FileSystem, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_s
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {loadTestFiles} from '../../../test/helpers';
import {NgccConfiguration} from '../../src/packages/configuration';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, INVALID_ENTRY_POINT, NO_ENTRY_POINT, getEntryPointInfo} from '../../src/packages/entry_point';
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
import {FileWriter} from '../../src/writing/file_writer';
import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer';
@ -103,8 +103,12 @@ runInEachFileSystem(() => {
fs = getFileSystem();
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
const config = new NgccConfiguration(fs, _('/'));
entryPoint = getEntryPointInfo(
const result = getEntryPointInfo(
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
return fail(`Expected an entry point but got ${result}`);
}
entryPoint = result;
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
});
@ -239,8 +243,12 @@ runInEachFileSystem(() => {
fs = getFileSystem();
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
const config = new NgccConfiguration(fs, _('/'));
entryPoint = getEntryPointInfo(
const result = getEntryPointInfo(
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
return fail(`Expected an entry point but got ${result}`);
}
entryPoint = result;
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
});
@ -364,8 +372,12 @@ runInEachFileSystem(() => {
fs = getFileSystem();
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
const config = new NgccConfiguration(fs, _('/'));
entryPoint = getEntryPointInfo(
const result = getEntryPointInfo(
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
return fail(`Expected an entry point but got ${result}`);
}
entryPoint = result;
esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
});

View File

@ -39,7 +39,7 @@ ts_library(
ts_library(
name = "api",
srcs = ["api.ts"],
srcs = glob(["api/**/*.ts"]),
deps = [
"@npm//typescript",
],

View File

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

View File

@ -0,0 +1,65 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/**
* A host backed by a build system which has a unified view of the module namespace.
*
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
* more details.
*/
export interface UnifiedModulesHost {
/**
* Converts a file path to a module name that can be used as an `import ...`.
*
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
* should be imported using a module specifier of `monorepo/lib/importedFile`.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
}
/**
* A host which additionally tracks and produces "resources" (HTML templates, CSS
* files, etc).
*/
export interface ResourceHost {
/**
* Converts a file path for a resource that is used in a source file or another resource
* into a filepath.
*/
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
/**
* Load a referenced resource either statically or asynchronously. If the host returns a
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
* cause a diagnostics diagnostic error or an exception to be thrown.
*/
readResource(fileName: string): Promise<string>|string;
/**
* Get the absolute paths to the changed files that triggered the current compilation
* or `undefined` if this is not an incremental build.
*/
getModifiedResourceFiles?(): Set<string>|undefined;
}
/**
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
* core interface.
*/
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
Partial<UnifiedModulesHost> {}
export interface LazyRoute {
route: string;
module: {name: string, filePath: string};
referencedModule: {name: string, filePath: string};
}

View File

@ -0,0 +1,57 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {BazelAndG3Options, I18nOptions, LegacyNgcOptions, MiscOptions, NgcCompatibilityOptions, StrictTemplateOptions} from './public_options';
/**
* Non-public options which are useful during testing of the compiler.
*/
export interface TestOnlyOptions {
/**
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
* import module specifiers. This is false by default, and exists to support running ngtsc
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
* behavior between Bazel and Blaze.
*
* @internal
*/
_useHostForImportGeneration?: boolean;
/**
* Turn on template type-checking in the Ivy compiler.
*
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
* by default before it's ready might break other users attempting to test the new compiler's
* behavior.
*
* @internal
*/
ivyTemplateTypeCheck?: boolean;
/** An option to enable ngtsc's internal performance tracing.
*
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
* (not all hosts support this mode of operation).
*
* This is currently not exposed to users as the trace format is still unstable.
*/
tracePerformance?: string;
}
/**
* A merged interface of all of the various Angular compiler options, as well as the standard
* `ts.CompilerOptions`.
*
* Also includes a few miscellaneous options.
*/
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions, MiscOptions {}

View File

@ -6,63 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/**
* A host backed by a build system which has a unified view of the module namespace.
*
* Such a build system supports the `fileNameToModuleName` method provided by certain build system
* integrations (such as the integration with Bazel). See the docs on `fileNameToModuleName` for
* more details.
*/
export interface UnifiedModulesHost {
/**
* Converts a file path to a module name that can be used as an `import ...`.
*
* For example, such a host might determine that `/absolute/path/to/monorepo/lib/importedFile.ts`
* should be imported using a module specifier of `monorepo/lib/importedFile`.
*/
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string;
}
/**
* A host which additionally tracks and produces "resources" (HTML templates, CSS
* files, etc).
*/
export interface ResourceHost {
/**
* Converts a file path for a resource that is used in a source file or another resource
* into a filepath.
*/
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
/**
* Load a referenced resource either statically or asynchronously. If the host returns a
* `Promise<string>` it is assumed the user of the corresponding `Program` will call
* `loadNgStructureAsync()`. Returning `Promise<string>` outside `loadNgStructureAsync()` will
* cause a diagnostics diagnostic error or an exception to be thrown.
*/
readResource(fileName: string): Promise<string>|string;
/**
* Get the absolute paths to the changed files that triggered the current compilation
* or `undefined` if this is not an incremental build.
*/
getModifiedResourceFiles?(): Set<string>|undefined;
}
/**
* A `ts.CompilerHost` interface which supports some number of optional methods in addition to the
* core interface.
*/
export interface ExtendedTsCompilerHost extends ts.CompilerHost, Partial<ResourceHost>,
Partial<UnifiedModulesHost> {}
/**
* Options supported by the legacy View Engine compiler, which are still consumed by the Angular Ivy
* compiler for backwards compatibility.
*
* These are expected to be removed at some point in the future.
*
* @publicApi
*/
export interface LegacyNgcOptions {
/** generate all possible generated files */
@ -134,6 +84,8 @@ export interface LegacyNgcOptions {
* existing View Engine applications.
*
* These are expected to be removed at some point in the future.
*
* @publicApi
*/
export interface NgcCompatibilityOptions {
/**
@ -168,6 +120,8 @@ export interface NgcCompatibilityOptions {
/**
* Options related to template type-checking and its strictness.
*
* @publicApi
*/
export interface StrictTemplateOptions {
/**
@ -291,6 +245,8 @@ export interface StrictTemplateOptions {
/**
* Options which control behavior useful for "monorepo" build cases using Bazel (such as the
* internal Google monorepo, g3).
*
* @publicApi
*/
export interface BazelAndG3Options {
/**
@ -332,6 +288,8 @@ export interface BazelAndG3Options {
/**
* Options related to i18n compilation support.
*
* @publicApi
*/
export interface I18nOptions {
/**
@ -358,69 +316,19 @@ export interface I18nOptions {
}
/**
* Non-public options which are useful during testing of the compiler.
*/
export interface TestOnlyOptions {
/**
* Whether to use the CompilerHost's fileNameToModuleName utility (if available) to generate
* import module specifiers. This is false by default, and exists to support running ngtsc
* within Google. This option is internal and is used by the ng_module.bzl rule to switch
* behavior between Bazel and Blaze.
*
* @internal
*/
_useHostForImportGeneration?: boolean;
/**
* Turn on template type-checking in the Ivy compiler.
*
* This is an internal flag being used to roll out template type-checking in ngtsc. Turning it on
* by default before it's ready might break other users attempting to test the new compiler's
* behavior.
*
* @internal
*/
ivyTemplateTypeCheck?: boolean;
}
/**
* A merged interface of all of the various Angular compiler options, as well as the standard
* `ts.CompilerOptions`.
* Miscellaneous options that don't fall into any other category
*
* Also includes a few miscellaneous options.
* @publicApi
*/
export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options,
NgcCompatibilityOptions, StrictTemplateOptions, TestOnlyOptions, I18nOptions {
export interface MiscOptions {
/**
* Whether the compiler should avoid generating code for classes that haven't been exported.
* This is only active when building with `enableIvy: true`. Defaults to `true`.
*/
compileNonExportedClasses?: boolean;
/**
* Whether to remove blank text nodes from compiled templates. It is `false` by default starting
* from Angular 6.
*/
preserveWhitespaces?: boolean;
/**
* Disable TypeScript Version Check.
*/
disableTypeScriptVersionCheck?: boolean;
/** An option to enable ngtsc's internal performance tracing.
*
* This should be a path to a JSON file where trace information will be written. An optional 'ts:'
* prefix will cause the trace to be written via the TS host instead of directly to the filesystem
* (not all hosts support this mode of operation).
*
* This is currently not exposed to users as the trace format is still unstable.
*/
tracePerformance?: string;
}
export interface LazyRoute {
route: string;
module: {name: string, filePath: string};
referencedModule: {name: string, filePath: string};
}
}

View File

@ -296,9 +296,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
const exportPipes = new Map<ts.Declaration, PipeMeta>();
// The algorithm is as follows:
// 1) Add directives/pipes declared in the NgModule to the compilation scope.
// 2) Add all of the directives/pipes from each NgModule imported into the current one to the
// compilation scope. At this point, the compilation scope is complete.
// 1) Add all of the directives/pipes from each NgModule imported into the current one to the
// compilation scope.
// 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
// compilation scope is complete.
// 3) For each entry in the NgModule's exports:
// a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
// one, add them to the export scope of this NgModule.
@ -307,31 +308,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
// c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
// is an error.
// 1) add declarations.
for (const decl of ngModule.declarations) {
const directive = this.localReader.getDirectiveMetadata(decl);
const pipe = this.localReader.getPipeMetadata(decl);
if (directive !== null) {
compilationDirectives.set(decl.node, {...directive, ref: decl});
} else if (pipe !== null) {
compilationPipes.set(decl.node, {...pipe, ref: decl});
} else {
this.taintedModules.add(ngModule.ref.node);
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
diagnostics.push(makeDiagnostic(
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
`The class '${decl.node.name.text}' is listed in the declarations of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe.
Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
continue;
}
declared.add(decl.node);
}
// 2) process imports.
// 1) process imports.
for (const decl of ngModule.imports) {
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
if (importScope === null) {
@ -353,6 +330,30 @@ Either remove it from the NgModule's declarations, or add an appropriate Angular
}
}
// 2) add declarations.
for (const decl of ngModule.declarations) {
const directive = this.localReader.getDirectiveMetadata(decl);
const pipe = this.localReader.getPipeMetadata(decl);
if (directive !== null) {
compilationDirectives.set(decl.node, {...directive, ref: decl});
} else if (pipe !== null) {
compilationPipes.set(decl.node, {...pipe, ref: decl});
} else {
this.taintedModules.add(ngModule.ref.node);
const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations !);
diagnostics.push(makeDiagnostic(
ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode,
`The class '${decl.node.name.text}' is listed in the declarations ` +
`of the NgModule '${ngModule.ref.node.name.text}', but is not a directive, a component, or a pipe. ` +
`Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`,
[{node: decl.node.name, messageText: `'${decl.node.name.text}' is declared here.`}]));
continue;
}
declared.add(decl.node);
}
// 3) process exports.
// Exports can contain modules, components, or directives. They're processed differently.
// Modules are straightforward. Directives and pipes from exported modules are added to the

View File

@ -2542,7 +2542,7 @@ describe('compiler compliance', () => {
type: LifecycleComp,
selectors: [["lifecycle-comp"]],
inputs: {nameMin: ["name", "nameMin"]},
features: [$r3$.ɵɵNgOnChangesFeature()],
features: [$r3$.ɵɵNgOnChangesFeature],
decls: 0,
vars: 0,
template: function LifecycleComp_Template(rf, ctx) {},
@ -2662,7 +2662,7 @@ describe('compiler compliance', () => {
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
type: ForOfDirective,
selectors: [["", "forOf", ""]],
features: [$r3$.ɵɵNgOnChangesFeature()],
features: [$r3$.ɵɵNgOnChangesFeature],
inputs: {forOf: "forOf"}
});
`;
@ -2742,7 +2742,7 @@ describe('compiler compliance', () => {
ForOfDirective.ɵdir = $r3$.ɵɵdefineDirective({
type: ForOfDirective,
selectors: [["", "forOf", ""]],
features: [$r3$.ɵɵNgOnChangesFeature()],
features: [$r3$.ɵɵNgOnChangesFeature],
inputs: {forOf: "forOf"}
});
`;
@ -3767,7 +3767,7 @@ describe('compiler compliance', () => {
// ...
BaseClass.ɵdir = $r3$.ɵɵdefineDirective({
type: BaseClass,
features: [$r3$.ɵɵNgOnChangesFeature()]
features: [$r3$.ɵɵNgOnChangesFeature]
});
// ...
`;

View File

@ -598,6 +598,213 @@ runInEachFileSystem(os => {
expect(jsContents).toContain('outputs: { output: "output" }');
});
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
env.tsconfig({});
env.write('test.ts', `
import {Component, Pipe, NgModule} from '@angular/core';
// ModuleA classes
@Pipe({name: 'number'})
class PipeA {}
@NgModule({
declarations: [PipeA],
exports: [PipeA]
})
class ModuleA {}
// ModuleB classes
@Pipe({name: 'number'})
class PipeB {}
@Component({
selector: 'app',
template: '{{ count | number }}'
})
export class App {}
@NgModule({
imports: [ModuleA],
declarations: [PipeB, App],
})
class ModuleB {}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain('pipes: [PipeB]');
});
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
() => {
env.tsconfig({});
env.write('test.ts', `
import {Component, Pipe, NgModule} from '@angular/core';
// ModuleA classes
@Pipe({name: 'number'})
class PipeA {}
@NgModule({
declarations: [PipeA],
exports: [PipeA]
})
class ModuleA {}
// ModuleB classes
@Pipe({name: 'number'})
class PipeB {}
@NgModule({
declarations: [PipeB],
exports: [PipeB]
})
class ModuleB {}
// ModuleC classes
@Component({
selector: 'app',
template: '{{ count | number }}'
})
export class App {}
@NgModule({
imports: [ModuleA, ModuleB],
declarations: [App],
})
class ModuleC {}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain('pipes: [PipeB]');
});
it('should add Directives and Components from `declarations` at the end of the list', () => {
env.tsconfig({});
env.write('test.ts', `
import {Component, Directive, NgModule} from '@angular/core';
// ModuleA classes
@Directive({selector: '[dir]'})
class DirectiveA {}
@Component({
selector: 'comp',
template: '...'
})
class ComponentA {}
@NgModule({
declarations: [DirectiveA, ComponentA],
exports: [DirectiveA, ComponentA]
})
class ModuleA {}
// ModuleB classes
@Directive({selector: '[dir]'})
class DirectiveB {}
@Component({
selector: 'comp',
template: '...',
})
export class ComponentB {}
@Component({
selector: 'app',
template: \`
<div dir></div>
<comp></comp>
\`,
})
export class App {}
@NgModule({
imports: [ModuleA],
declarations: [DirectiveB, ComponentB, App],
})
class ModuleB {}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
});
it('should respect imported module order while processing Directives and Components', () => {
env.tsconfig({});
env.write('test.ts', `
import {Component, Directive, NgModule} from '@angular/core';
// ModuleA classes
@Directive({selector: '[dir]'})
class DirectiveA {}
@Component({
selector: 'comp',
template: '...'
})
class ComponentA {}
@NgModule({
declarations: [DirectiveA, ComponentA],
exports: [DirectiveA, ComponentA]
})
class ModuleA {}
// ModuleB classes
@Directive({selector: '[dir]'})
class DirectiveB {}
@Component({
selector: 'comp',
template: '...'
})
class ComponentB {}
@NgModule({
declarations: [DirectiveB, ComponentB],
exports: [DirectiveB, ComponentB]
})
class ModuleB {}
// ModuleC classes
@Component({
selector: 'app',
template: \`
<div dir></div>
<comp></comp>
\`,
})
export class App {}
@NgModule({
imports: [ModuleA, ModuleB],
declarations: [App],
})
class ModuleC {}
`);
env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain('directives: [DirectiveA, DirectiveB, ComponentA, ComponentB]');
});
it('should compile Components with a templateUrl in a different rootDir', () => {
env.tsconfig({}, ['./extraRootDir']);
env.write('extraRootDir/test.html', '<p>Hello World</p>');

View File

@ -286,7 +286,7 @@ export class ASTWithSource extends AST {
export class TemplateBinding {
constructor(
public span: ParseSpan, sourceSpan: AbsoluteSourceSpan, public key: string,
public keyIsVar: boolean, public name: string, public expression: ASTWithSource|null) {}
public keyIsVar: boolean, public name: string, public value: ASTWithSource|null) {}
}
export interface AstVisitor {

View File

@ -118,12 +118,38 @@ export class Parser {
span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
}
parseTemplateBindings(tplKey: string, tplValue: string, location: any, absoluteOffset: number):
TemplateBindingParseResult {
const tokens = this._lexer.tokenize(tplValue);
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
* <div *ngFor="let item of items">
* ^ `absoluteOffset` for `tplValue`
* ```
* contains three bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
*
* This is apparent from the de-sugared template:
* ```
* <ng-template ngFor let-item [ngForOf]="items">
* ```
*
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
* @param templateValue RHS of the microsyntax attribute
* @param templateUrl template filename if it's external, component filename if it's inline
* @param absoluteOffset absolute offset of the `tplValue`
*/
parseTemplateBindings(
templateKey: string, templateValue: string, templateUrl: string,
absoluteOffset: number): TemplateBindingParseResult {
const tokens = this._lexer.tokenize(templateValue);
return new _ParseAST(
tplValue, location, absoluteOffset, tokens, tplValue.length, false, this.errors, 0)
.parseTemplateBindings(tplKey);
templateValue, templateUrl, absoluteOffset, tokens, templateValue.length,
false /* parseAction */, this.errors, 0 /* relative offset */)
.parseTemplateBindings(templateKey);
}
parseInterpolation(
@ -288,7 +314,7 @@ export class _ParseAST {
advance() { this.index++; }
optionalCharacter(code: number): boolean {
consumeOptionalCharacter(code: number): boolean {
if (this.next.isCharacter(code)) {
this.advance();
return true;
@ -301,11 +327,11 @@ export class _ParseAST {
peekKeywordAs(): boolean { return this.next.isKeywordAs(); }
expectCharacter(code: number) {
if (this.optionalCharacter(code)) return;
if (this.consumeOptionalCharacter(code)) return;
this.error(`Missing expected ${String.fromCharCode(code)}`);
}
optionalOperator(op: string): boolean {
consumeOptionalOperator(op: string): boolean {
if (this.next.isOperator(op)) {
this.advance();
return true;
@ -315,7 +341,7 @@ export class _ParseAST {
}
expectOperator(operator: string) {
if (this.optionalOperator(operator)) return;
if (this.consumeOptionalOperator(operator)) return;
this.error(`Missing expected operator ${operator}`);
}
@ -346,11 +372,11 @@ export class _ParseAST {
const expr = this.parsePipe();
exprs.push(expr);
if (this.optionalCharacter(chars.$SEMICOLON)) {
if (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
if (!this.parseAction) {
this.error('Binding expression cannot contain chained expression');
}
while (this.optionalCharacter(chars.$SEMICOLON)) {
while (this.consumeOptionalCharacter(chars.$SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`);
@ -363,7 +389,7 @@ export class _ParseAST {
parsePipe(): AST {
let result = this.parseExpression();
if (this.optionalOperator('|')) {
if (this.consumeOptionalOperator('|')) {
if (this.parseAction) {
this.error('Cannot have a pipe in an action expression');
}
@ -373,13 +399,13 @@ export class _ParseAST {
const name = this.expectIdentifierOrKeyword();
const nameSpan = this.sourceSpan(nameStart);
const args: AST[] = [];
while (this.optionalCharacter(chars.$COLON)) {
while (this.consumeOptionalCharacter(chars.$COLON)) {
args.push(this.parseExpression());
}
const {start} = result.span;
result =
new BindingPipe(this.span(start), this.sourceSpan(start), result, name, args, nameSpan);
} while (this.optionalOperator('|'));
} while (this.consumeOptionalOperator('|'));
}
return result;
@ -391,10 +417,10 @@ export class _ParseAST {
const start = this.inputIndex;
const result = this.parseLogicalOr();
if (this.optionalOperator('?')) {
if (this.consumeOptionalOperator('?')) {
const yes = this.parsePipe();
let no: AST;
if (!this.optionalCharacter(chars.$COLON)) {
if (!this.consumeOptionalCharacter(chars.$COLON)) {
const end = this.inputIndex;
const expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
@ -411,7 +437,7 @@ export class _ParseAST {
parseLogicalOr(): AST {
// '||'
let result = this.parseLogicalAnd();
while (this.optionalOperator('||')) {
while (this.consumeOptionalOperator('||')) {
const right = this.parseLogicalAnd();
const {start} = result.span;
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
@ -422,7 +448,7 @@ export class _ParseAST {
parseLogicalAnd(): AST {
// '&&'
let result = this.parseEquality();
while (this.optionalOperator('&&')) {
while (this.consumeOptionalOperator('&&')) {
const right = this.parseEquality();
const {start} = result.span;
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
@ -544,18 +570,18 @@ export class _ParseAST {
let result = this.parsePrimary();
const resultStart = result.span.start;
while (true) {
if (this.optionalCharacter(chars.$PERIOD)) {
if (this.consumeOptionalCharacter(chars.$PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) {
} else if (this.consumeOptionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter(chars.$LBRACKET)) {
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
this.rbracketsExpected++;
const key = this.parsePipe();
this.rbracketsExpected--;
this.expectCharacter(chars.$RBRACKET);
if (this.optionalOperator('=')) {
if (this.consumeOptionalOperator('=')) {
const value = this.parseConditional();
result = new KeyedWrite(
this.span(resultStart), this.sourceSpan(resultStart), result, key, value);
@ -563,7 +589,7 @@ export class _ParseAST {
result = new KeyedRead(this.span(resultStart), this.sourceSpan(resultStart), result, key);
}
} else if (this.optionalCharacter(chars.$LPAREN)) {
} else if (this.consumeOptionalCharacter(chars.$LPAREN)) {
this.rparensExpected++;
const args = this.parseCallArguments();
this.rparensExpected--;
@ -571,7 +597,7 @@ export class _ParseAST {
result =
new FunctionCall(this.span(resultStart), this.sourceSpan(resultStart), result, args);
} else if (this.optionalOperator('!')) {
} else if (this.consumeOptionalOperator('!')) {
result = new NonNullAssert(this.span(resultStart), this.sourceSpan(resultStart), result);
} else {
@ -582,7 +608,7 @@ export class _ParseAST {
parsePrimary(): AST {
const start = this.inputIndex;
if (this.optionalCharacter(chars.$LPAREN)) {
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
this.rparensExpected++;
const result = this.parsePipe();
this.rparensExpected--;
@ -609,7 +635,7 @@ export class _ParseAST {
this.advance();
return new ImplicitReceiver(this.span(start), this.sourceSpan(start));
} else if (this.optionalCharacter(chars.$LBRACKET)) {
} else if (this.consumeOptionalCharacter(chars.$LBRACKET)) {
this.rbracketsExpected++;
const elements = this.parseExpressionList(chars.$RBRACKET);
this.rbracketsExpected--;
@ -647,7 +673,7 @@ export class _ParseAST {
if (!this.next.isCharacter(terminator)) {
do {
result.push(this.parsePipe());
} while (this.optionalCharacter(chars.$COMMA));
} while (this.consumeOptionalCharacter(chars.$COMMA));
}
return result;
}
@ -657,7 +683,7 @@ export class _ParseAST {
const values: AST[] = [];
const start = this.inputIndex;
this.expectCharacter(chars.$LBRACE);
if (!this.optionalCharacter(chars.$RBRACE)) {
if (!this.consumeOptionalCharacter(chars.$RBRACE)) {
this.rbracesExpected++;
do {
const quoted = this.next.isString();
@ -665,7 +691,7 @@ export class _ParseAST {
keys.push({key, quoted});
this.expectCharacter(chars.$COLON);
values.push(this.parsePipe());
} while (this.optionalCharacter(chars.$COMMA));
} while (this.consumeOptionalCharacter(chars.$COMMA));
this.rbracesExpected--;
this.expectCharacter(chars.$RBRACE);
}
@ -676,7 +702,7 @@ export class _ParseAST {
const start = receiver.span.start;
const id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter(chars.$LPAREN)) {
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
this.rparensExpected++;
const args = this.parseCallArguments();
this.expectCharacter(chars.$RPAREN);
@ -688,14 +714,14 @@ export class _ParseAST {
} else {
if (isSafe) {
if (this.optionalOperator('=')) {
if (this.consumeOptionalOperator('=')) {
this.error('The \'?.\' operator cannot be used in the assignment');
return new EmptyExpr(this.span(start), this.sourceSpan(start));
} else {
return new SafePropertyRead(this.span(start), this.sourceSpan(start), receiver, id);
}
} else {
if (this.optionalOperator('=')) {
if (this.consumeOptionalOperator('=')) {
if (!this.parseAction) {
this.error('Bindings cannot contain assignments');
return new EmptyExpr(this.span(start), this.sourceSpan(start));
@ -716,84 +742,208 @@ export class _ParseAST {
const positionals: AST[] = [];
do {
positionals.push(this.parsePipe());
} while (this.optionalCharacter(chars.$COMMA));
} while (this.consumeOptionalCharacter(chars.$COMMA));
return positionals as BindingPipe[];
}
/**
* An identifier, a keyword, a string with an optional `-` in between.
* Parses an identifier, a keyword, a string with an optional `-` in between.
*/
expectTemplateBindingKey(): string {
expectTemplateBindingKey(): {key: string, keySpan: ParseSpan} {
let result = '';
let operatorFound = false;
const start = this.inputIndex;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.optionalOperator('-');
operatorFound = this.consumeOptionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return result.toString();
return {
key: result,
keySpan: new ParseSpan(start, start + result.length),
};
}
// Parses the AST for `<some-tag *tplKey=AST>`
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
let firstBinding = true;
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
* <div *ngFor="let item of items; index as i; trackBy: func">
* ```
* contains five bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
* 4. i -> NgForOfContext.index
* 5. ngForTrackBy -> func
*
* For a full description of the microsyntax grammar, see
* https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
*
* @param templateKey name of the microsyntax directive, like ngIf, ngFor, without the *
*/
parseTemplateBindings(templateKey: string): TemplateBindingParseResult {
const bindings: TemplateBinding[] = [];
const warnings: string[] = [];
do {
const start = this.inputIndex;
let rawKey: string;
let key: string;
let isVar: boolean = false;
if (firstBinding) {
rawKey = key = tplKey;
firstBinding = false;
// The first binding is for the template key itself
// In *ngFor="let item of items", key = "ngFor", value = null
// In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
bindings.push(...this.parseDirectiveKeywordBindings(
templateKey, new ParseSpan(0, templateKey.length), this.absoluteOffset));
while (this.index < this.tokens.length) {
// If it starts with 'let', then this must be variable declaration
const letBinding = this.parseLetBinding();
if (letBinding) {
bindings.push(letBinding);
} else {
isVar = this.peekKeywordLet();
if (isVar) this.advance();
rawKey = this.expectTemplateBindingKey();
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
this.optionalCharacter(chars.$COLON);
}
let name: string = null !;
let expression: ASTWithSource|null = null;
if (isVar) {
if (this.optionalOperator('=')) {
name = this.expectTemplateBindingKey();
// Two possible cases here, either `value "as" key` or
// "directive-keyword expression". We don't know which case, but both
// "value" and "directive-keyword" are template binding key, so consume
// the key first.
const {key, keySpan} = this.expectTemplateBindingKey();
// Peek at the next token, if it is "as" then this must be variable
// declaration.
const binding = this.parseAsBinding(key, keySpan, this.absoluteOffset);
if (binding) {
bindings.push(binding);
} else {
name = '\$implicit';
// Otherwise the key must be a directive keyword, like "of". Transform
// the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
const actualKey = templateKey + key[0].toUpperCase() + key.substring(1);
bindings.push(
...this.parseDirectiveKeywordBindings(actualKey, keySpan, this.absoluteOffset));
}
} else if (this.peekKeywordAs()) {
this.advance(); // consume `as`
name = rawKey;
key = this.expectTemplateBindingKey(); // read local var name
isVar = true;
} else if (this.next !== EOF && !this.peekKeywordLet()) {
const start = this.inputIndex;
const ast = this.parsePipe();
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
expression =
new ASTWithSource(ast, source, this.location, this.absoluteOffset + start, this.errors);
}
this.consumeStatementTerminator();
}
bindings.push(new TemplateBinding(
this.span(start), this.sourceSpan(start), key, isVar, name, expression));
if (this.peekKeywordAs() && !isVar) {
const letStart = this.inputIndex;
this.advance(); // consume `as`
const letName = this.expectTemplateBindingKey(); // read local var name
bindings.push(new TemplateBinding(
this.span(letStart), this.sourceSpan(letStart), letName, true, key, null !));
}
if (!this.optionalCharacter(chars.$SEMICOLON)) {
this.optionalCharacter(chars.$COMMA);
}
} while (this.index < this.tokens.length);
return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
}
return new TemplateBindingParseResult(bindings, warnings, this.errors);
/**
* Parse a directive keyword, followed by a mandatory expression.
* For example, "of items", "trackBy: func".
* The bindings are: ngForOf -> items, ngForTrackBy -> func
* There could be an optional "as" binding that follows the expression.
* For example,
* ```
* *ngFor="let item of items | slice:0:1 as collection".`
* ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
* keyword bound target optional 'as' binding
* ```
*
* @param key binding key, for example, ngFor, ngIf, ngForOf
* @param keySpan span of the key in the expression. keySpan might be different
* from `key.length`. For example, the span for key "ngForOf" is "of".
* @param absoluteOffset absolute offset of the attribute value
*/
private parseDirectiveKeywordBindings(key: string, keySpan: ParseSpan, absoluteOffset: number):
TemplateBinding[] {
const bindings: TemplateBinding[] = [];
this.consumeOptionalCharacter(chars.$COLON); // trackBy: trackByFunction
const valueExpr = this.getDirectiveBoundTarget();
const span = new ParseSpan(keySpan.start, this.inputIndex);
bindings.push(new TemplateBinding(
span, span.toAbsolute(absoluteOffset), key, false /* keyIsVar */, valueExpr?.source || '', valueExpr));
// The binding could optionally be followed by "as". For example,
// *ngIf="cond | pipe as x". In this case, the key in the "as" binding
// is "x" and the value is the template key itself ("ngIf"). Note that the
// 'key' in the current context now becomes the "value" in the next binding.
const asBinding = this.parseAsBinding(key, keySpan, absoluteOffset);
if (asBinding) {
bindings.push(asBinding);
}
this.consumeStatementTerminator();
return bindings;
}
/**
* Return the expression AST for the bound target of a directive keyword
* binding. For example,
* ```
* *ngIf="condition | pipe".
* ^^^^^^^^^^^^^^^^ bound target for "ngIf"
* *ngFor="let item of items"
* ^^^^^ bound target for "ngForOf"
* ```
*/
private getDirectiveBoundTarget(): ASTWithSource|null {
if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
return null;
}
const ast = this.parsePipe(); // example: "condition | async"
const {start, end} = ast.span;
const value = this.input.substring(start, end);
return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
}
/**
* Return the binding for a variable declared using `as`. Note that the order
* of the key-value pair in this declaration is reversed. For example,
* ```
* *ngFor="let item of items; index as i"
* ^^^^^ ^
* value key
* ```
*
* @param value name of the value in the declaration, "ngIf" in the example above
* @param valueSpan span of the value in the declaration
* @param absoluteOffset absolute offset of `value`
*/
private parseAsBinding(value: string, valueSpan: ParseSpan, absoluteOffset: number):
TemplateBinding|null {
if (!this.peekKeywordAs()) {
return null;
}
this.advance(); // consume the 'as' keyword
const {key} = this.expectTemplateBindingKey();
const valueAst = new AST(valueSpan, valueSpan.toAbsolute(absoluteOffset));
const valueExpr = new ASTWithSource(
valueAst, value, this.location, absoluteOffset + valueSpan.start, this.errors);
const span = new ParseSpan(valueSpan.start, this.inputIndex);
return new TemplateBinding(
span, span.toAbsolute(absoluteOffset), key, true /* keyIsVar */, value, valueExpr);
}
/**
* Return the binding for a variable declared using `let`. For example,
* ```
* *ngFor="let item of items; let i=index;"
* ^^^^^^^^ ^^^^^^^^^^^
* ```
* In the first binding, `item` is bound to `NgForOfContext.$implicit`.
* In the second binding, `i` is bound to `NgForOfContext.index`.
*/
private parseLetBinding(): TemplateBinding|null {
if (!this.peekKeywordLet()) {
return null;
}
const spanStart = this.inputIndex;
this.advance(); // consume the 'let' keyword
const {key} = this.expectTemplateBindingKey();
let valueExpr: ASTWithSource|null = null;
if (this.consumeOptionalOperator('=')) {
const {key: value, keySpan: valueSpan} = this.expectTemplateBindingKey();
const ast = new AST(valueSpan, valueSpan.toAbsolute(this.absoluteOffset));
valueExpr = new ASTWithSource(
ast, value, this.location, this.absoluteOffset + valueSpan.start, this.errors);
}
const spanEnd = this.inputIndex;
const span = new ParseSpan(spanStart, spanEnd);
return new TemplateBinding(
span, span.toAbsolute(this.absoluteOffset), key, true /* keyIsVar */, valueExpr?.source || '$implicit', valueExpr);
}
/**
* Consume the optional statement terminator: semicolon or comma.
*/
private consumeStatementTerminator() {
this.consumeOptionalCharacter(chars.$SEMICOLON) || this.consumeOptionalCharacter(chars.$COMMA);
}
error(message: string, index: number|null = null) {
@ -896,4 +1046,4 @@ class IvySimpleExpressionChecker extends SimpleExpressionChecker {
}
visitPrefixNot(ast: PrefixNot, context: any) { ast.expression.visit(this); }
}
}

View File

@ -87,7 +87,7 @@ function baseDirectiveFields(
*/
function addFeatures(
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
// e.g. `features: [NgOnChangesFeature()]`
// e.g. `features: [NgOnChangesFeature]`
const features: o.Expression[] = [];
const providers = meta.providers;
@ -107,7 +107,7 @@ function addFeatures(
features.push(o.importExpr(R3.CopyDefinitionFeature));
}
if (meta.lifecycle.usesOnChanges) {
features.push(o.importExpr(R3.NgOnChangesFeature).callFn(EMPTY_ARRAY));
features.push(o.importExpr(R3.NgOnChangesFeature));
}
if (features.length) {
definitionMap.set('features', o.literalArr(features));

View File

@ -134,10 +134,9 @@ export class BindingParser {
const binding = bindings[i];
if (binding.keyIsVar) {
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
} else if (binding.expression) {
} else if (binding.value) {
this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, undefined, targetMatchableAttrs,
targetProps);
binding.key, binding.value, sourceSpan, undefined, targetMatchableAttrs, targetProps);
} else {
targetMatchableAttrs.push([binding.key, '']);
this.parseLiteralAttr(
@ -165,8 +164,8 @@ export class BindingParser {
this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteValueOffset);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => {
if (binding.expression) {
this._checkPipes(binding.expression, sourceSpan);
if (binding.value) {
this._checkPipes(binding.value, sourceSpan);
}
});
bindingsResult.warnings.forEach(

View File

@ -248,14 +248,16 @@ describe('parser', () => {
describe('parseTemplateBindings', () => {
function keys(templateBindings: any[]) { return templateBindings.map(binding => binding.key); }
function keys(templateBindings: TemplateBinding[]) {
return templateBindings.map(binding => binding.key);
}
function keyValues(templateBindings: any[]) {
function keyValues(templateBindings: TemplateBinding[]) {
return templateBindings.map(binding => {
if (binding.keyIsVar) {
return 'let ' + binding.key + (binding.name == null ? '=null' : '=' + binding.name);
} else {
return binding.key + (binding.expression == null ? '' : `=${binding.expression}`);
return binding.key + (binding.value == null ? '' : `=${binding.value}`);
}
});
}
@ -265,9 +267,16 @@ describe('parser', () => {
binding => source.substring(binding.span.start, binding.span.end));
}
function exprSources(templateBindings: any[]) {
return templateBindings.map(
binding => binding.expression != null ? binding.expression.source : null);
function exprSources(templateBindings: TemplateBinding[]) {
return templateBindings.map(binding => binding.value != null ? binding.value.source : null);
}
function humanize(bindings: TemplateBinding[]): Array<[string, string | null, boolean]> {
return bindings.map(binding => {
const {key, value: expression, name, keyIsVar} = binding;
const value = keyIsVar ? name : (expression ? expression.source : expression);
return [key, value, keyIsVar];
});
}
it('should parse a key without a value',
@ -308,13 +317,51 @@ describe('parser', () => {
it('should store the sources in the result', () => {
const bindings = parseTemplateBindings('a', '1,b 2');
expect(bindings[0].expression !.source).toEqual('1');
expect(bindings[1].expression !.source).toEqual('2');
expect(bindings[0].value !.source).toEqual('1');
expect(bindings[1].value !.source).toEqual('2');
});
it('should store the passed-in location', () => {
const bindings = parseTemplateBindings('a', '1,b 2', 'location');
expect(bindings[0].expression !.location).toEqual('location');
expect(bindings[0].value !.location).toEqual('location');
});
it('should support common usage of ngIf', () => {
const bindings = parseTemplateBindings('ngIf', 'cond | pipe as foo, let x; ngIf as y');
expect(humanize(bindings)).toEqual([
// [ key, value, keyIsVar ]
['ngIf', 'cond | pipe ', false],
['foo', 'ngIf', true],
['x', '$implicit', true],
['y', 'ngIf', true],
]);
});
it('should support common usage of ngFor', () => {
let bindings: TemplateBinding[];
bindings = parseTemplateBindings(
'ngFor', 'let item; of items | slice:0:1 as collection, trackBy: func; index as i');
expect(humanize(bindings)).toEqual([
// [ key, value, keyIsVar ]
['ngFor', null, false],
['item', '$implicit', true],
['ngForOf', 'items | slice:0:1 ', false],
['collection', 'ngForOf', true],
['ngForTrackBy', 'func', false],
['i', 'index', true],
]);
bindings = parseTemplateBindings(
'ngFor', 'let item, of: [1,2,3] | pipe as items; let i=index, count as len');
expect(humanize(bindings)).toEqual([
// [ key, value, keyIsVar ]
['ngFor', null, false],
['item', '$implicit', true],
['ngForOf', '[1,2,3] | pipe ', false],
['items', 'ngForOf', true],
['i', 'index', true],
['len', 'count', true],
]);
});
it('should support let notation', () => {
@ -369,7 +416,7 @@ describe('parser', () => {
it('should parse pipes', () => {
const bindings = parseTemplateBindings('key', 'value|pipe');
const ast = bindings[0].expression !.ast;
const ast = bindings[0].value !.ast;
expect(ast).toBeAnInstanceOf(BindingPipe);
});

View File

@ -20,6 +20,19 @@ export function createNgcProgram(
// NGC program. In order to ensure that the migration runs properly, we set "enableIvy" to false.
options.enableIvy = false;
// Libraries which have been generated with CLI versions past v6.2.0, explicitly set the
// flat-module options in their tsconfig files. This is problematic because by default,
// those tsconfig files do not specify explicit source files which can be considered as
// entry point for the flat-module bundle. Therefore the `@angular/compiler-cli` is unable
// to determine the flat module entry point and throws a compile error. This is not an issue
// for the libraries built with `ng-packagr`, because the tsconfig files are modified in-memory
// to specify an explicit flat module entry-point. Our migrations don't distinguish between
// libraries and applications, and also don't run `ng-packagr`. To ensure that such libraries
// can be successfully migrated, we remove the flat-module options to eliminate the flat module
// entry-point requirement. More context: https://github.com/angular/angular/issues/34985.
options.flatModuleId = undefined;
options.flatModuleOutFile = undefined;
const host = createHost(options);
// For this migration, we never need to read resources and can just return

View File

@ -1473,6 +1473,40 @@ describe('Undecorated classes with DI migration', () => {
'TypeScript program failures');
});
// Regression test for: https://github.com/angular/angular/issues/34985.
it('should be able to migrate libraries with multiple source files and flat-module ' +
'options set',
async() => {
writeFile('/tsconfig.json', JSON.stringify({
compilerOptions: {
lib: ['es2015'],
},
angularCompilerOptions:
{flatModuleId: 'AUTOGENERATED', flatModuleOutFile: 'AUTOGENERATED'}
}));
writeFile('/second.ts', ``);
writeFile('/test.ts', `
import {Injectable, NgModule, NgZone} from '@angular/core';
export class BaseClass {
constructor(zone: NgZone) {}
}
@Injectable({template: ''})
export class MyService extends BaseClass {}
@NgModule({providers: [MyService]})
export class AppModule {}
`);
await runMigration();
expect(errorOutput.length).toBe(0);
expect(warnOutput.length).toBe(0);
expect(tree.readContent('/test.ts')).toMatch(/@Injectable\(\)\nexport class BaseClass {/);
});
it('should not throw if resources could not be read', async() => {
writeFile('/index.ts', `
import {Component, NgModule} from '@angular/core';

View File

@ -289,56 +289,56 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
*/
schemas?: SchemaMetadata[] | null;
}): never {
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
// See the `initNgDevMode` docstring for more information.
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
return noSideEffects(() => {
// Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
// See the `initNgDevMode` docstring for more information.
(typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
const type = componentDefinition.type;
const typePrototype = type.prototype;
const declaredInputs: {[key: string]: string} = {} as any;
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
type: type,
providersResolver: null,
decls: componentDefinition.decls,
vars: componentDefinition.vars,
factory: null,
template: componentDefinition.template || null !,
consts: componentDefinition.consts || null,
ngContentSelectors: componentDefinition.ngContentSelectors,
hostBindings: componentDefinition.hostBindings || null,
hostVars: componentDefinition.hostVars || 0,
hostAttrs: componentDefinition.hostAttrs || null,
contentQueries: componentDefinition.contentQueries || null,
declaredInputs: declaredInputs,
inputs: null !, // assigned in noSideEffects
outputs: null !, // assigned in noSideEffects
exportAs: componentDefinition.exportAs || null,
onChanges: null,
onInit: typePrototype.ngOnInit || null,
doCheck: typePrototype.ngDoCheck || null,
afterContentInit: typePrototype.ngAfterContentInit || null,
afterContentChecked: typePrototype.ngAfterContentChecked || null,
afterViewInit: typePrototype.ngAfterViewInit || null,
afterViewChecked: typePrototype.ngAfterViewChecked || null,
onDestroy: typePrototype.ngOnDestroy || null,
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
directiveDefs: null !, // assigned in noSideEffects
pipeDefs: null !, // assigned in noSideEffects
selectors: componentDefinition.selectors || EMPTY_ARRAY,
viewQuery: componentDefinition.viewQuery || null,
features: componentDefinition.features as DirectiveDefFeature[] || null,
data: componentDefinition.data || {},
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in the
// next line. Also `None` should be 0 not 2.
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
id: 'c',
styles: componentDefinition.styles || EMPTY_ARRAY,
_: null as never,
setInput: null,
schemas: componentDefinition.schemas || null,
tView: null,
};
def._ = noSideEffects(() => {
const type = componentDefinition.type;
const typePrototype = type.prototype;
const declaredInputs: {[key: string]: string} = {} as any;
const def: Mutable<ComponentDef<any>, keyof ComponentDef<any>> = {
type: type,
providersResolver: null,
decls: componentDefinition.decls,
vars: componentDefinition.vars,
factory: null,
template: componentDefinition.template || null !,
consts: componentDefinition.consts || null,
ngContentSelectors: componentDefinition.ngContentSelectors,
hostBindings: componentDefinition.hostBindings || null,
hostVars: componentDefinition.hostVars || 0,
hostAttrs: componentDefinition.hostAttrs || null,
contentQueries: componentDefinition.contentQueries || null,
declaredInputs: declaredInputs,
inputs: null !, // assigned in noSideEffects
outputs: null !, // assigned in noSideEffects
exportAs: componentDefinition.exportAs || null,
onChanges: null,
onInit: typePrototype.ngOnInit || null,
doCheck: typePrototype.ngDoCheck || null,
afterContentInit: typePrototype.ngAfterContentInit || null,
afterContentChecked: typePrototype.ngAfterContentChecked || null,
afterViewInit: typePrototype.ngAfterViewInit || null,
afterViewChecked: typePrototype.ngAfterViewChecked || null,
onDestroy: typePrototype.ngOnDestroy || null,
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
directiveDefs: null !, // assigned in noSideEffects
pipeDefs: null !, // assigned in noSideEffects
selectors: componentDefinition.selectors || EMPTY_ARRAY,
viewQuery: componentDefinition.viewQuery || null,
features: componentDefinition.features as DirectiveDefFeature[] || null,
data: componentDefinition.data || {},
// TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in
// the next line. Also `None` should be 0 not 2.
encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
id: 'c',
styles: componentDefinition.styles || EMPTY_ARRAY,
_: null as never,
setInput: null,
schemas: componentDefinition.schemas || null,
tView: null,
};
const directiveTypes = componentDefinition.directives !;
const feature = componentDefinition.features;
const pipeTypes = componentDefinition.pipes !;
@ -353,9 +353,9 @@ export function ɵɵdefineComponent<T>(componentDefinition: {
def.pipeDefs = pipeTypes ?
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
null;
}) as never;
return def as never;
return def as never;
});
}
/**

View File

@ -14,6 +14,7 @@ import {getInjectorDef} from '../di/interface/defs';
import {InjectFlags} from '../di/interface/injector';
import {Type} from '../interface/type';
import {assertDefined, assertEqual} from '../util/assert';
import {noSideEffects} from '../util/closure';
import {assertDirectiveDef} from './assert';
import {getFactoryDef} from './definition';
@ -655,15 +656,17 @@ export function ɵɵgetFactoryOf<T>(type: Type<any>): FactoryFn<T>|null {
* @codeGenApi
*/
export function ɵɵgetInheritedFactory<T>(type: Type<any>): (type: Type<T>) => T {
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
if (factory !== null) {
return factory;
} else {
// There is no factory defined. Either this was improper usage of inheritance
// (no Angular decorator on the superclass) or there is no constructor at all
// in the inheritance chain. Since the two cases cannot be distinguished, the
// latter has to be assumed.
return (t) => new t();
}
return noSideEffects(() => {
const proto = Object.getPrototypeOf(type.prototype).constructor as Type<any>;
const factory = (proto as any)[NG_FACTORY_DEF] || ɵɵgetFactoryOf<T>(proto);
if (factory !== null) {
return factory;
} else {
// There is no factory defined. Either this was improper usage of inheritance
// (no Angular decorator on the superclass) or there is no constructor at all
// in the inheritance chain. Since the two cases cannot be distinguished, the
// latter has to be assumed.
return (t) => new t();
}
});
}

View File

@ -35,26 +35,26 @@ type OnChangesExpando = OnChanges & {
* static ɵcmp = defineComponent({
* ...
* inputs: {name: 'publicName'},
* features: [NgOnChangesFeature()]
* features: [NgOnChangesFeature]
* });
* ```
*
* @codeGenApi
*/
export function ɵɵNgOnChangesFeature<T>(): DirectiveDefFeature {
// This option ensures that the ngOnChanges lifecycle hook will be inherited
// from superclasses (in InheritDefinitionFeature).
(NgOnChangesFeatureImpl as DirectiveDefFeature).ngInherit = true;
return NgOnChangesFeatureImpl;
}
function NgOnChangesFeatureImpl<T>(definition: DirectiveDef<T>): void {
export function ɵɵNgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
if (definition.type.prototype.ngOnChanges) {
definition.setInput = ngOnChangesSetInput;
(definition as{onChanges: Function}).onChanges = wrapOnChanges();
}
}
// This option ensures that the ngOnChanges lifecycle hook will be inherited
// from superclasses (in InheritDefinitionFeature).
/** @nocollapse */
// tslint:disable-next-line:no-toplevel-property-access
(ɵɵNgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
function wrapOnChanges() {
return function wrapOnChangesHook_inPreviousChangesStorage(this: OnChanges) {
const simpleChangesStore = getSimpleChangesStore(this);

View File

@ -455,19 +455,6 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
},
};
maybeUnwrapFn(def.declarations).forEach(declared => {
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
if (getPipeDef(declaredWithDefs)) {
scopes.compilation.pipes.add(declared);
} else {
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
// had its template compiled yet. In either case, it gets added to the compilation's
// directives.
scopes.compilation.directives.add(declared);
}
});
maybeUnwrapFn(def.imports).forEach(<I>(imported: Type<I>) => {
const importedType = imported as Type<I>& {
// If imported is an @NgModule:
@ -485,6 +472,19 @@ export function transitiveScopesFor<T>(moduleType: Type<T>): NgModuleTransitiveS
importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
});
maybeUnwrapFn(def.declarations).forEach(declared => {
const declaredWithDefs = declared as Type<any>& { ɵpipe?: any; };
if (getPipeDef(declaredWithDefs)) {
scopes.compilation.pipes.add(declared);
} else {
// Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
// had its template compiled yet. In either case, it gets added to the compilation's
// directives.
scopes.compilation.directives.add(declared);
}
});
maybeUnwrapFn(def.exports).forEach(<E>(exported: Type<E>) => {
const exportedType = exported as Type<E>& {
// Components, Directives, NgModules, and Pipes can all be exported.

View File

@ -15,6 +15,6 @@
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure
* compiler.
*/
export function noSideEffects(fn: () => void): string {
return '' + {toString: fn};
}
export function noSideEffects<T>(fn: () => T): T {
return {toString: fn}.toString() as unknown as T;
}

View File

@ -8,6 +8,10 @@
import {Type} from '../interface/type';
import {noSideEffects} from './closure';
/**
* An interface implemented by all Angular type decorators, which allows them to be used as
* decorators as well as Angular syntax.
@ -44,39 +48,41 @@ export function makeDecorator<T>(
additionalProcessing?: (type: Type<T>) => void,
typeFn?: (type: Type<T>, ...args: any[]) => void):
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
const metaCtor = makeMetadataCtor(props);
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function DecoratorFactory(
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
if (this instanceof DecoratorFactory) {
metaCtor.call(this, ...args);
return this as typeof DecoratorFactory;
function DecoratorFactory(
this: unknown | typeof DecoratorFactory, ...args: any[]): (cls: Type<T>) => any {
if (this instanceof DecoratorFactory) {
metaCtor.call(this, ...args);
return this as typeof DecoratorFactory;
}
const annotationInstance = new (DecoratorFactory as any)(...args);
return function TypeDecorator(cls: Type<T>) {
if (typeFn) typeFn(cls, ...args);
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
if (additionalProcessing) additionalProcessing(cls);
return cls;
};
}
const annotationInstance = new (DecoratorFactory as any)(...args);
return function TypeDecorator(cls: Type<T>) {
if (typeFn) typeFn(cls, ...args);
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
if (parentClass) {
DecoratorFactory.prototype = Object.create(parentClass.prototype);
}
if (additionalProcessing) additionalProcessing(cls);
return cls;
};
}
if (parentClass) {
DecoratorFactory.prototype = Object.create(parentClass.prototype);
}
DecoratorFactory.prototype.ngMetadataName = name;
(DecoratorFactory as any).annotationCls = DecoratorFactory;
return DecoratorFactory as any;
DecoratorFactory.prototype.ngMetadataName = name;
(DecoratorFactory as any).annotationCls = DecoratorFactory;
return DecoratorFactory as any;
});
}
function makeMetadataCtor(props?: (...args: any[]) => any): any {
@ -92,77 +98,82 @@ function makeMetadataCtor(props?: (...args: any[]) => any): any {
export function makeParamDecorator(
name: string, props?: (...args: any[]) => any, parentClass?: any): any {
const metaCtor = makeMetadataCtor(props);
function ParamDecoratorFactory(
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
if (this instanceof ParamDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
(<any>ParamDecorator).annotation = annotationInstance;
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS) ?
(cls as any)[PARAMETERS] :
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function ParamDecoratorFactory(
this: unknown | typeof ParamDecoratorFactory, ...args: any[]): any {
if (this instanceof ParamDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const annotationInstance = new (<any>ParamDecoratorFactory)(...args);
(parameters[index] = parameters[index] || []).push(annotationInstance);
return cls;
(<any>ParamDecorator).annotation = annotationInstance;
return ParamDecorator;
function ParamDecorator(cls: any, unusedKey: any, index: number): any {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const parameters = cls.hasOwnProperty(PARAMETERS) ?
(cls as any)[PARAMETERS] :
Object.defineProperty(cls, PARAMETERS, {value: []})[PARAMETERS];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
}
(parameters[index] = parameters[index] || []).push(annotationInstance);
return cls;
}
}
}
if (parentClass) {
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
ParamDecoratorFactory.prototype.ngMetadataName = name;
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
return ParamDecoratorFactory;
if (parentClass) {
ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
ParamDecoratorFactory.prototype.ngMetadataName = name;
(<any>ParamDecoratorFactory).annotationCls = ParamDecoratorFactory;
return ParamDecoratorFactory;
});
}
export function makePropDecorator(
name: string, props?: (...args: any[]) => any, parentClass?: any,
additionalProcessing?: (target: any, name: string, ...args: any[]) => void): any {
const metaCtor = makeMetadataCtor(props);
return noSideEffects(() => {
const metaCtor = makeMetadataCtor(props);
function PropDecoratorFactory(this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
if (this instanceof PropDecoratorFactory) {
metaCtor.apply(this, args);
return this;
function PropDecoratorFactory(
this: unknown | typeof PropDecoratorFactory, ...args: any[]): any {
if (this instanceof PropDecoratorFactory) {
metaCtor.apply(this, args);
return this;
}
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
function PropDecorator(target: any, name: string) {
const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
(constructor as any)[PROP_METADATA] :
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
if (additionalProcessing) additionalProcessing(target, name, ...args);
}
return PropDecorator;
}
const decoratorInstance = new (<any>PropDecoratorFactory)(...args);
function PropDecorator(target: any, name: string) {
const constructor = target.constructor;
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const meta = constructor.hasOwnProperty(PROP_METADATA) ?
(constructor as any)[PROP_METADATA] :
Object.defineProperty(constructor, PROP_METADATA, {value: {}})[PROP_METADATA];
meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
meta[name].unshift(decoratorInstance);
if (additionalProcessing) additionalProcessing(target, name, ...args);
if (parentClass) {
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
return PropDecorator;
}
if (parentClass) {
PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
}
PropDecoratorFactory.prototype.ngMetadataName = name;
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
PropDecoratorFactory.prototype.ngMetadataName = name;
(<any>PropDecoratorFactory).annotationCls = PropDecoratorFactory;
return PropDecoratorFactory;
});
}

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component, Directive, ElementRef, EventEmitter, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {Component, Directive, ElementRef, EventEmitter, NgModule, Output, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
import {Input} from '@angular/core/src/metadata';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
@ -633,4 +633,96 @@ describe('directives', () => {
expect(div.getAttribute('title')).toBe('a');
});
});
describe('directives with the same selector', () => {
it('should process Directives from `declarations` list after imported ones', () => {
const log: string[] = [];
@Directive({selector: '[dir]'})
class DirectiveA {
constructor() { log.push('DirectiveA.constructor'); }
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
}
@NgModule({
declarations: [DirectiveA],
exports: [DirectiveA],
})
class ModuleA {
}
@Directive({selector: '[dir]'})
class DirectiveB {
constructor() { log.push('DirectiveB.constructor'); }
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
}
@Component({
selector: 'app',
template: '<div dir></div>',
})
class App {
}
TestBed.configureTestingModule({
imports: [ModuleA],
declarations: [DirectiveB, App],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(log).toEqual([
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
'DirectiveB.ngOnInit'
]);
});
it('should respect imported module order', () => {
const log: string[] = [];
@Directive({selector: '[dir]'})
class DirectiveA {
constructor() { log.push('DirectiveA.constructor'); }
ngOnInit() { log.push('DirectiveA.ngOnInit'); }
}
@NgModule({
declarations: [DirectiveA],
exports: [DirectiveA],
})
class ModuleA {
}
@Directive({selector: '[dir]'})
class DirectiveB {
constructor() { log.push('DirectiveB.constructor'); }
ngOnInit() { log.push('DirectiveB.ngOnInit'); }
}
@NgModule({
declarations: [DirectiveB],
exports: [DirectiveB],
})
class ModuleB {
}
@Component({
selector: 'app',
template: '<div dir></div>',
})
class App {
}
TestBed.configureTestingModule({
imports: [ModuleA, ModuleB],
declarations: [App],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(log).toEqual([
'DirectiveA.constructor', 'DirectiveB.constructor', 'DirectiveA.ngOnInit',
'DirectiveB.ngOnInit'
]);
});
});
});

View File

@ -110,6 +110,86 @@ describe('pipe', () => {
expect(fixture.nativeElement.textContent).toEqual('value a b default 0 1 2 3');
});
it('should pick a Pipe defined in `declarations` over imported Pipes', () => {
@Pipe({name: 'number'})
class PipeA implements PipeTransform {
transform(value: any) { return `PipeA: ${value}`; }
}
@NgModule({
declarations: [PipeA],
exports: [PipeA],
})
class ModuleA {
}
@Pipe({name: 'number'})
class PipeB implements PipeTransform {
transform(value: any) { return `PipeB: ${value}`; }
}
@Component({
selector: 'app',
template: '{{ count | number }}',
})
class App {
count = 10;
}
TestBed.configureTestingModule({
imports: [ModuleA],
declarations: [PipeB, App],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
});
it('should respect imported module order when selecting Pipe (last imported Pipe is used)',
() => {
@Pipe({name: 'number'})
class PipeA implements PipeTransform {
transform(value: any) { return `PipeA: ${value}`; }
}
@NgModule({
declarations: [PipeA],
exports: [PipeA],
})
class ModuleA {
}
@Pipe({name: 'number'})
class PipeB implements PipeTransform {
transform(value: any) { return `PipeB: ${value}`; }
}
@NgModule({
declarations: [PipeB],
exports: [PipeB],
})
class ModuleB {
}
@Component({
selector: 'app',
template: '{{ count | number }}',
})
class App {
count = 10;
}
TestBed.configureTestingModule({
imports: [ModuleA, ModuleB],
declarations: [App],
});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('PipeB: 10');
});
it('should do nothing when no change', () => {
let calls: any[] = [];

View File

@ -167,6 +167,9 @@
{
"name": "attachPatchData"
},
{
"name": "autoRegisterModuleById"
},
{
"name": "baseResolveDirective"
},

View File

@ -223,5 +223,8 @@
},
{
"name": "ɵɵinject"
},
{
"name": "noSideEffects"
}
]

View File

@ -41,7 +41,7 @@ NgIf.ɵfac = () =>
NgTemplateOutlet.ɵdir = ɵɵdefineDirective({
type: NgTemplateOutletDef,
selectors: [['', 'ngTemplateOutlet', '']],
features: [ɵɵNgOnChangesFeature()],
features: [ɵɵNgOnChangesFeature],
inputs:
{ngTemplateOutlet: 'ngTemplateOutlet', ngTemplateOutletContext: 'ngTemplateOutletContext'}
});

View File

@ -30,6 +30,7 @@ filegroup(
testonly = True,
# do not sort
srcs = [
"@npm//:node_modules/core-js/client/core.js",
"@npm//:node_modules/@webcomponents/custom-elements/src/native-shim.js",
"@npm//:node_modules/reflect-metadata/Reflect.js",
"//packages/zone.js/dist:zone.js",
@ -42,11 +43,6 @@ karma_web_test_suite(
bootstrap = [
":elements_test_bootstrap_scripts",
],
tags = [
# FIXME: timed out in CI
"fixme-saucelabs-ivy",
"fixme-saucelabs-ve",
],
deps = [
":test_lib",
],

View File

@ -567,8 +567,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
}
}
if (binding.expression && inSpan(valueRelativePosition, binding.expression.ast.span)) {
this.processExpressionCompletions(binding.expression.ast);
if (binding.value && inSpan(valueRelativePosition, binding.value.ast.span)) {
this.processExpressionCompletions(binding.value.ast);
return;
}

View File

@ -91,10 +91,8 @@ function getVarDeclarations(
continue;
}
for (const variable of current.variables) {
let symbol = info.members.get(variable.value);
if (!symbol) {
symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
}
let symbol = getVariableTypeFromDirectiveContext(variable.value, info.query, current);
const kind = info.query.getTypeKind(symbol);
if (kind === BuiltinType.Any || kind === BuiltinType.Unbound) {
// For special cases such as ngFor and ngIf, the any type is not very useful.

View File

@ -209,10 +209,10 @@ function getSymbolInMicrosyntax(info: AstResult, path: TemplateAstPath, attribut
// Find the symbol that contains the position.
templateBindings.filter(tb => !tb.keyIsVar).forEach(tb => {
if (inSpan(valueRelativePosition, tb.expression?.ast.span)) {
if (inSpan(valueRelativePosition, tb.value?.ast.span)) {
const dinfo = diagnosticInfoFromTemplateInfo(info);
const scope = getExpressionScope(dinfo, path);
result = getExpressionSymbol(scope, tb.expression !, path.position, info.template.query);
result = getExpressionSymbol(scope, tb.value !, path.position, info.template.query);
} else if (inSpan(valueRelativePosition, tb.span)) {
const template = path.first(EmbeddedTemplateAst);
if (template) {

View File

@ -170,6 +170,8 @@ export class TemplateReference {
primitiveIndexType: {[name: string]: string} = {};
anyValue: any;
optional?: string;
// Use to test the `index` variable conflict between the `ngFor` and component context.
index = null;
myClick(event: any) {}
}

View File

@ -15,4 +15,15 @@ export class Diagnostics {
get hasErrors() { return this.messages.some(m => m.type === 'error'); }
warn(message: string) { this.messages.push({type: 'warning', message}); }
error(message: string) { this.messages.push({type: 'error', message}); }
formatDiagnostics(message: string): string {
const errors = this.messages !.filter(d => d.type === 'error').map(d => ' - ' + d.message);
const warnings = this.messages !.filter(d => d.type === 'warning').map(d => ' - ' + d.message);
if (errors.length) {
message += '\nERRORS:\n' + errors.join('\n');
}
if (warnings.length) {
message += '\nWARNINGS:\n' + warnings.join('\n');
}
return message;
}
}

View File

@ -142,7 +142,7 @@ export function translateFiles({sourceRootPath, sourceFilePaths, translationFile
[
new Xliff2TranslationParser(),
new Xliff1TranslationParser(),
new XtbTranslationParser(diagnostics),
new XtbTranslationParser(),
new SimpleJsonTranslationParser(),
],
diagnostics);

View File

@ -14,7 +14,9 @@ import {TranslationParser} from './translation_parsers/translation_parser';
* Use this class to load a collection of translation files from disk.
*/
export class TranslationLoader {
constructor(private translationParsers: TranslationParser[], private diagnostics: Diagnostics) {}
constructor(
private translationParsers: TranslationParser<any>[],
/** @deprecated */ private diagnostics?: Diagnostics) {}
/**
* Load and parse the translation files into a collection of `TranslationBundles`.
@ -34,22 +36,37 @@ export class TranslationLoader {
return translationFilePaths.map((filePath, index) => {
const fileContents = FileUtils.readFile(filePath);
for (const translationParser of this.translationParsers) {
if (translationParser.canParse(filePath, fileContents)) {
const providedLocale = translationFileLocales[index];
const {locale: parsedLocale, translations} =
translationParser.parse(filePath, fileContents);
const locale = providedLocale || parsedLocale;
if (locale === undefined) {
throw new Error(
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
}
if (parsedLocale !== undefined && providedLocale !== undefined &&
parsedLocale !== providedLocale) {
this.diagnostics.warn(
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
}
return {locale, translations};
const result = translationParser.canParse(filePath, fileContents);
if (!result) {
continue;
}
const {locale: parsedLocale, translations, diagnostics} =
translationParser.parse(filePath, fileContents, result);
if (diagnostics.hasErrors) {
throw new Error(diagnostics.formatDiagnostics(
`The translation file "${filePath}" could not be parsed.`));
}
const providedLocale = translationFileLocales[index];
const locale = providedLocale || parsedLocale;
if (locale === undefined) {
throw new Error(
`The translation file "${filePath}" does not contain a target locale and no explicit locale was provided for this file.`);
}
if (parsedLocale !== undefined && providedLocale !== undefined &&
parsedLocale !== providedLocale) {
diagnostics.warn(
`The provided locale "${providedLocale}" does not match the target locale "${parsedLocale}" found in the translation file "${filePath}".`);
}
// If we were passed a diagnostics object then copy the messages over to it.
if (this.diagnostics) {
this.diagnostics.messages.push(...diagnostics.messages);
}
return {locale, translations, diagnostics};
}
throw new Error(
`There is no "TranslationParser" that can parse this translation file: ${filePath}.`);

View File

@ -7,6 +7,7 @@
*/
import {ɵMessageId, ɵParsedTranslation, ɵparseTranslation} from '@angular/localize';
import {extname} from 'path';
import {Diagnostics} from '../../../diagnostics';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
/**
@ -32,6 +33,6 @@ export class SimpleJsonTranslationParser implements TranslationParser {
const targetMessage = translations[messageId];
parsedTranslations[messageId] = ɵparseTranslation(targetMessage);
}
return {locale: parsedLocale, translations: parsedTranslations};
return {locale: parsedLocale, translations: parsedTranslations, diagnostics: new Diagnostics()};
}
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
import {Diagnostics} from '../../../diagnostics';
/**
* An object that holds translations that have been parsed from a translation file.
@ -13,25 +14,62 @@ import {ɵMessageId, ɵParsedTranslation} from '@angular/localize/private';
export interface ParsedTranslationBundle {
locale: string|undefined;
translations: Record<ɵMessageId, ɵParsedTranslation>;
diagnostics: Diagnostics;
}
/**
* Implement this interface to provide a class that can parse the contents of a translation file.
*
* The `canParse()` method can return a hint that can be used by the `parse()` method to speed up
* parsing. This allows the parser to do significant work to determine if the file can be parsed
* without duplicating the work when it comes to actually parsing the file.
*
* Example usage:
*
* ```
* const parser: TranslationParser = getParser();
* const result = parser.canParse(filePath, content);
* if (result) {
* return parser.parse(filePath, content, result);
* }
* ```
*/
export interface TranslationParser {
export interface TranslationParser<Hint = true> {
/**
* Returns true if this parser can parse the given file.
* Can this parser parse the given file?
*
* @param filePath The absolute path to the translation file.
* @param contents The contents of the translation file.
* @returns A hint, which can be used in doing the actual parsing, if the file can be parsed by
* this parser; false otherwise.
*/
canParse(filePath: string, contents: string): boolean;
canParse(filePath: string, contents: string): Hint|false;
/**
* Parses the given file, extracting the target locale and translations.
*
* Note that this method should not throw an error. Check the `bundle.diagnostics` property for
* potential parsing errors and warnings.
*
* @param filePath The absolute path to the translation file.
* @param contents The contents of the translation file.
* @param hint A value that can be used by the parser to speed up parsing of the file. This will
* have been provided as the return result from calling `canParse()`.
* @returns The translation bundle parsed from the file.
* @throws No errors. If there was a problem with parsing the bundle will contain errors
* in the `diagnostics` property.
*/
parse(filePath: string, contents: string, hint: Hint): ParsedTranslationBundle;
/**
* Parses the given file, extracting the target locale and translations.
*
* @deprecated This overload is kept for backward compatibility. Going forward use the Hint
* returned from `canParse()` so that this method can avoid duplicating effort.
*
* @param filePath The absolute path to the translation file.
* @param contents The contents of the translation file.
* @returns The translation bundle parsed from the file.
* @throws An error if there was a problem parsing this file.
*/
parse(filePath: string, contents: string): ParsedTranslationBundle;
}

View File

@ -5,7 +5,8 @@
* 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 {Element, LexerRange, Node, XmlParser} from '@angular/compiler';
import {Element, LexerRange, Node, ParseError, ParseErrorLevel, ParseSourceSpan, XmlParser} from '@angular/compiler';
import {Diagnostics} from '../../../diagnostics';
import {TranslationParseError} from './translation_parse_error';
export function getAttrOrThrow(element: Element, attrName: string): string {
@ -22,6 +23,14 @@ export function getAttribute(element: Element, attrName: string): string|undefin
return attr !== undefined ? attr.value : undefined;
}
/**
* Parse the "contents" of an XML element.
*
* This would be equivalent to parsing the `innerHTML` string of an HTML document.
*
* @param element The element whose inner range we want to parse.
* @returns a collection of XML `Node` objects that were parsed from the element's contents.
*/
export function parseInnerRange(element: Element): Node[] {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(
@ -33,6 +42,10 @@ export function parseInnerRange(element: Element): Node[] {
return xml.rootNodes;
}
/**
* Compute a `LexerRange` that contains all the children of the given `element`.
* @param element The element whose inner range we want to compute.
*/
function getInnerRange(element: Element): LexerRange {
const start = element.startSourceSpan !.end;
const end = element.endSourceSpan !.start;
@ -42,4 +55,94 @@ function getInnerRange(element: Element): LexerRange {
startCol: start.col,
endPos: end.offset,
};
}
}
/**
* This "hint" object is used to pass information from `canParse()` to `parse()` for
* `TranslationParser`s that expect XML contents.
*
* This saves the `parse()` method from having to re-parse the XML.
*/
export interface XmlTranslationParserHint {
element: Element;
errors: ParseError[];
}
/**
* Can this XML be parsed for translations, given the expected `rootNodeName` and expected root node
* `attributes` that should appear in the file.
*
* @param filePath The path to the file being checked.
* @param contents The contents of the file being checked.
* @param rootNodeName The expected name of an XML root node that should exist.
* @param attributes The attributes (and their values) that should appear on the root node.
* @returns The `XmlTranslationParserHint` object for use by `TranslationParser.parse()` if the XML
* document has the expected format.
*/
export function canParseXml(
filePath: string, contents: string, rootNodeName: string,
attributes: Record<string, string>): XmlTranslationParserHint|false {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
if (xml.rootNodes.length === 0 ||
xml.errors.some(error => error.level === ParseErrorLevel.ERROR)) {
return false;
}
const rootElements = xml.rootNodes.filter(isNamedElement(rootNodeName));
const rootElement = rootElements[0];
if (rootElement === undefined) {
return false;
}
for (const attrKey of Object.keys(attributes)) {
const attr = rootElement.attrs.find(attr => attr.name === attrKey);
if (attr === undefined || attr.value !== attributes[attrKey]) {
return false;
}
}
if (rootElements.length > 1) {
xml.errors.push(new ParseError(
xml.rootNodes[1].sourceSpan,
'Unexpected root node. XLIFF 1.2 files should only have a single <xliff> root node.',
ParseErrorLevel.WARNING));
}
return {element: rootElement, errors: xml.errors};
}
/**
* Create a predicate, which can be used by things like `Array.filter()`, that will match a named
* XML Element from a collection of XML Nodes.
*
* @param name The expected name of the element to match.
*/
export function isNamedElement(name: string): (node: Node) => node is Element {
function predicate(node: Node): node is Element {
return node instanceof Element && node.name === name;
}
return predicate;
}
/**
* Add an XML parser related message to the given `diagnostics` object.
*/
export function addParseDiagnostic(
diagnostics: Diagnostics, sourceSpan: ParseSourceSpan, message: string,
level: ParseErrorLevel): void {
addParseError(diagnostics, new ParseError(sourceSpan, message, level));
}
/**
* Copy the formatted error message from the given `parseError` object into the given `diagnostics`
* object.
*/
export function addParseError(diagnostics: Diagnostics, parseError: ParseError): void {
if (parseError.level === ParseErrorLevel.ERROR) {
diagnostics.error(parseError.toString());
} else {
diagnostics.warn(parseError.toString());
}
}

View File

@ -5,19 +5,16 @@
* 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path';
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
import {ɵParsedTranslation} from '@angular/localize';
import {Diagnostics} from '../../../diagnostics';
import {BaseVisitor} from '../base_visitor';
import {MessageSerializer} from '../message_serialization/message_serializer';
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
import {TranslationParseError} from './translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
/**
* A translation parser that can load XLIFF 1.2 files.
@ -26,67 +23,132 @@ const XLIFF_1_2_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:1.2"/;
* http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
*
*/
export class Xliff1TranslationParser implements TranslationParser {
canParse(filePath: string, contents: string): boolean {
return (extname(filePath) === '.xlf') && XLIFF_1_2_NS_REGEX.test(contents);
export class Xliff1TranslationParser implements TranslationParser<XmlTranslationParserHint> {
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
return canParseXml(filePath, contents, 'xliff', {version: '1.2'});
}
parse(filePath: string, contents: string): ParsedTranslationBundle {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
const bundle = XliffFileElementVisitor.extractBundle(xml.rootNodes);
if (bundle === undefined) {
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
ParsedTranslationBundle {
if (hint) {
return this.extractBundle(hint);
} else {
return this.extractBundleDeprecated(filePath, contents);
}
}
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
const diagnostics = new Diagnostics();
errors.forEach(e => addParseError(diagnostics, e));
if (element.children.length === 0) {
addParseDiagnostic(
diagnostics, element.sourceSpan, 'Missing expected <file> element',
ParseErrorLevel.WARNING);
return {locale: undefined, translations: {}, diagnostics};
}
const files = element.children.filter(isNamedElement('file'));
if (files.length === 0) {
addParseDiagnostic(
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
ParseErrorLevel.WARNING);
} else if (files.length > 1) {
addParseDiagnostic(
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
ParseErrorLevel.WARNING);
}
const bundle: ParsedTranslationBundle = {locale: undefined, translations: {}, diagnostics};
const translationVisitor = new XliffTranslationVisitor();
const localesFound = new Set<string>();
for (const file of files) {
const locale = getAttribute(file, 'target-language');
if (locale !== undefined) {
localesFound.add(locale);
bundle.locale = locale;
}
visitAll(translationVisitor, file.children, bundle);
}
if (localesFound.size > 1) {
addParseDiagnostic(
diagnostics, element.sourceSpan,
`More than one locale found in translation file: ${JSON.stringify(Array.from(localesFound))}. Using "${bundle.locale}"`,
ParseErrorLevel.WARNING);
}
return bundle;
}
private extractBundleDeprecated(filePath: string, contents: string) {
const hint = this.canParse(filePath, contents);
if (!hint) {
throw new Error(`Unable to parse "${filePath}" as XLIFF 1.2 format.`);
}
const bundle = this.extractBundle(hint);
if (bundle.diagnostics.hasErrors) {
const message =
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 1.2 format`);
throw new Error(message);
}
return bundle;
}
}
class XliffFileElementVisitor extends BaseVisitor {
private bundle: ParsedTranslationBundle|undefined;
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
const visitor = new this();
visitAll(visitor, xliff);
return visitor.bundle;
}
visitElement(element: Element): any {
if (element.name === 'file') {
this.bundle = {
locale: getAttribute(element, 'target-language'),
translations: XliffTranslationVisitor.extractTranslations(element)
};
} else {
return visitAll(this, element.children);
visitElement(fileElement: Element): any {
if (fileElement.name === 'file') {
return {fileElement, locale: getAttribute(fileElement, 'target-language')};
}
}
}
class XliffTranslationVisitor extends BaseVisitor {
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
const visitor = new this();
visitAll(visitor, file.children);
return visitor.translations;
visitElement(element: Element, bundle: ParsedTranslationBundle): void {
if (element.name === 'trans-unit') {
this.visitTransUnitElement(element, bundle);
} else {
visitAll(this, element.children, bundle);
}
}
visitElement(element: Element): any {
if (element.name === 'trans-unit') {
const id = getAttrOrThrow(element, 'id');
if (this.translations[id] !== undefined) {
throw new TranslationParseError(
element.sourceSpan, `Duplicated translations for message "${id}"`);
}
private visitTransUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
// Error if no `id` attribute
const id = getAttribute(element, 'id');
if (id === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan,
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
return;
}
const targetMessage = element.children.find(isTargetElement);
if (targetMessage === undefined) {
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
// Error if there is already a translation with the same id
if (bundle.translations[id] !== undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
ParseErrorLevel.ERROR);
return;
}
// Error if there is no `<target>` child element
const targetMessage = element.children.find(isNamedElement('target'));
if (targetMessage === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
ParseErrorLevel.ERROR);
return;
}
try {
bundle.translations[id] = serializeTargetMessage(targetMessage);
} catch (e) {
// Capture any errors from serialize the target message
if (e.span && e.msg && e.level) {
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
} else {
throw e;
}
this.translations[id] = serializeTargetMessage(targetMessage);
} else {
return visitAll(this, element.children);
}
}
}
@ -98,7 +160,3 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
});
return serializer.serialize(parseInnerRange(source));
}
function isTargetElement(node: Node): node is Element {
return node instanceof Element && node.name === 'target';
}

View File

@ -5,19 +5,16 @@
* 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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {ɵMessageId, ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path';
import {Element, Node, ParseErrorLevel, visitAll} from '@angular/compiler';
import {ɵParsedTranslation} from '@angular/localize';
import {Diagnostics} from '../../../diagnostics';
import {BaseVisitor} from '../base_visitor';
import {MessageSerializer} from '../message_serialization/message_serializer';
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
import {TranslationParseError} from './translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
import {getAttrOrThrow, getAttribute, parseInnerRange} from './translation_utils';
const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, isNamedElement, parseInnerRange} from './translation_utils';
/**
* A translation parser that can load translations from XLIFF 2 files.
@ -25,84 +22,126 @@ const XLIFF_2_0_NS_REGEX = /xmlns="urn:oasis:names:tc:xliff:document:2.0"/;
* http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html
*
*/
export class Xliff2TranslationParser implements TranslationParser {
canParse(filePath: string, contents: string): boolean {
return (extname(filePath) === '.xlf') && XLIFF_2_0_NS_REGEX.test(contents);
export class Xliff2TranslationParser implements TranslationParser<XmlTranslationParserHint> {
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
return canParseXml(filePath, contents, 'xliff', {version: '2.0'});
}
parse(filePath: string, contents: string): ParsedTranslationBundle {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
const bundle = Xliff2TranslationBundleVisitor.extractBundle(xml.rootNodes);
if (bundle === undefined) {
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
ParsedTranslationBundle {
if (hint) {
return this.extractBundle(hint);
} else {
return this.extractBundleDeprecated(filePath, contents);
}
}
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
const diagnostics = new Diagnostics();
errors.forEach(e => addParseError(diagnostics, e));
const locale = getAttribute(element, 'trgLang');
const files = element.children.filter(isFileElement);
if (files.length === 0) {
addParseDiagnostic(
diagnostics, element.sourceSpan, 'No <file> elements found in <xliff>',
ParseErrorLevel.WARNING);
} else if (files.length > 1) {
addParseDiagnostic(
diagnostics, files[1].sourceSpan, 'More than one <file> element found in <xliff>',
ParseErrorLevel.WARNING);
}
const bundle = {locale, translations: {}, diagnostics};
const translationVisitor = new Xliff2TranslationVisitor();
for (const file of files) {
visitAll(translationVisitor, file.children, {bundle});
}
return bundle;
}
private extractBundleDeprecated(filePath: string, contents: string) {
const hint = this.canParse(filePath, contents);
if (!hint) {
throw new Error(`Unable to parse "${filePath}" as XLIFF 2.0 format.`);
}
const bundle = this.extractBundle(hint);
if (bundle.diagnostics.hasErrors) {
const message =
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XLIFF 2.0 format`);
throw new Error(message);
}
return bundle;
}
}
interface BundleVisitorContext {
parsedLocale?: string;
}
class Xliff2TranslationBundleVisitor extends BaseVisitor {
private bundle: ParsedTranslationBundle|undefined;
static extractBundle(xliff: Node[]): ParsedTranslationBundle|undefined {
const visitor = new this();
visitAll(visitor, xliff, {});
return visitor.bundle;
}
visitElement(element: Element, {parsedLocale}: BundleVisitorContext): any {
if (element.name === 'xliff') {
parsedLocale = getAttribute(element, 'trgLang');
return visitAll(this, element.children, {parsedLocale});
} else if (element.name === 'file') {
this.bundle = {
locale: parsedLocale,
translations: Xliff2TranslationVisitor.extractTranslations(element)
};
} else {
return visitAll(this, element.children, {parsedLocale});
}
}
interface TranslationVisitorContext {
unit?: string;
bundle: ParsedTranslationBundle;
}
class Xliff2TranslationVisitor extends BaseVisitor {
private translations: Record<ɵMessageId, ɵParsedTranslation> = {};
static extractTranslations(file: Element): Record<string, ɵParsedTranslation> {
const visitor = new this();
visitAll(visitor, file.children);
return visitor.translations;
}
visitElement(element: Element, context: any): any {
visitElement(element: Element, {bundle, unit}: TranslationVisitorContext): any {
if (element.name === 'unit') {
const externalId = getAttrOrThrow(element, 'id');
if (this.translations[externalId] !== undefined) {
throw new TranslationParseError(
element.sourceSpan, `Duplicated translations for message "${externalId}"`);
}
visitAll(this, element.children, {unit: externalId});
this.visitUnitElement(element, bundle);
} else if (element.name === 'segment') {
assertTranslationUnit(element, context);
const targetMessage = element.children.find(isTargetElement);
if (targetMessage === undefined) {
throw new TranslationParseError(element.sourceSpan, 'Missing required <target> element');
}
this.translations[context.unit] = serializeTargetMessage(targetMessage);
this.visitSegmentElement(element, bundle, unit);
} else {
return visitAll(this, element.children);
visitAll(this, element.children, {bundle, unit});
}
}
}
function assertTranslationUnit(segment: Element, context: any) {
if (context === undefined || context.unit === undefined) {
throw new TranslationParseError(
segment.sourceSpan, 'Invalid <segment> element: should be a child of a <unit> element.');
private visitUnitElement(element: Element, bundle: ParsedTranslationBundle): void {
// Error if no `id` attribute
const externalId = getAttribute(element, 'id');
if (externalId === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan,
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
return;
}
// Error if there is already a translation with the same id
if (bundle.translations[externalId] !== undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan,
`Duplicated translations for message "${externalId}"`, ParseErrorLevel.ERROR);
return;
}
visitAll(this, element.children, {bundle, unit: externalId});
}
private visitSegmentElement(
element: Element, bundle: ParsedTranslationBundle, unit: string|undefined): void {
// A `<segment>` element must be below a `<unit>` element
if (unit === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan,
'Invalid <segment> element: should be a child of a <unit> element.',
ParseErrorLevel.ERROR);
return;
}
const targetMessage = element.children.find(isNamedElement('target'));
if (targetMessage === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan, 'Missing required <target> element',
ParseErrorLevel.ERROR);
return;
}
try {
bundle.translations[unit] = serializeTargetMessage(targetMessage);
} catch (e) {
// Capture any errors from serialize the target message
if (e.span && e.msg && e.level) {
addParseDiagnostic(bundle.diagnostics, e.span, e.msg, e.level);
} else {
throw e;
}
}
}
}
@ -116,6 +155,6 @@ function serializeTargetMessage(source: Element): ɵParsedTranslation {
return serializer.serialize(parseInnerRange(source));
}
function isTargetElement(node: Node): node is Element {
return node instanceof Element && node.name === 'target';
function isFileElement(node: Node): node is Element {
return node instanceof Element && node.name === 'file';
}

View File

@ -5,7 +5,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 {Element, Node, XmlParser, visitAll} from '@angular/compiler';
import {Element, ParseErrorLevel, visitAll} from '@angular/compiler';
import {ɵParsedTranslation} from '@angular/localize';
import {extname} from 'path';
@ -14,83 +14,100 @@ import {BaseVisitor} from '../base_visitor';
import {MessageSerializer} from '../message_serialization/message_serializer';
import {TargetMessageRenderer} from '../message_serialization/target_message_renderer';
import {TranslationParseError} from './translation_parse_error';
import {ParsedTranslationBundle, TranslationParser} from './translation_parser';
import {getAttrOrThrow, parseInnerRange} from './translation_utils';
import {XmlTranslationParserHint, addParseDiagnostic, addParseError, canParseXml, getAttribute, parseInnerRange} from './translation_utils';
/**
* A translation parser that can load XB files.
*/
export class XtbTranslationParser implements TranslationParser {
constructor(private diagnostics: Diagnostics) {}
canParse(filePath: string, contents: string): boolean {
export class XtbTranslationParser implements TranslationParser<XmlTranslationParserHint> {
canParse(filePath: string, contents: string): XmlTranslationParserHint|false {
const extension = extname(filePath);
return (extension === '.xtb' || extension === '.xmb') &&
contents.includes('<translationbundle');
if (extension !== '.xtb' && extension !== '.xmb') {
return false;
}
return canParseXml(filePath, contents, 'translationbundle', {});
}
parse(filePath: string, contents: string): ParsedTranslationBundle {
const xmlParser = new XmlParser();
const xml = xmlParser.parse(contents, filePath);
const bundle = XtbVisitor.extractBundle(this.diagnostics, xml.rootNodes);
if (bundle === undefined) {
throw new Error(`Unable to parse "${filePath}" as XTB/XMB format.`);
parse(filePath: string, contents: string, hint?: XmlTranslationParserHint):
ParsedTranslationBundle {
if (hint) {
return this.extractBundle(hint);
} else {
return this.extractBundleDeprecated(filePath, contents);
}
}
private extractBundle({element, errors}: XmlTranslationParserHint): ParsedTranslationBundle {
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
const bundle: ParsedTranslationBundle = {
locale: langAttr && langAttr.value,
translations: {},
diagnostics: new Diagnostics()
};
errors.forEach(e => addParseError(bundle.diagnostics, e));
const bundleVisitor = new XtbVisitor();
visitAll(bundleVisitor, element.children, bundle);
return bundle;
}
private extractBundleDeprecated(filePath: string, contents: string) {
const hint = this.canParse(filePath, contents);
if (!hint) {
throw new Error(`Unable to parse "${filePath}" as XMB/XTB format.`);
}
const bundle = this.extractBundle(hint);
if (bundle.diagnostics.hasErrors) {
const message =
bundle.diagnostics.formatDiagnostics(`Failed to parse "${filePath}" as XMB/XTB format`);
throw new Error(message);
}
return bundle;
}
}
class XtbVisitor extends BaseVisitor {
static extractBundle(diagnostics: Diagnostics, messageBundles: Node[]): ParsedTranslationBundle
|undefined {
const visitor = new this(diagnostics);
const bundles: ParsedTranslationBundle[] = visitAll(visitor, messageBundles, undefined);
return bundles[0];
}
constructor(private diagnostics: Diagnostics) { super(); }
visitElement(element: Element, bundle: ParsedTranslationBundle|undefined): any {
visitElement(element: Element, bundle: ParsedTranslationBundle): any {
switch (element.name) {
case 'translationbundle':
if (bundle) {
throw new TranslationParseError(
element.sourceSpan, '<translationbundle> elements can not be nested');
}
const langAttr = element.attrs.find((attr) => attr.name === 'lang');
bundle = {locale: langAttr && langAttr.value, translations: {}};
visitAll(this, element.children, bundle);
return bundle;
case 'translation':
if (!bundle) {
throw new TranslationParseError(
element.sourceSpan, '<translation> must be inside a <translationbundle>');
// Error if no `id` attribute
const id = getAttribute(element, 'id');
if (id === undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan,
`Missing required "id" attribute on <trans-unit> element.`, ParseErrorLevel.ERROR);
return;
}
const id = getAttrOrThrow(element, 'id');
if (bundle.translations.hasOwnProperty(id)) {
throw new TranslationParseError(
element.sourceSpan, `Duplicated translations for message "${id}"`);
} else {
try {
bundle.translations[id] = serializeTargetMessage(element);
} catch (error) {
if (typeof error === 'string') {
this.diagnostics.warn(
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
error);
} else {
throw error;
}
// Error if there is already a translation with the same id
if (bundle.translations[id] !== undefined) {
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan, `Duplicated translations for message "${id}"`,
ParseErrorLevel.ERROR);
return;
}
try {
bundle.translations[id] = serializeTargetMessage(element);
} catch (error) {
if (typeof error === 'string') {
bundle.diagnostics.warn(
`Could not parse message with id "${id}" - perhaps it has an unrecognised ICU format?\n` +
error);
} else if (error.span && error.msg && error.level) {
addParseDiagnostic(bundle.diagnostics, error.span, error.msg, error.level);
} else {
throw error;
}
}
break;
default:
throw new TranslationParseError(element.sourceSpan, 'Unexpected tag');
addParseDiagnostic(
bundle.diagnostics, element.sourceSpan, `Unexpected <${element.name}> tag.`,
ParseErrorLevel.ERROR);
}
}
}

View File

@ -19,6 +19,7 @@ import {OutputPathFn} from './output_path';
export interface TranslationBundle {
locale: string;
translations: Record<ɵMessageId, ɵParsedTranslation>;
diagnostics?: Diagnostics;
}
/**

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